如何讓 Bean 深度感知 Spring 容器
來源:江南一點雨
Spring 有一個特點,就是建立出來的 Bean 對容器是無感的,一個 Bean 是怎麼樣被容器從一個 Class 整成一個 Bean 的,對於 Bean 本身來說是不知道的,當然也不需要知道,也就是 Bean 對容器的存在是無感的。
但是有時候我們可能會遇到一些場景,這些場景讓我們去感知容器的存在,松哥舉幾個例子:
Spring 容器提供的功能不止 IoC、AOP 這些,常見的 I18N 也是 Spring 的能力之一,如果我們想要在自己的 Bean 中去使用 I18N,那就得去找 Spring,這樣就感知到了 Spring 容器的存在了。 Spring 提供了資源載入器,如果我們想要使用這個資源載入器去載入配置,那就得去找 Spring 要,這樣就感知到了 Spring 容器的存在了。 想根據 beanName 去 Spring 容器中查詢 Bean,那不用多說,肯定得知道 Spring 容器的存在。 ...
也就是說,雖然 Spring 中的 Bean 可以不用去感知 Spring 容器的存在,但是在實際開發中,我們往往還是需要 Spring 容器提供的各種能力,這樣就迫使我們的 Bean 不得不去感知到 Spring 容器的存在。
那麼 Spring 中的 Bean 如何感知到 Spring 容器的存在呢?
1. Aware
Aware 本身就有感知的意思。
Spring Aware 是 Spring 框架中的一個特性,它允許我們的應用程式或元件與 Spring 容器進行互動。當一個類實現了 Spring Aware 介面並註冊到 Spring 容器中時,該類就能夠感知到 Spring 容器的存在,並且可以獲取容器的一些資源或進行一些特定的操作。
Spring Aware 介面包括了多個子介面,每個子介面對應於不同的 Spring 容器資源或功能。
Aware 的實現有很多,大的方向來說主要有如下一些:
每一個 Aware 的作用如下:
ApplicationEventPublisherAware:實現該介面的物件可以獲取事件釋出的能力。 ServletContextAware:實現該介面的物件可以獲取到 ServletContext 物件。 MessageSourceAware:實現該介面的物件可以獲取到 MessageSource 物件,MessageSource 支援多訊息源,主要用於主要用於國際化。 ResourceLoaderAware:實現該介面的物件可以獲取到一個 ResourceLoader,Spring ResourceLoader 則為我們提供了一個統一的 getResource() 方法來透過資源路徑檢索外部資源,例如文字檔案、XML 檔案、屬性檔案或影像檔案等。 ApplicationStartupAware:實現該介面的物件可以獲取到一個 ApplicationStartup 物件,這個比較新,是 Spring 5.3 中新推出的,透過 ApplicationStartup 可以標記應用程式啟動期間的步驟,並收集有關執行上下文或其處理時間的資料。 NotificationPublisherAware:實現該接的物件可以獲取到一個 NotificationPublisher 物件,透過該物件可以實現通知的傳送。 EnvironmentAware:實現該介面的物件可以獲取到一個 Environment 物件,透過 Environment 可以獲取到容器的環境資訊。 BeanFactoryAware:實現該介面的物件可以獲取到一個 BeanFactory 物件,透過 BeanFactory 可以完成 Bean 的查詢等操作。 ImportAware:實現該介面的物件可以獲取到一個 AnnotationMetadata 物件,ImportAware 介面是需要和 @Import 註解一起使用的。在 @Import 作為元註解使用時,透過 @Import 匯入的配置類如果實現了 ImportAware 介面就可以獲取到匯入該配置類介面的資料配置。 EmbeddedValueResolverAware:實現該介面的物件可以獲取到一個 StringValueResolver 物件,透過 StringValueResolver 物件,可以讀取到 Spring 容器中的 properties 配置的值(YAML 配置也可以)。 ServletConfigAware:實現該介面的物件可以獲取到一個 ServletConfig 物件,不過這個似乎沒什麼用,我們很少自己去配置 ServletConfig。 LoadTimeWeaverAware:實現該介面的物件可以獲取到一個 LoadTimeWeaver 物件,透過該物件可以獲取載入 Spring Bean 時織入的第三方模組,如 AspectJ 等。 BeanClassLoaderAware:實現該介面的物件可以獲取到一個 ClassLoader 物件,ClassLoader 能幹嘛不需要我多說了吧。 BeanNameAware:實現該介面的物件可以獲取到一個當前 Bean 的名稱。 ApplicationContextAware:實現該介面的物件可以獲取到一個 ApplicationContext 物件,透過 ApplicationContext 可以獲取容器中的 Bean、環境等資訊。
透過實現這些介面,我們可以在應用程式中獲取 Spring 容器提供的各種資源,並與容器進行互動,以實現更靈活和可擴充套件的功能。
2. 實踐
舉兩個例子小夥伴們來感受下 Aware 的具體用法。
2.1 案例
例如我想在 Bean 中感知到當前 Bean 的名字,那麼我們可以按照如下方式來使用:
@Service
public class UserService implements BeanNameAware {
private String beanName;
@Override
public void setBeanName(String name) {
this.beanName = name;
}
@Override
public String toString() {
return "UserService{" +
"beanName='" + beanName + '\'' +
'}';
}
}
讓當前 bean 實現 BeanNameAware 介面,並重寫 setBeanName 方法,這個方法會在 Spring 容器初始化 Bean 的時候自動被呼叫,我們就可以據此獲取到 bean 的名稱了。
再比如我想做一個工具 Bean,用來查詢其他 Bean,那麼我可以使用如下方式:
@Component
public class BeanUtils implements BeanFactoryAware {
private static BeanFactory beanFactory;
@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
this.beanFactory = beanFactory;
}
public static <T> T getBean(Class<T> clazz) {
return (T) beanFactory.getBean(clazz);
}
}
讓當前 Bean 實現 BeanFactoryAware 介面並重寫 setBeanFactory 方法,在系統初始化當前 Bean 的時候,會自動呼叫 setBeanFactory 方法,進而將 beanFactory 變數傳進來。
2.2 原理
當 Spring 容器建立一個 Bean 的時候,大致的流程是建立例項物件
-> 屬性填充
-> Bean 初始化
。
最後這個 Bean 的初始化,就是呼叫 init 方法、afterPropertiesSet 方法以及 BeanPostProcessor 中的方法的,如下:
protected Object initializeBean(String beanName, Object bean, @Nullable RootBeanDefinition mbd) {
invokeAwareMethods(beanName, bean);
Object wrappedBean = bean;
if (mbd == null || !mbd.isSynthetic()) {
wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName);
}
try {
invokeInitMethods(beanName, wrappedBean, mbd);
}
catch (Throwable ex) {
throw new BeanCreationException(
(mbd != null ? mbd.getResourceDescription() : null), beanName, ex.getMessage(), ex);
}
if (mbd == null || !mbd.isSynthetic()) {
wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);
}
return wrappedBean;
}
在這個方法一進來,首先有一個 invokeAwareMethods,這個就是用來觸發 Aware 的,來看下:
private void invokeAwareMethods(String beanName, Object bean) {
if (bean instanceof Aware) {
if (bean instanceof BeanNameAware beanNameAware) {
beanNameAware.setBeanName(beanName);
}
if (bean instanceof BeanClassLoaderAware beanClassLoaderAware) {
ClassLoader bcl = getBeanClassLoader();
if (bcl != null) {
beanClassLoaderAware.setBeanClassLoader(bcl);
}
}
if (bean instanceof BeanFactoryAware beanFactoryAware) {
beanFactoryAware.setBeanFactory(AbstractAutowireCapableBeanFactory.this);
}
}
}
小夥伴們可以看到,BeanNameAware、BeanClassLoaderAware 以及 BeanFactoryAware 這三種型別的 Aware 是在這裡觸發的。
每種 Aware 因為功能不同,因此作用的時機也不同。
invokeAwareMethods 方法執行完畢之後,接下來是執行 applyBeanPostProcessorsBeforeInitialization 方法,這個我們之前分析過,這個方法最終會觸發 BeanPostProcessor#postProcessBeforeInitialization 方法的執行,而 BeanPostProcessor 有一個子類專門處理 Aware 的,就是 ApplicationContextAwareProcessor:
@Override
@Nullable
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
if (!(bean instanceof EnvironmentAware || bean instanceof EmbeddedValueResolverAware ||
bean instanceof ResourceLoaderAware || bean instanceof ApplicationEventPublisherAware ||
bean instanceof MessageSourceAware || bean instanceof ApplicationContextAware ||
bean instanceof ApplicationStartupAware)) {
return bean;
}
invokeAwareInterfaces(bean);
return bean;
}
private void invokeAwareInterfaces(Object bean) {
if (bean instanceof Aware) {
if (bean instanceof EnvironmentAware environmentAware) {
environmentAware.setEnvironment(this.applicationContext.getEnvironment());
}
if (bean instanceof EmbeddedValueResolverAware embeddedValueResolverAware) {
embeddedValueResolverAware.setEmbeddedValueResolver(this.embeddedValueResolver);
}
if (bean instanceof ResourceLoaderAware resourceLoaderAware) {
resourceLoaderAware.setResourceLoader(this.applicationContext);
}
if (bean instanceof ApplicationEventPublisherAware applicationEventPublisherAware) {
applicationEventPublisherAware.setApplicationEventPublisher(this.applicationContext);
}
if (bean instanceof MessageSourceAware messageSourceAware) {
messageSourceAware.setMessageSource(this.applicationContext);
}
if (bean instanceof ApplicationStartupAware applicationStartupAware) {
applicationStartupAware.setApplicationStartup(this.applicationContext.getApplicationStartup());
}
if (bean instanceof ApplicationContextAware applicationContextAware) {
applicationContextAware.setApplicationContext(this.applicationContext);
}
}
}
大家看下,這七種型別的 Aware 是在這裡被觸發的。
另外像 ImportAware 是在 ImportAwareBeanPostProcessor#postProcessBeforeInitialization 方法中處理的;LoadTimeWeaverAware 是在 、LoadTimeWeaverAwareProcessor#postProcessBeforeInitialization 方法中處理的。
基本上,大部分的 Aware 介面都是在 BeanPostProcessor 中處理的。
好啦,現在小夥伴們理解 Aware 了吧~
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/70024923/viewspace-2993267/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- Spring Bean容器SpringBean
- Spring IOC與Bean容器SpringBean
- 如何向Spring IOC 容器 動態註冊beanSpringBean
- Spring IOC 一——Spring容器裝配BeanSpringBean
- Spring容器 —— 深入 bean 的載入(五、初始化 bean)SpringBean
- 你說,怎麼把Bean塞到Spring容器?BeanSpring
- spring原始碼深度解析— IOC 之 bean 建立Spring原始碼Bean
- 探索Spring系列(一)Spring容器和Bean的生命週期SpringBean
- 【String註解驅動開發】面試官讓我說說:如何使用FactoryBean向Spring容器中註冊bean?面試BeanSpring
- Spring學習筆記四: 從Spring容器中獲取BeanSpring筆記Bean
- Spring-Context之四:Spring容器及bean的定義SpringContextBean
- Spring(十二):IOC容器中Bean的生命週期方法SpringBean
- Java類獲取spring 容器的bean的5種方法JavaSpringBean
- Spring Bean如何初始化的SpringBean
- spring原始碼深度解析— IOC 之 bean 的初始化Spring原始碼Bean
- spring-IOC容器原始碼分析(一)bean初始化流程Spring原始碼Bean
- 4、Spring IOC容器 Bean物件例項化的3種方式SpringBean物件
- Spring容器啟動流程+Bean的生命週期【附原始碼】SpringBean原始碼
- 【Java面試】Spring中有哪些方式可以把Bean注入到IOC容器?Java面試SpringBean
- [Spring]BeanSpringBean
- 【spring原始碼學習】spring的IOC容器在初始化bean過程Spring原始碼Bean
- spring4.1.8擴充套件實戰之四:感知spring容器變化(SmartLifecycle介面)Spring套件
- spring原始碼深度解析— IOC 之 開啟 bean 的載入Spring原始碼Bean
- Spring(三):IoC容器裝配Bean(xml配置方式和註解方式)SpringBeanXML
- 《Spring 手擼專欄》第 2 章:小試牛刀(讓新手能懂),實現一個簡單的Bean容器SpringBean
- Spring中Bean及@Bean的理解SpringBean
- Spring bean到底是如何建立的?(上)SpringBean
- Spring如何控制Bean的載入順序SpringBean
- 【Spring】Bean管理SpringBean
- spring中如何向一個單例bean中注入非單例beanSpring單例Bean
- Spring Ioc原始碼分析系列--容器例項化Bean的四種方法Spring原始碼Bean
- 手寫Spring,定義標記型別Aware介面,實現感知容器物件Spring型別物件
- [深度學習]多層感知機(MLP)深度學習
- spring原始碼深度解析— IOC 之 容器的基本實現Spring原始碼
- 如何從Spring之外的非託管物件訪問 Spring Bean?Spring物件Bean
- Spring Bean 綜述SpringBean
- Spring bean 裝配SpringBean
- spring boot factory beanSpring BootBean