SpringBoot優(yōu)先加載指定Bean的實(shí)現(xiàn)
1. 背景
SpringBoot 框架在啟動(dòng)時(shí)可以自動(dòng)將托管的 Bean 實(shí)例化,一般情況下它的 依賴注入特性 可以正確處理 Bean 之間的依賴關(guān)系,無(wú)需手動(dòng)指定某個(gè) Bean 優(yōu)先創(chuàng)建實(shí)例。但是一些特殊的需求確實(shí)需要某個(gè) Bean 優(yōu)先實(shí)例化,要實(shí)現(xiàn)這樣的需求就要對(duì) Bean 對(duì)象的創(chuàng)建順序有一定了解
2. Bean 對(duì)象的創(chuàng)建順序分析
- 首先我們要知道,SpringBoot 源碼中 Bean 對(duì)象的實(shí)例化都是從 AbstractApplicationContext#refresh() 方法開(kāi)始的。這個(gè)方法包含了容器中對(duì)象創(chuàng)建的主流程,主要分為以下幾步:
- BeanFactory 對(duì)象工廠的獲取及內(nèi)置配置
- BeanFactory 對(duì)象工廠的后置處理,主要是通過(guò) BeanFactoryPostProcessor 添加、修改注冊(cè)到容器中的 BeanDefinition,BeanFactoryPostProcessor 的子類(lèi)實(shí)現(xiàn) BeanDefinitionRegistryPostProcessor在執(zhí)行順序上優(yōu)先級(jí)更高
- BeanDefinitionRegistryPostProcessor 的來(lái)源分為兩類(lèi),一類(lèi)是直接 new 創(chuàng)建后添加到容器,這種在執(zhí)行順序上優(yōu)先級(jí)更高;另一類(lèi)是框架內(nèi)部封裝為 BeanDefinition 后通過(guò)對(duì)象工廠使用反射創(chuàng)建,典型如 ConfigurationClassPostProcessor
- 對(duì)于通過(guò) @Component 等注解托管給容器的類(lèi),主要由ConfigurationClassPostProcessor 這個(gè) Bean 工廠后置處理器將其掃描封裝為 BeanDefinition 并注冊(cè),有興趣的讀者可參考 SpringBoot 注解 @Import 的原理-ConfigurationClassPostProcessor 源碼解析
- BeanPostProcessor 對(duì)象后置處理器的實(shí)例化
- Bean 對(duì)象創(chuàng)建及其 BeanPostProcessor 后置處理器在創(chuàng)建對(duì)象時(shí)的切面應(yīng)用,這部分邏輯主要在 AbstractApplicationContext#finishBeanFactoryInitialization() 方法中
@Override
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
// Prepare this context for refreshing.
prepareRefresh();
// Tell the subclass to refresh the internal bean factory.
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
// Prepare the bean factory for use in this context.
prepareBeanFactory(beanFactory);
try {
// Allows post-processing of the bean factory in context subclasses.
postProcessBeanFactory(beanFactory);
// Invoke factory processors registered as beans in the context.
invokeBeanFactoryPostProcessors(beanFactory);
// Register bean processors that intercept bean creation.
registerBeanPostProcessors(beanFactory);
// Initialize message source for this context.
initMessageSource();
// Initialize event multicaster for this context.
initApplicationEventMulticaster();
// Initialize other special beans in specific context subclasses.
onRefresh();
// Check for listener beans and register them.
registerListeners();
// Instantiate all remaining (non-lazy-init) singletons.
finishBeanFactoryInitialization(beanFactory);
// Last step: publish corresponding event.
finishRefresh();
}
catch (BeansException ex) {
if (logger.isWarnEnabled()) {
logger.warn("Exception encountered during context initialization - " +
"cancelling refresh attempt: " + ex);
}
// Destroy already created singletons to avoid dangling resources.
destroyBeans();
// Reset 'active' flag.
cancelRefresh(ex);
// Propagate exception to caller.
throw ex;
}
finally {
// Reset common introspection caches in Spring's core, since we
// might not ever need metadata for singleton beans anymore...
resetCommonCaches();
}
}
}
AbstractApplicationContext#finishBeanFactoryInitialization()方法的核心是調(diào)用DefaultListableBeanFactory#preInstantiateSingletons()方法實(shí)例化 Bean 對(duì)象
protected void finishBeanFactoryInitialization(ConfigurableListableBeanFactory beanFactory) {
// Initialize conversion service for this context.
if (beanFactory.containsBean(CONVERSION_SERVICE_BEAN_NAME) &&
beanFactory.isTypeMatch(CONVERSION_SERVICE_BEAN_NAME, ConversionService.class)) {
beanFactory.setConversionService(
beanFactory.getBean(CONVERSION_SERVICE_BEAN_NAME, ConversionService.class));
}
// Register a default embedded value resolver if no bean post-processor
// (such as a PropertyPlaceholderConfigurer bean) registered any before:
// at this point, primarily for resolution in annotation attribute values.
if (!beanFactory.hasEmbeddedValueResolver()) {
beanFactory.addEmbeddedValueResolver(strVal -> getEnvironment().resolvePlaceholders(strVal));
}
// Initialize LoadTimeWeaverAware beans early to allow for registering their transformers early.
String[] weaverAwareNames = beanFactory.getBeanNamesForType(LoadTimeWeaverAware.class, false, false);
for (String weaverAwareName : weaverAwareNames) {
getBean(weaverAwareName);
}
// Stop using the temporary ClassLoader for type matching.
beanFactory.setTempClassLoader(null);
// Allow for caching all bean definition metadata, not expecting further changes.
beanFactory.freezeConfiguration();
// Instantiate all remaining (non-lazy-init) singletons.
beanFactory.preInstantiateSingletons();
}
- DefaultListableBeanFactory#preInstantiateSingletons() 方法會(huì)遍歷容器內(nèi)部的 beanDefinitionNames列表 進(jìn)行 Bean 實(shí)例化,也就說(shuō)這個(gè)列表的順序就決定了 Bean 的創(chuàng)建順序,而實(shí)際上 beanDefinitionNames列表 中的元素是 BeanDefinition 注冊(cè)到 BeanDefinitionRegistry 時(shí)產(chǎn)生的
在 本節(jié)步驟1第2步 中,筆者提到通過(guò) @Component 等注解托管給容器的類(lèi)主要由 ConfigurationClassPostProcessor 掃描注冊(cè),那么要想讓指定的 Bean 優(yōu)先加載,只需要在 ConfigurationClassPostProcessor 掃描之前注冊(cè)指定 Bean 即可
@Override
public void preInstantiateSingletons() throws BeansException {
if (logger.isTraceEnabled()) {
logger.trace("Pre-instantiating singletons in " + this);
}
// Iterate over a copy to allow for init methods which in turn register new bean definitions.
// While this may not be part of the regular factory bootstrap, it does otherwise work fine.
List<String> beanNames = new ArrayList<>(this.beanDefinitionNames);
// Trigger initialization of all non-lazy singleton beans...
for (String beanName : beanNames) {
RootBeanDefinition bd = getMergedLocalBeanDefinition(beanName);
if (!bd.isAbstract() && bd.isSingleton() && !bd.isLazyInit()) {
if (isFactoryBean(beanName)) {
Object bean = getBean(FACTORY_BEAN_PREFIX + beanName);
if (bean instanceof FactoryBean) {
final FactoryBean<?> factory = (FactoryBean<?>) bean;
boolean isEagerInit;
if (System.getSecurityManager() != null && factory instanceof SmartFactoryBean) {
isEagerInit = AccessController.doPrivileged((PrivilegedAction<Boolean>)
((SmartFactoryBean<?>) factory)::isEagerInit,
getAccessControlContext());
}
else {
isEagerInit = (factory instanceof SmartFactoryBean &&
((SmartFactoryBean<?>) factory).isEagerInit());
}
if (isEagerInit) {
getBean(beanName);
}
}
}
else {
getBean(beanName);
}
}
}
// Trigger post-initialization callback for all applicable beans...
for (String beanName : beanNames) {
Object singletonInstance = getSingleton(beanName);
if (singletonInstance instanceof SmartInitializingSingleton) {
final SmartInitializingSingleton smartSingleton = (SmartInitializingSingleton) singletonInstance;
if (System.getSecurityManager() != null) {
AccessController.doPrivileged((PrivilegedAction<Object>) () -> {
smartSingleton.afterSingletonsInstantiated();
return null;
}, getAccessControlContext());
}
else {
smartSingleton.afterSingletonsInstantiated();
}
}
}
}
3. 實(shí)現(xiàn)方式
經(jīng)過(guò)上一節(jié)分析,我們知道只要找到一個(gè)切入點(diǎn),在 ConfigurationClassPostProcessor 掃描注冊(cè) Bean 之前注冊(cè)指定 Bean 到容器中就能實(shí)現(xiàn)優(yōu)先加載。SpringBoot 提供了不少這樣的切入點(diǎn),本文主要涉及如下兩個(gè):
- ApplicationContextInitializer
- ApplicationListener
3.1 實(shí)現(xiàn) ApplicationListener 監(jiān)聽(tīng)初始化事件
該方式實(shí)現(xiàn)的步驟如下:
- 在 SpringBoot 主類(lèi)中調(diào)用 SpringApplication#addListeners() 方法添加一個(gè) ContextInitializedListener 監(jiān)聽(tīng)器
- ContextInitializedListener 監(jiān)聽(tīng) ApplicationContextInitializedEvent事件,在事件觸發(fā)的時(shí)候往容器中注冊(cè)指定的 BeanDefinitionRegistryPostProcessor 后置處理器
- BeanDefinitionRegistryPostProcessor 后置處理器實(shí)現(xiàn)類(lèi)在 BeanDefinitionRegistryPostProcessor#postProcessBeanDefinitionRegistry() 方法中將指定 Bean 注冊(cè)到容器中,從而實(shí)現(xiàn)優(yōu)先加載
@SpringBootApplication()
public class ApiApplication {
public static void main(String[] args) {
SpringApplication application = new SpringApplication(ApiApplication.class);
application.addListeners(new ContextInitializedListener());
application.run(args);
}
static class ContextInitializedListener implements ApplicationListener<ApplicationContextInitializedEvent>, BeanDefinitionRegistryPostProcessor {
@Override
public void onApplicationEvent(ApplicationContextInitializedEvent event) {
AbstractApplicationContext context = (AbstractApplicationContext) event.getApplicationContext();
context.addBeanFactoryPostProcessor(this);
}
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
registry.registerBeanDefinition("example", new RootBeanDefinition(ContentDTO.class));
}
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
}
}
}
3.2 實(shí)現(xiàn) ApplicationContextInitializer
該方式實(shí)現(xiàn)的原理與事件監(jiān)聽(tīng)類(lèi)似,不再贅述
@SpringBootApplication()
public class ApiApplication {
public static void main(String[] args) {
SpringApplication application = new SpringApplication(ApiApplication.class);
application.addInitializers(new MyApplicationContextInitializer());
application.run(args);
}
static class MyApplicationContextInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext>, BeanDefinitionRegistryPostProcessor {
@Override
public void initialize(ConfigurableApplicationContext applicationContext) {
applicationContext.addBeanFactoryPostProcessor(this);
}
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
registry.registerBeanDefinition("example", new RootBeanDefinition(ContentDTO.class));
}
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
}
}
}
以上就是SpringBoot優(yōu)先加載指定Bean的實(shí)現(xiàn)的詳細(xì)內(nèi)容,更多關(guān)于SpringBoot加載Bean的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Spring?MVC內(nèi)置過(guò)濾器功能示例詳解
這篇文章主要為大家介紹了Spring?MVC內(nèi)置過(guò)濾器使用示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-09-09
java安全?ysoserial?CommonsCollections1示例解析
這篇文章主要介紹了java安全?ysoserial?CommonsCollections1示例解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-10-10
Java實(shí)戰(zhàn)之基于swing的QQ郵件收發(fā)功能實(shí)現(xiàn)
這篇文章主要介紹了Java實(shí)戰(zhàn)之基于swing的QQ郵件收發(fā)功能實(shí)現(xiàn),文中有非常詳細(xì)的代碼示例,對(duì)正在學(xué)習(xí)java的小伙伴們有非常好的幫助,需要的朋友可以參考下2021-04-04
MybatisPlus逆向工程的項(xiàng)目實(shí)踐
Mybatis-Plus逆向工程,是MP官方提供的一款代碼生成器,可以自動(dòng)生成對(duì)應(yīng)的實(shí)體類(lèi)、Mapper接口和配置文件,,本文主要介紹了MybatisPlus逆向工程的項(xiàng)目實(shí)踐,感興趣的可以了解一下2024-03-03
Java中args參數(shù)數(shù)組的用法說(shuō)明
這篇文章主要介紹了Java中args參數(shù)數(shù)組的用法說(shuō)明,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2021-02-02

