Spring5源碼解析-Spring中的Bean post processors
我們之前已經對Spring中的bean工廠後置處理器說道過了。但是,依然留下了一個類似的概念的小尾巴需要來解釋,這就是bean後置處理器(bean post processors)。
本文将分為兩部分。在第一部分,将了解下Spring的單個後處理器bean。第二部分将涉及一些後置處理器(post processors)實際使用的例子。
什麼是bean post processor?
bean生命周期始于加載bean的定義。通過拿到的這個定義,Spring可以構造出(construct嘛)bean并注入元件(因為我們常用的就是在controller裡 service裡使用)。之後,所有的bean都可以進行後置處理。這意味着我們可以實作一些自定義邏輯并調用它。并在調用bean的初始化方法(xml配置所定義的init-method 屬性)之前和/或之後進行調用(當然預設的上下文環境是Spring容器)。
你不能為給定的bean類型明确指定一個bean後置處理器。每個定義的後處理器可以應用于application context中的所有定義的bean。後置處理器bean必須實作org.springframework.beans.factory.config.BeanPostProcessor接口并定義postProcessBeforeInitialization和postProcessAfterInitialization方法。第一個在調用初始化方法(init-method所指定的方法)之前被調用,第二個在調用初始化方法之後被調用。這兩個方法都有兩個參數:
Object:表示已處理的bean的執行個體。
字元串:包含已處理的bean的名稱。
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;
}
}
一起來思考下,當我們需要檢測一個bean是否可以被後置處理(其實就是構造函數執行完畢,init-method所指定的方法執行前後所要調用的處理邏輯)。為了避免寫很多的if-else判斷,我們可以建立一個支援後置處理的抽象出來的接口,然後由所有bean來實作。這樣,我們的代碼将更加具有可讀性,這也就是這個接口的抽取思想。
假如沒有看過我的Spring工廠後置處理器這篇文章,請先閱讀完,因為本章是對其做的一個補充來講的。接着,要講大家關心的東西:他們之間的一些差別。Bean Factory後置處理器隻适用于bean定義這塊。它們在對象建立之前被調用,這就是為什麼他們隻能更改bean中繼資料的原因。不像BeanPostProcessors bean 可以更改對象的屬性。你再思考該問題,如果bean工廠後置處理器和bean後置處理器重寫覆寫同一對象的屬性,則最後保留的值将由bean後置處理器設定的這個,這是因為它是在bean factory後置處理器之後才被調用的。
init-method 執行釋疑
關于init-method的執行的位置,有些人可能有疑問,這裡拿個之前存的一個例子:
在配置檔案中添加如下Bean定義:
public class InitSequenceBean implements InitializingBean {
public InitSequenceBean() {
System.out.println("InitSequenceBean: constructor");
}
@PostConstruct
public void postConstruct() {
System.out.println("InitSequenceBean: postConstruct");
}
public void initMethod() {
System.out.println("InitSequenceBean: init-method");
}
@Override
public void afterPropertiesSet() throws Exception {
System.out.println("InitSequenceBean: afterPropertiesSet");
}
}
執行結果:
InitSequenceBean: constructor
InitSequenceBean: postConstruct
InitSequenceBean: afterPropertiesSet
InitSequenceBean: init-method
通過上述輸出結果,三者的先後順序也就一目了然了:
Constructor > @PostConstruct > InitializingBean > init-method
@PostConstruct通過Debug追源碼可以找到這個類:org.springframework.context.annotation.CommonAnnotationBeanPostProcessor,從命名上,我們就可以得到某些資訊—>這是一個BeanPostProcessor。
bean後置處理器Demo
在我們的例子中,我們要使在程式部署時将無法使用的bean無效化。假如我們有一個VOD流媒體網站,所有的電影都可以在一個月的第一個星期免費觀看(這個僅在部署時實作此效果,因為舉例嘛,完善的話可以做個定時任務啥的,後面會拿一篇來講)。驗證的代碼如下:
@Controller
public MovieController {
@Autowired
private ViewChecker viewChecker;
// some of request mapped methods
// check method
private boolean movieCanBeWatched(Movie movie) {
if (viewChecker == null) {
return true;
}
return viewChecker.canBeWatched(movie);
}
}
我們對一個bean進行A&B測試,以擷取并格式化網店中的産品清單。第一個bean用來擷取通路量最多的商品。第二個是基于使用者的喜好。也就是說通過這個A&B就可以得到最受歡迎的商品(本來想舉個複雜的例子的,還是算了,搞簡單點吧,要不篇幅太長了)。首先,我們來定義一個bean配置:
第一個bean代表後置處理器bean。第二個,viewChecker是一個用來檢查使用者是否可以檢視電影的類。我們先來看看這第二個class的:
public class ViewChecker implements ProcessedBean {
@Override
public boolean isValid() {
// visitors can watch movies freely between the 1st and 7th day of every month
Calendar calendar = Calendar.getInstance();
return calendar.get(Calendar.DAY_OF_MONTH) > 8;
}
}
可以看到,代碼量很少。ProcessedBean接口如下所示:
public interface ProcessedBean {
public boolean isValid();
}
實作此接口的所有bean必須實作isValid()方法,這樣就可以用來判斷該應用程式上下文是否可以使用該bean。在BeanPostProcessorSample中調用ifValid方法:
public class BeanPostProcessorSample implements BeanPostProcessor {
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
if (bean instanceof ProcessedBean) {
if (!((ProcessedBean)bean).isValid()) {
return null;
}
}
return bean;
}
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
}
由上可知,我們僅實作afterInitialization這個後置處理器方法。通過它,我們就可以确定所分析的bean可能在init-method(如果指定)中所設定的資料。如果分析的bean的isValid()是false,我們傳回null。但請注意傳回值null(再強調一遍)。如果無效bean還存在另一個依賴關系,可以看到類似于下面這樣的異常(這個異常我們經常見,空指針異常):
org.springframework.beans.factory.BeanCreationException: Error creating bean with name ‘adminController’: Injection of resource dependencies failed; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name ‘adminService’: Injection of resource dependencies failed; nested exception is
//也就是容器裡找不到DataSource 這個bean的執行個體
java.lang.IllegalArgumentException: DataSource must not be null
at org.springframework.context.annotation.CommonAnnotationBeanPostProcessor.postProcessPropertyValues(CommonAnnotationBeanPostProcessor.java:307)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1185)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:537)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:475)
at org.springframework.beans.factory.support.AbstractBeanFactory1.getObject(AbstractBeanFactory.java:304)atorg.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:228)atorg.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:300)atorg.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:195)atorg.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:700)atorg.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:760)atorg.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:482)atorg.springframework.web.context.ContextLoader.configureAndRefreshWebApplicationContext(ContextLoader.java:381)atorg.springframework.web.context.ContextLoader.initWebApplicationContext(ContextLoader.java:293)atorg.springframework.web.context.ContextLoaderListener.contextInitialized(ContextLoaderListener.java:106)atorg.apache.catalina.core.StandardContext.listenerStart(StandardContext.java:4701)atorg.apache.catalina.core.StandardContext1.call(StandardContext.java:5204)
at org.apache.catalina.core.StandardContext1.call(StandardContext.java:5199)atjava.util.concurrent.FutureTaskSync.innerRun(Unknown Source)
at java.util.concurrent.FutureTask.run(Unknown Source)
at java.util.concurrent.ThreadPoolExecutorWorker.runTask(UnknownSource)atjava.util.concurrent.ThreadPoolExecutorWorker.run(Unknown Source)
at java.lang.Thread.run(Unknown Source)
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name ‘adminService’: Injection of resource dependencies failed; nested exception is java.lang.IllegalArgumentException: DataSource must not be null
at org.springframework.context.annotation.CommonAnnotationBeanPostProcessor.postProcessPropertyValues(CommonAnnotationBeanPostProcessor.java:307)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1185)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:537)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:475)
at org.springframework.beans.factory.support.AbstractBeanFactory1.getObject(AbstractBeanFactory.java:304)atorg.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:228)atorg.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:300)atorg.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:200)atorg.springframework.context.annotation.CommonAnnotationBeanPostProcessor.autowireResource(CommonAnnotationBeanPostProcessor.java:445)atorg.springframework.context.annotation.CommonAnnotationBeanPostProcessor.getResource(CommonAnnotationBeanPostProcessor.java:419)atorg.springframework.context.annotation.CommonAnnotationBeanPostProcessorResourceElement.getResourceToInject(CommonAnnotationBeanPostProcessor.java:544)
at org.springframework.beans.factory.annotation.InjectionMetadataInjectedElement.inject(InjectionMetadata.java:150)atorg.springframework.beans.factory.annotation.InjectionMetadata.inject(InjectionMetadata.java:87)atorg.springframework.context.annotation.CommonAnnotationBeanPostProcessor.postProcessPropertyValues(CommonAnnotationBeanPostProcessor.java:304)…21moreCausedby:java.lang.IllegalArgumentException:DataSourcemustnotbenullatorg.springframework.util.Assert.notNull(Assert.java:112)atorg.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate.(NamedParameterJdbcTemplate.java:89)atorg.springframework.jdbc.core.simple.SimpleJdbcTemplate.(SimpleJdbcTemplate.java:70)atcom.migo.service.AdminService.setDataSource(AdminService.java:38)atsun.reflect.NativeMethodAccessorImpl.invoke0(NativeMethod)atsun.reflect.NativeMethodAccessorImpl.invoke(UnknownSource)atsun.reflect.DelegatingMethodAccessorImpl.invoke(UnknownSource)atjava.lang.reflect.Method.invoke(UnknownSource)atorg.springframework.beans.factory.annotation.InjectionMetadataInjectedElement.inject(InjectionMetadata.java:159)
at org.springframework.beans.factory.annotation.InjectionMetadata.inject(InjectionMetadata.java:87)
at org.springframework.context.annotation.CommonAnnotationBeanPostProcessor.postProcessPropertyValues(CommonAnnotationBeanPostProcessor.java:304)
… 34 more
這問題發生在adminService建立的時候:
@Service(“adminService”)
public class AdminService implements GenericService {
private SimpleJdbcTemplate jdbcTemplate;
@Resource(name=”dataSource”)
public void setDataSource(DataSource dataSource) {
this.jdbcTemplate = new SimpleJdbcTemplate(dataSource);
}
}
通過這篇文章,我們在bean生命周期幾乎每個階段都可以對它進行操作。我們可以使用BeanFactoryPostProcessors來更改Bean的定義,也可以使用bean post processors(後置處理器)來更改bean對象。但在更改任何内容之前,你需要分析其依賴關系。因為無效的bean(通過在後置處理器方法中傳回null)可能會導緻所依賴bean初始化(即空指針)的問題。
大家可以點選加入群:478052716【JAVA進階程式員】裡面有Java進階大牛直播講解知識點 走的就是高端路線 (如果你想跳槽換工作 但是技術又不夠 或者工作上遇到了瓶頸 我這裡有一個JAVA的免費直播課程 講的是高端的知識基礎不好的誤入喲 隻要你有1-5年的開發經驗可以加群找我要課堂連結 注意:是免費的 沒有開發經驗誤入哦)