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优化)了解三级缓存的主要内容,如果未能解决你的问题,请参考以下文章