zoukankan      html  css  js  c++  java
  • Spring学习之——手写Spring源码V2.0(实现IOC、DI、MVC、AOP)

    前言

    在上一篇《Spring学习之——手写Spring源码(V1.0)》中,我实现了一个Mini版本的Spring框架,在这几天,博主又看了不少关于Spring源码解析的视频,受益匪浅,也对Spring的各组件有了自己的理解和认识,于是乎,在空闲时间把之前手写Spring的代码重构了一遍,遵循了单一职责的原则,使结构更清晰,并且实现了AOP,这次还是只引用一个servlet包,其他全部手写实现。

    全部源码照旧放在文章末尾~

    开发工具

    环境:jdk8 + IDEA + maven

    jar包:javax.servlet-2.5

    项目结构

    具体实现

    配置文件

    web.xml 与之前一样  并无改变

    application.properties   增加了html页面路径和AOP的相关配置

    #扫描路径#
    scanPackage=com.wqfrw
    
    #模板引擎路径#
    templateRoot=template
    
    
    #切面表达式#
    pointCut=public .* com.wqfrw.service.impl..*ServiceImpl..*(.*)
    #切面类#
    aspectClass=com.wqfrw.aspect.LogAspect
    #切面前置通知#
    aspectBefore=before
    #切面后置通知#
    aspectAfter=after
    #切面异常通知#
    aspectAfterThrowing=afterThrowing
    #切面异常类型#
    aspectAfterThrowingName=java.lang.Exception

    IOC与DI实现

    1.在DispatcherServlet的init方法中初始化ApplicationContent;

    2.ApplicationContent是Spring容器的主入口,通过创建BeanDefintionReader对象加载配置文件;

    3.在BeanDefintionReader中将扫描到的类解析成BeanDefintion返回;

    4.ApplicationContent中通过BeanDefintionMap这个缓存来关联BeanName与BeanDefintion对象之间的关系;

    5.通过getBean方法,进行Bean的创建并封装为BeanWrapper对象,进行依赖注入,缓存到IoC容器中

      /**
        * 功能描述: 初始化MyApplicationContext
        *
        * @创建人: 我恰芙蓉王
        * @创建时间: 2020年08月03日 18:54:01
        * @param configLocations
        * @return:
        **/
        public MyApplicationContext(String... configLocations) {
            this.configLocations = configLocations;
    
            try {
                //1.读取配置文件并解析BeanDefinition对象
                beanDefinitionReader = new MyBeanDefinitionReader(configLocations);
                List<MyBeanDefinition> beanDefinitionList = beanDefinitionReader.loadBeanDefinitions();
    
                //2.将解析后的BeanDefinition对象注册到beanDefinitionMap中
                doRegisterBeanDefinition(beanDefinitionList);
    
                //3.触发创建对象的动作,调用getBean()方法(Spring默认是延时加载)
                doCreateBean();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        /**
         * 功能描述: 真正触发IoC和DI的动作  1.创建Bean  2.依赖注入
         *
         * @param beanName
         * @创建人: 我恰芙蓉王
         * @创建时间: 2020年08月03日 19:48:58
         * @return: java.lang.Object
         **/
        public Object getBean(String beanName) {
            //============ 创建实例 ============
    
            //1.获取配置信息,只要拿到beanDefinition对象即可
            MyBeanDefinition beanDefinition = beanDefinitionMap.get(beanName);
    
            //用反射创建实例  这个实例有可能是代理对象 也有可能是原生对象   封装成BeanWrapper统一处理
            Object instance = instantiateBean(beanName, beanDefinition);
            MyBeanWrapper beanWrapper = new MyBeanWrapper(instance);
    
            factoryBeanInstanceCache.put(beanName, beanWrapper);
    
            //============ 依赖注入 ============
            populateBean(beanName, beanDefinition, beanWrapper);
    
            return beanWrapper.getWrapperInstance();
        }
      /**
         * 功能描述: 依赖注入
         *
         * @param beanName
         * @param beanDefinition
         * @param beanWrapper
         * @创建人: 我恰芙蓉王
         * @创建时间: 2020年08月03日 20:09:01
         * @return: void
         **/
        private void populateBean(String beanName, MyBeanDefinition beanDefinition, MyBeanWrapper beanWrapper) {
            Object instance = beanWrapper.getWrapperInstance();
            Class<?> clazz = beanWrapper.getWrapperClass();
    
            //只有加了注解的类才需要依赖注入
            if (!(clazz.isAnnotationPresent(MyController.class) || clazz.isAnnotationPresent(MyService.class))) {
                return;
            }
    
            //拿到bean所有的字段 包括private、public、protected、default
            for (Field field : clazz.getDeclaredFields()) {
    
                //如果没加MyAutowired注解的属性则直接跳过
                if (!field.isAnnotationPresent(MyAutowired.class)) {
                    continue;
                }
    
                MyAutowired annotation = field.getAnnotation(MyAutowired.class);
                String autowiredBeanName = annotation.value().trim();
                if ("".equals(autowiredBeanName)) {
                    autowiredBeanName = field.getType().getName();
                }
                //强制访问
                field.setAccessible(true);
                try {
                    if (factoryBeanInstanceCache.get(autowiredBeanName) == null) { continue; }
                    //赋值
                    field.set(instance, this.factoryBeanInstanceCache.get(autowiredBeanName).getWrapperInstance());
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                }
            }
        }

    MVC实现

    1.在DispatcherServlet的init方法中调用initStrategies方法初始化九大核心组件;

    2.通过循环BeanDefintionMap拿到每个接口的url、实例对象、对应方法封装成一个HandlerMapping对象的集合,并建立HandlerMapping与HandlerAdapter(参数适配器)的关联;

    3.初始化ViewResolver(视图解析器),解析配置文件中模板文件路径(即html文件的路径,其作用类似于BeanDefintionReader);

    4.在运行阶段,调用doDispatch方法,根据请求的url找到对应的HandlerMapping;

    5.在HandlerMapping对应的HandlerAdapter中,调用handle方法,进行参数动态赋值,反射调用接口方法,拿到返回值与返回页面封装成一个MyModelAndView对象返回;

    6.通过ViewResolver拿到View(模板页面文件),在View中通过render方法,通过正则将返回值与页面取值符号进行适配替换,渲染成html页面返回

      /**
         * 功能描述: 初始化核心组件 在Spring中有九大核心组件,这里只实现三种
         *
         * @param context
         * @创建人: 我恰芙蓉王
         * @创建时间: 2020年08月04日 11:51:55
         * @return: void
         **/
        protected void initStrategies(MyApplicationContext context) {
            //多文件上传组件
            //initMultipartResolver(context);
            //初始化本地语言环境
            //initLocaleResolver(context);
            //初始化模板处理器
            //initThemeResolver(context);
            //初始化请求分发处理器
            initHandlerMappings(context);
            //初始化参数适配器
            initHandlerAdapters(context);
            //初始化异常拦截器
            //initHandlerExceptionResolvers(context);
            //初始化视图预处理器
            //initRequestToViewNameTranslator(context);
            //初始化视图转换器
            initViewResolvers(context);
            //缓存管理器(值栈)
            //initFlashMapManager(context);
        }
      /**
         * 功能描述: 进行参数适配
         *
         * @创建人: 我恰芙蓉王
         * @创建时间: 2020年08月05日 19:41:38
         * @param req
         * @param resp
         * @param mappedHandler
         * @return: com.framework.webmvc.servlet.MyModelAndView
         **/
        public MyModelAndView handle(HttpServletRequest req, HttpServletResponse resp, MyHandlerMapping mappedHandler) throws Exception {
    
            //保存参数的名称和位置
            Map<String, Integer> paramIndexMapping = new HashMap<>();
    
            //获取这个方法所有形参的注解   因一个参数可以添加多个注解  所以是一个二维数组
            Annotation[][] pa = mappedHandler.getMethod().getParameterAnnotations();
    
            /**
             * 获取加了MyRequestParam注解的参数名和位置   放入到paramIndexMapping中
             */
            for (int i = 0; i < pa.length; i++) {
                for (Annotation annotation : pa[i]) {
                    if (!(annotation instanceof MyRequestParam)) {
                        continue;
                    }
                    String paramName = ((MyRequestParam) annotation).value();
                    if (!"".equals(paramName.trim())) {
                        paramIndexMapping.put(paramName, i);
                    }
                }
            }
    
            //方法的形参列表
            Class<?>[] parameterTypes = mappedHandler.getMethod().getParameterTypes();
    
            /**
             * 获取request和response的位置(如果有的话)   放入到paramIndexMapping中
             */
            for (int i = 0; i < parameterTypes.length; i++) {
                Class<?> parameterType = parameterTypes[i];
                if (parameterType == HttpServletRequest.class || parameterType == HttpServletResponse.class) {
                    paramIndexMapping.put(parameterType.getName(), i);
                }
            }
    
            //拿到一个请求所有传入的实际实参  因为一个url上可以多个相同的name,所以此Map的结构为一个name对应一个value[]
            //例如:request中的参数t1=1&t1=2&t2=3形成的map结构:
            //key=t1;value[0]=1,value[1]=2
            //key=t2;value[0]=3
            Map<String, String[]> paramsMap = req.getParameterMap();
    
            //自定义初始实参列表(反射调用Controller方法时使用)
            Object[] paramValues = new Object[parameterTypes.length];
    
            /**
             * 从paramIndexMapping中取出参数名与位置   动态赋值
             */
            for (Map.Entry<String, String[]> entry : paramsMap.entrySet()) {
                //拿到请求传入的实参
                String value = entry.getValue()[0];
    
                //如果包含url参数上的key 则动态转型赋值
                if (paramIndexMapping.containsKey(entry.getKey())) {
                    //获取这个实参的位置
                    int index = paramIndexMapping.get(entry.getKey());
                    //动态转型并赋值
                    paramValues[index] = caseStringValue(value, parameterTypes[index]);
                }
            }
    
            /**
             * request和response单独赋值
             */
            if (paramIndexMapping.containsKey(HttpServletRequest.class.getName())) {
                int index = paramIndexMapping.get(HttpServletRequest.class.getName());
                paramValues[index] = req;
            }
            if (paramIndexMapping.containsKey(HttpServletResponse.class.getName())) {
                int index = paramIndexMapping.get(HttpServletResponse.class.getName());
                paramValues[index] = resp;
            }
    
            //方法调用 拿到返回结果
            Object result = mappedHandler.getMethod().invoke(mappedHandler.getController(), paramValues);
            if (result == null || result instanceof Void) {
                return null;
            } else if (mappedHandler.getMethod().getReturnType() == MyModelAndView.class) {
                return (MyModelAndView) result;
            }
            return null;
        }
    
        /**
         * 功能描述: 动态转型
         *
         * @param value String类型的value
         * @param clazz 实际对象的class
         * @创建人: 我恰芙蓉王
         * @创建时间: 2020年08月04日 16:34:40
         * @return: java.lang.Object  实际对象的实例
         **/
        private Object caseStringValue(String value, Class<?> clazz) throws Exception {
            //通过class对象获取一个入参为String的构造方法  没有此方法则抛出异常
            Constructor constructor = clazz.getConstructor(new Class[]{String.class});
            //通过构造方法new一个实例返回
            return constructor.newInstance(value);
        }
        /**
         * 功能描述: 对页面内容进行渲染
         *
         * @创建人: 我恰芙蓉王
         * @创建时间: 2020年08月04日 17:54:40
         * @param model
         * @param req
         * @param resp
         * @return: void
         **/
        public void render(Map<String, ?> model, HttpServletRequest req, HttpServletResponse resp) throws Exception {
            StringBuilder sb = new StringBuilder();
            //只读模式 读取文件
            RandomAccessFile ra = new RandomAccessFile(this.viewFile, "r");
    
            String line = null;
            while ((line = ra.readLine()) != null) {
                line = new String(line.getBytes("ISO-8859-1"), "utf-8");
    
                //%{name}
                Pattern pattern = Pattern.compile("%\{[^\}]+\}", Pattern.CASE_INSENSITIVE);
                Matcher matcher = pattern.matcher(line);
    
                while (matcher.find()) {
                    String paramName = matcher.group();
    
                    paramName = paramName.replaceAll("%\{|\}", "");
                    Object paramValue = model.get(paramName);
                    line = matcher.replaceFirst(makeStringForRegExp(paramValue.toString()));
                    matcher = pattern.matcher(line);
                }
                sb.append(line);
            }
    
            resp.setCharacterEncoding("utf-8");
            resp.getWriter().write(sb.toString());
        }

    html页面

    404.html

    <!DOCTYPE html>
    <html lang="zh-cn">
    <head>
        <meta charset="UTF-8">
        <title>页面没有找到</title>
    </head>
    <body>
        <font size="25" color="red">Exception Code : 404  Not Found</font>
        <br><br><br>
        @我恰芙蓉王
    </body>
    </html>

    500.html

    <!DOCTYPE html>
    <html lang="zh-cn">
    <head>
        <meta charset="UTF-8">
        <title>服务器崩溃</title>
    </head>
    <body>
        <font size="25" color="red">Exception Code : 500 <br/> 服务器崩溃了~</font>
        <br/>
        <br/>
        <b>Message:%{message}</b>
        <br/>
        <b>StackTrace:%{stackTrace}</b>
        <br/>
        <br><br><br>
        @我恰芙蓉王
    </body>
    </html>

    index.html

    <!DOCTYPE html>
    <html lang="zh-cn">
    <head>
        <meta charset="UTF-8">
        <title>自定义SpringMVC模板引擎Demo</title>
    </head>
    <center>
        <h1>大家好,我是%{name}</h1>
        <h2>我爱%{food}</h2>
        <font color="red">
            <h2>时间:%{date}</h2>
        </font>
        <br><br><br>
        @我恰芙蓉王
    </center>
    </html>

    测试接口调用返回页面

    404.html  接口未找到

    500.html  服务器错误

    index.html   正常返回页面

    AOP实现

    1.参照IOC与DI实现第五点,在对象实例化之后,依赖注入之前,将配置文件中AOP的配置解析至AopConfig中;

    2.通过配置的pointCut参数,正则匹配此实例对象的类名与方法名,如果匹配上,将配置的三个通知方法(Advice)与此方法建立联系,生成一个  Map<Method, Map<String, MyAdvice>> methodCache  的缓存;

    3.将原生对象、原生对象class、原生对象方法与通知方法的映射关系封装成AdviceSupport对象;

    4.如果需要代理,则使用JdkDynamicAopProxy中getProxy方法,获得一个此原生对象的代理对象,并将原生对象覆盖;

    5.JdkDynamicAopProxy实现了InvocationHandler接口(使用JDK的动态代理),重写invoke方法,在此方法中执行切面方法与原生对象方法。

      /**
         * 功能描述: 反射实例化对象
         *
         * @param beanName
         * @param beanDefinition
         * @创建人: 我恰芙蓉王
         * @创建时间: 2020年08月03日 20:08:50
         * @return: java.lang.Object
         **/
        private Object instantiateBean(String beanName, MyBeanDefinition beanDefinition) {
            String className = beanDefinition.getBeanClassName();
    
            Object instance = null;
            try {
                Class<?> clazz = Class.forName(className);
                instance = clazz.newInstance();
    
                /**
                 *  ===========接入AOP begin===========
                 */
                MyAdviceSupport support = instantiateAopConfig(beanDefinition);
                support.setTargetClass(clazz);
                support.setTarget(instance);
                //如果需要代理  则用代理对象覆盖目标对象
                if (support.pointCutMatch()) {
                    instance = new MyJdkDynamicAopProxy(support).getProxy();
                }
                /**
                 * ===========接入AOP end===========
                 */
    
                factoryBeanObjectCache.put(beanName, instance);
            } catch (Exception e) {
                e.printStackTrace();
            }
            return instance;
        }
      /**
         * 功能描述: 解析配置  pointCut
         *
         * @param
         * @创建人: 我恰芙蓉王
         * @创建时间: 2020年08月05日 11:20:21
         * @return: void
         **/
        private void parse() {
            String pointCut = aopConfig.getPointCut()
                    .replaceAll("\.", "\\.")
                    .replaceAll("\\.\*", ".*")
                    .replaceAll("\(", "\\(")
                    .replaceAll("\)", "\\)");
    
            //public .*.com.wqfrw.service..*impl..*(.*)
            String pointCutForClassRegex = pointCut.substring(0, pointCut.lastIndexOf("\(") - 4);
            this.pointCutClassPattern = Pattern.compile(pointCutForClassRegex.substring(pointCutForClassRegex.lastIndexOf(" ") + 1));
    
            methodCache = new HashMap<>();
            //匹配方法的正则
            Pattern pointCutPattern = Pattern.compile(pointCut);
    
            //1.对回调通知进行缓存
            Map<String, Method> aspectMethods = new HashMap<>();
            try {
                //拿到切面类的class  com.wqfrw.aspect.LogAspect
                Class<?> aspectClass = Class.forName(this.aopConfig.getAspectClass());
                //将切面类的通知方法缓存到aspectMethods
                Stream.of(aspectClass.getMethods()).forEach(v -> aspectMethods.put(v.getName(), v));
    
                //2.扫描目标类的方法,去循环匹配
                for (Method method : targetClass.getMethods()) {
                    String methodString = method.toString();
                    //如果目标方法有抛出异常  则截取
                    if (methodString.contains("throws")) {
                        methodString = methodString.substring(0, methodString.lastIndexOf("throws")).trim();
                    }
                    /**
                     * 匹配目标类方法   如果匹配上,就将缓存好的通知与它建立联系  如果没匹配上,则忽略
                     */
                    Matcher matcher = pointCutPattern.matcher(methodString);
                    if (matcher.matches()) {
                        Map<String, MyAdvice> adviceMap = new HashMap<>();
                        //前置通知
                        if (!(null == aopConfig.getAspectBefore() || "".equals(aopConfig.getAspectBefore()))) {
                            adviceMap.put("before", new MyAdvice(aspectClass.newInstance(), aspectMethods.get(aopConfig.getAspectBefore())));
                        }
    
                        //后置通知
                        if (!(null == aopConfig.getAspectAfter() || "".equals(aopConfig.getAspectAfter()))) {
                            adviceMap.put("after", new MyAdvice(aspectClass.newInstance(), aspectMethods.get(aopConfig.getAspectAfter())));
                        }
    
                        //异常通知
                        if (!(null == aopConfig.getAspectAfterThrowing() || "".equals(aopConfig.getAspectAfterThrowing()))) {
                            MyAdvice advice = new MyAdvice(aspectClass.newInstance(), aspectMethods.get(aopConfig.getAspectAfterThrowing()));
                            advice.setThrowingName(aopConfig.getAspectAfterThrowingName());
                            adviceMap.put("afterThrowing", advice);
                        }
                        //建立关联
                        methodCache.put(method, adviceMap);
                    }
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
      /**
         * 功能描述: 返回一个代理对象
         *
         * @创建人: 我恰芙蓉王
         * @创建时间: 2020年08月05日 14:17:22
         * @param
         * @return: java.lang.Object
         **/
        public Object getProxy() {
            return Proxy.newProxyInstance(this.getClass().getClassLoader(), this.support.getTargetClass().getInterfaces(), this);
        }
    
        /**
         * 功能描述: 重写invoke
         *
         * @创建人: 我恰芙蓉王
         * @创建时间: 2020年08月05日 20:29:19
         * @param proxy
         * @param method
         * @param args
         * @return: java.lang.Object
         **/
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    
            Map<String, MyAdvice> advices = support.getAdvices(method, support.getTargetClass());
    
            Object result = null;
            try {
                //调用前置通知
                invokeAdvice(advices.get("before"));
    
                //执行原生目标方法
                result = method.invoke(support.getTarget(), args);
    
                //调用后置通知
                invokeAdvice(advices.get("after"));
            } catch (Exception e) {
                //调用异常通知
                invokeAdvice(advices.get("afterThrowing"));
                throw e;
            }
    
            return result;
        }
    
        /**
         * 功能描述: 执行切面方法
         *
         * @创建人: 我恰芙蓉王
         * @创建时间: 2020年08月05日 11:09:32
         * @param advice
         * @return: void
         **/
        private void invokeAdvice(MyAdvice advice) {
            try {
                advice.getAdviceMethod().invoke(advice.getAspect());
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (InvocationTargetException e) {
                e.printStackTrace();
            }
        }
    /**
     * @ClassName LogAspect
     * @Description TODO(切面类)
     * @Author 我恰芙蓉王
     * @Date 2020年08月05日 10:03
     * @Version 2.0.0
     **/
    
    public class LogAspect {
    
        /**
         * 功能描述: 前置通知
         *
         * @创建人: 我恰芙蓉王
         * @创建时间: 2020年08月05日 17:24:30
         * @param
         * @return: void
         **/
        public void before(){
            System.err.println("=======前置通知=======");
        }
    
        /**
         * 功能描述: 后置通知
         *
         * @创建人: 我恰芙蓉王
         * @创建时间: 2020年08月05日 17:24:40
         * @param
         * @return: void
         **/
        public void after(){
            System.err.println("=======后置通知=======
    ");
        }
    
        /**
         * 功能描述: 异常通知
         *
         * @创建人: 我恰芙蓉王
         * @创建时间: 2020年08月05日 17:24:47
         * @param
         * @return: void
         **/
        public void afterThrowing(){
            System.err.println("=======出现异常=======");
        }
    }

    执行结果

    总结

    以上只贴出了部分核心实现代码,有兴趣的童鞋可以下载源码调试,具体的注释我都在代码中写得很清楚。

    代码已经提交至Git : https://github.com/wqfrw/HandWritingSpringV2.0

  • 相关阅读:
    spring mvc 参数校验
    spring-boot 配置jsp
    java 多线程之取消与关闭
    spring transaction 初识
    java 读取环境变量和系统变量的方法
    每天一命令 git checkout
    每天一命令 git stash
    每天一命令 git reset
    log4j2配置详解
    专业名词及解释记录
  • 原文地址:https://www.cnblogs.com/-tang/p/13442984.html
Copyright © 2011-2022 走看看