做者:铂赛东
连接:www.jianshu.com/p/38d834db7413前端
Spring的核心思想就是容器,当容器refresh的时候,外部看上去风平浪静,其实内部则是一片惊涛骇浪,汪洋一片。Springboot更是封装了Spring,遵循约定大于配置,加上自动装配的机制。不少时候咱们只要引用了一个依赖,几乎是零配置就能完成一个功能的装配。java
我很是喜欢这种自动装配的机制,因此在本身开发中间件和公共依赖工具的时候也会用到这个特性。让使用者以最小的代价接入。想要把自动装配玩的转,就必需要了解spring对于bean的构造生命周期以及各个扩展接口。固然了解了bean的各个生命周期也能促进咱们加深对spring的理解。业务代码也能合理利用这些扩展点写出更加漂亮的代码。web
在网上搜索spring扩展点,发现不多有博文说的很全的,只有一些经常使用的扩展点的说明。spring
因此在这篇文章里,我总结了几乎Spring & Springboot全部的扩展接口,以及各个扩展点的使用场景。而且整理出了一个bean在spring内部从被加载到最后初始化完成全部可扩展点的顺序调用图。从而咱们也能窥探到bean是如何一步步加载到spring容器中的。数据库
如下是我整理的spring容器中Bean的生命周期内全部可扩展的点的调用顺序,下面会一个个分析缓存
org.springframework.context.ApplicationContextInitializerspringboot
这是整个spring容器在刷新以前初始化ConfigurableApplicationContext
的回调接口,简单来讲,就是在容器刷新以前调用此类的initialize
方法。这个点容许被用户本身扩展。用户能够在整个spring容器还没被初始化以前作一些事情。intellij-idea
能够想到的场景可能为,在最开始激活一些配置,或者利用这时候class还没被类加载器加载的时机,进行动态字节码注入等操做。app
扩展方式为:框架
public class TestApplicationContextInitializer implements ApplicationContextInitializer { @Override public void initialize(ConfigurableApplicationContext applicationContext) { System.out.println("[ApplicationContextInitializer]"); } }
由于这时候spring容器还没被初始化,因此想要本身的扩展的生效,有如下三种方式:
在启动类中用springApplication.addInitializers(new TestApplicationContextInitializer())
语句加入
配置文件配置context.initializer.classes=com.example.demo.TestApplicationContextInitializer
Spring SPI扩展,在spring.factories中加入org.springframework.context.ApplicationContextInitializer=com.example.demo.TestApplicationContextInitializer
org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor
这个接口在读取项目中的beanDefinition
以后执行,提供一个补充的扩展点
使用场景:你能够在这里动态注册本身的beanDefinition
,能够加载classpath以外的bean
扩展方式为:
public class TestBeanDefinitionRegistryPostProcessor implements BeanDefinitionRegistryPostProcessor { @Override public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException { System.out.println("[BeanDefinitionRegistryPostProcessor] postProcessBeanDefinitionRegistry"); } @Override public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { System.out.println("[BeanDefinitionRegistryPostProcessor] postProcessBeanFactory"); } }
org.springframework.beans.factory.config.BeanFactoryPostProcessor
这个接口是beanFactory
的扩展接口,调用时机在spring在读取beanDefinition
信息以后,实例化bean以前。
在这个时机,用户能够经过实现这个扩展接口来自行处理一些东西,好比修改已经注册的beanDefinition
的元信息。
扩展方式为:
public class TestBeanFactoryPostProcessor implements BeanFactoryPostProcessor { @Override public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { System.out.println("[BeanFactoryPostProcessor]"); } }
org.springframework.beans.factory.config.InstantiationAwareBeanPostProcessor
该接口继承了BeanPostProcess
接口,区别以下:
BeanPostProcess
接口只在bean的初始化阶段进行扩展(注入spring上下文先后),而InstantiationAwareBeanPostProcessor
接口在此基础上增长了3个方法,把可扩展的范围增长了实例化阶段和属性注入阶段。
该类主要的扩展点有如下5个方法,主要在bean生命周期的两大阶段:实例化阶段和初始化阶段,下面一块儿进行说明,按调用顺序为:
postProcessBeforeInstantiation
:实例化bean以前,至关于new这个bean以前
postProcessAfterInstantiation
:实例化bean以后,至关于new这个bean以后
postProcessPropertyValues
:bean已经实例化完成,在属性注入时阶段触发,@Autowired
,@Resource
等注解原理基于此方法实现
postProcessBeforeInitialization
:初始化bean以前,至关于把bean注入spring上下文以前
postProcessAfterInitialization
:初始化bean以后,至关于把bean注入spring上下文以后
使用场景:这个扩展点很是有用 ,不管是写中间件和业务中,都能利用这个特性。好比对实现了某一类接口的bean在各个生命期间进行收集,或者对某个类型的bean进行统一的设值等等。
扩展方式为:
public class TestInstantiationAwareBeanPostProcessor implements InstantiationAwareBeanPostProcessor { @Override public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { System.out.println("[TestInstantiationAwareBeanPostProcessor] before initialization " + beanName); return bean; } @Override public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { System.out.println("[TestInstantiationAwareBeanPostProcessor] after initialization " + beanName); return bean; } @Override public Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) throws BeansException { System.out.println("[TestInstantiationAwareBeanPostProcessor] before instantiation " + beanName); return null; } @Override public boolean postProcessAfterInstantiation(Object bean, String beanName) throws BeansException { System.out.println("[TestInstantiationAwareBeanPostProcessor] after instantiation " + beanName); return true; } @Override public PropertyValues postProcessPropertyValues(PropertyValues pvs, PropertyDescriptor[] pds, Object bean, String beanName) throws BeansException { System.out.println("[TestInstantiationAwareBeanPostProcessor] postProcessPropertyValues " + beanName); return pvs; } }
org.springframework.beans.factory.config.SmartInstantiationAwareBeanPostProcessor
该扩展接口有3个触发点方法:
predictBeanType
:该触发点发生在postProcessBeforeInstantiation
以前(在图上并无标明,由于通常不太须要扩展这个点),这个方法用于预测Bean的类型,返回第一个预测成功的Class类型,若是不能预测返回null;当你调用BeanFactory.getType(name)
时当经过bean的名字没法获得bean类型信息时就调用该回调方法来决定类型信息。
determineCandidateConstructors
:该触发点发生在postProcessBeforeInstantiation
以后,用于肯定该bean的构造函数之用,返回的是该bean的全部构造函数列表。用户能够扩展这个点,来自定义选择相应的构造器来实例化这个bean。
getEarlyBeanReference
:该触发点发生在postProcessAfterInstantiation
以后,当有循环依赖的场景,当bean实例化好以后,为了防止有循环依赖,会提早暴露回调方法,用于bean实例化的后置处理。这个方法就是在提早暴露的回调方法中触发。
扩展方式为:
public class TestSmartInstantiationAwareBeanPostProcessor implements SmartInstantiationAwareBeanPostProcessor { @Override public Class<?> predictBeanType(Class<?> beanClass, String beanName) throws BeansException { System.out.println("[TestSmartInstantiationAwareBeanPostProcessor] predictBeanType " + beanName); return beanClass; } @Override public Constructor<?>[] determineCandidateConstructors(Class<?> beanClass, String beanName) throws BeansException { System.out.println("[TestSmartInstantiationAwareBeanPostProcessor] determineCandidateConstructors " + beanName); return null; } @Override public Object getEarlyBeanReference(Object bean, String beanName) throws BeansException { System.out.println("[TestSmartInstantiationAwareBeanPostProcessor] getEarlyBeanReference " + beanName); return bean; } }
org.springframework.beans.factory.BeanFactoryAware
这个类只有一个触发点,发生在bean的实例化以后,注入属性以前,也就是Setter以前。这个类的扩展点方法为setBeanFactory
,能够拿到BeanFactory
这个属性。
使用场景为,你能够在bean实例化以后,但还未初始化以前,拿到 BeanFactory
,在这个时候,能够对每一个bean做特殊化的定制。也或者能够把BeanFactory
拿到进行缓存,往后使用。
扩展方式为:
public class TestBeanFactoryAware implements BeanFactoryAware { @Override public void setBeanFactory(BeanFactory beanFactory) throws BeansException { System.out.println("[TestBeanFactoryAware] " + beanFactory.getBean(TestBeanFactoryAware.class).getClass().getSimpleName()); } }
org.springframework.context.support.ApplicationContextAwareProcessor
该类自己并无扩展点,可是该类内部却有6个扩展点可供实现 ,这些类触发的时机在bean实例化以后,初始化以前
能够看到,该类用于执行各类驱动接口,在bean实例化以后,属性填充以后,经过执行以上红框标出的扩展接口,来获取对应容器的变量。因此这里应该来讲是有6个扩展点,这里就放一块儿来讲了
EnvironmentAware
:用于获取EnviromentAware
的一个扩展类,这个变量很是有用, 能够得到系统内的全部参数。固然我的认为这个Aware不必去扩展,由于spring内部均可以经过注入的方式来直接得到。
EmbeddedValueResolverAware
:用于获取StringValueResolver
的一个扩展类, StringValueResolver
用于获取基于String
类型的properties的变量,通常咱们都用@Value
的方式去获取,若是实现了这个Aware接口,把StringValueResolver
缓存起来,经过这个类去获取String
类型的变量,效果是同样的。
ResourceLoaderAware
:用于获取ResourceLoader
的一个扩展类,ResourceLoader
能够用于获取classpath内全部的资源对象,能够扩展此类来拿到ResourceLoader
对象。
ApplicationEventPublisherAware
:用于获取ApplicationEventPublisher
的一个扩展类,ApplicationEventPublisher
能够用来发布事件,结合ApplicationListener
来共同使用,下文在介绍ApplicationListener
时会详细提到。这个对象也能够经过spring注入的方式来得到。
MessageSourceAware
:用于获取MessageSource
的一个扩展类,MessageSource
主要用来作国际化。
ApplicationContextAware
:用来获取ApplicationContext
的一个扩展类,ApplicationContext
应该是不少人很是熟悉的一个类了,就是spring上下文管理器,能够手动的获取任何在spring上下文注册的bean,咱们常常扩展这个接口来缓存spring上下文,包装成静态方法。同时ApplicationContext
也实现了BeanFactory
,MessageSource
,ApplicationEventPublisher
等接口,也能够用来作相关接口的事情。
org.springframework.beans.factory.BeanNameAware
能够看到,这个类也是Aware扩展的一种,触发点在bean的初始化以前,也就是postProcessBeforeInitialization
以前,这个类的触发点方法只有一个:setBeanName
使用场景为:用户能够扩展这个点,在初始化bean以前拿到spring容器中注册的的beanName,来自行修改这个beanName的值。
扩展方式为:
public class NormalBeanA implements BeanNameAware{ public NormalBeanA() { System.out.println("NormalBean constructor"); } @Override public void setBeanName(String name) { System.out.println("[BeanNameAware] " + name); } }
javax.annotation.PostConstruct
这个并不算一个扩展点,其实就是一个标注。其做用是在bean的初始化阶段,若是对一个方法标注了@PostConstruct
,会先调用这个方法。这里重点是要关注下这个标准的触发点,这个触发点是在postProcessBeforeInitialization
以后,InitializingBean.afterPropertiesSet
以前。
使用场景:用户能够对某一方法进行标注,来进行初始化某一个属性
扩展方式为:
public class NormalBeanA { public NormalBeanA() { System.out.println("NormalBean constructor"); } @PostConstruct public void init(){ System.out.println("[PostConstruct] NormalBeanA"); } }
org.springframework.beans.factory.InitializingBean
这个类,顾名思义,也是用来初始化bean的。InitializingBean
接口为bean提供了初始化方法的方式,它只包括afterPropertiesSet
方法,凡是继承该接口的类,在初始化bean的时候都会执行该方法。这个扩展点的触发时机在postProcessAfterInitialization
以前。
使用场景:用户实现此接口,来进行系统启动的时候一些业务指标的初始化工做。
扩展方式为:
public class NormalBeanA implements InitializingBean{ @Override public void afterPropertiesSet() throws Exception { System.out.println("[InitializingBean] NormalBeanA"); } }
org.springframework.beans.factory.FactoryBean
通常状况下,Spring经过反射机制利用bean的class属性指定支线类去实例化bean,在某些状况下,实例化Bean过程比较复杂,若是按照传统的方式,则须要在bean中提供大量的配置信息。配置方式的灵活性是受限的,这时采用编码的方式可能会获得一个简单的方案。Spring为此提供了一个org.springframework.bean.factory.FactoryBean
的工厂类接口,用户能够经过实现该接口定制实例化Bean的逻辑。FactoryBean
接口对于Spring框架来讲占用重要的地位,Spring自身就提供了70多个FactoryBean
的实现。它们隐藏了实例化一些复杂bean的细节,给上层应用带来了便利。从Spring3.0开始,FactoryBean
开始支持泛型,即接口声明改成FactoryBean<T>
的形式
使用场景:用户能够扩展这个类,来为要实例化的bean做一个代理,好比为该对象的全部的方法做一个拦截,在调用先后输出一行log,模仿ProxyFactoryBean
的功能。
扩展方式为:
public class TestFactoryBean implements FactoryBean<TestFactoryBean.TestFactoryInnerBean> { @Override public TestFactoryBean.TestFactoryInnerBean getObject() throws Exception { System.out.println("[FactoryBean] getObject"); return new TestFactoryBean.TestFactoryInnerBean(); } @Override public Class<?> getObjectType() { return TestFactoryBean.TestFactoryInnerBean.class; } @Override public boolean isSingleton() { return true; } public static class TestFactoryInnerBean{ } }
org.springframework.beans.factory.SmartInitializingSingleton
这个接口中只有一个方法afterSingletonsInstantiated
,其做用是是 在spring容器管理的全部单例对象(非懒加载对象)初始化完成以后调用的回调接口。其触发时机为postProcessAfterInitialization
以后。
使用场景:用户能够扩展此接口在对全部单例对象初始化完毕后,作一些后置的业务处理。
扩展方式为:
public class TestSmartInitializingSingleton implements SmartInitializingSingleton { @Override public void afterSingletonsInstantiated() { System.out.println("[TestSmartInitializingSingleton]"); } }
org.springframework.boot.CommandLineRunner
这个接口也只有一个方法:run(String... args)
,触发时机为整个项目启动完毕后,自动执行。若是有多个CommandLineRunner
,能够利用@Order
来进行排序。
使用场景:用户扩展此接口,进行启动项目以后一些业务的预处理。
扩展方式为:
public class TestCommandLineRunner implements CommandLineRunner { @Override public void run(String... args) throws Exception { System.out.println("[TestCommandLineRunner]"); } }
org.springframework.beans.factory.DisposableBean
这个扩展点也只有一个方法:destroy()
,其触发时机为当此对象销毁时,会自动执行这个方法。好比说运行applicationContext.registerShutdownHook
时,就会触发这个方法。
扩展方式为:
public class NormalBeanA implements DisposableBean { @Override public void destroy() throws Exception { System.out.println("[DisposableBean] NormalBeanA"); } }
org.springframework.context.ApplicationListener
准确的说,这个应该不算spring&springboot当中的一个扩展点,ApplicationListener
能够监听某个事件的event
,触发时机能够穿插在业务方法执行过程当中,用户能够自定义某个业务事件。可是spring内部也有一些内置事件,这种事件,能够穿插在启动调用中。咱们也能够利用这个特性,来本身作一些内置事件的监听器来达到和前面一些触发点大体相同的事情。
接下来罗列下spring主要的内置事件:
ContextRefreshedEvent
ApplicationContext 被初始化或刷新时,该事件被发布。这也能够在ConfigurableApplicationContext
接口中使用 refresh()
方法来发生。此处的初始化是指:全部的Bean被成功装载,后处理Bean被检测并激活,全部Singleton Bean 被预实例化,ApplicationContext
容器已就绪可用。
ContextStartedEvent
当使用 ConfigurableApplicationContext
(ApplicationContext子接口)接口中的 start() 方法启动 ApplicationContext
时,该事件被发布。你能够调查你的数据库,或者你能够在接受到这个事件后重启任何中止的应用程序。
ContextStoppedEvent
当使用 ConfigurableApplicationContext
接口中的 stop()
中止ApplicationContext
时,发布这个事件。你能够在接受到这个事件后作必要的清理的工做
ContextClosedEvent
当使用 ConfigurableApplicationContext
接口中的 close()
方法关闭 ApplicationContext
时,该事件被发布。一个已关闭的上下文到达生命周期末端;它不能被刷新或重启
RequestHandledEvent
这是一个 web-specific 事件,告诉全部 bean HTTP 请求已经被服务。只能应用于使用DispatcherServlet的Web应用。在使用Spring做为前端的MVC控制器时,当Spring处理用户请求结束后,系统会自动触发该事件
咱们从这些spring&springboot的扩展点当中,大体能够窥视到整个bean的生命周期。在业务开发或者写中间件业务的时候,能够合理利用spring提供给咱们的扩展点,在spring启动的各个阶段内作一些事情。以达到自定义初始化的目的。此篇总结,若是有错误或者疏漏的地方,恳请指正。
近期热文推荐:
1.Java 15 正式发布, 14 个新特性,刷新你的认知!!
2.终于靠开源项目弄到 IntelliJ IDEA 激活码了,真香!
3.我用 Java 8 写了一段逻辑,同事直呼看不懂,你试试看。。
以为不错,别忘了随手点赞+转发哦!