不要再说不会Spring了!Spring第一天,学会进大厂!
Spring第二天,你必须知道容器注册组件的几种方式!学废它吊打面试官!
Spring第三天,详解Bean的生命周期,学会后让面试官无话可说!
今天讲解Spring底层对BeanPostProcessor的使用。
背景:BeanPostProcessor是什么?有什么用?
Spring所有对Bean的处理,BeanPostProcessor接口都贯穿其中,都离不开该接口。BeanPostProcessor接口可以管理bean工厂内所有beandefinition(未实例化)数据,可任意修改属性。
首先来看一下有哪些类实现了BeanPostProcessor接口
可以从上图看出,有这么多的接口及类都实现了BeanPostProcessor接口,今天重点来讲解ApplicationContextAwareProcessor类、BeanValidationPostProcessor类、InitDestoryAnnotationBeanPostProcessor类的原理。其中ApplicationContextAwareProcessor类是为一些Aware类型的接口,注入对应的属性值。此类帮我们组件IOC容器,是一个后置处理器,跟进ApplicationContextAwareProcessor类我们发现,这个后置处理器其实就是判断我们的bean有没有实现ApplicationContextArea接口,并处理相应的逻辑,所有的后置处理器的原理都是如此。
(其他的类和接口在这里暂且不讨论,重点关注ApplicationContextAwareProcessor类是如何帮助组件IOC容器的。ps:单词都很长,一定要注意区分对应的类和接口)
一、ApplicationContextAwareProcessor实现分析
问题:ApplicationContextAwareProcessor如何帮助我们组件IOC容器呢?
回答:只需要实现ApplicationContextAware接口即可。
定义实体类Plane.java类
我们再来分析一下ApplicationContextAwareProcessor类的源码方法
1. 在创建Plane对象,并且还没有初始化之前,先判断是否是实现了ApplicationContextAware接口,如果实现了的话,就调用invokeAwareInterfaces()方法,并给里面注入值。
2. 接下来我们进入invokeAreaInterfaces()方法,判断是哪一个aware,如果是ApplicationContextAware,就将当前的bean转成ApplicationContextAware类型,调用setApplicationContext(),把plane注入到IOC容器中去。
3. 通过debug调用,测试用例打断点来调试:
4. 也可以用debug调用栈来分析。
ps:debug可以打在ApplicationContextAwareProcessor处理器类的PostProcessorsBeforeInitialization()方法里,便于调试,当bean为plane类型时,F5跟进看,最终在InvokeAwareInterfaces()方法里返回我们的IOC容器applicationContext。
二、BeanValidationPostProcess实现分析
该类主要用于数据校验,当创建完对象,给bean赋值后,在Web用的特别多;把页面提交的值进行校验。
通过源码可以从BeanValidationPostProcess类中可以看到87和95行,有postProcessBeforeInitialization() 和 postProcessAfterInitialization() 方法,这两个方法分别在bean初始化之前 和 初始化之后进行校验。
三、InitDestoryAnnotationBeanPostProcessor实现分析
此类用来处理JSR250(JDK提供)规范中@PostConstruct 和 @PreDestory,怎么知道这两注解是前后开始调用的呢?
定义实体类Jeep.java类
以@PostConstruct为例,为什么声明了这个注解之后,就可以找到初始化init方法呢?
继续执行,执行到invokeInitMethods()方法中可以看到,利用反射调用了该方法
从上面的debug调试可以得知,InitDestoryAnnotationBeanPostProcessor来处理了这两个注解是前后开始调用的。
接下来,我们来了解一下@Autowired自动装配
四、@Autowired自动装配
什么是自动装配?自动装配:Spring利用依赖注入(DI),完成对IOC容器中的各个组件的依赖关系赋值。
1. 新建TestController.java 、TestService.java 、 TestDao.java 类分别建立在指定包内,通过配置类,将所有这些Java对象扫描后保存在IOC容器中管理。
2. 新建配置类,扫描并将以上bean都扫描并加载到容器中
3. 针对以上类建立完成后,在TestService.java中,使用@Autowired注入,并把testDao打印出来
4. 新建测试类,比较TestService 拿到testDao与直接从容器拿到的testDao是否为同一个?
结果很明显,打印出来是同一个testDao,地址一样
小结:
@Autowired表示默认优先按类型去容器中找对应的组件,相当于anno.getBean(TestDao.class)去容器中获取id为testDao的bean,并注入到TestService的bean中;
使用方式如下:
public class TestService {
// 默认去容器中找testDao的bean
@Autowired
private TestDao testDao;
}
5. 注意事项
5.1 如果容器中找到多个testDao,会加载哪一个testDao呢?
操作步骤
在配置类声明@Bean("testDao2")
并将TestDao加入flag属性和get、set以及toString()方法,用来分辨加载了哪个bean。
如何区分TestService是使用了(@Reponstry的testDao的flag=1)的bean还是(testDao2的flag=2)的bean?
测试步骤如下:
1. 直接使用@Autowired, 将testDao注入到TestService
bean id为testDao,根据id默认去找@Reponsitory注解的testDao
测试结果为:
service...testDao...TestDao [flag=1]
2. 如果一定要使用容器中的testDao2呢?操作如下:
将bean id指向testDao2即可
测试结果为:
service...testDao...TestDao [flag=2]
3. 虽然以上定义了private TestDao testDao2, 但还是想加载bean id为testDao(flag=1)的bean,怎么办?此时可以使用@Autowired和@Qualifier结合来指定注入哪一个bean,
操作如下:
通过使用@Qualifier注解,指定加载testDao
测试结果:
service...testDao...TestDao [flag=1]
4. 如果容器中没有任何一个testDao, 会出现什么状况呢?
操作如下: 注释掉@Repository和@Bean("testDao2")
此时容器启动时这两个bean都不会加载(因为注解被注释啦.......)
测试结果如下:
很明显报错了, 因为@Autowired注解里的属性默认required=true.必须找到bean
那怎么解决呢?
将TestService中@Autowired中required设置为false,指定为非必须,当容器中无此bean,也不会报错。
测试结果如下:
null
5. @Primary注解指定bean如何加载呢?
(注:将以上原注释掉的@Repository和@Bean("testDao2") 恢复,见下图)
重要:为了验证@Qualifier与@Primary两注解的加载顺序,测试如下:
当对于testDao在容器中同时存在多个时, 且@Qualifier与@Primary注解同时存在,会发生什么呢?
见下操作: 打开@Qualifier与@Primary注解.
测试结果:
TestDao [flag=1]
unit....test....TestDao [flag=2]
此时只能说明一点: @Qualifier是根据bean id指定获取testDao, 不受@Primary影响
那么@Primary的功能在哪呢?继续测试.....
测试结果:
5.2 除了@Autowired, 是不是还用过@Resource(JSR250) 和@Inject(JSR330)
将Qualifier和Autowired注释掉(注意: 此时@Primary 还没注释......)
测试结果:
效果也是一样的, 但它不先优先装配@Primary的bean
小结:@Resource和Autowired的区别如下:
@Resource和Autowired一样可以装配bean
@Resource缺点:
① 不能支持@Primary功能
② 不能支持@Autowired(required = false)的功能
当然也可以在TestService里按以下方式指定要注入的Bean
测试结果:
5.3 @Inject自动装配的使用:
注:@Inject与@Autowired的区别如下:
@Inject和Autowired一样可以装配bean, 并支持@Primary功能, 可用于非spring框架.
@Inject缺点: 但不能支持@Autowired(required = false)的功能,需要引入第三方包javax.inject
操作步骤:
1,pom.xml导入javax.inject包
2,使用@Inject注解
结论:@Inject不支持required=false, 但支持primary
Autowired属于spring的, 不能脱离spring, @Resource和@Inject都是JAVA规范
推荐大家使用@Autowired
这一章节比较生涩难懂。可以在下方留言进行一起沟通讨论。
(来自享学IT的总结)
阅读原文:Spring第四天,BeanPostProcessor源码分析,彻底搞懂IOC注入及注解优先级问题!
明天将会介绍一章重头戏,AOP底层介绍,理论实践。
关注公众号【Java极客思维】