本篇是针对如何写一个比较好的spring工具的一个探讨。
下面三种方式,你觉得哪种最好?
ApplicationContext
;ApplicationContextAware
接口;BeanFactoryPostProcessor
接口;它的功能如下,它有国际化功能,beanFactory功能,事件发布功能,以及资源加载功能,作为上下文,他这个功能已经很强大了。
它发生的时机是在bean实例化后的依赖注入。
示例:
@Component
public class CustomConfig9 {
@Autowired
private ApplicationContext applicationContext;
@PostConstruct
public void init() {
CustomConfig3 bean = applicationContext.getBean(CustomConfig3.class);
System.out.println("customConfig9 获取了 customConfig3" + bean);
}
}
优点: 这种方式也比较简单,在需要用的bean中直接注入就行;
缺点: 它的局限就是在bean中才能使用,如果你要给工具类,或一个静态方法中使用,你就不太好这样做,你得控制你业务的执行时机;
这种方式是通过Bean初始化后,执行Aware接口回调方式实现,我见过很多项目,他们都是这样做的:
@Component
public class SpringUtil implements ApplicationContextAware {
private static ApplicationContext applicationContext;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
SpringUtil.applicationContext = applicationContext;
}
public static <T> T getBean(Class<T> clazz) {
return applicationContext.getBean(clazz);
}
}
这写法没毛病,可是,它存在一个bug :不是任何地方都能使用。
为何这么说?
那么我再增加一个类:
@Component
public class CustomConfig6 implements InitializingBean {
private CustomConfig config;
@Override
public void afterPropertiesSet() throws Exception {
config = SpringUtil.getBean(CustomConfig.class);
// 这里示例比较简单,一般业务场景可能是jdbc检索,redis缓存这些逻辑
}
}
在我增加了这样的一个类后,你觉得你的项目会是正常的吗?
请思考3秒钟…
.
.
.
那么答案是有可能是异常的,为何?
大家还记得Aware
接口是在哪个时机调用的,它是在bean初始化后调用的,spring bean的生命周期是单线程的,如果说Spring先实例化了CustomConfig6
,那么它会先调用afterPropertiesSet
里的SpringUtil.getBean
,而这时SpringUtil
还没有被实例化,SpringUtil
里的applicationContext
必然是null
;
为何会有先后顺序?
我们先复习一下Spring怎么扫描bean的,Spring是先扫描的当前包下的class,顺序扫描,扫描到的class,在经过一些了的校验后,会放到一个容器里,实例化时,再根据bean的名称(它是一个list)进行遍历实例化,到这,大概的一个原因应该明了了吧,如果SpringUtil
所在的文件位置考前,在其他类之前扫描到,就能先实例化,那么就是正常的,如果它靠后,就会出现其他业务bean回调时,通过SpringUtil
使用ApplicationContext
功能而出现空指针异常。
优点: 使用时直接静态方法调用,方便;
缺点: 可能存在bug;
在第二种的方式上进行优化,我们需要考虑,它的一个初始化时机,bean实例化都是统一进行的,所以,我们要打破这个规则,提前进行对SpringUtil
中的applicationContext
进行赋值,所以我们可以使用BeanFactoryPostProcessor
,这个后置处理器是在beanFactory
准备完成后端一个回调操作,我们的bean,配置类等等这些都是在这里被扫描出来的,是bean生命周期开始的开端。
@Component
public class SpringUtil2 implements BeanFactoryPostProcessor {
private static ConfigurableListableBeanFactory applicationContext;
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
SpringUtil2.applicationContext = beanFactory;
}
public static <T> T getBean(Class<T> clazz) {
return applicationContext.getBean(clazz);
}
}
优点: 可用在任何地方法;
ApplicationContext
它是通过依赖注入进行注入的,我们直接看创建bean的方法
位置:org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#doCreateBean
那我们的ApplicationContext
就是在populateBean
方法中被注入点,但是在此之前,它需要查找注入点,然后在注入时,可以直接通过注入点进行属性注入。(图片没有写全,注入点包含@Value, @Inject, 依赖注入的也包含@Value这写, 详细的看:spring源码篇(四)依赖注入(控制反转))
@Autowired
注入是由AutowiredAnnotationBeanPostProcessor
后置处理器进行处理,而@Resource
由CommonAnnotationBeanPostProcessor
处理
第二种是Aware
方法调用,也是在bean初始化时调用的,如上面图片描述的,他会在initializeBean
方法中调用,位置:org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#initializeBean(java.lang.String, java.lang.Object, org.springframework.beans.factory.support.RootBeanDefinition)
invokeAwareMethods
它提供了3个Aware
setBeanName
,实际上调用有我们控制,你想做什么就做什么;其他的在ApplicationContextAwareProcessor
如上,只要你实现ResourceLoaderAware, ApplicationEventPublisherAware, MessageSourceAware, ApplicationContextAware
他都会吧applicationContext
给你设置上。
这第三种Spring启动时执行的一个方法,就是进行bean容器的初始化;
这就是我们main
方法里的SpringApplication.run
:
这部分就是执行我们自定义的beanFactoryPostProcessor
,它分排序的和没有排序的,这个方法已经来会先进行BeanDefinitionRegistryPostProcessor
这个处理器的执行,这里就是进行扫描,解析配置类和bean(@Component, @Configuation, @Bean....
)的地方。所以我们在后面才能获取的我们自定义的bean,并提前实例化。