Spring生命周期
上面分析了如何创建一个ApplicationContext,让我们可以在各种特殊环境下构建一个Spring应用,但是日常工作中大多数还是直接使用的Spring提供的创建方式,如xml、注解、Groovy脚本等。
工作中更多的时候我们对Spring的定制化改造是在其Spring应用生命周期中,如统一的拦截,特定的加密,对bena的特殊的初始化等等操作。
上面我们分析了,一个Spring应用就是一个ApplicationContext的具体实现,这个就比较宏观了,我们知道怎么创建启动一个Spring应用了没用呀,毕竟工作中是在Spring框架范围内,进行研发,因此要知道细节。
一个Bean粗略一生
按照我们自己的理解来想一下,如果要自己实现一个IOC容器功能,需要有哪几步?
- 加载资源(配置),告诉IOC要创建哪些Bean
- 创建完成后为bean设置互相依赖关联,实现依赖注入
- 执行完2后,应用基本上就创建好了,最后完成一些创建完成后初始化值操作
- 等着销毁,执行一些扫尾操作
抛开Spring的那些强大功能和多元化的bean操控方式,大致也就上面这几个步骤。接下来仔细看下Spring是如何实现,上面几步,并且是怎样提供扩展接口的。
Spring周期总览
上面介绍的ApplicationContext你会发现现有Spring的无论哪个ApplicationContext最后都会执行一个方法就是org.springframework.context.support.AbstractApplicationContext#refresh该方法基本上可以说就是Spring生命周期体现,源码如下:
public void refresh() throws BeansException, IllegalStateException {
// 保证同一时刻,只能由一个线程初始化ApplicationContext
synchronized(this.startupShutdownMonitor) {
// 创建前准备,加载资源***************************************************************************************
// 创建bean前做准备,如记录启动时间,是否启动标识,初始化环境等操作;
// 这一步我们也可以重写,可以在这里加点自己公司的logo啥的,提前判断环境是否具备应用启动条件等。
// 如我们就在自己的ApplicationContext里面检测环境下是否配备了某些环境变量,某些工具脚手架,安全环境等,如果不具备提示或者抛出异常
// 做这一步的目的是为了杜绝在应用运行过程中发现环境不满足导致程序出错,比如我们在平常有个功能不会使用,但是每个季度会有一次数据收集,需要借助一个第三方工具,需要在机器上安装,因此可以在应用启动的时候就去判断,当然不止这一个,如果就为了这一个功能还可以做到应用里面更加方便,简单来说这个方法可以直接用来做应用前启动的状态检查
this.prepareRefresh();
// *********************************** 准备BeanFactory
// 这一步非常关键,它决定了如何创建bean,通过名字可以判断这一步用于刷新BeanFacotry
// 在AbstractRefreshableApplicationContext中,这一步会创建beanFactory并且同时加载bean
// 在GenericApplicationContext中,这一步没有过多的操作,只会保证线程安全的刷新即可
// 这两个ApplicationContext后面会详说
ConfigurableListableBeanFactory beanFactory = this.obtainFreshBeanFactory();
// prepareBeanFactory为了beanfactory做准备工作,如设置classloader、添加ApplicationContextAwareProcessor等
this.prepareBeanFactory(beanFactory);
try {
// postProcessBeanFactory是一个纯粹提供给子类实现的一个空白方法,如我们常用到到的ServletContextAware实现接口处理器就是ServletWebServerApplicationContext通过重写该方法在这里注册的
this.postProcessBeanFactory(beanFactory);
// 上面的prepareBeanFactory和postProcessBeanFactory算是ApplicationContext层面定制化接口,甚至我很少看到有定制prepareBeanFactory方法的,重写postProcessBeanFactory方法的倒还见过几次
// invokeBeanFactoryPostProcessors 调用BeanFactoryPostProcessor接口的具体实现,这个方法的具体调用就看过很多了
// 该方法一般用于处理beanFactory开始工作前最后的一些回调,如动态添加一个BeanDefinition,总的来说,这里可以完全接管到准备好的beanFactory,可以让你随便操控
// 这个地方用得挺多的
this.invokeBeanFactoryPostProcessors(beanFactory);
// 向BeanFactory中注册BeanPostProcessor实现类
this.registerBeanPostProcessors(beanFactory);
// 设置 context 消息的国际化实现类。如果没有配置,则使用 DelegatingMessageSource 实现。
this.initMessageSource();
// 监听器******************************** ↓↓ 这一块我们自己定义的用得挺少的,一般都是直接用回调就能满足需求,很少自定义监听器
// 初始化事件监听处理器,主要是用来处理发生了相应事件进行通知,如常用的ApplicationListener:ContextRefreshedEvent,ContextStartedEvent事件等
this.initApplicationEventMulticaster();
// 该方法类似于benfactory的prepareBeanFactory方法,主要是在bean初始化前进行一些自定义回调,在AbstracApplicationContext中是个空方法
this.onRefresh();
// 这个见名知其意,就是注册监听器的到ApplicationEventMulticaster里面便于调用
this.registerListeners();
// BeanFactory最终的初始化操作,用来对上面的beanFactory扫尾操作,也是初始化bean,属性注入实例的入口
this.finishBeanFactoryInitialization(beanFactory);
// 完成上下文创建,如发送 ContextRefreshedEvent 事件广播就是在这一步完成的
this.finishRefresh();
} catch (BeansException var9) {
if (this.logger.isWarnEnabled()) {
this.logger.warn("Exception encountered during context initialization - cancelling refresh attempt: " + var9);
}
this.destroyBeans();
this.cancelRefresh(var9);
throw var9;
} finally {
this.resetCommonCaches();
}
}
}
Spring的生命周期就是AbstractApplicationContext#refresh 作为入口贯彻整个应用的,大致如下图(图是网上找的)。
在日常工作中,基于Spring不修改源码情况下开发主要就是基于这些过程定制。
bean的生命周期
上面对Spring生命周期的BeanFactory的自定义和扩展场景进行详细记录。接下来,我们进入到开发中最最最常用的bean的生命周期阶段。
前面的BeanFactory是对bean创建做的准备工作,可以简单总结一下就是读取配置,生成BeanDefinition,放入BeanFactory的
beanDefinitionMap里面,等待创建。
前面说到了Spring的ApplicationContext虽然实现了BeanFactory但是具体的实现却不是在ApplicationContext的实现类实现的,是AbstractApplicationContext#refresh方法而它跟BeanFactory的关系是相互依赖的关系。
因此接下来bean的创建就跟ApplicationContext没有多大关系了,具体实现落到了相应的BeanFactory头上,在Spring中BeanFactory的具体实现,上面也有说到是DefaultListableBeanFactory这个类,是由AbstractRefreshableApplicationContext和GenericApplicationContext创建的。
初始化Bean的方法入口:
AbstractApplicationContext#refresh(入口)
AbstractApplicationContext#finishBeanFactoryInitialization(初始化单例对象入口)
ConfigurableListableBeanFactory#preInstantiateSingletons(初始化单例对象入口)(ps:ConfigurableListableBeanFactory的默认实现DefaultListableBeanFactory)
Bean的生命周期主要回调方法
实例化:
构造器:众所周知,Spring创建bean实例是通过反射创建的,当然无论何种方式创建都避不开肯定要调用其实例类的构造器。
通知阶段:
xxxxAware接口实现:该阶段发生在bean实例已经创建完成,将进行一系列的通知,用于给bean通知ApplicationContext的的相关属性,主要分为两种一种是AwareMethod一种是AwareInterface。AwareMethod主要是通知bean自身相关属性,如它在Spring中的beanName,创建这个bean的工厂和ClassLoader等;AwareInterface主要是为了通知相关上下文环境,跟bean自身属性关系不大,并且这个Aware通知是属于BeanPostProcessor阶段调用。
前置拦截阶段:
BeanPostProcessor#before:这里的before是缩写,实际为postProcessBeforeInitialization方法;BeanPostProcessor主要是对bean实例化过程中的拦截处理,每个bean的创建,都会执行该回调,它能够直接接管到已经实例化好了的所有bean,并且可以决定该bean是否加入到容器中。
初始化阶段:
InitializingBean#afterPropertiesSet:该回调主要用于bean实例化完成,进行之后的属性设置。
init-method:上面的afterPropertiesSet方法必须要实现InitializingBean接口,并且只有一个方法,因此不太方便使用xml的init-method和@PostConstruct可以在类中定义多个初始化方法,并且不用实现任何接口。
后置拦截阶段:
BeanPostProcessor#after:与前置拦截相对应,after为postProcessAfterInitialization的缩写;和before的功能一样,只是是在执行完初始化方法后面拦截。
运行阶段
实际运行使用。
终止阶段;
DisposableBean#destroy:最后Spring终止的时候,对bean的回调用于做销毁扫尾操作,与 InitializingBean#afterPropertiesSet相对应。 destory-method:销毁操作,与init-method相对应。
实例化
原理:反射
源码:
调用链:
AbstractApplicationContext#refresh(入口)
AbstractApplicationContext#finishBeanFactoryInitialization(初始化单例对象入口)
ConfigurableListableBeanFactory#preInstantiateSingletons(初始化单例对象入口)
AbstractBeanFactory#getBean
AbstractBeanFactory#doGetBean
DefaultSingletonBeanRegistry#getSingleton(java.lang.String)
AbstractAutowireCapableBeanFactory#createBean
AbstractAutowireCapableBeanFactory#doCreateBean
AbstractAutowireCapableBeanFactory#createBeanInstance(实际创建bean实例的方法)
创建bean的三种方法:
protected BeanWrapper createBeanInstance(String beanName, RootBeanDefinition mbd, @Nullable Object[] args) {
Class<?> beanClass = this.resolveBeanClass(mbd, beanName, new Class[0]);
// 检查
if (beanClass != null && !Modifier.isPublic(beanClass.getModifiers()) && !mbd.isNonPublicAccessAllowed()) {
throw new BeanCreationException(mbd.getResourceDescription(), beanName, "Bean class isn't public, and non-public access not allowed: " + beanClass.getName());
}
Supplier<?> instanceSupplier = mbd.getInstanceSupplier();
// 第一种使用InstanceSupplier创建bean,InstanceSupplier在可以在BeanDefinition中定义
if (instanceSupplier != null) {
return this.obtainFromSupplier(instanceSupplier, beanName);
// 第二种,使用工厂方法创建,如@Bean修饰的方法返回一个对象,就是这种方式
}
if (mbd.getFactoryMethodName() != null) {
return this.instantiateUsingFactoryMethod(beanName, mbd, args);
}
// 第三种使用构造函数创建
boolean resolved = false;
boolean autowireNecessary = false;
if (args == null) {
synchronized(mbd.constructorArgumentLock) {
if (mbd.resolvedConstructorOrFactoryMethod != null) {
resolved = true;
autowireNecessary = mbd.constructorArgumentsResolved;
}
}
}
if (resolved) {
return autowireNecessary ? this.autowireConstructor(beanName, mbd, (Constructor[])null, (Object[])null) : this.instantiateBean(beanName, mbd);
} else {
Constructor<?>[] ctors = this.determineConstructorsFromBeanPostProcessors(beanClass, beanName);
if (ctors == null && mbd.getResolvedAutowireMode() != 3 && !mbd.hasConstructorArgumentValues() && ObjectUtils.isEmpty(args)) {
ctors = mbd.getPreferredConstructors();
return ctors != null ? this.autowireConstructor(beanName, mbd, ctors, (Object[])null) : this.instantiateBean(beanName, mbd);
} else {
return this.autowireConstructor(beanName, mbd, ctors, args);
}
}
}
实例化bean主要就是上面三种方法,其中第一种和第二种是我们手动创建的bean,而第三种就是spring默认创建bean的方式,底层通过native方法调用构造器反射实现,也就是我们通常配置的@Component或者xml的bean注解就是通过这种方式实现。
因此对于我们要自定义创建bean的方式,可以通过构造器和工厂方法以及Supplier来进行创建,其中Supplier用得比较少,因为这种方式需要往BeanDefinition设置相应属性,但是其它两种方式就用得比较多了。特别是构造器创建,其中这里面还涉及到推导构造器的逻辑,单独拧出来都可以研究很多。但是这里主要是记录一下spring的扩展时机因此不对原理性的东西太过研究。
通知阶段(Aware)
原理:Aware钩子函数回调
源码:
调用链:
AbstractApplicationContext#refresh(入口)
AbstractApplicationContext#finishBeanFactoryInitialization(初始化单例对象入口)
ConfigurableListableBeanFactory#preInstantiateSingletons(初始化单例对象入口)
AbstractBeanFactory#getBean
AbstractBeanFactory#doGetBean
DefaultSingletonBeanRegistry#getSingleton(java.lang.String)
AbstractAutowireCapableBeanFactory#createBean
AbstractAutowireCapableBeanFactory#doCreateBean
AbstractAutowireCapableBeanFactory#initializeBean(初始化bean)
AbstractAutowireCapableBeanFactory#invokeAwareMethods或者ApplicationContextAwareProcessor#applyBeanPostProcessorsBeforeInitialization(有两种Aware的调用,时机不同)
=====
Aware接口定义:
public interface Aware { }
什么是Bean Aware?
Bean Aware是Spring在bean创建成功后会发出一系列的通知告知bean在被创建过程中的一些内容,让bean能够知道它的相关信息。
就比如你去公司上班,注册了信息后,最后公司人力会告诉你,你所属哪个部门,你工号多少,给你安排的座位在哪儿,你归属谁管等等信息,这一系列的通知都是由Aware实现。
bean通知给谁?
通知给实现了Aware接口的被创建的bean。
通知时机:
通知时机都是是在bean被创建之后,有两个时机。
一个是在属性注入后,执行初始化的时候,还有一个是在BeanPostProcesser的回调里面。
源码如下:
protected Object initializeBean(String beanName, Object bean, @Nullable RootBeanDefinition mbd) {
if (System.getSecurityManager() != null) {
AccessController.doPrivileged(() -> {
this.invokeAwareMethods(beanName, bean);
return null;
}, this.getAccessControlContext());
} else {
// Spring提供的自带已有的Aware,进行通知
this.invokeAwareMethods(beanName, bean);
}
Object wrappedBean = bean;
if (mbd == null || !mbd.isSynthetic()) {
wrappedBean = this.applyBeanPostProcessorsBeforeInitialization(bean, beanName);
}
}
this.invokeAwareMethods(beanName, bean):Spring对自带的Aware方法进行通知,其中可通知接口有:
private void invokeAwareMethods(String beanName, Object bean) {
if (bean instanceof Aware) {
if (bean instanceof BeanNameAware) {
((BeanNameAware)bean).setBeanName(beanName);
}
if (bean instanceof BeanClassLoaderAware) {
ClassLoader bcl = this.getBeanClassLoader();
if (bcl != null) {
((BeanClassLoaderAware)bean).setBeanClassLoader(bcl);
}
}
if (bean instanceof BeanFactoryAware) {
((BeanFactoryAware)bean).setBeanFactory(this);
}
}
}
wrappedBean = this.applyBeanPostProcessorsBeforeInitialization(bean, beanName):这个方法按道理是下一阶段,BeanPostProcessor前置拦截阶段的回调方法;但是因为Spring-beans原先提供的Aware接口有限,因此后面其它的组件spring-context、spring-web、spring-tx等组件自己在BeanPostProcessor实现了Aware功能的回调,如果我们要自己实现Bean Aware的功能,也可以参考这些组件的实现,源码如下:
通知内容
BeanAware的实现接口,可以进行任何方法的通知。
Spring自带的AwareinvokeAwareMethods里面的;
BeanNameAware#setBeanName告诉bean在容器中的beanName;
BeanClassLoaderAware#setBeanClassLoader告诉bean使用的classLoader;
BeanFactoryAware#setBeanFactory告诉bean是由哪一个beanFactory创建实例化的;
扩展的ApplicationContextAware:
private void invokeAwareInterfaces(Object bean) { if (bean instanceof EnvironmentAware) { ((EnvironmentAware)bean).setEnvironment(this.applicationContext.getEnvironment()); } if (bean instanceof EmbeddedValueResolverAware) { ((EmbeddedValueResolverAware)bean).setEmbeddedValueResolver(this.embeddedValueResolver); } if (bean instanceof ResourceLoaderAware) { ((ResourceLoaderAware)bean).setResourceLoader(this.applicationContext); } if (bean instanceof ApplicationEventPublisherAware) { ((ApplicationEventPublisherAware)bean).setApplicationEventPublisher(this.applicationContext); } if (bean instanceof MessageSourceAware) { ((MessageSourceAware)bean).setMessageSource(this.applicationContext); } if (bean instanceof ApplicationContextAware) { ((ApplicationContextAware)bean).setApplicationContext(this.applicationContext); } }这些都是常用的Aware,当然还有很多扩展的如ServletContextAware这些。
工作中实际使用示例
BeanNameAware:在Spring运行过程中获取到BeanFactory,进行相关操作,如果你要问啥操作,你想咋操作就就咋操作。我们是在运维阶段对一些特定的组件用来生成默认文件URL路径。
ApplicationContextAware:这个用得最多,比如拿来做工具类,在某些不存在Spring容器的静态方法或者工具类,进行获取ApplicationContext的引用,来动态获取bean实例。因为ApplicationContext还有BeanFactory的功能,还可以用来做应用的监控bean状态,为后续运维做支持;甚至是在运行阶段来动态替换相应的class实例,比如说,获取到BeanFactory引用后删除指定beanName的实例,然后上传一个class进行生成实例注册进Spring容器中,接下来后续使用就可以使用这个新的实例,不用重启(这个的前提是每次使用相应bean的时候要重新获取,如果是属性注入的话得刷新容器)。
ps:其它还有很多,如Web层面的在任意地方获取WebContext等。
下面给出一个精简版在我们自己定义的Spring应用的代码,可以在运行阶段动态替换bean实例:
自定义BeanFactory,将删除单例bean方法暴露出来,我们真实运用中不止这么点逻辑,这只是精简版的
public class CustomDefaultListableBeanFactory extends DefaultListableBeanFactory {
@Override
public void removeSingleton(String beanName) {
super.removeSingleton(beanName);
}
}
自定义ApplicationContext,使用我们自定义的BeanFactory
public class CustomApplicationContext extends AnnotationConfigApplicationContext {
public CustomApplicationContext(Class<?>... componentClasses) {
super(new CustomDefaultListableBeanFactory());
super.register(componentClasses);
super.refresh();
}
}
利用Aware获取到BeanFactory的引用
@Component
public class BeanManager implements BeanFactoryAware {
private CustomDefaultListableBeanFactory customDefaultListableBeanFactory;
@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
// 获取当前使用的BeanFactory
customDefaultListableBeanFactory = (CustomDefaultListableBeanFactory) beanFactory;
}
// 替换bean实例
public void replaceBeanInstance(String beanName, Object bean) {
Object existBean = customDefaultListableBeanFactory.getBean(beanName);
if(Objects.isNull(existBean))
throw new UnsupportedOperationException("没有bean");
// 删除bean
customDefaultListableBeanFactory.removeSingleton(beanName);
// 注入新的bean
customDefaultListableBeanFactory.registerSingleton(beanName,bean);
}
}
测试:
public static void main(String[] args) {
CustomApplicationContext customApplicationContext =
new CustomApplicationContext(DyReplaceSpringBeanApp.class);
final String beanName = "student";
Student oldStudent = (Student) customApplicationContext.getBean(beanName);
System.out.println("oldStudent:" + oldStudent);
// 替换
BeanManager replaceBeanProcesser = customApplicationContext.getBean(BeanManager.class);
replaceBeanProcessor.replaceBeanInstance(beanName,new Student());
Student newStudent = (Student) customApplicationContext.getBean(beanName);
System.out.println("newStudent:" + newStudent);
}
结果:
oldStudent:com.example.demo.blogs.spring.dyreplace.Student@79defdc
newStudent:com.example.demo.blogs.spring.dyreplace.Student@9cb8225
可以看到测试结果,最后一次获取Student实例的时候已经是我们替换后的student实例了。
其实还可以更加简单,不用自定义ApplicationContext和BeanFactory,就直接用DefaultListableBeanFactory然后反射调用其方法,但是实际中因为有很多定制化的东西,所以重写更加合理和便于扩展。
ps:上面只是一个简单演示,真实运用中还得涉及各种考虑,比如已经被动态注入了该对象的实例属性如何处理等。还有这种方式使用,最好不要暴露处理,最好只提供在服务器本地才能使用,要不然这就相当于开放了一个很容易被攻击的漏洞。
自己实现一个Bean Aware
前面说到了很多spring的其它组件如spring-context、spring-tx等这些,它们都自己实现了其Aware的处理逻辑,也就是实现了自定义Aware。
步骤很简单:
- 定义自己的xxxAware接口,实现Aware接口
- 定义BeanPostProcessor实现类,重写before方法,判断类是否是自定义Aware的实现类,如果是则回调其方法。
可以参考一下ApplicationContextAware的实现就知道了。
前置拦截阶段(BeanPostProcessor#postProcessBeforeInitialization)
原理:BeanPostProcessor钩子函数回调
源码:
调用链:
AbstractApplicationContext#refresh(入口)
AbstractApplicationContext#finishBeanFactoryInitialization(初始化单例对象入口)
ConfigurableListableBeanFactory#preInstantiateSingletons(初始化单例对象入口)
AbstractBeanFactory#getBean
AbstractBeanFactory#doGetBean
DefaultSingletonBeanRegistry#getSingleton(java.lang.String)
AbstractAutowireCapableBeanFactory#createBean
AbstractAutowireCapableBeanFactory#doCreateBean
AbstractAutowireCapableBeanFactory#initializeBean(初始化bean)
ApplicationContextAwareProcessor#applyBeanPostProcessorsBeforeInitialization
=====
BeanPostProcesser接口定义:
public interface BeanPostProcessor { @Nullable default Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { return bean; } @Nullable default Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { return bean; } }
BeanPostProcessor是一个用于针对bean在进行初始化前和初始化后进行统一处理的一个接口,通常用于在Spring应用启动的时候统一处理Spring里面的bean,有点类似拦截器的功能,主要拦截通过spring实例化的bean。
这里主要说明postProcessBeforeInitializati方法,也就是bean初始化前的回调方法,使用非常简单,实现BeanPostProcessor接口,重写postProcessBeforeInitializati方法,并将类加入到容器让其扫描即可。
实际运用
上面既然说到了BeanPostProcessor的作用,那么就不难想到其的应用场景了。这里介绍几个Spring组件和关键的AOP实现。
ApplicationContextAware:上面介绍Aware的时候说都了上下文的某些通知处理就是通过该接口实现的。源码如下:
class ApplicationContextAwareProcessor implements BeanPostProcessor {
private final ConfigurableApplicationContext applicationContext;
private final StringValueResolver embeddedValueResolver;
public ApplicationContextAwareProcessor(ConfigurableApplicationContext applicationContext) {
......
}
@Nullable
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
......
this.invokeAwareInterfaces(bean);
......
return bean;
}
}
private void invokeAwareInterfaces(Object bean) {
if (bean instanceof EnvironmentAware) {
((EnvironmentAware)bean).setEnvironment(this.applicationContext.getEnvironment());
}
......
}
}
可以看到ApplicationContextAware回调响应Aware接口的时候原理就是BeanPostProcesser回调的时候判断bean是否实现了指定接口,如果是则进行回调。
AbstractAutoProxyCreator:AOP增强的关键实现,AOP对bean的增强主要就是通过这里进行增强对象创建的
public Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) {
Object cacheKey = this.getCacheKey(beanClass, beanName);
if (!StringUtils.hasLength(beanName) || !this.targetSourcedBeans.contains(beanName)) {
if (this.advisedBeans.containsKey(cacheKey)) {
return null;
}
if (this.isInfrastructureClass(beanClass) || this.shouldSkip(beanClass, beanName)) {
this.advisedBeans.put(cacheKey, Boolean.FALSE);
return null;
}
}
TargetSource targetSource = this.getCustomTargetSource(beanClass, beanName);
if (targetSource != null) {
if (StringUtils.hasLength(beanName)) {
this.targetSourcedBeans.add(beanName);
}
Object[] specificInterceptors = this.getAdvicesAndAdvisorsForBean(beanClass, beanName, targetSource);
// 根据原来的bean创建增强后的bean
Object proxy = this.createProxy(beanClass, beanName, specificInterceptors, targetSource);
this.proxyTypes.put(cacheKey, proxy.getClass());
return proxy;
} else {
return null;
}
我认为这个BeanPostProcessor接口的主要实现就是为了实现AOP的。
以上的Spring的BeanPostProcessor的应用,我们实际扩展中也能参考这些实现,自己组件的相关功能。
实例化时机
在说明实例化实际的时候,先说下为什么要研究这玩意儿。
当初我们有个场景在我们自定义的ApplicationContext里面,重写了
onRefresh()因为这时候对于不熟悉BeanFactory实例化和调用时机的同学,默认就是我们普通的bean还没有初始化,因为在onRefresh()执行在this.finishBeanFactoryInitialization(beanFactory);之前,基于这点就写了很多逻辑,最后在某些场景下发现在onRefresh()的时候容器中已经存在了某些普通bean的实例。(这里的普通bean都是指,跟Spring回调等组件无关的bean,像Controller、Service、Dao啥的。
为了排查上诉所说的问题,则提出如下问题:
BeanPostProcessor实例化时机是和普通bean一起实例化还是先于bean实例化?
BeanPostProcessor作为Spring的一个component,能否拦截到自己?
BeanPostProcessor的初始化方法是否能够执行?
在回答上面三个问题前先回顾一下AbstractApplicationContext#refresh()方法:
public void refresh() throws BeansException, IllegalStateException {
// 向BeanFactory中注册BeanPostProcessor实现类
this.registerBeanPostProcessors(beanFactory);
// 该方法类似于benfactory的prepareBeanFactory方法,主要是在bean初始化前进行一些自定义回调,在AbstracApplicationContext中是个空方法
this.onRefresh();
// BeanFactory最终的初始化操作,用来对上面的beanFactory扫尾操作,也是初始化bean,属性注入实例的入口
this.finishBeanFactoryInitialization(beanFactory);
}
省略掉了多余的方法,只保留了注册BeanPostProcessor和实例化bean的方法。
BeanPostProcessor实例化时机是和普通bean一起实例化还是先于bean实例化?
我们看到上面的方法,在执行BeanFactory初始化之前就会注册BeanPostProcessor,因此我们只需要看在注册BeanPostProcessor的时候是否对BeanPostProcessor实例化了,就能确定BeanPostProcessor是实例化在普通bean之前还是之后了。
protected void registerBeanPostProcessors(ConfigurableListableBeanFactory beanFactory) {
// 使用委派模式,将注册BeanPostProcessor的责任委派给PostProcessorRegistrationDelegate
PostProcessorRegistrationDelegate.registerBeanPostProcessors(beanFactory, this);
}
// PostProcessorRegistrationDelegate#registerBeanPostProcessors
public static void registerBeanPostProcessors(ConfigurableListableBeanFactory beanFactory, AbstractApplicationContext applicationContext) {
// 根据类型
String[] postProcessorNames = beanFactory.getBeanNamesForType(BeanPostProcessor.class, true, false);
// 调用BeanFactory#getBean方法获取bean实例
BeanPostProcessor pp = (BeanPostProcessor)beanFactory.getBean(ppName, BeanPostProcessor.class);
// 最后将其注册进入BeanFactory
}
以上PostProcessorRegistrationDelegate#registerBeanPostProcessors代码是对BeanPostProcessor注册的简单摘录了几行,可以看到最主要的是,beanFactory.getBean(ppName, BeanPostProcessor.class)获取到了BeanPostProcesser实例,前面介绍实例化的时候就说到,beanFactory#getBean最后就是负责创建bean实例的入口。
结论:BeanPostProcesser实例化跟普通bean不是一起的,要先于普通bean。
调用时机
BeanPostProcessor作为Spring的一个bean,能否拦截到自己?
我们前面已经说了BeanPostProcesser的主要功能就是在bean创建后进行统一拦截,那么它能不能拦截到同类型的BeanPostProcessor和自己呢?
要回答这个问题,我们继续看PostProcessorRegistrationDelegate#registerBeanPostProcessors的源码
public static void registerBeanPostProcessors(ConfigurableListableBeanFactory beanFactory, AbstractApplicationContext applicationContext) {
// 首先注册的是实现了PriorityOrderd接口的BeanPostProcessor接口实现类
registerBeanPostProcessors(beanFactory, (List)priorityOrderedPostProcessors);
// 注册实现了Ordered接口的BeanPostProcessor接口实现类
registerBeanPostProcessors(beanFactory, (List)orderedPostProcessors);
// 注册没有任何排序的BeanPostProcessor实现类
registerBeanPostProcessors(beanFactory, (List)nonOrderedPostProcessors);
// 最后注册内部BeanPostProcessor,必须要实现MergedBeanDefinitionPostProcessor接口
registerBeanPostProcessors(beanFactory, (List)internalPostProcessors);
}
这里的源码只贴出了注册BeanPostProcessors的顺序,可以看到注册BeanPostProcessor有多个,并且有一定顺序,接下来看调用BeanPostProcesser的调用链:
AbstractApplicationContext#refresh(入口)
AbstractApplicationContext#finishBeanFactoryInitialization(初始化单例对象入口)
ConfigurableListableBeanFactory#preInstantiateSingletons(初始化单例对象入口)
AbstractBeanFactory#getBean
AbstractBeanFactory#doGetBean
DefaultSingletonBeanRegistry#getSingleton(java.lang.String)
AbstractAutowireCapableBeanFactory#createBean
AbstractAutowireCapableBeanFactory#doCreateBean
AbstractAutowireCapableBeanFactory#initializeBean(初始化bean)
ApplicationContextAwareProcessor#applyBeanPostProcessorsBeforeInitialization
可以看到BeanPostProcessor的回调主要发生在bean初始化的方法initializeBean里面,这时候如果IOC中已存在BeanPostProcessor则会被调用。
因此综上分析,BeanPostProcessor的回调方法是否能够执行,取决于IOC容器中是否已经有该BeanPostProcessor的实例。
结论:
BeanPostProcessor不能拦截到自己,因为在BeanPostProcessor实例化的时候,当前IOC中还没有这个实例,因此无法拦截到自己。
BeanPostProcessor是可以拦截到同类型的BeanPostProcessor的,但是得取决于它们是否是同一种类型,如果是同一种类型的BeanPostProcessor则不会被拦截到。优先级高(先注册进IOC)的BeanPostProcessor可以拦截到优先级低(后注册进IOC)的BeanpostProcessor实例。
与Bean自身声明周期方法是否干扰
BeanPostProcessor的初始化方法(init-method,afterProperties)是否能够执行?
这个问题直接说了吧,肯定是能执行的,因为init-method和afterProperties是bean自己的生命周期回调方法,它是自身的属性方法。也就是只要是在Bean创建后就可以执行,BeanPostProcessor是一个属于第三方拦截bean的组件,而生命周期回调方法是bean组件内部回调方法。
源码:
protected Object initializeBean(String beanName, Object bean, @Nullable RootBeanDefinition mbd) {
// 调用Aware
if (System.getSecurityManager() != null) {
AccessController.doPrivileged(() -> {
this.invokeAwareMethods(beanName, bean);
return null;
}, this.getAccessControlContext());
} else {
this.invokeAwareMethods(beanName, bean);
}
// 调用BeanPostProcessor#before
Object wrappedBean = bean;
if (mbd == null || !mbd.isSynthetic()) {
wrappedBean = this.applyBeanPostProcessorsBeforeInitialization(bean, beanName);
}
// 调用初始化方法
try {
this.invokeInitMethods(beanName, wrappedBean, mbd);
} catch (Throwable var6) {
throw new BeanCreationException(mbd != null ? mbd.getResourceDescription() : null, beanName, "Invocation of init method failed", var6);
}
// 调用BeanPostProcessor#after
if (mbd == null || !mbd.isSynthetic()) {
wrappedBean = this.applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);
}
return wrappedBean;
}
从代码也很好的证明了结论
挨过的毒打
上面之所以要分析其实例化时机、调用时机与其生命周期方法等这些都是为了了解其底层运行机制,方便出现问题的时候排查。
以下都是我们遇到过的实际问题:
在BeanPostProcesser注入其它组件,导致组件被提前初始化
因为BeanPostProcessor实例化在普通bean之前,因此会导致注入到BeanPostProcessor里面的实例属性被提前初始化。
public class BeanPostProcesserTest implements BeanPostProcessor { @Autowired private BeanCompnent beanCompnent; @Override public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { return bean; } @Override public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { return bean; } }就像这样,如果这个BeanComponent,该组件在创建的时候会执行相关自定义的相关变量值,这些值是在onRefresh()方法里面进行初始化的,但是因为注册BeanPostProcessor发生在了onRefresh()方法之前,因此导致BeanCompnent被初始化的时候,相关变量值还没有准备好,发生了某些意想不到的错误。
BeanPostProcessor拦截其它BeanPostProcessor
上面说到了,BeanPostProcessor要拦截实现了BeanPostProcessor接口的bean,需要考虑到优先级等问题。
以前发生过一次问题就是定义了一个component,需要统一调用相关回调方法类似Aware,平常都没问题,后面需求变更这个组件实现了BeanPostProcessor接口,导致统一拦截没有成功拦截到这个组件没有进行回调,运行时导致相关参数不完整。
BeanPostProcessor返回null
我们看到BeanPostProcessor#postProcessBeforeInitialization和对应的after方法最后都要求返回一个bean实例。如果思考一下如果返回null会发生情况。
答案是对于bean来说不会发生任何情况,但是对于实现了BeanPostProcessor组件来说可能会失效。
先来看一段源码:
// AbstractAutowireCapableBeanFactory#applyBeanPostProcessorsBeforeInitialization public Object applyBeanPostProcessorsBeforeInitialization(Object existingBean, String beanName) throws BeansException { Object result = existingBean; Object current; for(Iterator var4 = this.getBeanPostProcessors().iterator(); var4.hasNext(); result = current) { BeanPostProcessor processor = (BeanPostProcessor)var4.next(); current = processor.postProcessBeforeInitialization(result, beanName); // 如果方法返回null,就直接终止接下后续的BeanPostProcessor拦截调用,直接返回 if (current == null) { return result; } } return result; } // AbstractAutowireCapableBeanFactory#applyBeanPostProcessorsAfterInitialization public Object applyBeanPostProcessorsAfterInitialization(Object existingBean, String beanName) throws BeansException { Object result = existingBean; Object current; for(Iterator var4 = this.getBeanPostProcessors().iterator(); var4.hasNext(); result = current) { BeanPostProcessor processor = (BeanPostProcessor)var4.next(); current = processor.postProcessAfterInitialization(result, beanName); // 如果方法返回null,就直接终止接下后续的BeanPostProcessor拦截调用,直接返回 if (current == null) { return result; } } return result; }注意看我上面加注释的地方,这里很关键,比如我们当时的场景是一个BeanPostProcessor组件实现类,在发生了异常的时候返回了null。然后导致后面的BeanPostProcessor不起作用;
我们有个组件类似观察者模式,会使用BeanPostProcessor来进行拦截获取bean的相关实例注解并记录其回调方法,然后当事件发生的时候进行回调。最后组件前的BeanPostProcessor返回了一个null,导致实现这个功能的扫描组件直接没有调用其before和after方法,导致组件没有生效,观察者通知失败。
ps:这种问题不好排查,因为它是属于运行时才会暴露处理,不熟悉其原理的就容易导致出现bug。
实际中还遇到些问题,上面这两个问题,比较影响深刻,这些归根结底都是BeanPostProcessor实例化的问题,所以要特别注意使用这种类型的接口组件的时候最好将类功能做的单一一点,不要和其它组件耦合,这样能够避免影响到其它组件。
初始化阶段(InitializingBean;init-method)
Bean创建初始化
bean初始化指的是bean内部在创建完成后加入IOC之前调用的初始化方法,这个时机很重要。
一般我们对bean执行初始化有两种方式,一种是InitializingBean的回调方法afterProperties(),一种是自定义init-method。
自定义init-method相较于afterProperties更加灵活,在xml中可以通过标签init-method来指定初始化方法,在注解中可以通过@PostConstruct来指定,但是它底层是BeanPostProcessor的实现类InitDestroyAnnotationBeanPostProcessors实现的,因此这种方式的初始化方法它的执行时机,实际是在BeanPostProcessor调用的,依赖于BeanPostProcessor,并且可以在一个类里面标注多个初始化方法。
源码如下:
// InitDestroyAnnotationBeanPostProcessor#postProcessBeforeInitialization
// BeanPostProcessor的回调
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
InitDestroyAnnotationBeanPostProcessor.LifecycleMetadata metadata = this.findLifecycleMetadata(bean.getClass());
try {
// 调用初始化方法
metadata.invokeInitMethods(bean, beanName);
return bean;
} catch (InvocationTargetException var5) {
throw new BeanCreationException(beanName, "Invocation of init method failed", var5.getTargetException());
} catch (Throwable var6) {
throw new BeanCreationException(beanName, "Failed to invoke init method", var6);
}
}
afterProperties需要实现InitializingBean接口,并且该方法是标准的初始化方法,不依赖于其它组件,其调用时机和调用方就是Spring的AbstractAutowireCapableBeanFactory。
// AbstractAutowireCapableBeanFactory#initializeBean
protected Object initializeBean(String beanName, Object bean, @Nullable RootBeanDefinition mbd) {
// 调用通知
if (System.getSecurityManager() != null) {
AccessController.doPrivileged(() -> {
this.invokeAwareMethods(beanName, bean);
return null;
}, this.getAccessControlContext());
} else {
this.invokeAwareMethods(beanName, bean);
}
// BeanPostProcessor#before
Object wrappedBean = bean;
if (mbd == null || !mbd.isSynthetic()) {
wrappedBean = this.applyBeanPostProcessorsBeforeInitialization(bean, beanName);
}
// 实际的初始化方法
try {
this.invokeInitMethods(beanName, wrappedBean, mbd);
} catch (Throwable var6) {
throw new BeanCreationException(mbd != null ? mbd.getResourceDescription() : null, beanName, "Invocation of init method failed", var6);
}
// BeanPostProcessor#after
if (mbd == null || !mbd.isSynthetic()) {
wrappedBean = this.applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);
}
return wrappedBean;
}
以上就是初始化方法的实现原理,工作中使用得比较多的是使用注解,在初始化方法我们通常会用来初始化资源,比如加载文件内容到组件中,还有缓存,在spring-support的cache就是通过在这里进行初始化缓存的。
public abstract class AbstractCacheManager implements CacheManager, InitializingBean
ps:注意初始化方法被调用的时候,只能代表当前实例被创建好了,并不是代表整个Spring都已经启动完成了。
Spring启动完成初始化
上面我们看到初始化方法init-method和afterProperties都是bean在创建过程中执行的初始化方法,如果我们的组件初始化依赖于容器中某个组件,用以上的方法就行不通了,因为上面的初始化方法是属于bean创建自身的生命周期方法,无法知道其它bena是否创建完成。因此要实现这个需求的初始化方法得用到spring的事件监听。
// 实现ApplicationListener<ContextRefreshedEvent>接口,监听ContextRefreshedEvent事件
@Component
public class ApplicationEventTest implements ApplicationListener<ContextRefreshedEvent> {
@Override
public void onApplicationEvent(ContextRefreshedEvent event) {
// 在spring中如果多个context要加载该类的话,就需要做等待根Context初始化完成后再进行逻辑处理否则会调用多次
// 如web mvc应用有ApplicationContext还有servlet的conttext作为子容器;还有swagger2点几的版本也会有个自己的子容器,需要判断到祖父级别才进行初始化
if(event.getApplicationContext().getParent()==null){
// 实现初始化逻辑
}
}
}
调用链:
AbstractApplicationContext#refresh(入口)
AbstractApplicationContext#finishRefresh(容器刷新完毕)
AbstractApplicationContext#publishEvent(发布事件)
...
原理就是判断泛型事件,然后在相应发生事件的时候进行发布事件,进行监听,获取泛型类型的方式:
eventType = ResolvableType.forClass(listenerType).as(ApplicationListener.class).getGeneric();
具体可以参考一下这篇文章:Spring监听器源码解读及使用 - 杨七 - 博客园
可以看到上面调用ApplicationEvent的回调是从finishRefresh方法进入的,该方法被调用的时候,在refresh中,实例已经初始化完毕了,在这里可以放心使用Spring容器中的组件了。
该方法我们一般用来初始化加载数据库数据到应用中和使用spring整合后的中间件组件,如RabbitmqTemplate,JdbcTemplate等。
后置拦截阶段(BeanPostProcessor#after)
BeanPostProcessor#after后置拦截和BeanPostProcessor#before一样只是调用时机不同,在执行完初始化方法之后调用
// AbstractAutowireCapableBeanFactory#initializeBean
protected Object initializeBean(String beanName, Object bean, @Nullable RootBeanDefinition mbd) {
// 调用通知
if (System.getSecurityManager() != null) {
AccessController.doPrivileged(() -> {
this.invokeAwareMethods(beanName, bean);
return null;
}, this.getAccessControlContext());
} else {
this.invokeAwareMethods(beanName, bean);
}
// BeanPostProcessor#before
Object wrappedBean = bean;
if (mbd == null || !mbd.isSynthetic()) {
wrappedBean = this.applyBeanPostProcessorsBeforeInitialization(bean, beanName);
}
// 实际的初始化方法
try {
this.invokeInitMethods(beanName, wrappedBean, mbd);
} catch (Throwable var6) {
throw new BeanCreationException(mbd != null ? mbd.getResourceDescription() : null, beanName, "Invocation of init method failed", var6);
}
// BeanPostProcessor#after
if (mbd == null || !mbd.isSynthetic()) {
wrappedBean = this.applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);
}
它的实例化事件和一些运用已经在BeanPostProcessor#before有说明了,这里就不做展开了。它的实际运用跟before一样非常广泛,其中鼎鼎有名的就是AbstractAutoProxyCreator组件,看到是不是很熟悉,没错就是AOP,要创建动态代理对象就是发生在对象已经初始化完成后,进行创建的,而且是整个容器都需要,因此这个回调方法就尤为合适了。还包括一些事件通知发布等等,都是由after来执行的,因为在这里可以明确该bean已经实例完成并且初始化了,业务开发中用得比较少,但是组件开发用于后置通知挺多的。
运行阶段
运行阶段就是我们普通使用已经实例化好或者代理好的组件,可以程序运行,但是我们也可以做些骚操作,比如动态替换掉bean的实例,动态重新刷新容器等等,可以为Spring做一个热加载,这个在监控或者现在查bug,打日志的时候还是挺有用的,但是风险也非常大,一定要谨慎使用。
具体实现方式如下:
在应用启动的时候持有BeanFactory的引用,利用BeanFactoryAware组件
自定义BeanFactory和ApplicationContext,暴露管理bean的相关接口,如删除bean
实现替换bean的逻辑,或者classLoader重新加载等逻辑
简单实现如下:
要替换的bean:
@Component
public class Student {}
自定义BeanFactory
public class CustomDefaultListableBeanFactory extends DefaultListableBeanFactory {
// 暴露删除bean实例的方法
@Override
public void removeSingleton(String beanName) {
super.removeSingleton(beanName);
}
}
自定义ApplicationContext
public class CustomApplicationContext extends AnnotationConfigApplicationContext {
public CustomApplicationContext(Class<?>... componentClasses) {
// 声明使用自定义的BeanFactory
super(new CustomDefaultListableBeanFactory());
super.register(componentClasses);
super.refresh();
}
}
定义bean管理组件
@Component
public class BeanManager implements BeanFactoryAware {
private CustomDefaultListableBeanFactory customDefaultListableBeanFactory;
@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
// 获取BeanFactory的引用,这里的BeanFactory为自定义的BeanFactory
customDefaultListableBeanFactory = (CustomDefaultListableBeanFactory) beanFactory;
}
// 简单的替换bean
public void replaceBeanInstance(String beanName, Object bean) {
Object existBean = customDefaultListableBeanFactory.getBean(beanName);
if(Objects.isNull(existBean))
throw new UnsupportedOperationException("没有bean");
// 删除bean
customDefaultListableBeanFactory.removeSingleton(beanName);
// 注入新的bean
customDefaultListableBeanFactory.registerSingleton(beanName,bean);
}
}
ps:实际中,替换bean的方法不可能这么简单,这里得做很多处理,如判断被替换的bean是否在使用,被那些实例持有,替换需要加锁等等,这些都是要考虑的。
测试
public static void main(String[] args) {
CustomApplicationContext customApplicationContext =
new CustomApplicationContext(DyReplaceSpringBeanApp.class);
final String beanName = "student";
Student oldStudent = (Student) customApplicationContext.getBean(beanName);
System.out.println("oldStudent:" + oldStudent);
// 替换
BeanManager replaceBeanProcesser = customApplicationContext.getBean(BeanManager.class);
replaceBeanProcesser.replaceBeanInstance(beanName,new Student());
Student newStudent = (Student) customApplicationContext.getBean(beanName);
System.out.println("newStudent:" + newStudent);
}
总结:
可以看到上面在Spring运行过程中操作Context可以做很多东西,这些知识都是来自于上面的生命周期方法和组件的应用扩展,但是在运行过程中操作bean是相当危险的一件事,因此如果不了解spring整个框架和相关流程不要轻易去操作,防止发生线上故障和安全问题。像这种能够直接运行接管实例的,一定要限制只能服务器本地使用,不能开放远程接口。
终止阶段(DisposableBean;destory-method)
终止阶段对应了初始化,也有两种方式可以实现终止逻辑,分别是实现DisposableBean接口实现destroy方法和使用注解@PreDestroy。
@Component
public class DisposableBeanTest implements DisposableBean {
@Override
public void destroy() throws Exception {
System.out.println("destory.....");
}
@PreDestroy
public void destoryMethod(){
System.out.println("PreDestroy.....");
}
}
发生在容器关闭的时候org.springframework.context.support.AbstractApplicationContext#close,执行流程也是先执行注解标注方法再执行destory方法。
通常我们会在这里用来做应用扫尾操作,如释放连接资源,如释放数据库连接,释放远程长链接等,我们会在这里进行一些清理缓存操作和扫描校验数据完整性的。