天天看點

Spring5源碼解析-Spring中的Bean post processors

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年的開發經驗可以加群找我要課堂連結 注意:是免費的 沒有開發經驗誤入哦)

繼續閱讀