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("=======后置通知=======\n");
    }

    /**
     * 功能描述: 异常通知
     *
     * @创建人: 我恰芙蓉王
     * @创建时间: 2020年08月05日 17:24:47
     * @param
     * @return: void
     **/
    public void afterThrowing(){
        System.err.println("=======出现异常=======");
    }
}

 

执行结果

 

 

 

总结

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

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