Loading [MathJax]/jax/output/CommonHTML/config.js
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >手写SpringMVC框架

手写SpringMVC框架

作者头像
田维常
发布于 2021-10-27 08:41:25
发布于 2021-10-27 08:41:25
70000
代码可运行
举报
运行总次数:0
代码可运行

大家好,我是老田,今天给大家分享:手写SpringMVC框架

本文目录

写在前面

Spring 想必大家都听说过,可能现在更多流行的是Spring Boot 和Spring Cloud 框架;但是SpringMVC 作为一款实现了MVC 设计模式的web (表现层) 层框架,其高开发效率和高性能也是现在很多公司仍在采用的框架;除此之外,Spring 源码大师级的代码规范和设计思想都十分值得学习;退一步说,SpringMVC框架底层也有很多Spring 的东西,而且面试的时候还会经常被问到SpringMVC原理,一般人可能也就是只能把SpringMVC 的运行原理背出来罢了,至于问到有没有了解其底层实现(代码层面),那很可能就歇菜了,但您要是可以手写SpringMVC框架就肯定可以令面试官刮目相看,所以手写SpringMVC 值得一试。

在设计自己的SpringMVC框架之前,需要了解下其运行流程。

一、SpringMVC 运行流程

图1. SpringMVC 运行流程

通过上面流程图,我们总结为以下几个步骤:

1、用户向服务器发送请求,请求被Spring 前端控制器DispatcherServlet捕获;

2、DispatcherServlet收到请求后调用HandlerMapping处理器映射器;

3、处理器映射器对请求URL 进行解析,得到请求资源标识符(URI);然后根据该URI,调用HandlerMapping获得该Handler 配置的所有相关的对象(包括Handler 对象以及Handler 对象对应的拦截器),再以HandlerExecutionChain 对象的形式返回给DispatcherServlet

4、DispatcherServlet根据获得的Handler,通过HandlerAdapter 处理器适配器选择一个合适的HandlerAdapter;(附注:如果成功获得HandlerAdapter 后,此时将开始执行拦截器的preHandler(...)方法);

5、提取Request 中的模型数据,填充Handler 入参,开始执行Handler(即Controller);【在填充Handler的入参过程中,根据你的配置,Spring 将帮你做一些额外的工作如:HttpMessageConveter:将请求消息(如Json、xml等数据)转换成一个对象,将对象转换为指定的响应信息;数据转换:对请求消息进行数据转换,如String转换成Integer、Double等;数据格式化:对请求消息进行数据格式化,如将字符串转换成格式化数字或格式化日期等;数据验证:验证数据的有效性(长度、格式等),验证结果存储到BindingResult或Error中 】

6、Controller 执行完成返回ModelAndView对象;

7、HandlerAdapter 将controller 执行结果ModelAndView 对象返回给DispatcherServlet;

8、DispatcherServlet将ModelAndView 对象传给ViewReslover 视图解析器;

9、ViewReslover 根据返回的ModelAndView,选择一个适合的ViewResolver (必须是已经注册到Spring容器中的ViewResolver)返回给DispatcherServlet;

10、DispatcherServlet对View 进行渲染视图(即将模型数据填充至视图中);

11、DispatcherServlet将渲染结果响应用户(客户端)。

二、SpringMVC 框架设计思路

1、读取配置阶段

图2. SpringMVC 继承关系

第一步就是配置web.xml,加载自定义的DispatcherServlet。而从图中可以看出,Spring MVC 本质上是一个Servlet,这个Servlet继承自HttpServlet,此外,FrameworkServlet负责初始Spring MVC的容器,并将Spring 容器设置为父容器;为了读取web.xml中的配置,需要用到ServletConfig 这个类,它代表当前Servlet 在web.xml 中的配置信息,然后通过web.xml中加载我们自己写的MyDispatcherServlet和读取配置文件。

2、初始化阶段

初始化阶段会在DispatcherServlet 类中,按顺序实现下面几个步骤:

1、加载配置文件;

2、扫描当前项目下的所有文件;

3、拿到扫描到的类,通过反射机制将其实例化,并且放到ioc 容器中(Map的键值对 beanName-bean) beanName默认是首字母小写;

4、初始化path 与方法的映射;

5、获取请求传入的参数并处理参数通过初始化好的handlerMapping 中拿出url对应的方法名,反射调用。

3、运行阶段

运行阶段,每一次请求将会调用doGetdoPost 方法,它会根据url 请求去HandlerMapping中匹配到对应的Method,然后利用反射机制调用Controller 中的url 对应的方法,并得到结果返回。

实现Spring MVC 框架

首先,Spring MVC 框架只实现自己的@Controller@RequestMapping注解,其它注解功能实现方式类似,实现注解较少所以项目比较简单,可以看到如下工程文件及目录截图。

项目目录

创建Java Web项目

创建Java Web 项目,勾选JavaEE 下方的Web Application 选项,Next。

创建Java Web 项目

配置maven

配置一个pom文件,内容如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
         version="4.0">

    <servlet>
        <servlet-name>DispatcherServlet</servlet-name>
        <servlet-class>com.tjt.springmvc.DispatcherServlet</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>DispatcherServlet</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>

</web-app>
创建自定义的controller注解
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
package com.tjt.springmvc;


import java.lang.annotation.*;


/**
 * @MyController 自定义注解类
 *
 * @@Target(ElementType.TYPE)
 * 表示该注解可以作用在类上;
 *
 * @Retention(RetentionPolicy.RUNTIME)
 * 表示该注解会在class 字节码文件中存在,在运行时可以通过反射获取到
 *
 * @Documented
 * 标记注解,表示可以生成文档
 */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyController {

    /**
     * public class MyController
     * 把 class 替换成 @interface 该类即成为注解类
     */

    /**
     * 为Controller 注册别名
     * @return
     */
    String value() default "";

}
创建自定义的RequestMapping注解
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
package com.tjt.springmvc;


import java.lang.annotation.*;


/**
 * @MyRequestMapping 自定义注解类
 *
 * @Target({ElementType.METHOD,ElementType.TYPE})
 * 表示该注解可以作用在方法、类上;
 *
 * @Retention(RetentionPolicy.RUNTIME)
 * 表示该注解会在class 字节码文件中存在,在运行时可以通过反射获取到
 *
 * @Documented
 * 标记注解,表示可以生成文档
 */
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyRequestMapping {

    /**
     * public @interface MyRequestMapping
     * 把 class 替换成 @interface 该类即成为注解类
     */

    /**
     * 表示访问该方法的url
     * @return
     */
    String value() default "";

}
设计用于获取项目工程下所有的class 文件的封装工具类
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
package com.tjt.springmvc;


import java.io.File;
import java.io.FileFilter;
import java.net.JarURLConnection;
import java.net.URL;
import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

/**
 * 从项目工程包package 中获取所有的Class 工具类
 */
public class ClassUtils {

    /**
     * 静态常量
     */
    private static String FILE_CONSTANT = "file";
    private static String UTF8_CONSTANT = "UTF-8";
    private static String JAR_CONSTANT = "jar";
    private static String POINT_CLASS_CONSTANT = ".class";
    private static char POINT_CONSTANT = '.';
    private static char LEFT_LINE_CONSTANT = '/';


    /**
     * 定义私有构造函数来屏蔽隐式公有构造函数
     */
    private ClassUtils() {
    }


    /**
     * 从项目工程包package 中获取所有的Class
     * getClasses
     *
     * @param packageName
     * @return
     */
    public static List<Class<?>> getClasses(String packageName) throws Exception {


        List<Class<?>> classes = new ArrayList<Class<?>>();  // 定义一个class 类的泛型集合
        boolean recursive = true;  // recursive 是否循环迭代
        String packageDirName = packageName.replace(POINT_CONSTANT, LEFT_LINE_CONSTANT);  // 获取包的名字 并进行替换
        Enumeration<URL> dirs;  // 定义一个枚举的集合 分别保存该目录下的所有java 类文件及Jar 包等内容
        dirs = Thread.currentThread().getContextClassLoader().getResources(packageDirName);
        /**
         * 循环迭代 处理这个目录下的things
         */
        while (dirs.hasMoreElements()) {
            URL url = dirs.nextElement();  // 获取下一个元素
            String protocol = url.getProtocol();  // 得到协议的名称 protocol
            // 如果是
            /**
             * 若protocol 是文件形式
             */
            if (FILE_CONSTANT.equals(protocol)) {
                String filePath = URLDecoder.decode(url.getFile(), UTF8_CONSTANT); // 获取包的物理路径
                findAndAddClassesInPackageByFile(packageName, filePath, recursive, classes); // 以文件的方式扫描整个包下的文件 并添加到集合中
                /**
                 * 若protocol 是jar 包文件
                 */
            } else if (JAR_CONSTANT.equals(protocol)) {
                JarFile jar;  // 定义一个JarFile
                jar = ((JarURLConnection) url.openConnection()).getJarFile();  // 获取jar
                Enumeration<JarEntry> entries = jar.entries();  // 从jar 包中获取枚举类
                /**
                 * 循环迭代从Jar 包中获得的枚举类
                 */
                while (entries.hasMoreElements()) {
                    JarEntry entry = entries.nextElement();  // 获取jar里的一个实体,如目录、META-INF等文件
                    String name = entry.getName();
                    /**
                     * 若实体名是以 / 开头
                     */
                    if (name.charAt(0) == LEFT_LINE_CONSTANT) {
                        name = name.substring(1);  // 获取后面的字符串
                    }
                    // 如果
                    /**
                     * 若实体名前半部分和定义的包名相同
                     */
                    if (name.startsWith(packageDirName)) {
                        int idx = name.lastIndexOf(LEFT_LINE_CONSTANT);
                        /**
                         * 并且实体名以为'/' 结尾
                         * 若其以'/' 结尾则是一个包
                         */
                        if (idx != -1) {
                            packageName = name.substring(0, idx).replace(LEFT_LINE_CONSTANT, POINT_CONSTANT);  // 获取包名 并把'/' 替换成'.'
                        }
                        /**
                         * 若实体是一个包 且可以继续迭代
                         */
                        if ((idx != -1) || recursive) {
                            if (name.endsWith(POINT_CLASS_CONSTANT) && !entry.isDirectory()) {  // 若为.class 文件 且不是目录
                                String className = name.substring(packageName.length() + 1, name.length() - 6);  // 则去掉.class 后缀并获取真正的类名
                                classes.add(Class.forName(packageName + '.' + className)); // 把获得到的类名添加到classes
                            }
                        }
                    }
                }
            }
        }

        return classes;
    }


    /**
     * 以文件的形式来获取包下的所有Class
     * findAndAddClassesInPackageByFile
     *
     * @param packageName
     * @param packagePath
     * @param recursive
     * @param classes
     */
    public static void findAndAddClassesInPackageByFile(
            String packageName, String packagePath,
            final boolean recursive,
            List<Class<?>> classes) throws Exception {


        File dir = new File(packagePath);  // 获取此包的目录并建立一个File

        if (!dir.exists() || !dir.isDirectory()) {  // 若dir 不存在或者 也不是目录就直接返回
            return;
        }

        File[] dirfiles = dir.listFiles(new FileFilter() {  // 若dir 存在 则获取包下的所有文件、目录

            /**
             * 自定义过滤规则 如果可以循环(包含子目录) 或则是以.class 结尾的文件(编译好的java 字节码文件)
             * @param file
             * @return
             */
            @Override
            public boolean accept(File file) {
                return (recursive && file.isDirectory()) || (file.getName().endsWith(POINT_CLASS_CONSTANT));
            }
        });

        /**
         * 循环所有文件获取java 类文件并添加到集合中
         */
        for (File file : dirfiles) {
            if (file.isDirectory()) {  // 若file 为目录 则继续扫描
                findAndAddClassesInPackageByFile(packageName + "." + file.getName(), file.getAbsolutePath(), recursive,
                        classes);
            } else {  // 若file 为java 类文件 则去掉后面的.class 只留下类名
                String className = file.getName().substring(0, file.getName().length() - 6);
                classes.add(Class.forName(packageName + '.' + className));  // 把className 添加到集合中去

            }
        }
    }
}
访问跳转页面index.jsp
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
<%--
  Created by IntelliJ IDEA.
  User: apple
  Date: 2019-11-07
  Time: 13:28
  To change this template use File | Settings | File Templates.
--%>
<%--
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
--%>
<html>
  <head>
    <title>My Fucking SpringMVC</title>
  </head>
  <body>
  <h2>The Lie We Live!</h2>
  <H2>My Fucking SpringMVC</H2>
  </body>
</html>
自定义DispatcherServlet设计,继承HttpServlet,重写initdoGetdoPost等方法,以及自定义注解要实现的功能。
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
package com.tjt.springmvc;


import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;



/**
 * DispatcherServlet 处理SpringMVC 框架流程
 * 主要流程:
 * 1、包扫描获取包下面所有的类
 * 2、初始化包下面所有的类
 * 3、初始化HandlerMapping 方法,将url 和方法对应上
 * 4、实现HttpServlet 重写doPost 方法
 *
 */
public class DispatcherServlet extends HttpServlet {

    /**
     * 部分静态常量
     */
    private static String PACKAGE_CLASS_NULL_EX = "包扫描后的classes为null";
    private static String HTTP_NOT_EXIST = "sorry http is not exit 404";
    private static String METHOD_NOT_EXIST = "sorry method is not exit 404";
    private static String POINT_JSP = ".jsp";
    private static String LEFT_LINE = "/";

    /**
     * 用于存放SpringMVC bean 的容器
     */
    private ConcurrentHashMap<String, Object> mvcBeans = new ConcurrentHashMap<>();
    private ConcurrentHashMap<String, Object> mvcBeanUrl = new ConcurrentHashMap<>();
    private ConcurrentHashMap<String, String> mvcMethodUrl = new ConcurrentHashMap<>();
    private static String PROJECT_PACKAGE_PATH = "com.tjt.springmvc";


    /**
     * 按顺序初始化组件
     * @param config
     */
    @Override
    public void init(ServletConfig config) {
        String packagePath = PROJECT_PACKAGE_PATH;
        try {
            //1.进行报扫描获取当前包下面所有的类
            List<Class<?>> classes = comscanPackage(packagePath);
            //2.初始化springmvcbean
            initSpringMvcBean(classes);
        } catch (Exception e) {
            e.printStackTrace();
        }
        //3.将请求地址和方法进行映射
        initHandMapping(mvcBeans);
    }


    /**
     * 调用ClassUtils 工具类获取工程中所有的class
     * @param packagePath
     * @return
     * @throws Exception
     */
    public List<Class<?>> comscanPackage(String packagePath) throws Exception {
        List<Class<?>> classes = ClassUtils.getClasses(packagePath);
        return classes;
    }

    /**
     * 初始化SpringMVC bean
     *
     * @param classes
     * @throws Exception
     */
    public void initSpringMvcBean(List<Class<?>> classes) throws Exception {
        /**
         * 若包扫描出的classes 为空则直接抛异常
         */
        if (classes.isEmpty()) {
            throw new Exception(PACKAGE_CLASS_NULL_EX);
        }

        /**
         * 遍历所有classes 获取@MyController 注解
         */
        for (Class<?> aClass : classes) {
            //获取被自定义注解的controller 将其初始化到自定义springmvc 容器中
            MyController declaredAnnotation = aClass.getDeclaredAnnotation(MyController.class);
            if (declaredAnnotation != null) {
                //获取类的名字
                String beanid = lowerFirstCapse(aClass.getSimpleName());
                //获取对象
                Object beanObj = aClass.newInstance();
                //放入spring 容器
                mvcBeans.put(beanid, beanObj);
            }
        }

    }

    /**
     * 初始化HandlerMapping 方法
     *
     * @param mvcBeans
     */
    public void initHandMapping(ConcurrentHashMap<String, Object> mvcBeans) {
        /**
         * 遍历springmvc 获取注入的对象值
         */
        for (Map.Entry<String, Object> entry : mvcBeans.entrySet()) {
            Object objValue = entry.getValue();
            Class<?> aClass = objValue.getClass();
            //获取当前类 判断其是否有自定义的requestMapping 注解
            String mappingUrl = null;
            MyRequestMapping anRequestMapping = aClass.getDeclaredAnnotation(MyRequestMapping.class);
            if (anRequestMapping != null) {
                mappingUrl = anRequestMapping.value();
            }
            //获取当前类所有方法,判断方法上是否有注解
            Method[] declaredMethods = aClass.getDeclaredMethods();
            /**
             * 遍历注解
             */
            for (Method method : declaredMethods) {
                MyRequestMapping methodDeclaredAnnotation = method.getDeclaredAnnotation(MyRequestMapping.class);
                if (methodDeclaredAnnotation != null) {
                    String methodUrl = methodDeclaredAnnotation.value();
                    mvcBeanUrl.put(mappingUrl + methodUrl, objValue);
                    mvcMethodUrl.put(mappingUrl + methodUrl, method.getName());
                }
            }

        }

    }

    /**
     * @param str
     * @return 类名首字母小写
     */
    public static String lowerFirstCapse(String str) {
        char[] chars = str.toCharArray();
        chars[0] += 32;
        return String.valueOf(chars);

    }

    /**
     * doPost 请求
     * @param req
     * @param resp
     * @throws ServletException
     * @throws IOException
     */
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        try {
            /**
             * 处理请求
             */
            doServelt(req, resp);
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
    }

    /**
     * doServelt 处理请求
     * @param req
     * @param resp
     * @throws IOException
     * @throws NoSuchMethodException
     * @throws InvocationTargetException
     * @throws IllegalAccessException
     * @throws ServletException
     */
    private void doServelt(HttpServletRequest req, HttpServletResponse resp) throws IOException, NoSuchMethodException, InvocationTargetException, IllegalAccessException, ServletException {
        //获取请求地址
        String requestUrl = req.getRequestURI();
        //查找地址所对应bean
        Object object = mvcBeanUrl.get(requestUrl);
        if (Objects.isNull(object)) {
            resp.getWriter().println(HTTP_NOT_EXIST);
            return;
        }
        //获取请求的方法
        String methodName = mvcMethodUrl.get(requestUrl);
        if (methodName == null) {
            resp.getWriter().println(METHOD_NOT_EXIST);
            return;
        }


        //通过构反射执行方法
        Class<?> aClass = object.getClass();
        Method method = aClass.getMethod(methodName);

        String invoke = (String) method.invoke(object);
        // 获取后缀信息
        String suffix = POINT_JSP;
        // 页面目录地址
        String prefix = LEFT_LINE;
        req.getRequestDispatcher(prefix + invoke + suffix).forward(req, resp);




    }

    /**
     * doGet 请求
     * @param req
     * @param resp
     * @throws ServletException
     * @throws IOException
     */
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        this.doPost(req, resp);
    }


}
测试手写SpringMVC 框架效果类TestMySpringMVC
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
package com.tjt.springmvc;


/**
 * 手写SpringMVC 测试类
 * TestMySpringMVC
 */
@MyController
@MyRequestMapping(value = "/tjt")
public class TestMySpringMVC {


    /**
     * 测试手写SpringMVC 框架效果 testMyMVC1
     * @return
     */
    @MyRequestMapping("/mvc")
    public String testMyMVC1() {
        System.out.println("he Lie We Live!");
        return "index";
    }


}
配置Tomcat 用于运行Web 项目

配置Tomcat

运行项目,访问测试

1、输入正常路径http://localhost:8080/tjt/mvc访问测试效果如下:

正常路径测试效果

2、输入非法(不存在)路径http://localhost:8080/tjt/mvc8 访问测试效果如下:

非法路径请求效果

3、控制台打印“The Lie We Live”如下:

控制台输出

测试效果如上则证明手写SpringMVC 框架 已成功。

彩蛋

本文知识按照Spring MVC的大致思想,写了一个简单版的,如果感兴趣可以把这个项目继续完善。

最近,我一直也在搞Spring源码这一块,有兴趣可以加入,一起搞。

目前,Spring源码分析文章已分享了如下文章:

参考:www.cnblogs.com/taojietaoge

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2021-10-26,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 Java后端技术全栈 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
小伙伴想写个 IDEA 插件么?这些 API 了解一下!
" 在看完 IDEA 插件开发简易教程后,小伙伴们是否迫不及待的想自己上手整一个插件了?心里规划好了一二三,但是却不知道从哪里开始下手。下面我分享下自己整理的一些常用的 API。 "
程序员小航
2020/11/23
2.3K0
小伙伴想写个 IDEA 插件么?这些 API 了解一下!
Idea插件开发
https://www.w3cschool.cn/intellij_idea_doc/
码客说
2024/08/04
4730
Idea插件开发
《IntelliJ IDEA 插件开发》第六节:选定对象批量织入“x.set(y.get)”代码,自动生成vo2dto
这些年从事编程开发以来,我好像发现了大部分研发那些不愿意干的事,都成就了别人。就像部署服务麻烦,有了Docker、简单CRUD不想开发,有了低代码、给方法代码加监控繁琐、有了非入侵的全链路监控。
小傅哥
2021/12/15
8730
《IntelliJ IDEA 插件开发》第六节:选定对象批量织入“x.set(y.get)”代码,自动生成vo2dto
为 TheRouter 开发一个 IDEA 插件
这篇文章是假定你已经有了 idea 插件开发的入门知识,来教你如何实现一个实际项目的功能。如果你还不知道如何开发一个插件,建议先从这个链接查看官网相关文档 https://plugins.jetbrains.com/docs/intellij/welcome.html。
用户1907613
2023/10/19
3970
为 TheRouter 开发一个 IDEA 插件
开发属于自己的插件 | IDEA &amp; Android Studio插件开发指南
谷轩宇——从事安卓开发,目前效力于通天塔技术开放组是否曾经被ide重复繁琐的操作所困扰,又或者没有心仪的UI控件而难受。那么请阅读这篇文章,掌握idea插件的开发流程,开发属于自己的插件,造福开源社区。
京东技术
2018/09/28
5K0
开发属于自己的插件 | IDEA &amp; Android Studio插件开发指南
idea插件开发记录
插件开发示例 ---- 功能开发代码示例 java package com.cjl.plugins.code.hints; import com.cjl.plugins.code.code.NavigatorPanel; import com.cjl.plugins.code.http.HttpUtils; import com.cjl.plugins.code.json.Json; import com.intellij.codeInsight.hint.HintManager; import com.
司夜
2023/03/31
7700
idea插件开发记录
只需三步实现Databinding插件化
首先为何我要实现Databinding这个小插件,主要是在日常开发中,发现每次通过Android Studio的Layout resource file来创建xml布局文件时,布局文件的格式都没有包含Databinding所要的标签。导致的问题就是每次都要重复手动修改布局文件,添加标签等。
Rouse
2019/07/22
1K0
只需三步实现Databinding插件化
《IntelliJ IDEA 插件开发》第四节:扩展创建工程向导步骤,开发DDD脚手架
你做这个东西的价值是什么?有竞品调研吗?能赋能业务吗?那不已经有同类的了,你为什么还自己造轮子?
小傅哥
2021/12/01
1.2K0
《IntelliJ IDEA 插件开发》第四节:扩展创建工程向导步骤,开发DDD脚手架
IDEA Cody 插件实现原理
近年来,智能编程助手 在开发者日常工作中变得越来越重要。IDEA Cody 插件是 JetBrains 生态中一个重要的插件,它可以帮助开发者 快速生成代码、自动补全、并提供智能提示,从而大大提升开发效率。今天我们将深入探讨 Cody 插件的实现原理,看看它是如何工作的。
井九
2024/10/12
3440
IDEA Cody 插件实现原理
Android MVP 代码自动生成插件开发
本文会出现的原因是,lucio在遵循Google的Android MVP示例代码的模式开发一个小的程序,发现我们会需要写很多重复的代码,更加麻烦的是,我们需要创建很多重复的文件。每开发一个小的模块,至少会需要创建Activity、Contract、Fragment和Presenter四个文件。
luciozhang
2023/04/22
5880
Android MVP 代码自动生成插件开发
IntelliJ插件开发-京东工程师教你改造你的IDE
王帅廷,京东 Android高级开发工程师,6年以上开发经验,对设计框架有着深刻的认识,负责京东商城研发工具的开发,设计并完成了多个IntelliJ插件的开发工作。
京东技术
2018/07/30
3.4K1
IntelliJ插件开发-京东工程师教你改造你的IDE
你们要的Intellij IDEA 插件开发秘籍,来了!
王昭霞,软件开发工程师,先后从事脚本工具编写、工具开发、Android基础模块开发等工作。
京东技术
2018/09/28
57.3K13
你们要的Intellij IDEA 插件开发秘籍,来了!
《IntelliJ IDEA 插件开发》第三节:开发工具栏和Tab页,展示股票行情和K线
以前,我不懂。写的技术就是技术内容,写的场景就是场景分析,但从读者的阅读我发现,大家更喜欢的是技术与场景结合,尤其是用技术结合那些羞羞答答的场景,虽然嘴上都不说。
小傅哥
2021/11/19
2.7K0
让代码自动补全的全套流程
作者:熊唯,黄飞 ,腾讯 PCG/QQ研发中心/CV应用研究组 AI 如果真的可以写代码了,程序员将何去何从?近几年,NLP 领域的生成式任务有明显的提升,那通过 AI 我们可以让代码自动完成后续补全吗?本文主要介绍了如何使用 GPT2 框架实现代码自动补全的功能。 如果 AI 真的可以自己写代码了,程序员将何去何从? 我去年做过一个代码补全的小功能,打包为 androidStudio 插件,使用效果如下: 代码补全模型预测出的结果有时的确会惊吓到我,这也能学到~ 那如果给它见识了全世界的优秀
腾讯技术工程官方号
2020/07/31
2.4K0
《IntelliJ IDEA 插件开发》第 五 节:IDEA工程右键菜单,自动生成ORM代码
几年前,大家并不是这样,那时候还有很多东西可以创新,乱世出英雄总能在一个方向深耕并做出一款款好用的产品功能、框架服务、技术组件等。但后来好像这样的情况开始减少了,取而代之的是重复、复刻、照搬,换个新的皮肤、换个新的样式、换个新的名字,就是取巧的新东西了。
小傅哥
2021/12/13
2.5K0
《IntelliJ IDEA 插件开发》第 五 节:IDEA工程右键菜单,自动生成ORM代码
p3c 插件,是怎么检查出你那屎山的代码?
虽然我们都被称为码农,也都是写着代码,但因为所处场景需求的不同,所以各类码农也都做着不一样都事情。
小傅哥
2021/10/08
1.1K0
IDEA插件:快速删除Java代码中的注释
本文针对Java语言,介绍一种利用第三方库的方式,可以方便快速地移除代码中的注释。
xiaoxi666
2021/02/17
3.2K0
IntelliJ IDEA/Android Studio插件开发指南
目前在为安卓手机QQ做自动化的相关工作,包括UI自动化,逻辑层自动化等。使用到的uiautomator等框架,需要在Android Studio进行编码工作。 其中很多工作如果做到插件化的话,可以有效地节省时间成本,提升大家的自动化效率。 比如运行自动化的时候,需要用到我们自定义的shell命令。我们可以通过插件来实现一键运行。 在运行adb shell am instrument命令的时候,需要编译出test APK和target APK。手Q整体的git仓库很大,编译耗时很久。我们想着通过一些方法来优化这个耗时。其中一个步骤就是,把我们代码目录下的变更,同步到一个编译目录下。 这个小功能的最合适的形态,自然就是Android Studio上的一个插件。点击一个按钮,一键同步,那可真是在米奇妙妙屋吃妙脆角——妙到家了! Android Studio是基于Intellij IDEA开发的,所以开发Android Studio的插件,其实就是开发IDEA的插件。 根据官方推荐,使用IDEA IDE来开发IDEA插件。
于果
2021/08/25
2.8K0
《IntelliJ IDEA 插件开发》第一节:两种方式创建插件工程
对于码农这一行业的编程学习生涯来说,会遇到很多的不会,不会搭建IDEA工程、不会写老师的案例、不会完成书中的效果、不会做项目的需求、不会实现复杂的逻辑、不会抽象工程的结构等等。但这些不会当中并不是所有的不会,都因为太复杂学不会,而是很大一部分内容因为找不到好的资料、没有清晰的文档、缺少完整的案例,导致不知道所以不会。
小傅哥
2021/10/20
3.1K0
技术调研,IDEA 插件怎么开发「脚手架、低代码可视化编排、接口生成测试」?
你觉得肯德基全家桶是什么?一家人一起吃的桶吗,就那么一点点?不是,肯德基全家桶说的是,鸡的全家桶!
小傅哥
2021/09/14
1.7K0
推荐阅读
相关推荐
小伙伴想写个 IDEA 插件么?这些 API 了解一下!
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验