Spring之手写SpringMVC5个注解(之IOC,DI优化)了解三级缓存

Posted Huterox

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Spring之手写SpringMVC5个注解(之IOC,DI优化)了解三级缓存相关的知识,希望对你有一定的参考价值。

文章目录

前言

又鸽了辣么久,失策,失策,那么今天的这个东西呢,其实是对前面写的那个东西(手写那个mvc5个注解的那篇文章的优化)。当然这个内容也是来自咱们《Spring5核心原理与30个类手写实战 》这本书。强推,有时间背面试题不如看这本书,咱们把全流程走一下。
我们先大体说一说流程,然后在讨论一些细节。

流程回顾

咱们先回顾一下咱们原来写的流程。

原来我们是把所有的方法都放在了这个一个类里面,HUDispatcherServlet 这个上帝类里面。
这样显然是不行的,所以咱们得优化一下下。不过这里咱们目前是只优化这个IOC,DI 的内容。后面咱们优化
AOP,MVC的内容。

项目结构

那么接下来咱们得重新修改一下咱们的项目了。

配置文件

首先这个配置文件和上一次的一样,不会发生改变。

项目结构


光看这个咱们可能有点迷糊,那么咱们来说说这个流程。
这个IOC和DI的启动流程。

IOC/DI流程


这里我解释一下

ApplicationContext

这个呢,是咱们整个IOC的入口,其实在Spring里面有一个BeanFactory这个是顶层接口,然后那个ApplicationContext是那个接口的实现。这里没有搞那么复杂,不过流程大体流程是这样子的。

这个ApplicationContext提供了一个非常重要的方法,就是getBean()方法。这个方法就可以帮我们把那个容器里面的对象拿出来。

BeanDefinitionReader

这个是一个解析器嘛,负责解析各种配置文件,比如xml,yaml,properites之类的。
解析之后,要做的是把这个配置文件对应的内容给解析出来,然后把东西封装到BeanDefinition里面

BeanDefinition

这个玩意呢,是封装的我们的类的(被扫描出来)的信息。比如
我们在配置文件里面写的玩意是

我们要扫描这个包下面的所有类,那么这个扫描首先是reader读取了要扫描包的信息,然后reader去扫描
之后把扫描结果,类名,全包名封装起来。下面是它的代码,可以看到。

package com.huterox.spring.framework.beans.config;

public class HUBeanDefinition 
    private String factoryBeanName;
    private String beanClassName;

    public void setFactoryBeanName(String factoryBeanName) 
        this.factoryBeanName = factoryBeanName;
    

    public void setBeanClassName(String beanClassName) 
        this.beanClassName = beanClassName;
    

    public String getFactoryBeanName() 
        return factoryBeanName;
    

    public String getBeanClassName() 
        return beanClassName;
    


BeanWrapper

这玩意呢,其实是实例化后的对象,同样我们也是把这玩意封装起来了。

然后我们可以再看到这个代码

package com.huterox.spring.framework.beans;

public class HUBeanWrapper 
    private Object wrapperInstance;
    private Class<?> wrapperClass;
    public HUBeanWrapper(Object instance) 
        this.wrapperInstance = instance;
        this.wrapperClass = this.wrapperInstance.getClass();
    

    public Object getWrapperInstance() 
        return wrapperInstance;
    

    public Class<?> getWrapperClass() 
        return wrapperClass;
    


getBean()方法

最后就是我们的这个方法,这个方法呢,是我们的核心

我们的依赖注入就是在这里完成的,同时也是在这里面会产生这个ABA问题嘛
下面这个就是别人博客里面的什么三级缓存

我们接下来会好好说说这个。
这个一级缓存到底是啥,二级是啥,三级是啥
看到这里咱们也用了

IOC启动流程(HUApplicationContext)

启动/调用

接下来咱们好好聊聊这个IOC
首先是咱们的这个MVC部分,咱们是做了一个拆分,把IOC独立了
所以我们可以看到这里,这里的话就被启动了,调用了

这个MVC部分呢,调用了咱们的容器,然后我们的容器开始启动工作。

流程

OK到了咱们这个流程了
一来进入咱们的构造方法

获取Definition

先获取到咱们的Definition。这里有两步嘛。
主要是reader读取配置文件,然后扫描包封装信息嘛。
这里我把这个reader的代码给出来

package com.huterox.spring.framework.beans.suports;

import com.huterox.spring.framework.beans.config.HUBeanDefinition;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.Properties;

public class HUBeanDefinitionReader 

    private Properties contextConfig = new Properties();
    //需要被注册到容器里面的类
    private List<String> registryBeanClass = new ArrayList<>();
    public HUBeanDefinitionReader(String[] configLocations) 
        //1.加载配置文件并解析,当前只支持properties先这样处理
        doLoadConfig(configLocations[0]);
        //2.读取配置文件,并且封装为BeanDefinition对象
        doScanner(contextConfig.getProperty("scanPackage"));
        doLoadBeanDefinitions();
    

    //负责读取配置文件
    public List<HUBeanDefinition> doLoadBeanDefinitions() 
        //这里注意此时我们存入的是类的包名com.xx.xx.xx
        List<HUBeanDefinition> result = new ArrayList<>();
        try 

            for (String className:registryBeanClass)
                Class<?> beanClass = Class.forName(className);
                if(beanClass.isInterface()) continue;
                //把类名放在里面 beanClass.getName()是获取全包名
                result.add(doCreateBeanDefition(toLowerFirstCase(beanClass.getSimpleName()), beanClass.getName()));
                //把这玩意的接口也给它
                for(Class<?> InfClass:beanClass.getInterfaces())
                    result.add(doCreateBeanDefition(InfClass.getSimpleName(), beanClass.getName()));
                
            

        catch (Exception e)
            e.printStackTrace();
        

        return result;
    

    private HUBeanDefinition doCreateBeanDefition(String factoryBeanName, String beanClassName) 
        HUBeanDefinition beanDefinition = new HUBeanDefinition();
        beanDefinition.setFactoryBeanName(factoryBeanName);
        beanDefinition.setBeanClassName(beanClassName);
        return beanDefinition;
    


    private void doLoadConfig(String contextproperties) 
        InputStream is = this.getClass().getClassLoader().getResourceAsStream(contextproperties);
        try 
            contextConfig.load(is);
         catch (IOException e) 
            e.printStackTrace();
        finally 
            if(is!=null)
                try 
                    is.close();
                 catch (IOException e) 
                    e.printStackTrace();
                
            
        
    


    private void doScanner(String scanPackage)
        // 扫描我们想要控制的那个包下面的所有类
        //存进去的是 com.huterox.test.xx(类名)
        URL url = this.getClass().getClassLoader().getResource("/" + scanPackage.replaceAll("\\\\.", "/"));

        assert url != null;
        File classDir = new File(url.getFile());
        for(File file: Objects.requireNonNull(classDir.listFiles()))
            if(file.isDirectory())
                this.doScanner(scanPackage+"."+file.getName());
            else 
                if (!file.getName().endsWith(".class")) continue;
                String clazzName = (scanPackage + "." + file.getName().replace(".class", ""));
                registryBeanClass.add(clazzName);
            
        
    

    private String toLowerFirstCase(String simpleName)
        //字母转换,符合javabean标准
        char[] chars = simpleName.toCharArray();
        if(67<=(int)chars[0]&&(int)chars[0]<=92)
            chars[0] +=32;
        return String.valueOf(chars);
    



BeanDefinition的代码上面给了,咱们就不重复了(所以好好看看本文,代码是在每一步给你的,不是最后面全部贴出来滴!不过方向,绝对不会让读者手打代码,毕竟我也要回过来看呀)

然后这里注意的是,此时我们把这个BeanDefinition给放在了我们的Map里面

这个存储的是所有的类(test包下面)的信息,对比刚刚被人博客里面的三级缓存的图


你发现这个一级缓存的初始容量很大,那咱们的definition你会发现这个其实存的东西是最多的。
再来对比一下给出的spring里面的那个关于三级缓存的解释:

1. 一级缓存-singletonObjects是用来存放就绪状态的Bean。保存在该缓存中的Bean所实现Aware子接口的方法已经回调完毕,自定义初始化方法已经执行完毕,也经过BeanPostProcessor实现类的postProcessorBeforeInitialization、postProcessorAfterInitialization方法处理;

2. 二级缓存-earlySingletonObjects是用来存放早期曝光的Bean,一般只有处于循环引用状态的Bean才会被保存在该缓存中。保存在该缓存中的Bean所实现Aware子接口的方法还未回调,自定义初始化方法未执行,也未经过BeanPostProcessor实现类的postProcessorBeforeInitialization、postProcessorAfterInitialization方法处理。如果启用了Spring AOP,并且处于切点表达式处理范围之内,那么会被增强,即创建其代理对象。 这里额外提一点,普通Bean被增强(JDK动态代理或CGLIB)的时机是在AbstractAutoProxyCreator实现的BeanPostProcessor的postProcessorAfterInitialization方法中,而处于循环引用状态的Bean被增强的时机是在AbstractAutoProxyCreator实现的SmartInstantiationAwareBeanPostProcessor的getEarlyBeanReference方法中。

3. 三级缓存-singletonFactories是用来存放创建用于获取Bean的工厂类-ObjectFactory实例。在IoC容器中,所有刚被创建出来的Bean,默认都会保存到该缓存中。

不过在咱们这里由于还没涉及到那么多复杂的东西,所以我们这边只是简单处理,这个一级缓存就是我们首次扫描到的Bean

创建容器

这里的话咱们是一个方法嘛
doCreateBean();
这个doCreateBean()其实调用的是getBean()

getBean()方法(重点)

这个getBean方法就是做了好几步骤

instanceBean

这个方法是负责实例化咱们的对象

你会发现此时我们又是把那个第一次没有DI注入的对象给存进去了 这里

private Map<String, Object> factoryBeanObjectCache = new HashMap<>();

对比刚刚这句话

2. 二级缓存-earlySingletonObjects是用来存放早期曝光的Bean,一般只有处于循环引用状态的Bean才会被保存在该缓存中。保存在该缓存中的Bean所实现Aware子接口的方法还未回调,自定义初始化方法未执行,也未经过BeanPostProcessor实现类的postProcessorBeforeInitialization、postProcessorAfterInitialization方法处理。如果启用了Spring AOP,并且处于切点表达式处理范围之内,那么会被增强,即创建其代理对象。 这里额外提一点,普通Bean被增强(JDK动态代理或CGLIB)的时机是在AbstractAutoProxyCreator实现的BeanPostProcessor的postProcessorAfterInitialization方法中,而处于循环引用状态的Bean被增强的时机是在AbstractAutoProxyCreator实现的SmartInstantiationAwareBeanPostProcessor的getEarlyBeanReference方法中。

当然咱们现在没有AOP哈,不过这个算不算是我们早期暴露出来的Bean

populateBean

最后就是完成我们的这个注入
这个的话和我们以前的那一套一样


然后加入我们最后一级缓存

HUApplicationContext完整代码

package com.huterox.spring.framework.context;

import com.huterox.spring.framework.annotation.HUAutowired;
import com.huterox.spring.framework.annotation.HUController;
import com.huterox.spring.framework.annotation.HUService;
import com.huterox.spring.framework.beans.HUBeanWrapper;
import com.huterox.spring.framework.beans.suports.HUBeanDefinitionReader;
import com.huterox.spring.framework.beans.config.HUBeanDefinition;

import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class HUApplicationContext 

    private String[] configLocations;
    private HUBeanDefinitionReader reader;

    private Map<String, HUBeanDefinition> beanDefinitionMap = new HashMap<>();
    private Map<String, HUBeanWrapper> factoryBeanInstanceCache = new HashMap<>();
    private Map<String, Object> factoryBeanObjectCache = new HashMap<>();

    public HUApplicationContext(String... configLocations) 
        this.configLocations = configLocations;
        //加载配置文件读取器
        this.reader = new HUBeanDefinitionReader(this.configLocations);
        List<HUBeanDefinition> beanDefinitions = this.reader.doLoadBeanDefinitions();
        //2.缓存beanDefinitions对象
        try 
            doRegistryBeanDefintition(beanDefinitions);
         catch (Exception e) 
            e.printStackTrace();
        

        //3.创建IOC容器,把类都放在IOC容器里面
        doCreateBean();
    

    private void doCreateBean() 
        for (Map.Entry<String, HUBeanDefinition> beanDefinitionEntry : this.beanDefinitionMap.entrySet()) 
            String beanName = beanDefinitionEntry.getKey();
            //非延时加载
            getBean(beanName);
        
    

    private void doRegistryBeanDefintition(List<HUBeanDefinition> beanDefinitions) throws Exception 
        //把Definition对象放在咱们的map里面,beanDefinition是保存的类名,全包名
        for (HUBeanDefinition beanDefinition : beanDefinitions) 
            //双键存储便于双向查找
            if (this.beanDefinitionMap.containsKey(beanDefinition.getFactoryBeanName())) 
                throw new Exception("The " + beanDefinition.getFactoryBeanName() + " is exists!");
            
            this.beanDefinitionMap.put(beanDefinition.getFactoryBeanName(), beanDefinition);
            this.beanDefinitionMap.put(beanDefinition.getBeanClassName(), beanDefinition);
        
    

    //创建Bean的实例,完成依赖注入
    public Object getBean(String beanName) 
        //得到对象的全包名,当然这个信息封装在beanDefinition里面
        HUBeanDefinition beanDefinition = this.beanDefinitionMap.get(beanName);
        //实例化
        Object instance = instanceBean(beanName, beanDefinition);
        if (instance == null) return null;
        //实例封装为beanWrapper当中
        HUBeanWrapper beanWrapper = new HUBeanWrapper(instance);
        //将这个wrapper对象放在容器里面IOC里面
        this.factoryBeanInstanceCache.put(beanName, beanWrapper);
        //完成依赖注入
        populateBean(beanName, beanDefinition, beanWrapper);
        return this.factoryBeanInstanceCache.get(beanName).getWrapperInstance();
    

    private void populateBean(String beanName, HUBeanDefinition beanDefinition, HUBeanWrapper beanWrapper) 
        //开始做依赖注入给值
        Object instance = beanWrapper.getWrapperInstance();
        Class<?> clazz = beanWrapper.getWrapperClass();
        Field[] fields = clazz.getDeclaredFields();
        for (Field field : fields) 
            if (!(field.isAnnotationPresent(HUAutowired.class))) continue;
            HUAutowired autowired = field.getAnnotation(HUAutowired.class);
            String autowiredBeanName = autowired.value().trim();
            if ("".equals(autowiredBeanName)) 
                autowiredBeanName = field.getType().getName();
            
            field.setAccessible(true);

            try 
                if (!this.factoryBeanInstanceCache.containsKey(autowiredBeanName)) continue;
                //这个就是为什么要按照标准来写首字母要小写
                field.set(instance, this.factoryBeanInstanceCache.get(autowiredBeanName).getWrapperInstance());
             catch (IllegalAccessException e) 
                e.printStackTrace();
            
        

    

    private Object instanceBean(String beanName, HUBeanDefinition beanDefinition) 
        String className = beanDefinition.getBeanClassName();
        Object instance = null;
        try 
            Class<?> clazz = Class.forName(className);
            if (!(clazz.isAnnotationPresent(HUService.class) || clazz.isAnnotationPres

以上是关于Spring之手写SpringMVC5个注解(之IOC,DI优化)了解三级缓存的主要内容,如果未能解决你的问题,请参考以下文章

用30个类手写Spring V2.0版本之MVC实现

用30个类手写Spring V2.0版本之MVC实现

用30个类手写Spring V2.0版本之AOP实现与总结

Java开发Spring之AOP详解(xml--注解->方法增强事务管理(声明事务的实现))

Spring之手写MVC三大核心组件

透彻理解Spring事务设计思想之手写实现