SpringMvc源码分析第六弹 - 基于SpringMvc源码后的手写高仿版

这是SpringMVC体系的最后一篇文章了。时至今日,只有一个字,就是‘妙’。
经过最开始的简单手写,到整个SpringMVC的源码分析。本篇的内容就在之前的简单版上升个级,使之更加的契合源码。

预先准备

之前的手写简单源码,获取手写简单版
因为是在简单版代码上做升级,就只贴几个关键点了

升级版构造

  1. 初始化配置,从配置中获取需要扫描的包,扫描对应的class文件
  2. 解析路径下的class对象,将beanName和className保存起来
  3. 存储到注册信息Map,配置阶段结束,接下来就是依赖注入阶段
  4. 从注册Map中拿到BeanDefinition信息,实例化对象(统一走getBean),AOP切面在此完成代理类和代码织入
  5. 保存到IOC容器,存在class对象和实例化对象
  6. 对加了Autowired注解的声明使用反射进行注入
  7. 对被代理的原始类如果也有声明的,也进行注入
  8. 对延时加载的对象,在获取对象时统一走getBean()获取
  9. MVC DispatcherServlet init初始化直接调用IOC、DI和上下文
  10. 初始化9大组件(mapping放到初始化阶段完成,从IOC拿到实例化对象,包含RequestMapping注解的,取出url和方法,保存对应关系原始类对象、方法对象、reqUrl)
  11. 调用阶段,通过url获取原始类的对象和方法,使用invoke反射调用,非页面的通过write()返回
  12. 页面渲染,读取对应的html并替换约定字符传参,返回给网页

1.ApplicationContext容器

  • 注册阶段分离,实例化阶段统一用源码中的getBean()。
  • 依赖注入阶段,检查AOP切面配置
public class TestApplicationContext {

    //保存application.properties配置文件中的内容
    public TBeanDefinitionReader reader;
    //注册信息保存
    Map<String, TBeanDefinition> beanDefinitionMap = new HashMap<String, TBeanDefinition>();
    //传说中的IOC容器 beanName -> TBeanWarpper 设计
    public Map<String, TBeanWarpper> iocCacheMap = new HashMap<String, TBeanWarpper>();
    //beanName -> 实例化的类对象
    public Map<String, Object> iocObjectCacheMap = new HashMap<String, Object>();
    //被代理对象的原始对象 最后需要反射注入
    public List<TBeanWarpper> targetCacheList = new ArrayList<TBeanWarpper>();

    public TestApplicationContext(String... classpaths) {
        init(classpaths);
    }

    public void init(String... classpaths) {
        try {
            //1.读取配置文件 加载配置 && 扫描对应的类
            this.reader = new TBeanDefinitionReader(classpaths);
            //2.解析类路径下的class对象(不做实例化) 把beanName和className保存起来
            List<TBeanDefinition> beanDefinitions = this.reader.loadBeanDefinitions();
            //3.存储到注册信息map ioc到这里就完成了 接下来就是依赖注入阶段了
            doRegistBeanDefinition(beanDefinitions);
            //4.依赖注入
            doAutowired();

            System.out.println("application is init");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private void doAutowired() {
        //配置阶段完成 真正开始实例化对象
        for (Map.Entry<String, TBeanDefinition> entry : this.beanDefinitionMap.entrySet()) {
            String beanName = entry.getKey();
            getBean(beanName);
        }
        //被代理的原始类 如果有声明 也需要注入
        for (TBeanWarpper warpper : this.targetCacheList) {
            populateBean(null, null, warpper);
        }
    }

    //开始Bean的实例化 和 DI依赖注入
    public Object getBean(String beanName) {
        //1.从缓存中拿到TBeanDefinition信息
        TBeanDefinition beanDefinition = this.beanDefinitionMap.get(beanName);
        //2.实例化对象
        Object instance = instanceBean(beanDefinition);

        //3.封装成TBeanWarpper对象 存储class对象 和 实例化对象
        TBeanWarpper beanWarpper = new TBeanWarpper(instance);

        //4.保存到IOC容器
        iocCacheMap.put(beanName, beanWarpper);

        //5.执行依赖注入
        populateBean(beanName, beanDefinition, beanWarpper);

        return instance;
    }

    //实例化 && AOP在此完成
    private Object instanceBean(TBeanDefinition beanDefinition) {
        String beanName = beanDefinition.getFactoryBeanName();
        if (beanDefinition.getInterfaceImplBeanName() != null) {   //接口取实现类的beanName 保持单列
            beanName = beanDefinition.getInterfaceImplBeanName();
        }
        String beanClassName = beanDefinition.getBeanClassName();
        Object instance = null;
        try {
            //先从实例化对象容器中取
            if (this.iocObjectCacheMap.containsKey(beanName)) {
                instance = this.iocObjectCacheMap.get(beanName);
            } else {
                Class<?> clazz = Class.forName(beanClassName);
                //如果满足AOP切面条件 此处应该生成代理对象,那么最后调用处invoke应该自己实现,里面需要保存原有的实例对象
                instance = checkAopCase(clazz);

                //beanName和beanClassName都存一份 保持单例
                this.iocObjectCacheMap.put(beanName, instance);
                this.iocObjectCacheMap.put(beanClassName, instance);

            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return instance;
    }

    private Object checkAopCase(Class<?> clazz) throws Exception {
        Object instance = clazz.newInstance();
        //获取切面切点 正则匹配全类名 匹配上就生成代理对象
        Properties config = this.reader.getConfig();
        String pointCut = config.getProperty("pointCut");

        String regxMethodStr = pointCut.replaceAll("\\.", "\\\\.")
                .replaceAll("\\\\.\\*", ".*")
                .replaceAll("\\(", "\\\\(")
                .replaceAll("\\)", "\\\\)");

        //类名是 class com.xxx.demo.service.impl.HelloServiceImp 所以匹配类名做如下处理
        String regxClassStr = regxMethodStr.substring(0, regxMethodStr.lastIndexOf("\\("));
        Pattern classPattern = Pattern.compile("class " + regxClassStr.substring(regxClassStr.lastIndexOf(" ") + 1));
        if (classPattern.matcher(instance.getClass().toString()).matches()) {
            TAopConfig aopConfig = new TAopConfig();
            aopConfig.setPointCut(pointCut);
            aopConfig.setAspectClass(config.getProperty("aspectClass"));
            aopConfig.setAspectBefore(config.getProperty("aspectBefore"));
            aopConfig.setAspectAfter(config.getProperty("aspectAfter"));
            aopConfig.setAspectAfterThrowing(config.getProperty("aspectAfterThrowing"));

            TAdvisedSupport advisedSupport = new TAdvisedSupport(aopConfig);
            //匹配方法的正则
            Pattern methodPattern = Pattern.compile(regxMethodStr);
            advisedSupport.setMethodPattern(methodPattern);
            advisedSupport.setTarget(instance);
            advisedSupport.setTargetClass(clazz);

            //被代理的保留原始实例化对象 如果有声明其他类 需要注入
            Object target = instance;
            this.targetCacheList.add(new TBeanWarpper(target));
            //匹配上的生成代理对象
            instance = new TJDKDynamicAopProxy(advisedSupport).getProxy();
        }
        return instance;
    }


    private void populateBean(String beanName, TBeanDefinition beanDefinition, TBeanWarpper beanWarpper) {
        Class<?> clazz = beanWarpper.getWarpperClass();
        Object instance = beanWarpper.getWarpperInstance();

        //1.前面加了注解的的类已经实例化完成 这里判断需要注入的声明即可
        //获取类里面所有的声明对象 如果有TAutowired注解 反射赋值
        Field[] fields = clazz.getDeclaredFields();
        for (Field field : fields) {
            if (!field.isAnnotationPresent(TAutowired.class)) continue;

            //TAutowired这里加了个value 可以自定义beanName 等同于Qualifier("xxx")
            TAutowired autowired = field.getAnnotation(TAutowired.class);
            String autowireNname = field.getType().getName();
            if (!"".equals(autowired.value())) {
                autowireNname = autowired.value();
            }

            //延时加载 或者循环依赖 导致IOC容器还没有实例化的 在下次调用才会初始化
            if (!this.iocCacheMap.containsKey(autowireNname)) continue;

            field.setAccessible(true);
            try {
                field.set(instance, this.iocCacheMap.get(autowireNname).getWarpperInstance());
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }
        }
    }


    private void doRegistBeanDefinition(List<TBeanDefinition> beanDefinitions) throws Exception {
        for (TBeanDefinition beanDefinition : beanDefinitions) {
            if (this.beanDefinitionMap.containsKey(beanDefinition.getFactoryBeanName())) {
                throw new Exception("Create bean Exception ! The bean " + beanDefinition.getFactoryBeanName() + " is exists!!!");
            }
            this.beanDefinitionMap.put(beanDefinition.getFactoryBeanName(), beanDefinition);
            //类名全路径也保存一份
            this.beanDefinitionMap.put(beanDefinition.getBeanClassName(), beanDefinition);
        }
    }

    public Object getBean(Class<?> clazz) {
        return this.getBean(clazz.getName());
    }

    public Map<String, TBeanDefinition> getBeanDefinitionMap() {
        return this.beanDefinitionMap;
    }

    public Properties getConfig() {
        return this.reader.getConfig();
    }

2.初始化类注册信息 TBeanDefinitionReader

//保存扫描的所有的类名
    private List<String> beanDefinitionNames = new ArrayList<String>();
    private Properties contextConfig = new Properties();


    public TBeanDefinitionReader(String[] classpaths) {
        //初始化配置
        doLoadConfig(classpaths[0]);
        //扫描对应的类
        doScanner(contextConfig.getProperty("packscanner"));
    }

    private void doScanner(String packscanner) {
        URL url = this.getClass().getResource("/" + packscanner.replaceAll("\\.", "/"));
        File file = new File(url.getPath());
        for (File f : file.listFiles()) {
            if (f.isDirectory()) {
                //com.xxx.service  -> com.xxx.service.action
                doScanner(packscanner + "." + f.getName());
            } else {
                if (!f.getName().endsWith(".class")) continue;
                this.beanDefinitionNames.add(packscanner + "." + f.getName().replaceAll("\\.class", ""));
            }
        }
    }

    private void doLoadConfig(String packetName) {
        InputStream in = this.getClass().getClassLoader()
                .getResourceAsStream(packetName.replace("classPath:", ""));
        try {
            contextConfig.load(in);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public List<TBeanDefinition> loadBeanDefinitions() {
        List<TBeanDefinition> beanDefinitions = new ArrayList<TBeanDefinition>();
        for (String className : this.beanDefinitionNames) {
            try {
                //这里只解析成class 不做实例化
                //存储 beanName 和className
                Class<?> clazz = Class.forName(className);

                //需要注册的类 应该是加了注解的类 接口要排除掉
                //只有加了注解的类才需要实例化 TController TService
                if (!(clazz.isAnnotationPresent(TController.class)
                        || clazz.isAnnotationPresent(TService.class)) || clazz.isInterface())
                    continue;

                String beanName = toFristLowerCase(clazz.getSimpleName());
                if (clazz.isAnnotationPresent(TService.class)) {
                    TService service = clazz.getAnnotation(TService.class);
                    if (!"".equals(service.value())) {
                        beanName = service.value();
                    }
                }

                beanDefinitions.add(new TBeanDefinition(beanName, clazz.getName()));

                //如果有实现接口的 需要加载接口 接口全名当key 同时存入实现类beanName 注入需要用到
                Class<?>[] interfaces = clazz.getInterfaces();
                for (Class<?> i : interfaces) {
                    beanDefinitions.add(new TBeanDefinition(i.getName(), clazz.getName(), beanName));
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return beanDefinitions;
    }

    //排除类名小写开头
    private String toFristLowerCase(String simpleName) {
        char[] chars = simpleName.toCharArray();
        chars[0] += 32;
        return new String(chars);
    }

    public Properties getConfig() {
        return contextConfig;
    }

3.注册信息的封装 TBeanDefinition

    String factoryBeanName;     //beanName
    String beanClassName;       //类名全路径
    String interfaceImplBeanName;       //如果是接口 对应实现类的beanNma

    public TBeanDefinition(String beanName, String beanClassName) {
        this.factoryBeanName = beanName;
        this.beanClassName = beanClassName;
    }

    public TBeanDefinition(String factoryBeanName, String beanClassName, String interfaceImplBeanName) {
        this.factoryBeanName = factoryBeanName;
        this.beanClassName = beanClassName;
        this.interfaceImplBeanName = interfaceImplBeanName;
    }

4.AOP切面,JDK代理类

  • 只贴了部分,修改了切面的参数传递。让日志打印更加的还原
/**
     * 代理会走到这个方法
     * 1.获取aspect AopConfig配置,切面信息和原始实例类
     * 2.保存method -> list<TAopConfig>配置 到map 可能多个切面
     * 3.依次调用 invoke
     */
    public Object invoke(Object proxy, Method method, Object[] args) throws Exception {
        Map<String, List<TAdvice>> advicesMap = new HashMap<String, List<TAdvice>>();

        Object result = null;
        JoinPoint joinPoint = new JoinPoint();
        joinPoint.setArgs(args);
        joinPoint.setTarget(config.getTarget());
        joinPoint.setMethod(method);
        try {
            advicesMap = config.getMethodAdvices(method);

            invokeAspect(advicesMap.get(config.getAopConfig().getAspectBefore()), joinPoint);

            result = method.invoke(config.getTarget(), args);

            joinPoint.setResult(result);
            invokeAspect(advicesMap.get(config.getAopConfig().getAspectAfter()), joinPoint);
        } catch (Exception e) {
            joinPoint.setThrowName(e.getCause().getMessage());
            invokeAspect(advicesMap.get(config.getAopConfig().getAspectAfterThrowing()), joinPoint);
            throw e;
        }
        return result;
    }

    private void invokeAspect(List<TAdvice> advices, JoinPoint joinPoint) {
        try {
            if (advices == null || advices.size() == 0) return;
            for (TAdvice advice : advices) {
                joinPoint.setChainTarget(advice.getAspectClass());
                joinPoint.setChainMethod(advice.getAspectMethod());
                joinPoint.proceed();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

5.MVC初始类TDispatcherServlet

  • 各司其职,保证单一原则。
  • 参数处理统一交给THandlerAdapter、页面渲染交给TView
public class TDispatcherServlet extends HttpServlet {

    private TestApplicationContext context;

    //spring 源码复制出来的 为什么不用map 可能不止用url请求 源码用的是遍历
    //通过urlPath 拿到 THandlerMapping
    private List<THandlerMapping> handlerMappings = new ArrayList<THandlerMapping>();
//    Map<String,THandlerMapping> hadlerMappingMap = new HashMap<String, THandlerMapping>();

    private List<THandlerAdapter> handlerAdapters = new ArrayList<THandlerAdapter>();
    //spring采用的是list 这里简化吧
    private TViewResolver viewResolver;
//    private List<TViewResolver> viewResolvers = new ArrayList<TViewResolver>();

    @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 {
        try {
            //调用
            doDispatcher(req, resp);
        } catch (Exception e) {
            //404的 直接返回了 这里的异常全是服务器500异常
            Map<String, Object> modelMap = new HashMap<String, Object>();
            modelMap.put("msg", e.getMessage());
            modelMap.put("error", Arrays.toString(e.getStackTrace()));
            try {
                processDispatchResult(req, resp, new TModelAndView("500", modelMap));
            } catch (Exception e1) {
                e1.printStackTrace();
                resp.getWriter().write("500 Exception,Detail : " + Arrays.toString(e.getStackTrace()));
            }
        }
    }

    private void doDispatcher(HttpServletRequest req, HttpServletResponse resp) throws Exception {
        //先匹配url拿到mapping
        THandlerMapping handler = getHandler(req);
        if (handler == null) {
            processDispatchResult(req, resp, new TModelAndView("404"));
            return;
        }
        //从这里开始 异常都是 服务器异常了
        //根据mapping到HandlerAdapter 取回参数处理器
        THandlerAdapter ha = getHandlerAdapter(handler);

        //处理参数 反射调用
        TModelAndView mv = ha.handle(req, resp, handler);

        //返回值处理 是否返回页面
        processDispatchResult(req, resp, mv);
    }

    private void processDispatchResult(HttpServletRequest req, HttpServletResponse resp, TModelAndView mv) throws Exception {
        if(mv != null){
            TView view = viewResolver.resolveViewName(mv.getHtmlName());
            //渲染
            view.render(req,resp,mv.getModel());
        }
    }

    private THandlerAdapter getHandlerAdapter(THandlerMapping handler) {
        for (THandlerAdapter adapter : this.handlerAdapters) {
            if (adapter.handlerMapping.equals(handler)) {
                return adapter;
            }
        }
        return null;
    }

    private THandlerMapping getHandler(HttpServletRequest req) {
        String reqUrl = req.getRequestURI().replaceAll(req.getContextPath(), "");
        for (THandlerMapping handlerMapping : this.handlerMappings) {
            if (handlerMapping.getUrl().equals(reqUrl)) { //如果是正则 这里直接正则匹配
                return handlerMapping;
            }
        }
        return null;
    }

    @Override
    public void init(ServletConfig config) throws ServletException {
        //IOC DI阶段 直接调用上下文
        this.context = new TestApplicationContext(
                config.getInitParameter("contextConfigLocation"));

        //2.参考springMvc DispatcherServlet 初始化9大组件
        initStrategies(context);

        System.out.println("spring mvc is init");
    }

    //源码里复制
    private void initStrategies(TestApplicationContext context) {
//        //多文件上传的组件
//        this.initMultipartResolver(context);
//        //初始化本地语言环境
//        this.initLocaleResolver(context);
//        //初始化模板处理器
//        this.initThemeResolver(context);
        //url请求
        this.initHandlerMappings(context);
        //初始化参数适配器
        this.initHandlerAdapters(context);
//        //初始化异常拦截器
//        this.initHandlerExceptionResolvers(context);
//        //初始化视图预处理器
//        this.initRequestToViewNameTranslator(context);
        //初始化视图转换器
        this.initViewResolvers(context);
//        //FlashMap管理器
//        this.initFlashMapManager(context);
    }

    private void initViewResolvers(TestApplicationContext context) {
        //初始化模板的位置
        URL url = this.getClass().getClassLoader().getResource(context.getConfig().getProperty("htmlRootTemplate"));
        this.viewResolver = new TViewResolver(url.getPath());
    }

    private void initHandlerAdapters(TestApplicationContext context) {
        //初始化参数的位置
        for (THandlerMapping handlerMapping : this.handlerMappings) {
            //通过构造方法初始化 初始化时处理好参数
            this.handlerAdapters.add(new THandlerAdapter(handlerMapping));
        }
    }

    private void initHandlerMappings(TestApplicationContext context) {
        //从IOC容器中拿到实例化对象 遍历方法找到TRequestMapping 注解 取到URL
        Map<String, TBeanDefinition> beanDefinitionMap = context.getBeanDefinitionMap();
        for (String beanName : beanDefinitionMap.keySet()) {
            Object instance = context.getBean(beanName);
            Class<?> clazz = instance.getClass();
            //只有加了TController注解才扫描
            if (!clazz.isAnnotationPresent(TController.class)) continue;
            //类路径
            String baseUrl = "";
            if (clazz.isAnnotationPresent(TRequestMapping.class)) {
                TRequestMapping baseMapping = clazz.getAnnotation(TRequestMapping.class);
                if (!"".equals(baseMapping.value())) {
                    baseUrl = baseMapping.value();
                }
                //获取所有的方法 扫描注解
                for (Method method : clazz.getMethods()) {
                    if (!method.isAnnotationPresent(TRequestMapping.class)) continue;

                    TRequestMapping mapping = method.getAnnotation(TRequestMapping.class);
                    if ("".equals(mapping)) {
                        throw new RuntimeException("the method " + method.toString() + " TRequestMapping.value is null!");
                    }
                    //如果请求路径想要用到正则 将requrl转成 正则匹配即可
                    String reqUrl = (baseUrl + "/" + mapping.value()).replaceAll("/+", "/");

                    //保存对应关系 原始类对象 方法对象 reqUrl 后面invoke调用
                    handlerMappings.add(new THandlerMapping(instance, method, reqUrl));
                    System.out.println("添加访问路径:" + reqUrl);
                }
            }
        }
    }
}

6.Spring测试

  • 运行里面的测试类
public static void main(String[] args) {
    TestApplicationContext context = new TestApplicationContext("classPath:application.properties");
    HelloAction action = (HelloAction) context.getBean(HelloAction.class);
    String hello = action.hello("张三");
    System.out.println(hello);
}
  • 输出结果
方法之前调用:JoinPoint(target=com.xxx.demo.service.impl.HelloServiceImpl@5f150435, method=public abstract java.lang.String com.xxx.demo.service.HelloService.sayHello(java.lang.String), args=[张三], result=null, throwName=null, chainTarget=com.xxx.demo.aspect.LogAspect@50cbc42f, chainMethod=public void com.xxx.demo.aspect.LogAspect.before(com.xxx.framework.Model.JoinPoint))
方法之后调用:JoinPoint(target=com.xxx.demo.service.impl.HelloServiceImpl@5f150435, method=public abstract java.lang.String com.xxx.demo.service.HelloService.sayHello(java.lang.String), args=[张三], result=my name is 张三, throwName=null, chainTarget=com.xxx.demo.aspect.LogAspect@50cbc42f, chainMethod=public void com.xxx.demo.aspect.LogAspect.after(com.xxx.framework.Model.JoinPoint))
my name is 张三

7.MVC返回页面测试

  • 打包,配置tomcat。网页调用http://localhost:8080/mvc/index.html?name=helloWorld
  • Title welcome to helloWorld
    当前时间: 2022-01-11 16:45:36
    token: bfd333b4-295e-412c-ab38-d8379bfb66d8

8.MVC返回字符串测试

  • 网页调用http://localhost:8080/mvc/hello1?name=helloWorld
  • 日志输出
  • 方法之前调用:JoinPoint(target=com.xxx.demo.service.impl.HelloServiceImpl@2907b742, method=public abstract java.lang.String com.xxx.demo.service.HelloService.sayHello(java.lang.String), args=[helloWorld], result=null, throwName=null, chainTarget=com.xxx.demo.aspect.LogAspect@51454af0, chainMethod=public void com.xxx.demo.aspect.LogAspect.before(com.xxx.framework.Model.JoinPoint))
    方法之后调用:JoinPoint(target=com.xxx.demo.service.impl.HelloServiceImpl@2907b742, method=public abstract java.lang.String com.xxx.demo.service.HelloService.sayHello(java.lang.String), args=[helloWorld], result=my name is helloWorld, throwName=null, chainTarget=com.xxx.demo.aspect.LogAspect@51454af0, chainMethod=public void com.xxx.demo.aspect.LogAspect.after(com.xxx.framework.Model.JoinPoint))

  • 结果输出
  • my name is helloWorld

本文仅供参考,不支持转载源码地址点这里获取
以上就是本章的全部内容了。

上一篇:Spring源码分析第五弹 - 神级的spring还有其他什么功效?
下一篇:Springboot源码分析第一弹 - 自动装配实现

问渠那得清如许,为有源头活水来

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值