Spring 源码个人总结
1、spring的bean和beanDefinition。
2、我对Spring有着自己的一套理解的模型,这个模型里,简单来说主要包含了五个东西:
- BeanDefinitionMap
- singletonObjectMap
- ApplicationContext(AnnotationConfigApplicationContext、ClassPathXMLApplicationContext)
- BeanDefinition的后置处理器,代表:ConfigurationClassPostProcesser
- Bean的后置处理器,代表:AutowiredAnnotationBeanPostProcessor
这五个的关系图,就是之前那个知乎大神画的图:
AnnotationConfigApplicationContext:
内置scanner和reader,以scanner为例:
扫描指定包里面的所有类,转成Resource,... ,最后将所有的BeanDefinition存到BeanDefinitionMap中
同时,会注册7个后置处理器,这里面就包含了我所提到的两个后置处理器。
接着,就会调用refresh()方法。
refresh()方法中,会先对BeanDefinition使用BeanDefinition的后置处理器,例如ConfigurationClassPostProcesser。
ConfigurationClassPostProcesser:
AnnotationConfigApplicationContext只能找到满足条件的@Component类,
但是除此之外还有很多别的注解,例如@ComponentScan,@Import,@PropertySource等,
这些注解的类的父级并不是@Component类,所以此时并不在BeanDefinitionMap里,
因此本后置处理器会去将这部分类转成BeanDefinition存到BeanDefinitionMap里。
接着会根据BeanDefinition生成Bean,此时就会用到bean的后置处理器:
AutowiredAnnotationBeanPostProcessor:
获取bean内部的@Autowired 或@Value注解的属性,根据属性的Class类型,从BeanDefinitionNames(List)中进行匹配,找出对应的beanDefinition,然后再生成bean,开启反射注入到这个类中。
0 学习spring原理对我的帮助(真实场景):
在实际开发中,经常会遇到引入了某个包之后,项目启动不起来了,或者spring容器报错导致项目起不来。因为我对spring容器的底层源码比较熟悉,所以同事都会来找我帮忙快速定位,我举个例子:
编写sdk时,借鉴了Springboot的自动配置。使用spring.factories注入Configuration。
但是在测试的时候,一直在报Configuration里没有引入Bean。在spring源码成了体系后,我可以立马去制定排查方案:
- 是Configuration没有注入还是Configuration里的Bean没有注入
- ConfigurationClassPostProcesser这个后置处理器会去解析Configuration类,将里面的Bean解析成BeanDefinition
所以,只需要在refresh上debug一下,看看BeanFactoryPostProcesser处理后的BeanDefinition包不包含我注入的类就能确定是哪出了问题。
为什么:Configuration不在BeanFactoryPostProcesser的BeanDefinition中?因为配置的ComponentScan里没有覆盖到对应的包名。
例子2:
启动的时候报类XXX找不到,在populateBean的代码里打断点,加上BeanName.equals(报错的类)作为判断条件,进入断点后,直接去容器的BeanDefinitionMap里搜一下对应的bean在不在,这个时候看看是不是有别的相同的BeanName,或者压根没有这个bean
1 Bean的生命周期(重要)
(注:1、2、3步就是createBean()的三部曲)
看这个,这个文章的图很清晰:请别再问Spring Bean的生命周期了!
几个关键点:
- 第一大类:作用于多个bean的接口,触发时机如上图。
- 第二大类中的 aware,分为了两组,各自的触发时机。
- 第二大类中的 InitializingBean,DisposableBean。
- createBeanInstance():实例化bean
需要注意,这里的实例化仅仅是生成了这么一个bean,还并没有进行初始化, - populateBean():属性的装配
将bean的一些属性注入到bean中,例如@AutoWired和@Value就需要用到AutowiredAnnotationBeanPostProcessor这个后置处理器来进行注入。 - initializeBean():这里实际上分为了三步:
3.1. 处理Aware接口:调用各种Aware类的各种setXX()方法,设置各种spring的属性到bean中:https://www.jianshu.com/p/c5c61c31080b
3.2 postProcessBeforeInitialization()
3.3 如果Bean实现了InitializingBean接口,执行afeterPropertiesSet()方法。
3.4 如果beanDefinition中设置了init-method,则根据method名并利用反射调用自定义的初始化方法。
3.5 postProcessAfterInitialization() - 如果Bean实现了DisposableBean接口,执行destroy()方法。
- 如果Bean在配置文件中的定义包含destroy-method属性,执行指定的方法。
2 beanFactory和factoryBean(重要)
BeanFactory
:就是生产bean的工厂,用于生成任意bean。
FactoryBean
:特殊bean,用于生成特定的bean。FactoryBean在IOC容器的基础上给Bean的实现加上了一个简单工厂模式和装饰模式,这样便可以在getObject()方法中对要生成的bean进行灵活配置,比方说生成bean的前后都可以加入一些代码。
在spring配置文件中配置了名字 myFcBean 的一个类型,该类型是 FactoryBean 的实现类。那么通过
BeanFactory.getBean(“myFcBean”) 返回的并不是这个类型本身的对象,而是调用这个对象的getObject方法的返回值。
需要注意的是:getBean(“factoryBean”); 返回的不是这个工厂bean,而是它生产的具体bean。
使用:比方说在生成前加一段话。
public class ProductFactory implements FactoryBean<Object> {
...
@Override
public Object getObject() throws Exception {
logger.debug("getObject......");
return new Product();
}
@Override
public Class<?> getObjectType() {
return Product.class;
}
@Override
public boolean isSingleton() {
return true;
}
...
}
配置:
<bean id="ProductFactory" class="com.xxx.ProductFactory"/>
3 spring的循环依赖,三级缓存
这一篇讲的比较清晰:https://blog.csdn.net/u010013573/article/details/90573901
3.1 个人总结
循环依赖指的是:
A的属性有@Autowired了B,B的属性有@Autowired了A。
在创建A的时候,因为注入属性时会先去创建属性,所会去创建B,然后创建B的时候,同样会去创建A,导致无限循环。
解决这个问题的方法:找一块地方来暂存A或者B的半成品。
我们来看看spring是怎么解决的:
spring解决循环依赖的方法是“三级缓存”,也就是三个map。
/** Cache of singleton objects: bean name --> bean instance */
//singletonObjects,存放初始化后的单例对象
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<String, Object>(256);
/** Cache of early singleton objects: bean name --> bean instance */
//存放已经实例化,但未注入属性及未完成初始化的单例对象
private final Map<String, Object> earlySingletonObjects = new HashMap<String, Object>(16);
/** Cache of singleton factories: bean name --> ObjectFactory */
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<String, ObjectFactory<?>>(16);
关于第三级缓存为什么存放着的是beanFactory。会在后面"为什么是三级而不是二级缓存"中进行讲解。
创建bean有三部曲:createBeanInstance()、 populateBean()、initializeBean()
循环依赖发生在populateBean()上。但在createBeanInstance()之后,populateBean()之前会有一个addSingletonFactory()方法,这个方法会将bean的半成品存到第三级缓存中:
protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
Assert.notNull(singletonFactory, "Singleton factory must not be null");
synchronized(this.singletonObjects) {
if (!this.singletonObjects.containsKey(beanName)) {
this.singletonFactories.put(beanName, singletonFactory);
this.earlySingletonObjects.remove(beanName);
this.registeredSingletons.add(beanName);
}
}
}
具体流程:
我们以A为例,A的半成品存入第三级缓存中,然后调用populateBean,这时遇到A的属性B时,会转去调用createBean(B)创建B。
同样的,B也会发现自己有属性A,然后获取A的时候在三级缓存中发现了半成品的A,这样就能继续完成B自己的创建工作。
B完成后,会将自己放到一级缓存中,然后回到了A,因为A能拿到对象B,所以便可以顺利的完成创建工作。
3.2 为什么在构造方法中设置属性无法解决循环依赖?
这是因为调用构造函数是在第一步createBeanInstance()中,这一步是在放入三级缓存前,所以无法解决。
3.3 为什么要用三级缓存,而不是用二级?
假设我是spring的设计者,最开始我和你们想的一样,所以设计了一个二级缓存用于存储bean的半成品,解决了前面提到的AB循环依赖问题。
但是测试的时候发现这么一个case:如果A或者B是需要进行AOP代理的,那么会导致A注入的是B本身,实际上预期的结果应该是注入B的代理对象。
针对这个case,最初的解决方案是:
- 不管三七二十一,所有 Bean 在实例化后就要完成 AOP 代理再存入二级缓存中。
这种方法的好处是简单,但是它会直接破坏了 对Spring 设计的原则,Spring 在设计原则是在 Bean 生命周期的最后一步来完成 AOP 代理,而不是在实例化后就立马进行 AOP 代理。
所以,要做的应该只是将需要提前暴露出来(循环依赖)的bean生成代理对象,其他的还是放在最后再统一进行AOP代理。所以设计者采用了方法二:
- 在二级缓存的基础上,再加一层缓存,用于存放beanFactory,之所以存beanFactory是因为beanFactory可以自定义生成bean的方法。所以,我可以只对需要提前暴露的beanFactory中提供代理方法,这样就能实现小部分的提前代理。
具体实现为:
protected Object doCreateBean( ... ){
...
boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences && isSingletonCurrentlyInCreation(beanName));
// 需要提前暴露(支持循环依赖),就注册一个ObjectFactory到三级缓存
if (earlySingletonExposure) {
// 添加BeanFactory,并注入方法getEarlyBeanReference
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
}
//注入属性
populateBean(beanName, mbd, instanceWrapper);
getEarlyBeanReference方法会判断是否需要进行AOP代理:
protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
Object exposedObject = bean;
if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
for (BeanPostProcessor bp : getBeanPostProcessors()) {
//如果有SmartInstantiationAwareBeanPostProcessor,则需要进行对bean进行AOP代理
if (bp instanceof SmartInstantiationAwareBeanPostProcessor) {
SmartInstantiationAwareBeanPostProcessor ibp = (SmartInstantiationAwareBeanPostProcessor) bp;
exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName);
}
}
}
return exposedObject;
}
@Override
public Object getEarlyBeanReference(Object bean, String beanName) throws BeansException {
Object cacheKey = getCacheKey(bean.getClass(), beanName);
this.earlyProxyReferences.put(cacheKey, bean);
// 对bean进行提前Spring AOP代理
return wrapIfNecessary(bean, beanName, cacheKey);
}
详见:烂了大街的 Spring 循环依赖问题,你以为自己就真会了吗
4 springboot自动装配
详见:https://blog.csdn.net/bintoYu/article/details/105497852
因为在网上看到了比较好的介绍文章,因此就不重复劳动了,传送门:
springboot 源码 - 自动配置
也可以看看视频:https://www.bilibili.com/video/BV1Et411Y7tQ?p=18
总结:
顺序:@EnableAutoConfiguration -> spring.factories -> ConfigurationClassPostProcessor ->@EnableConfigurationProperties -> @Conditional
Spring Boot启动的时候会通过@EnableAutoConfiguration注解找到META-INF/spring.factories配置文件中的对应的所有自动配置类,并解析存放到spring容器中,这些自动配置类会根据条件配置大量的组件,同时,因为在配置组件时会从Properties类中获取属性(@EnableConfigurationProperties),所以我们可以在配置文件中指定这些属性的值,另外,需要注意不是所有的自动配置类都会生效,需要满足特定条件才会生效(@Conditional)。
5 Spring AOP和AspectJ的区别:
- spring aop使用的是JDK和CGLIB动态代理,因此其用于代码运行时织入,而ApsectJ是在编译时和class文件加载时织入。
- Spring aop是基于代理,而AspectJ是基于字节码操作。
- 速度的话,AspectJ比Spring AOP要快得多,但Spring AOP简单,因此切面少的时候使用Spring aop,切面多的时候就要开始考虑使用AspectJ了。
JDK 动态代理 —— Spring AOP 的首选方式。只要目标对象实现甚至一个接口, 就会使用 JDK 动态代理;
CGLIB 代理 —— 如果目标对象没有实现接口, 则可以使用 CGLIB 代理。
6 Spring的单例模式和Java的单例的区别
java的单例指的是jvm中单例,而Spring的单例模式指的是spring容器中的单例。
7 spring的bean是线程安全的吗?
这个问题实际上是有一定的误导性,实际上,spring的bean是否是线程安全,与spring容器无关。spring仅仅是负责托管这些bean。
我们知道,spring的bean分为了单例和多例。多例下每次从容器里取出(getBean)的都是新的bean实例,不存在被共享的问题,所以是线程安全的。
spring的单例模式还分为了两种情况:
- 无状态的bean:只对bean的成员变量进行查询操作。 线程安全
- 有状态的bean:会对bean的成员变量进行更新操作。存在线程安全问题
此时该如何处理线程安全问题?
- 将单例改成多例。
- 将可变的成员变量存到ThreadLocal中。接下来面试官就可能会问ThreadLocal相关的问题。
8 Spring如何处理线程并发问题?
在一般情况下,只有无状态的Bean才可以在多线程环境下共享,在Spring中,绝大部分Bean都可以声明为singleton作用域,因为Spring对一些Bean中非线程安全状态采用ThreadLocal进行处理,解决线程安全问题。
9 Spring 框架中都用到了哪些设计模式?
https://blog.csdn.net/a745233700/article/details/80959716
10 spring的多例模式
注:多例会从BeanDefinitionMap里取出BeanDefinition,然后每一次getBean()都会创建新的bean实例(每一次都不同),需要注意的是,这些实例不会存入singletonObjectMap中。