美文网首页
手写Mini版Spring实现的基本思路

手写Mini版Spring实现的基本思路

作者: AC编程 | 来源:发表于2022-09-13 07:59 被阅读0次
    手写Mini版Spring实现的基本思路

    github-demo源码

    alanchenyan/alanchen-mvc

    一、实现功能

    手写实现简单Spring:MVC、IOC、DI功能。

    二、说明

    该项目需要部署在tomcat等容器中运行。

    三、SpringMVC(alanchen-mvc)加载顺序

    • 1、tomcat
    • 2、web.xml
    • 3、DispatchServlet(AlanChenDispatcherServlet)
    • 4、Servlet.init()初始化方法

    四、init核心代码

    @Override
    public void init(ServletConfig config) {
    
        // 1、加载配置文件
        doLoadConfig(config);
    
        // 2、扫描所有的class文件
        doScanPackage(contextConfig.getProperty("scanPackage"));
    
        // 3、初始化IOC容器,将扫描到的类实例化,保存到IOC容器中(IOC部分)
        doInstance();
    
        // TODO 在DI之前完成AOP(新生成的代理对象)
    
        // 4、完成依赖注入(DI部分)
        doAutowired();
    
        // 5、映射访问路径和方法的关系(MVC部分)
        initHandlerMapping();
    }
    

    五、pom.xml

    加入javax.servlet依赖

    <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
        <modelVersion>4.0.0</modelVersion>
        <groupId>com.alanchen</groupId>
        <artifactId>alanchen-mvc</artifactId>
        <version>0.0.1-SNAPSHOT</version>
        <build>
            <plugins>
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-compiler-plugin</artifactId>
                    <configuration>
                        <source>6</source>
                        <target>6</target>
                    </configuration>
                </plugin>
            </plugins>
        </build>
        <packaging>war</packaging>
    
        <dependencies>
            <dependency>
                <groupId>javax.servlet</groupId>
                <artifactId>servlet-api</artifactId>
                <version>3.0-alpha-1</version>
                <scope>provided</scope>
            </dependency>
        </dependencies>
    </project>
    

    六、WEB-INF/web.xml配置

    配置Servlet

    <?xml version="1.0" encoding="UTF-8"?>
    <web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xmlns="http://java.sun.com/xml/ns/javaee"
        xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
        version="2.5">
        <display-name>alanchen-mvc</display-name>
        
        <servlet>
            <servlet-name>AlanChenDispatcherServlet</servlet-name>
            <servlet-class>com.spring.alanchen.myspring.servlet.AlanChenDispatcherServlet</servlet-class>
            <init-param>
                <param-name>contextConfigLocation</param-name>
                <param-value>application.properties</param-value>
            </init-param>
            <!-- 表示容器在应用启动时就加载并初始化这个servlet,实例化并调用其init()方法 -->
            <load-on-startup>0</load-on-startup>
        </servlet>
        
        <servlet-mapping>
            <servlet-name>AlanChenDispatcherServlet</servlet-name>
            <url-pattern>/</url-pattern>
        </servlet-mapping>
        
    </web-app>
    

    七、代码明细

    7.1 手写Spring核心代码
    package com.spring.alanchen.myspring.servlet;
    
    import java.lang.reflect.Method;
    
    /**
     * @author Alan Chen
     * @description
     * @date 2020-07-28
     */
    public class Handler {
    
        private Object controller;
        
        private Method method;
    
        public Object getController() {
            return controller;
        }
    
        public void setController(Object controller) {
            this.controller = controller;
        }
    
        public Method getMethod() {
            return method;
        }
    
        public void setMethod(Method method) {
            this.method = method;
        }
    }
    
    
    /**
     * @author Alan Chen
     * @description
     * 
     *  SpringMVC加载顺序 1、tomcat 2、web.xml 3、DispatchServlet
     * 
     *  MVC作为入口 启动IOC容器 完成DI
     * 
     * @date 2020-07-28
     */
    public class AlanChenDispatcherServlet extends HttpServlet {
    
        private static final long serialVersionUID = 1L;
    
        private Properties contextConfig = new Properties();
    
        private List<String> classNames = new ArrayList<String>();
    
        private Map<String, Object> beans = new HashMap<String, Object>();
    
        private Map<String, Handler> handlerMapping = new HashMap<String, Handler>();
    
        @Override
        public void init(ServletConfig config) {
    
            // 1、加载配置文件
            doLoadConfig(config);
    
            // 2、扫描所有的class文件
            doScanPackage(contextConfig.getProperty("scanPackage"));
    
            // 3、初始化IOC容器,将扫描到的类实例化,保存到IOC容器中(IOC部分)
            doInstance();
    
            // TODO 在DI之前完成AOP(新生成的代理对象)
    
            // 4、完成依赖注入(DI部分)
            doAutowired();
    
            // 5、映射访问路径和方法的关系(MVC部分)
            initHandlerMapping();
    
            System.out.println("alanchenMVC is init.");
        }
    
        /**
         * 加载配置文件
         * 
         * @param config
         */
        private void doLoadConfig(ServletConfig config) {
            String contextConfigLocation = config.getInitParameter("contextConfigLocation");
            InputStream inStream = this.getClass().getClassLoader().getResourceAsStream(contextConfigLocation);
            try {
                contextConfig.load(inStream);
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                if (inStream != null) {
                    try {
                        inStream.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    
        @Override
        protected void doGet(HttpServletRequest req, HttpServletResponse resp) {
            doDispatch(req, resp);
        }
    
        @Override
        protected void doPost(HttpServletRequest req, HttpServletResponse resp) {
            doDispatch(req, resp);
        }
    
        /**
         * 6、委派,通过URL去找到一个对应的Method并通过Response返回
         * 
         * @param req
         * @param resp
         */
        private void doDispatch(HttpServletRequest req, HttpServletResponse resp) {
            // uri=/alanchen-mvc/user/query
            String uri = req.getRequestURI();
            
            //context=/alanchen-mvc
            String context = req.getContextPath();
            
            //path=/user/query
            String path = uri.replaceAll(context, "").replaceAll("/+", "/");
            
            if(!handlerMapping.containsKey(path)) {
                writeResult(resp,"404 NOT FOUND");
                return;
            }
    
            Handler handler = handlerMapping.get(path);
            Method method = handler.getMethod();
            Object controller = handler.getController();
    
            Object arg[] = handArg(req, resp, method);
            Object result=null;
            try {
                result = method.invoke(controller, arg);
            }catch(Exception e) {
                writeResult(resp,"500 服务器异常");
                e.printStackTrace();
            }
            
            if(result!=null) {
                writeResult(resp,result);
            }else {
                System.out.println("没有返回值");
            }
        }
        
        /**
         * 将返回值写回客户端
         * @param resp
         * @param result
         */
        private void writeResult(HttpServletResponse resp,Object result) {
            PrintWriter pw = null;
            try {
                String obj = String.valueOf(result);
                pw = resp.getWriter();
                pw.write(obj);
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                if (pw != null) {
                    pw.close();
                }
            }
        }
    
        /**
         * 获取请求参数值
         * 
         * @param req
         * @param resp
         * @param method
         * @return
         */
        private Object[] handArg(HttpServletRequest req, HttpServletResponse resp, Method method) {
            //获取形参列表
            Class<?>[] paramClazzs = method.getParameterTypes();
            Object[] args = new Object[paramClazzs.length];
    
            int args_i = 0;
            int index = 0;
    
            for (Class<?> paramClazz : paramClazzs) {
                if (ServletRequest.class.isAssignableFrom(paramClazz)) {
                    args[args_i++] = req;
                }
    
                if (ServletResponse.class.isAssignableFrom(paramClazz)) {
                    args[args_i++] = resp;
                }
    
                Annotation[] paramAns = method.getParameterAnnotations()[index];
                if (paramAns.length > 0) {
                    for (Annotation paramAn : paramAns) {
                        if (AlanChenRequestParam.class.isAssignableFrom(paramAn.getClass())) {
                            AlanChenRequestParam rp = (AlanChenRequestParam) paramAn;
                            args[args_i++] = req.getParameter(rp.value());
                        }
                    }
                }
                index++;
            }
            return args;
        }
    
        /**
         * 映射访问路径和方法的关系
         */
        private void initHandlerMapping() {
            if (beans.entrySet().size() == 0) {
                System.out.println("实例化的类数量为0");
                return;
            }
    
            try {
                for (Map.Entry<String, Object> entry : beans.entrySet()) {
                    Object instance = entry.getValue();
                    Class<?> clazz = instance.getClass();
                    
                    //用取反的方式减少if嵌套层数
                    if (!clazz.isAnnotationPresent(AlanChenController.class)) {
                        continue;
                    }
                    
                    AlanChenRequestMapping mapping = clazz.getAnnotation(AlanChenRequestMapping.class);
                    String classPath = "/"+mapping.value();
            
                    /**
                     * clazz.getDeclaredMethods() 是获取所有方法
                     * clazz.getMethods() 只获取public修饰的方法
                     * Spring也只处理public的方法
                     */
                    Method[] methods = clazz.getMethods();
                    
                    for (Method method : methods) {
                        if (!method.isAnnotationPresent(AlanChenRequestMapping.class)) {
                            continue;
                        }
                        
                        AlanChenRequestMapping methodMapping = method.getAnnotation(AlanChenRequestMapping.class);
                        String methodPath = "/"+methodMapping.value();
    
                        Handler handler = new Handler();
                        handler.setController(instance);
                        handler.setMethod(method);
                        
                        //将多个重复的斜杠替换成一个斜杠
                        String url = (classPath + methodPath).replaceAll("/+", "/");
                        handlerMapping.put(url, handler);
                        
                        System.out.println("url="+url+";method="+method.getName());
                    }
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    
        /**
         * 完成依赖注入(DI)
         */
        private void doAutowired() {
            if (beans.entrySet().size() == 0) {
                System.out.println("实例化的类数量为0");
                return;
            }
    
            try {
                for (Map.Entry<String, Object> entry : beans.entrySet()) {
                    Object instance = entry.getValue();
                    Class<?> clazz = instance.getClass();
                    
                    //获取所有属性,包括private修饰的属性
                    Field[] fileds = clazz.getDeclaredFields();
    
                    for (Field field : fileds) {
                        if (!field.isAnnotationPresent(AlanChenAutowired.class)) {
                            continue;
                        }
                        
                        AlanChenAutowired autowired = field.getAnnotation(AlanChenAutowired.class);
    
                        // 即使属性是private,也强制设置为可访问
                        field.setAccessible(true);
    
                        // 1、优先通过用户配置的value去取对象
                        String beanName = autowired.value();
                        if (beanName.isEmpty()) {
                            // 2、通过类名首字母小写取对象
                            beanName = field.getName();
                        }
    
                        Object obj = beans.get(beanName);
                        // 如果通过名称无法获取到对象,则通过类型获取
                        if (obj == null) {
                            // 3、通过接口类型取对象,注入接口的实现类
                            beanName = field.getType().getName();
                            obj = beans.get(beanName);
                        }
                        field.set(instance, obj);
                    }
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    
        /**
         * 初始化IOC容器,将扫描到的类实例化,保存到IOC容器中(IOC部分)
         */
        private void doInstance() {
            if (classNames.size() == 0) {
                System.out.println("扫描到的class文件数量为0");
                return;
            }
    
            try {
                for (String className : classNames) {
                    Class<?> clazz = Class.forName(className);
    
                    if (clazz.isAnnotationPresent(AlanChenController.class)) {
                        // 初始化Controller类
                        Object instace = clazz.newInstance();
                        String beanName = lowerFirstCase(clazz.getSimpleName());
                        beans.put(beanName, instace);
                    } else if (clazz.isAnnotationPresent(AlanChenService.class)) {
                        // 初始化Service类
                        Object instace = clazz.newInstance();
                        AlanChenService service = clazz.getAnnotation(AlanChenService.class);
    
                        // 1、优先使用用户配置的value值做为key
                        String beanName = service.value();
                        if (beanName.isEmpty()) {
                            // 2、如果没有配置value值,则用类名首字母小写的类名做为key
                            beanName = lowerFirstCase(clazz.getSimpleName());
                        }
                        System.out.println("beanName=" + beanName);
                        beans.put(beanName, instace);
    
                        // 3、用实现接口的类型名称做为key,注入接口的实现类
                        Class<?>[] intefaces = clazz.getInterfaces();
                        for (Class inteface : intefaces) {
                            System.out.println("inteface=" + inteface.getName());
                            beans.put(inteface.getName(), instace);
                        }
                    } else {
                        continue;
                    }
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    
        /**
         * 类名首字母转小写
         * 
         * @param str
         * @return
         */
        private String lowerFirstCase(String str) {
            if(str == null || str.isEmpty()) {
                return str;
            }
            
            /**
             * 首字母是大写才转小写,否则不需要处理
             * 大写字母范围:65-90
             */
            char[] chars = str.toCharArray();
            char first = chars[0];
            if(first>=65 && first <=90) {
                chars[0] += 32;
                return String.valueOf(chars);
            }
            return str;
        }
    
        /**
         * 扫描所有的class文件
         * 
         * @param basePackage
         */
        private void doScanPackage(String basePackage) {
            //硬盘上的目录是用斜杠/分割,因此要将.替换成斜杠/
            URL url = this.getClass().getClassLoader().getResource("/" + basePackage.replaceAll("\\.", "/"));
            
            //fileStr=D://alanchen-mvc/WEB-INF/classes/com/spring/alanchen/
            String fileStr = url.getFile();
    
            File file = new File(fileStr);
            String[] filesStr = file.list();
            for (String path : filesStr) {
                File filePath = new File(fileStr + path);
                if (filePath.isDirectory()) {
                    // 递归直到找到文件为止
                    doScanPackage(basePackage + "." + path);
                } else {
                    if(filePath.getName().endsWith(".class")) {
                        classNames.add(basePackage + "." + filePath.getName().replace(".class", ""));
                    }
                }
            }
        }
    }
    
    7.2 annaotation
    package com.spring.alanchen.myspring.annaotation;
    
    import java.lang.annotation.*;
    
    @Target({ElementType.FIELD}) // 该注解用在类的成员变量上
    @Retention(RetentionPolicy.RUNTIME) // 运行时可通过反射机制获取该注解
    @Documented // 该注解包含到javadoc中
    
    public @interface AlanChenAutowired {
    
        String value() default "";
    }
    
    
    @Target({ElementType.TYPE}) // 该注解用在类上
    @Retention(RetentionPolicy.RUNTIME) // 运行时可通过反射机制获取该注解
    @Documented // 该注解包含到javadoc中
    // @Inherited 该注解可被继承
    
    public @interface AlanChenController {
    
        String value() default "";
    }
    
    
    @Target({ElementType.TYPE,ElementType.METHOD}) // 该注解用在类上、方法上
    @Retention(RetentionPolicy.RUNTIME) // 运行时可通过反射机制获取该注解
    @Documented // 该注解包含到javadoc中
    
    public @interface AlanChenRequestMapping {
    
        String value() default "";
    }
    
    
    @Target({ElementType.PARAMETER}) // 该注解用参数上
    @Retention(RetentionPolicy.RUNTIME) // 运行时可通过反射机制获取该注解
    @Documented // 该注解包含到javadoc中
    
    public @interface AlanChenRequestParam {
    
        String value() default "";
    }
    
    
    @Target({ElementType.TYPE}) // 该注解用在类上
    @Retention(RetentionPolicy.RUNTIME) // 运行时可通过反射机制获取该注解
    @Documented // 该注解包含到javadoc中
    
    public @interface AlanChenService {
    
        String value() default "";
    }
    
    7.3 service
    package com.spring.alanchen.service;
    
    /**
     * @author Alan Chen
     * @description
     * @date 2020-07-28
     */
    public interface IUserSerivce {
    
        String query(String name,String age);
    }
    
    @AlanChenService
    public class UserSerivceImpl implements IUserSerivce {
    
        public String query(String name, String age) {
            return "name="+name+";age"+age;
        }
    }
    
    7.4 controller
    package com.spring.alanchen.controller;
    
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    
    import com.spring.alanchen.myspring.annaotation.AlanChenAutowired;
    import com.spring.alanchen.myspring.annaotation.AlanChenController;
    import com.spring.alanchen.myspring.annaotation.AlanChenRequestMapping;
    import com.spring.alanchen.myspring.annaotation.AlanChenRequestParam;
    import com.spring.alanchen.service.IUserSerivce;
    
    
    /**
     * @author Alan Chen
     * @description 访问:http://127.0.0.1:8080/alanchen-mvc/user/query?name=ac&age=18
     * @date 2020-07-29
     */
    @AlanChenController
    @AlanChenRequestMapping("/user")
    public class UserController {
    
        @AlanChenAutowired
        IUserSerivce userSerivceImpl;
    
        @AlanChenRequestMapping("query")
        public String query(HttpServletRequest request, HttpServletResponse response,
                @AlanChenRequestParam("name") String name, @AlanChenRequestParam("age") String age) {
            
            return userSerivceImpl.query(name, age);
        }
    }
    
    7.5 application.properties
    scanPackage=com.spring.alanchen
    

    相关文章

      网友评论

          本文标题:手写Mini版Spring实现的基本思路

          本文链接:https://www.haomeiwen.com/subject/pvjyirtx.html