zoukankan      html  css  js  c++  java
  • 用300行代码手写提炼Spring的核心原理

    本篇内容定位:

    • 具有一年以上的SpringMVC使用经验。
    • 希望深入了解Spring源码的人群,对Spring有一个整体的宏观感受。
    • 全程手写实现SpringMVC的核心功能,帮助大家更深刻地理解设计模式,从最单一的v1版本一步一步优化为v2版本,最后到v3版本。

    mini版Spring实现的基本思路:

     

    web.xml

    <?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/j2ee" xmlns:javaee="http://java.sun.com/xml/ns/javaee"
             xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
             xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"
             version="2.4">
      <display-name>Web Application</display-name>
    
      <!-- mvcframework config start -->
      <servlet>
        <servlet-name>dispatcher</servlet-name>
        <servlet-class>com.zc.mvcframework.v3.servlet.MyDispatcherServlet</servlet-class>
        <init-param>
          <param-name>contextConfigLocation</param-name>
          <param-value>application.properties</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
      </servlet>
      <servlet-mapping>
        <servlet-name>dispatcher</servlet-name>
        <url-pattern>/*</url-pattern>
      </servlet-mapping>
      <!-- mvcframework config end -->
    
      <!-- welcome page -->
      <welcome-file-list>
        <welcome-file>/index.html</welcome-file>
      </welcome-file-list>
    </web-app>

    application.properties

    scanPackage=com.zc.demo

    自定义的注解

    /**
     * @author 周聪
     */
    @Target({ElementType.CONSTRUCTOR, ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD, ElementType.ANNOTATION_TYPE})
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    public @interface MyAutowired {
        String value() default "";
    }
    /**
     * @author 周聪
     */
    @Target({ElementType.TYPE})
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    public @interface MyController {
    
            String value() default "";
    
    }
    /**
     * @author 周聪
     */
    @Target({ElementType.METHOD, ElementType.TYPE})
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    public @interface MyRequestMapping {
        String value() default "";
    }
    /**
     * @author 周聪
     */
    @Target(ElementType.PARAMETER)
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    public @interface MyRequestParam {
        String value() default "";
    }
    /**
     * @author 周聪
     */
    @Target({ElementType.TYPE})
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    public @interface MyService {
        String value() default "";
    }

    DemoController

    /**
     * @ClassName DemoAction
     * @Author 周聪
     * @Date 2021/1/29 10:52
     * @Version 1.0
     * @Description
     */
    @MyController
    @MyRequestMapping(value = "/demo")
    public class DemoAction {
        @MyAutowired
        private IDemoService demoService;
    
        @MyRequestMapping(value = "query")
        public void query(HttpServletRequest req, HttpServletResponse resp, @MyRequestParam(value = "name") String name) {
    //        String result = demoService.get(name);
            String result = "My name is " + name;
            try {
                resp.getWriter().write(result);
            } catch (IOException e) {
                e.printStackTrace();
            }
    
        }
    
        @MyRequestMapping(value = "add")
        public void add(HttpServletRequest req, HttpServletResponse resp, @MyRequestParam(value = "a") Integer a, @MyRequestParam(value = "b") Integer b) {
            try {
                resp.getWriter().write(a + "+" + b + "=" + (a + b));
            } catch (IOException e) {
                e.printStackTrace();
            }
    
        }
    
        @MyRequestMapping("/remove")
        public String remove(@MyRequestParam("id") Integer id){
            return "" + id;
        }
    
    }

    DispatcherServlet的v1版本

    package com.zc.mvcframework.v2.servlet;
    
    import com.zc.mvcframework.annotation.*;
    
    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.File;
    import java.io.IOException;
    import java.io.InputStream;
    import java.lang.reflect.Field;
    import java.lang.reflect.Method;
    import java.lang.reflect.Parameter;
    import java.net.URL;
    import java.util.*;
    
    /**
     * @ClassName MyDispatcherServlet
     * @Author 周聪
     * @Date 2021/1/29 11:50
     * @Version 1.0
     * @Description 用到工厂模式、注册式单例模式、委派模式、模板模式、策略模式(HandlerMappings解决了if...else...)
     */
    public class MyDispatcherServlet extends HttpServlet {
    
        /**
         * 保存application。properties配置文件中的内容
         */
        private Properties contextConfig = new Properties();
    
        /**
         * 保存扫描的所有的类名
         */
        private List<String> classNames = new ArrayList<>();
    
        /**
         * 传说中的IOC容器,我们来揭开它的神秘面纱
         * 为了简化程序,暂时不考虑ConcurrentHashMap,主要还是关注设计思想
         */
        private Map<String, Object> ioc = new HashMap<String, Object>();
    
        /**
         * 保存url和method对应关系
         */
        private Map<String, Method> handlerMappings = new HashMap<String, Method>();
    
        @Override
        protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
            this.doPost(req, resp);
        }
    
        @Override
        protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    //        6、调用 运行阶段
            try {
                doDispatch(req, resp);
    
            } catch (Exception e) {
                e.printStackTrace();
                resp.getWriter().write("500 Exception, Detail : " + Arrays.toString(e.getStackTrace()));
            }
        }
    
        /**
         * 初始化阶段
         *
         * @param config
         * @throws ServletException
         */
        @Override
        public void init(ServletConfig config) throws ServletException {
    //        1、加载配置文件
            doLoadConfig(config.getInitParameter("contextConfigLocation"));
    //        2、扫描相关的类
            doScanner(contextConfig.getProperty("scanPackage"));
    //        3、初始化扫描到的类,并且将它们放入到IOC容器之中
            doInstance();
    //        4、完成依赖注入
            doAutowired();
    //        5、初始化HandlerMapping
            initHandlerMapping();
    
            System.out.println("Spring framework is init");
        }
    
        /**
         * 执行请求调用方法
         *
         * @param req
         * @param resp
         */
        private void doDispatch(HttpServletRequest req, HttpServletResponse resp) throws Exception {
    
           /*
            basePath:http://localhost:8080/test/
            getContextPath:/test
            getServletPath:/test.jsp
            getRequestURI:/test/test.jsp
            getRequestURL:http://localhost:8080/test/test.jsp
            getRealPath:D:Tomcat 6.0webapps	est
            getServletContext().getRealPath:D:Tomcat 6.0webapps	est
            getQueryString:p=fuck
            */
    
    //        绝对路径
            String requestURI = req.getRequestURI();
    //        处理成相对路径
            String contextPath = req.getContextPath();
            String url = requestURI.replace(contextPath, "").replaceAll("/+", "/");
            if (!this.handlerMappings.containsKey(url)) {
                resp.getWriter().write("404 Not Found !!!");
                return;
            }
            Method method = this.handlerMappings.get(url);
    //        投机取巧的方式
    //        通过反射拿到method所在class,拿到class之后还会拿到class的名称
    //        再调用toLowerFirstCase活动beanName
            String beanName = toLowerFirstCase(method.getDeclaringClass().getSimpleName());
    //        获取请求参数
            Map<String, String[]> params = req.getParameterMap();
    //        获取方法的形参列表
            Class<?>[] parameterTypes = method.getParameterTypes();
    //        获取方法的参数列表
            Parameter[] parameters = method.getParameters();
    //        保存赋值参数的位置
            Object[] paramValues = new Object[parameterTypes.length];
    //        根据参数位置动态赋值
            for (int i = 0; i < parameterTypes.length; i++) {
                Class<?> parameterType = parameterTypes[i];
                if (parameterType.equals(HttpServletRequest.class)) {
                    paramValues[i] = req;
                    continue;
                } else if (parameterType.equals(HttpServletResponse.class)) {
                    paramValues[i] = resp;
                    continue;
                }
                MyRequestParam annotation = parameters[i].getAnnotation(MyRequestParam.class);
                if (params.containsKey(annotation.value())) {
    //                            拿到key所对应的值,而拿到的这个值,有一对多的关系,一个key对应一个数组
    //                            http://127.0.0.1:8080/Spring-1.0/demo/query?name=zhangsan&name=lisi&name=wangwu
                        System.out.println(Arrays.toString(params.get(annotation.value())));
    //                  类型的强制转换
                        paramValues[i] = convert(parameterType, Arrays.toString(params.get(annotation.value())));
                }
    
            }
    
    //        第一个参数:方法所在的实例,第二个参数:调用时所需要的参数
            method.invoke(ioc.get(beanName), paramValues);
        }
    
        /**
         * url传过来的参数都是String类型的,http是基于字符串协议
         * 只需要把String转换成任意类型
         *
         * @param type
         * @param value
         * @return
         */
        private Object convert(Class<?> type, String value) {
    //        去掉数组的中括号
            value = value.replaceAll("\[|\]", "")
                    .replaceAll("\s", "");
    
    //        如果是int
            if (Integer.class == type) {
                return Integer.valueOf(value);
            }
    //        如果还有double或者其他类型,继续加if
    //        这时候我们应该想到策略模式了
    //        这里暂时不实现
            return value;
        }
    
        /**
         * 初始化url和Method绑定关系
         */
        private void initHandlerMapping() {
            if (ioc.isEmpty()) {
                return;
            }
            for (Map.Entry<String, Object> entry : ioc.entrySet()) {
                Class<?> clazz = entry.getValue().getClass();
                if (!clazz.isAnnotationPresent(MyController.class)) {
                    continue;
                }
    //            保存写在类上面的@MyRequestMapping(name = "/demo")
                String baseUrl = "";
                if (clazz.isAnnotationPresent(MyRequestMapping.class)) {
                    MyRequestMapping annotation = clazz.getAnnotation(MyRequestMapping.class);
                    baseUrl = annotation.value();
                }
    
    //            默认获取所有的public方法
                for (Method method : clazz.getMethods()) {
                    if (!method.isAnnotationPresent(MyRequestMapping.class)) {
                        continue;
                    }
                    MyRequestMapping annotation = method.getAnnotation(MyRequestMapping.class);
    //                优化,多个 / 替换成一个 ,用户不管自己写不写/,我都加一个/
                    String url = ("/" + baseUrl + "/" + annotation.value()).replaceAll("/+", "/");
                    handlerMappings.put(url, method);
                    System.out.println("Mapped : " + url + " , " + method);
                }
            }
        }
    
        /**
         * 依赖注入
         */
        private void doAutowired() {
            if (ioc.isEmpty()) {
                return;
            }
            for (Map.Entry<String, Object> entry : ioc.entrySet()) {
    //            Declared 获取所有的,特定的字段 包括private/protected/default
    //            正常来说,普通的OOP变成只能拿到public属性
                Field[] fields = entry.getValue().getClass().getDeclaredFields();
                for (Field field : fields) {
                    if (!field.isAnnotationPresent(MyAutowired.class)) {
                        continue;
                    }
    //                自定义beanName赋值
                    MyAutowired annotation = field.getAnnotation(MyAutowired.class);
                    String beanName = annotation.value().trim();
                    if ("".equals(beanName)) {
    //                    根据接口类型赋值
                        beanName = field.getType().getName();
                    }
    //                如果是public以外的修饰符,只要加了Autowired注解,都要强制赋值,暴力访问
                    field.setAccessible(true);
                    try {
    //                    用反射机制,动态给字段赋值
                        field.set(entry.getValue(), ioc.get(beanName));
                    } catch (IllegalAccessException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    
    
        /**
         * 初始化,为DI做准备
         */
        private void doInstance() {
            if (classNames.isEmpty()) {
                return;
            }
            try {
                for (String className : classNames) {
                    Class<?> clazz = Class.forName(className);
    //                什么样的类才需要初始化?
    //                加了注解的类,才初始化,怎么判断?
    //                为了简化代码逻辑,主要体会设计思想,只举例@Controller和@Service
    //                @Component ....就不一一举例了
                    if (clazz.isAnnotationPresent(MyController.class)) {
                        Object instance = clazz.newInstance();
    //                    Spring默认类名首字母小写
                        String beanName = toLowerFirstCase(clazz.getSimpleName());
                        ioc.put(beanName, instance);
                    } else if (clazz.isAnnotationPresent(MyService.class)) {
    //                    1、自定义的beanName
                        MyService annotation = clazz.getAnnotation(MyService.class);
                        String beanName = annotation.value();
    //                    2、默认类名首字母小写
                        if ("".equals(annotation.value().trim())) {
                            beanName = toLowerFirstCase(clazz.getSimpleName());
                        }
                        Object instance = clazz.newInstance();
                        ioc.put(beanName, instance);
    //                    3、根据类型自动赋值,投机取巧的方式
                        for (Class<?> i : clazz.getInterfaces()) {
                            if (ioc.containsKey(i.getName())) {
                                throw new Exception("The “" + i.getName() + "” is exists !");
                            }
                            ioc.put(i.getName(), instance);
                        }
                    } else {
                        continue;
                    }
    
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
    
        }
    
        /**
         * 类名首字母小写
         * 如果类名本身是小写字母确实会出现问题
         * 但是我要说明的是:这个方法是我自己用,private的
         * 传值也是自己传,类也都遵循了驼峰命名法
         * 默认传入的值,不存在首字母小写的情况,也不可能出现非首字母的情况
         * 为了简化简化程序就不做判断了
         *
         * @param simpleName
         * @return
         */
        private String toLowerFirstCase(String simpleName) {
            char[] chars = simpleName.toCharArray();
    //        之所以加32,是因为大小写字母的ASCII码相差32
    //        大写字母的ASCII码要小于小写字母的ASCII码
    //        在Java中,对char做算学运算,实际上就是对ASCII码做算学运算
            chars[0] += 32;
            return String.valueOf(chars);
        }
    
        /**
         * 扫描出相关的类
         *
         * @param scanPackage
         */
        private void doScanner(String scanPackage) {
    //        scanPackage  =  com.zc.demo  存储的是包路径
    //        转换为文件路径,实际上就是把.替换成/
            URL url = this.getClass().getClassLoader().getResource(scanPackage.replaceAll("\.", "/"));
            File classPath = new File(url.getFile());
            for (File file : classPath.listFiles()) {
                if (file.isDirectory()) {
                    doScanner(scanPackage + "." + file.getName());
                } else {
                    if (!file.getName().endsWith(".class")) {
                        continue;
                    }
                    String className = scanPackage + "." + file.getName().replace(".class", "");
                    this.classNames.add(className);
                }
            }
        }
    
        /**
         * 加载配置文件
         *
         * @param contextConfigLocation
         */
        private void doLoadConfig(String contextConfigLocation) {
    //        直接从类路径下找到Spring主配置文件所在的路径
    //        并且将其读取进来放到Properties对象中
    //        相当于scanPackage=com.zc.demo 从文件中保存到了内存中
            InputStream resourceAsStream = this.getClass().getClassLoader().getResourceAsStream(contextConfigLocation);
            try {
                contextConfig.load(resourceAsStream);
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                if (resourceAsStream != null) {
                    try {
                        resourceAsStream.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    
    }

    DispatcherServlet的v2版本

    package com.zc.mvcframework.v3.servlet;
    
    import com.zc.mvcframework.annotation.*;
    
    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.File;
    import java.io.IOException;
    import java.io.InputStream;
    import java.lang.annotation.Annotation;
    import java.lang.reflect.Field;
    import java.lang.reflect.Method;
    import java.lang.reflect.Parameter;
    import java.net.URL;
    import java.util.*;
    
    /**
     * @ClassName MyDispatcherServlet
     * @Author 周聪
     * @Date 2021/1/29 11:50
     * @Version 1.0
     * @Description
     */
    public class MyDispatcherServlet extends HttpServlet {
    
        /**
         * 保存application。properties配置文件中的内容
         */
        private Properties contextConfig = new Properties();
    
        /**
         * 保存扫描的所有的类名
         */
        private List<String> classNames = new ArrayList<>();
    
        /**
         * 传说中的IOC容器,我们来揭开它的神秘面纱
         * 为了简化程序,暂时不考虑ConcurrentHashMap,主要还是关注设计思想
         */
        private Map<String, Object> ioc = new HashMap<String, Object>();
    
        /**
         * 保存url和method对应关系
         */
    //    private Map<String, Method> handlerMappings = new HashMap<String, Method>();
    
        /**
         * 思考: 为什么不用Map
         * 用map的话 key只能是url
         * HandlerMapping本身的功能就是把url和method对应关系,已经具备了Map的功能
         * 根据设计原则:冗余,单一职责,最少知道原则
         * 从性能上来说,与其交给Map去遍历不然自己遍历,map内部也是list
         */
        private List<HandlerMapping> handlerMappings = new ArrayList<HandlerMapping>();
    
        @Override
        protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
            this.doPost(req, resp);
        }
    
        @Override
        protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    //        6、调用 运行阶段
            try {
                doDispatch(req, resp);
    
            } catch (Exception e) {
                e.printStackTrace();
                resp.getWriter().write("500 Exception, Detail : " + Arrays.toString(e.getStackTrace()));
            }
        }
    
        /**
         * 初始化阶段
         *
         * @param config
         * @throws ServletException
         */
        @Override
        public void init(ServletConfig config) throws ServletException {
    //        1、加载配置文件
            doLoadConfig(config.getInitParameter("contextConfigLocation"));
    //        2、扫描相关的类
            doScanner(contextConfig.getProperty("scanPackage"));
    //        3、初始化扫描到的类,并且将它们放入到IOC容器之中
            doInstance();
    //        4、完成依赖注入
            doAutowired();
    //        5、初始化HandlerMapping
            initHandlerMapping();
    
            System.out.println("Spring framework is init");
        }
    
        /**
         * 执行请求调用方法
         *
         * @param req
         * @param resp
         */
        private void doDispatch(HttpServletRequest req, HttpServletResponse resp) throws Exception {
    
           /*
            basePath:http://localhost:8080/test/
            getContextPath:/test
            getServletPath:/test.jsp
            getRequestURI:/test/test.jsp
            getRequestURL:http://localhost:8080/test/test.jsp
            getRealPath:D:Tomcat 6.0webapps	est
            getServletContext().getRealPath:D:Tomcat 6.0webapps	est
            getQueryString:p=fuck
            */
    
    
           HandlerMapping handlerMapping = getHandler(req);
           if (handlerMapping == null){
               resp.getWriter().write("404 Not Found !!!");
               return;
           }
    
    //       获得形参列表
            Class<?>[] paramTypes = handlerMapping.getParamTypes();
    
            Object[] paramValues = new Object[paramTypes.length];
    
            Map<String, String[]> parameterMap = req.getParameterMap();
            for (Map.Entry<String, String[]> entry : parameterMap.entrySet()) {
                String value = Arrays.toString(entry.getValue());
    
                if (!handlerMapping.paramIndexMapping.containsKey(entry.getKey())){
                    continue;
                }
                Integer index = handlerMapping.paramIndexMapping.get(entry.getKey());
    //            类型转换
                paramValues[index] = convert(paramTypes[index],value);
                if (handlerMapping.paramIndexMapping.containsKey(HttpServletRequest.class.getName())){
                    Integer reqIndex = handlerMapping.paramIndexMapping.get(HttpServletRequest.class.getName());
                    paramValues[reqIndex] = req;
                }
                if (handlerMapping.paramIndexMapping.containsKey(HttpServletResponse.class.getName())) {
                    Integer respIndex = handlerMapping.paramIndexMapping.get(HttpServletResponse.class.getName());
                    paramValues[respIndex] = resp;
                }
            }
    
            Object returnValue = handlerMapping.method.invoke(handlerMapping.controller, paramValues);
            if (returnValue instanceof Void || returnValue == null) {return;}
            resp.getWriter().write(returnValue.toString());
        }
    
        /**
         * 根据请求路径获取handlerMapping
         * @param req
         * @return
         */
        private HandlerMapping getHandler(HttpServletRequest req) {
            if (handlerMappings.isEmpty()){
                return null;
            }
            //        绝对路径
            String requestURI = req.getRequestURI();
    //        处理成相对路径
            String contextPath = req.getContextPath();
            String url = requestURI.replace(contextPath, "").replaceAll("/+", "/");
            for (HandlerMapping handlerMapping : this.handlerMappings) {
                if (handlerMapping.getUrl().equals(url)){
                    return handlerMapping;
                }
            }
            return null;
    
        }
    
        /**
         * url传过来的参数都是String类型的,http是基于字符串协议
         * 只需要把String转换成任意类型
         *
         * @param type
         * @param value
         * @return
         */
        private Object convert(Class<?> type, String value) {
    //        去掉数组的中括号
            value = value.replaceAll("\[|\]", "")
                    .replaceAll("\s", "");
    
    //        如果是int
            if (Integer.class == type) {
                return Integer.valueOf(value);
            }
    //        如果还有double或者其他类型,继续加if
    //        这时候我们应该想到策略模式了
    //        这里暂时不实现
            return value;
        }
    
        /**
         * 初始化url和Method绑定关系
         */
        private void initHandlerMapping() {
            if (ioc.isEmpty()) {
                return;
            }
            for (Map.Entry<String, Object> entry : ioc.entrySet()) {
                Class<?> clazz = entry.getValue().getClass();
                if (!clazz.isAnnotationPresent(MyController.class)) {
                    continue;
                }
    //            保存写在类上面的@MyRequestMapping(name = "/demo")
                String baseUrl = "";
                if (clazz.isAnnotationPresent(MyRequestMapping.class)) {
                    MyRequestMapping annotation = clazz.getAnnotation(MyRequestMapping.class);
                    baseUrl = annotation.value();
                }
    
    //            默认获取所有的public方法
                for (Method method : clazz.getMethods()) {
                    if (!method.isAnnotationPresent(MyRequestMapping.class)) {
                        continue;
                    }
                    MyRequestMapping annotation = method.getAnnotation(MyRequestMapping.class);
    //                优化,多个 / 替换成一个 ,用户不管自己写不写/,我都加一个/
                    String url = ("/" + baseUrl + "/" + annotation.value()).replaceAll("/+", "/");
                    handlerMappings.add(new HandlerMapping(url,entry.getValue(),method));
                    System.out.println("Mapped : " + url + " , " + method);
                }
            }
        }
    
        /**
         * 依赖注入
         */
        private void doAutowired() {
            if (ioc.isEmpty()) {
                return;
            }
            for (Map.Entry<String, Object> entry : ioc.entrySet()) {
    //            Declared 获取所有的,特定的字段 包括private/protected/default
    //            正常来说,普通的OOP变成只能拿到public属性
                Field[] fields = entry.getValue().getClass().getDeclaredFields();
                for (Field field : fields) {
                    if (!field.isAnnotationPresent(MyAutowired.class)) {
                        continue;
                    }
    //                自定义beanName赋值
                    MyAutowired annotation = field.getAnnotation(MyAutowired.class);
                    String beanName = annotation.value().trim();
                    if ("".equals(beanName)) {
    //                    根据接口类型赋值
                        beanName = field.getType().getName();
                    }
    //                如果是public以外的修饰符,只要加了Autowired注解,都要强制赋值,暴力访问
                    field.setAccessible(true);
                    try {
    //                    用反射机制,动态给字段赋值
                        field.set(entry.getValue(), ioc.get(beanName));
                    } catch (IllegalAccessException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    
    
        /**
         * 初始化,为DI做准备
         */
        private void doInstance() {
            if (classNames.isEmpty()) {
                return;
            }
            try {
                for (String className : classNames) {
                    Class<?> clazz = Class.forName(className);
    //                什么样的类才需要初始化?
    //                加了注解的类,才初始化,怎么判断?
    //                为了简化代码逻辑,主要体会设计思想,只举例@Controller和@Service
    //                @Component ....就不一一举例了
                    if (clazz.isAnnotationPresent(MyController.class)) {
                        Object instance = clazz.newInstance();
    //                    Spring默认类名首字母小写
                        String beanName = toLowerFirstCase(clazz.getSimpleName());
                        ioc.put(beanName, instance);
                    } else if (clazz.isAnnotationPresent(MyService.class)) {
    //                    1、自定义的beanName
                        MyService annotation = clazz.getAnnotation(MyService.class);
                        String beanName = annotation.value();
    //                    2、默认类名首字母小写
                        if ("".equals(annotation.value().trim())) {
                            beanName = toLowerFirstCase(clazz.getSimpleName());
                        }
                        Object instance = clazz.newInstance();
                        ioc.put(beanName, instance);
    //                    3、根据类型自动赋值,投机取巧的方式
                        for (Class<?> i : clazz.getInterfaces()) {
                            if (ioc.containsKey(i.getName())) {
                                throw new Exception("The “" + i.getName() + "” is exists !");
                            }
                            ioc.put(i.getName(), instance);
                        }
                    } else {
                        continue;
                    }
    
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
    
        }
    
        /**
         * 类名首字母小写
         * 如果类名本身是小写字母确实会出现问题
         * 但是我要说明的是:这个方法是我自己用,private的
         * 传值也是自己传,类也都遵循了驼峰命名法
         * 默认传入的值,不存在首字母小写的情况,也不可能出现非首字母的情况
         * 为了简化简化程序就不做判断了
         *
         * @param simpleName
         * @return
         */
        private String toLowerFirstCase(String simpleName) {
            char[] chars = simpleName.toCharArray();
    //        之所以加32,是因为大小写字母的ASCII码相差32
    //        大写字母的ASCII码要小于小写字母的ASCII码
    //        在Java中,对char做算学运算,实际上就是对ASCII码做算学运算
            chars[0] += 32;
            return String.valueOf(chars);
        }
    
        /**
         * 扫描出相关的类
         *
         * @param scanPackage
         */
        private void doScanner(String scanPackage) {
    //        scanPackage  =  com.zc.demo  存储的是包路径
    //        转换为文件路径,实际上就是把.替换成/
            URL url = this.getClass().getClassLoader().getResource(scanPackage.replaceAll("\.", "/"));
            File classPath = new File(url.getFile());
            for (File file : classPath.listFiles()) {
                if (file.isDirectory()) {
                    doScanner(scanPackage + "." + file.getName());
                } else {
                    if (!file.getName().endsWith(".class")) {
                        continue;
                    }
                    String className = scanPackage + "." + file.getName().replace(".class", "");
                    this.classNames.add(className);
                }
            }
        }
    
        /**
         * 加载配置文件
         *
         * @param contextConfigLocation
         */
        private void doLoadConfig(String contextConfigLocation) {
    //        直接从类路径下找到Spring主配置文件所在的路径
    //        并且将其读取进来放到Properties对象中
    //        相当于scanPackage=com.zc.demo 从文件中保存到了内存中
            InputStream resourceAsStream = this.getClass().getClassLoader().getResourceAsStream(contextConfigLocation);
            try {
                contextConfig.load(resourceAsStream);
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                if (resourceAsStream != null) {
                    try {
                        resourceAsStream.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    
        /**
         * 保存一个url和一个Method的关系
         */
        public class HandlerMapping{
    //        url只能放在里面
            private String url;
            private Method method;
            private Object controller;
            private Class<?>[] paramTypes;
    //        形参列表,参数名字作为key,参数的位置顺序作为值
            private Map<String, Integer> paramIndexMapping;
    
            public HandlerMapping(String url, Object controller, Method method) {
                this.url = url;
                this.method = method;
                this.controller = controller;
                paramTypes = method.getParameterTypes();
                this.paramIndexMapping = new HashMap<String, Integer>();
                putParamIndexMapping(method);
            }
    
            private void putParamIndexMapping(Method  method) {
    //            提取方法中加了注解的参数
    //            因为一个参数可以有多个注解,而一个方法又有多个参数,所以是一个二维数组
                Annotation[][] parameterAnnotations = method.getParameterAnnotations();
                for (int i = 0; i < parameterAnnotations.length; i++) {
                    for (Annotation annotation : parameterAnnotations[i]) {
                        if ( annotation instanceof MyRequestParam){
                            String value = ((MyRequestParam) annotation).value();
                            if (!"".equals(value.trim())){
                                paramIndexMapping.put(value,i);
                            }
                        }
                    }
                }
    
    //            提取方法中的request和response参数
                Class<?>[] parameterTypes = method.getParameterTypes();
                for (int i = 0; i < parameterTypes.length; i++) {
                    Class<?> parameterType = parameterTypes[i];
                    if (parameterType == HttpServletRequest.class || parameterType == HttpServletResponse.class){
                        paramIndexMapping.put(parameterType.getName(),i);
                    }
                }
            }
    
            public String getUrl() {
                return url;
            }
    
            public void setUrl(String url) {
                this.url = url;
            }
    
            public Method getMethod() {
                return method;
            }
    
            public void setMethod(Method method) {
                this.method = method;
            }
    
            public Object getController() {
                return controller;
            }
    
            public void setController(Object controller) {
                this.controller = controller;
            }
    
            public Class<?>[] getParamTypes() {
                return paramTypes;
            }
    
            public void setParamTypes(Class<?>[] paramTypes) {
                this.paramTypes = paramTypes;
            }
    
            public Map<String, Integer> getParamIndexMapping() {
                return paramIndexMapping;
            }
    
            public void setParamIndexMapping(Map<String, Integer> paramIndexMapping) {
                this.paramIndexMapping = paramIndexMapping;
            }
        }
    
    }

    总结

    面试题1:Spring中的Bean是线程安全的吗?Spring中的Bean是哪里来的?

    回答spring的bean是通过扫描,然后利用反射new出来的,并且缓存在IOC容器中(CrrentHashMap),Spring并没有对你的Bean做任何处理,Bean是不是线程安全取决于Bean本事。

    面试题2:Spring中的Bean是如何被回收的?GC回收?不回收?(面试官喜欢埋坑,你是否了解原理)其实就是Spring中的bean的生命周期的问题,不同作用域存活时间不同

    回答:Spring中的bean生命周期有:

    •   1、singleton:单例,默认作用域。
    •   2、prototype:原型,每次创建一个新对象。
    •   3、request:请求,每次Http请求创建一个新对象,适用于WebApplicationContext环境下。
    •   4、session:会话,同一个会话共享一个实例,不同会话使用不用的实例。
    •   5、global-session:全局会话,所有会话共享一个实例。

    GC回收原则:当Bean的引用没有指向任何地方的时候

    SingletonMap<String,Object> ioc 本事就是单例,基于Spring上下文的,Spring上下文不消失,ioc也就不消失,保存的Bean也不消失,所以singleton对象是随着Spring的存亡而存亡

    欢迎批评指正。 附:300行代码手写Spring源码地址

  • 相关阅读:
    Java在Web开发语言上败给了PHP(转)
    很开心收到了Andreas Loew发给我的注册key
    Cocos2d-x开发移植到安卓平台横竖屏设置及相关
    学习实战二:利用Cocos2d-x写的第一个游戏
    Cocos2d-x项目移植到安卓平台易出现的错误
    cocos2d-x帧动画实现(写下备忘)
    cocos2d-x学习遇到的问题
    C++指针的管理
    Win7开自带的虚拟WIFI
    【SICP练习】21 练习1.27
  • 原文地址:https://www.cnblogs.com/itzhoucong/p/14343615.html
Copyright © 2011-2022 走看看