1. 程式人生 > 其它 >Spring框架擴充套件點詳解(BeanPostProcessor等)

Spring框架擴充套件點詳解(BeanPostProcessor等)

在日常使用Spring框架的業務開發中,利用框架提供的擴充套件點完成某些功能的設計是很常見的,瞭解這些擴充套件點的原理也對理解框架非常有幫助。這裡做一個簡單的整理、總結。

1. BeanPostProcessor

BeanPostProcessor 介面定義了基本的Bean初始化回撥方法,可以實現對應的回撥方法來在Spring容器完成Bean的例項化、初始化前後實現某些自定義邏輯。
一段來自注釋中的翻譯:

ApplicationContext可以在其 beanDefinitions 中自動檢測框架中預置和我們自行擴充套件的BeanPostProcessor,並將這些後處理器應用於隨後建立的任何 bean。
在ApplicationContext中自動檢測的BeanPostProcessor bean 將根據PriorityOrdered和Ordered語義進行排序。 相比之下,以程式設計方式註冊到BeanFactory BeanPostProcessor bean 將按註冊順序應用; 對於以程式設計方式註冊的後處理器,通過實現PriorityOrdered或Ordered介面表達的任何排序語義都將被忽略。PS:(所謂的程式設計方式就是說通過手動呼叫BeanFactory的addBeanPostProcessor方法進行新增BeanPostProcessor )

下面是BeanPostProcessor的介面定義。

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;
	}

}

1.1 BeanPostProcessor 基本示例:

下面這個PersonBeanPostProcessor 對於每一個完成例項化的Bean判斷其 BeanName ,如果與 person相等就列印一行日誌

@Component
public class Person {
    private Integer id;
    private String name;
	//省略 Getter、Setter
}

@Component
public class PersonBeanPostProcessor implements BeanPostProcessor {
    private final Logger logger = LoggerFactory.getLogger(PersonBeanPostProcessor.class);

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        if ("person".equals(beanName)) {
            logger.info("person完成例項化");
        }
        return null;
    }
}

//啟動應用:
  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v2.3.8.RELEASE)

2021-06-24 07:40:41.126  INFO 27308 --- [           main] com.landscape.spring.Application         : No active profile set, falling back to default profiles: default
2021-06-24 07:40:41.508  INFO 27308 --- [           main] c.l.spring.bean.PersonBeanPostProcessor  : person完成例項化
2021-06-24 07:40:41.592  INFO 27308 --- [           main] com.landscape.spring.Application         : Started Application in 0.939 seconds (JVM running for 2.145)

可以看到,第二行日誌中自定義的 BeanPostProcessor 生效並按照預期的打印出了日誌。

1.2 BeanPostProcessor 實際使用

從一個簡單的示例可能無法感受到它能在實際的開發中做什麼,現在找一點實際的例子來看BeanPostProcessor的用處。一個非常簡單且有效的例子是Spring Validation包下的 BeanValidationPostProcessor,它負責對Spring中的例項化的Bean做JSR-303的註解校驗,如果違反了校驗規則就丟擲異常。

public class BeanValidationPostProcessor implements BeanPostProcessor, InitializingBean {

	@Nullable
	private Validator validator;
    //省略主題無關的程式碼
	 
    //通過一個變數 afterInitialization 來判斷是在初始化前還是在初始化後做判斷
	@Override
	public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
		if (!this.afterInitialization) {
			doValidate(bean);
		}
		return bean;
	}

	@Override
	public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
		if (this.afterInitialization) {
			doValidate(bean);
		}
		return bean;
	}

	/**
	 * Perform validation of the given bean.
	 * @param bean the bean instance to validate
	 * @see javax.validation.Validator#validate
	 */
	protected void doValidate(Object bean) {
		//省略主題無關的程式碼
	}

}

不過這個處理器並不是預設注入到容器的,所以需要我們手動配置:

@Configuration
public class BeanValidationConfiguration {

    @Bean
    public BeanValidationPostProcessor beanValidationPostProcessor() {
        return new BeanValidationPostProcessor();
    }
}

現在寫一個帶有JSR-303註解的實體類:

@Component
@Scope(BeanDefinition.SCOPE_PROTOTYPE)
public class Person {
    @Min(1)
    @NotNull
    private Integer id;
    @NotBlank
    private String name;
    @NotBlank
    private String address;

    private LocalDateTime birthday;

    public Person() {
    }
}

//由於這個Bean的作用域被設定為 prototype ,所以必須要手動獲取才會觸發例項化:
public static void main(String[] args) {
    ConfigurableApplicationContext context = SpringApplication.run(SpringBootContainer.class, args);
    System.out.println(context.getBean(Person.class));
}

//執行後丟擲異常:
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:602)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:516)
	at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:342)
	at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:227)
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveNamedBean(DefaultListableBeanFactory.java:1175)
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveBean(DefaultListableBeanFactory.java:420)
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:349)
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:342)
	at org.springframework.context.support.AbstractApplicationContext.getBean(AbstractApplicationContext.java:1127)
	at com.landscape.demo.SpringBootContainer.main(SpringBootContainer.java:19)
Caused by: org.springframework.beans.factory.BeanInitializationException: Bean state is invalid: Bean state is invalid: address - 不能為空; name - 不能為空; id - 不能為null
	at org.springframework.validation.beanvalidation.BeanValidationPostProcessor.doValidate(BeanValidationPostProcessor.java:127)
	at

如果稍微改變一下程式碼,給實體類屬性加上預設值即可通過校驗。同理,在實際的開發中,也可以使用BeanPostProcessor類似的進行Bean校驗、設值、掃包等操作。


1.3 BeanPostProcessor 的呼叫時機

1.3.1 BeanPostProcessor

現在來看一些原理上的細節。首先,BeanPostProcessor 的實現方法是在什麼時候進行回撥的?下圖是整體Spring Bean的例項化過程,而紅框標註的部分就是 BeanPostProcessor 的呼叫

圖片來源:Hands-On High Performance with Spring 5

結合著Spring原始碼來看:

//org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#doCreateBean
//這裡是Spring框架執行過程中建立Bean的方法
protected Object doCreateBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)
    throws BeanCreationException {

    // Instantiate the bean.
    //省略前面建立Bean的過程

    // Initialize the bean instance.
    Object exposedObject = bean;
    try {
        //這裡是填充Bean的屬性
        populateBean(beanName, mbd, instanceWrapper);
        //執行Bean的初始化過程,BeanPostProcessor在這裡被呼叫
        exposedObject = initializeBean(beanName, exposedObject, mbd);
    }
    //省略主題無關的程式碼

    return exposedObject;
}

//下面是initializeBean方法內部的邏輯
protected Object initializeBean(String beanName, Object bean, @Nullable RootBeanDefinition mbd) {
    if (System.getSecurityManager() != null) {
        AccessController.doPrivileged((PrivilegedAction<Object>) () -> {
            invokeAwareMethods(beanName, bean);
            return null;
        }, getAccessControlContext());
    }
    else {
        invokeAwareMethods(beanName, bean);
    }

    Object wrappedBean = bean;
    if (mbd == null || !mbd.isSynthetic()) {
        //這裡執行了BeanPostProcessor的 postProcessBeforeInitialization方法
        wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName);
    }

    try {
        invokeInitMethods(beanName, wrappedBean, mbd);
    }
    catch (Throwable ex) {
        throw new BeanCreationException(
            (mbd != null ? mbd.getResourceDescription() : null),
            beanName, "Invocation of init method failed", ex);
    }
    if (mbd == null || !mbd.isSynthetic()) {
        //這裡執行了BeanPostProcessor的 postProcessAfterInitialization方法
        wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);
    }

    return wrappedBean;
}

另外 BeanPostProcessor 還有一些比較重要的子介面,Spring官方並不推薦我們使用這些子介面,因為大多屬於內建功能,不過了解一下也對理解框架原理很有幫助。來看一下各自的作用和原始碼中的呼叫位置:

1.3.2 InstantiationAwareBeanPostProcessor

BeanPostProcessor子介面,用於新增例項化前回調,以及例項化後但在設定顯式屬性或自動裝配之前的回撥。從名字上就能看出來是與Bean的例項化相關的處理器。之所以這裡重點介紹這個介面是因為AOP不少相關的類都是這個通過這個介面來返回代理物件的

通常用於抑制特定目標 bean 的預設例項化,例如建立具有特殊 TargetSource 的代理(池化目標、延遲初始化目標等),或實現額外的注入策略,例如欄位注入。Spring文件中並沒有提到這個介面,因為該介面是一個特殊用途的介面,主要供框架內部使用。

邏輯上該介面的postProcessBeforeInstantiation方法呼叫處於下圖位置(真正例項化Bean之前)(自己整理的思維導圖,截了一小部分,可能不是很全面= =)

從程式碼中看則位於:

//org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#createBean()
protected Object createBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)
    throws BeanCreationException {

    //省略主題無關程式碼
    //resolveBeanClass
    //prepareMethodOverrides

    try {
        // InstantiationAwareBeanPostProcessor 介面的呼叫在這裡,下面這行註釋也解釋的很清楚了
        // 給BeanPostProcessors一個返回代理而不是目標bean例項的機會。
        // Give BeanPostProcessors a chance to return a proxy instead of the target bean instance.
        Object bean = resolveBeforeInstantiation(beanName, mbdToUse);
        if (bean != null) {
            return bean;
        }
    }

    //如果沒有相關的InstantiationAwareBeanPostProcessor返回作為替代的Bean則立即進入實際的建立Bean過程
    try {
        Object beanInstance = doCreateBean(beanName, mbdToUse, args);
        if (logger.isTraceEnabled()) {
            logger.trace("Finished creating instance of bean '" + beanName + "'");
        }
        return beanInstance;
    }
    //省略異常處理

}

//進入到resolveBeforeInstantiation方法體中:
//org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#resolveBeforeInstantiation
protected Object resolveBeforeInstantiation(String beanName, RootBeanDefinition mbd) {
    Object bean = null;
    if (!Boolean.FALSE.equals(mbd.beforeInstantiationResolved)) {
        // Make sure bean class is actually resolved at this point.
        if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
            Class<?> targetType = determineTargetType(beanName, mbd);
            if (targetType != null) {
                //可以看到如果Bean在這一步如果被代理物件替代則立即進入到 AfterInitialization 的後處理中
                //因為不會繼續標準化的例項化流程了
                bean = applyBeanPostProcessorsBeforeInstantiation(targetType, beanName);
                if (bean != null) {
                    bean = applyBeanPostProcessorsAfterInitialization(bean, beanName);
                }
            }
        }
        mbd.beforeInstantiationResolved = (bean != null);
    }
    return bean;
}

postProcessAfterInstantiation 方法的呼叫則處於屬性填充之前,這是在Spring的自動裝配開始之前,在給定的bean例項上執行自定義欄位注入的理想回調。(這個方法的返回值是 boolean型別,用於告訴Spring是否應該繼續後續的屬性填充過程)

//org.springframework.beans.factory.config.InstantiationAwareBeanPostProcessor#postProcessAfterInstantiation
default boolean postProcessAfterInstantiation(Object bean, String beanName) throws BeansException {
    return true;
}

邏輯檢視處於屬性填充方法的開始部分,如果返回值為false 則不會進行後面的屬性注入

程式碼檢視如下:

//org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#populateBean
protected void populateBean(String beanName, RootBeanDefinition mbd, @Nullable BeanWrapper bw) {

    // 給任何InstantiationAwareBeanPostProcessors 一個機會在屬性設定之前修改bean的狀態。
    // 例如,這可以用於支援欄位注入的樣式。
    // Give any InstantiationAwareBeanPostProcessors the opportunity to modify the
    // state of the bean before properties are set. This can be used, for example,
    // to support styles of field injection.
    if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
        for (BeanPostProcessor bp : getBeanPostProcessors()) {
            if (bp instanceof InstantiationAwareBeanPostProcessor) {
                InstantiationAwareBeanPostProcessor ibp = (InstantiationAwareBeanPostProcessor) bp;
                //在這裡進行 postProcessAfterInstantiation 方法的呼叫
                if (!ibp.postProcessAfterInstantiation(bw.getWrappedInstance(), beanName)) {
                    return;
                }
            }
        }
    }
    //這裡省略了一段屬性填充的過程
    PropertyDescriptor[] filteredPds = null;
    if (hasInstAwareBpps) {
        if (pvs == null) {
            pvs = mbd.getPropertyValues();
        }
        //下面這一部分分別呼叫了 postProcessProperties 和 postProcessPropertyValues 方法
        for (BeanPostProcessor bp : getBeanPostProcessors()) {
            if (bp instanceof InstantiationAwareBeanPostProcessor) {
                InstantiationAwareBeanPostProcessor ibp = (InstantiationAwareBeanPostProcessor) bp;
                PropertyValues pvsToUse = ibp.postProcessProperties(pvs, bw.getWrappedInstance(), beanName);
                if (pvsToUse == null) {
                    if (filteredPds == null) {
                        filteredPds = filterPropertyDescriptorsForDependencyCheck(bw, mbd.allowCaching);
                    }
                    pvsToUse = ibp.postProcessPropertyValues(pvs, filteredPds, bw.getWrappedInstance(), beanName);
                    if (pvsToUse == null) {
                        return;
                    }
                }
                pvs = pvsToUse;
            }
        }
    }
    if (needsDepCheck) {
        if (filteredPds == null) {
            filteredPds = filterPropertyDescriptorsForDependencyCheck(bw, mbd.allowCaching);
        }
        checkDependencies(beanName, mbd, filteredPds, pvs);
    }

    if (pvs != null) {
        applyPropertyValues(beanName, mbd, bw, pvs);
    }
}

1.3.3 其他子介面

  • DestructionAwareBeanPostProcessor 用於新增銷燬前回調的BeanPostProcessor子介面。典型的用法是在特定的 bean 型別上呼叫自定義銷燬回撥,匹配相應的初始化回撥。實現方法將在Bean的destroy方法之前被呼叫。

  • MergedBeanDefinitionPostProcessor 執行時合併bean 定義的後處理器回撥介面。 BeanPostProcessor實現可以實現這個子介面,以便對 Spring BeanFactory用來建立 bean 例項的合併 bean 定義(原始 bean 定義的處理副本)進行後處理。

1.4 BeanPostProcessor本身的例項化時機

同樣是被標記 @Component 註解或者以其他方式被宣告為一個Bean,Spring如何保證 BeanPostProcessor 的實現能處理到每一個Bean?

首先,BeanPostProcessor 本身是在容器重新整理時被初始化:

而在程式碼中實際呼叫的是 PostProcessorRegistrationDelegateregisterBeanPostProcessors 的方法:

public static void registerBeanPostProcessors(
      ConfigurableListableBeanFactory beanFactory, AbstractApplicationContext applicationContext) {

   String[] postProcessorNames = beanFactory.getBeanNamesForType(BeanPostProcessor.class, true, false);

   // Register BeanPostProcessorChecker that logs an info message when
   // a bean is created during BeanPostProcessor instantiation, i.e. when
   // a bean is not eligible for getting processed by all BeanPostProcessors.
   int beanProcessorTargetCount = beanFactory.getBeanPostProcessorCount() + 1 + postProcessorNames.length;
   beanFactory.addBeanPostProcessor(new BeanPostProcessorChecker(beanFactory, beanProcessorTargetCount));

	//篇幅問題,省略下面的方法
}

這個方法內看著步驟挺多的,事實上只是對BeanPostProcessor進行有序註冊,步驟為:

  1. 獲取所有BeanPostProcessor的Name
  2. 將內建的BeanPostProcessor和應用程式的BeanPostProcessor進行計數+1(計數+1是因為緊接著添加了一個BeanPostProcessorChecker,這個類本身也是一個BeanPostProcessor)
  3. 註冊所有實現了PriorityOrdered 的BeanPostProcessor
  4. 註冊所有實現了Ordered 的BeanPostProcessor
  5. 註冊所有其他的BeanPostProcessor
  6. 重新註冊所有內部的BeanPostProcessor(這裡的“內建”指的是實現了MergedBeanDefinitionPostProcessor的BeanPostProcessor,將他們重新註冊到列表的末尾)
  7. 重新註冊一個ApplicationListenerDetector到列表末尾(這裡的重新註冊內建BeanPostProcessor和ListenerDetector都是為了內建的元件能夠獲取到被代理取代後的物件)

對於使用Spring進行業務開發的我們來說,上述步驟裡我們需要關心的只有BeanPostProcessor 的介面排序而已,也就是:

  1. 優先註冊所有實現了PriorityOrdered 的BeanPostProcessor
  2. 其次是實現了Ordered 的BeanPostProcessor
  3. 最後是沒有實現任何介面的BeanPostProcessor

其他的步驟都屬於Spring框架內建程式碼使用的功能,除非需要對Spring框架做深度擴充套件,否則無需關心。

1.5 BeanPostProcessor並不能處理所有Bean

這個很好理解,首先BeanPostProcessor本身就是被宣告的Bean,那麼就一定有先後順序,優先例項化的BeanPostProcessor可以處理後面例項化的BeanPostProcessor,這沒什麼問題。

一個很好的例子是Spring文件中關於AOP的說明:

因為 AOP 自動代理被實現為BeanPostProcessor本身,不是BeanPostProcessor例項或它們直接引用的bean都符合自動代理的條件,反之則不包含切面。

也就是說我們在實際的開發中需要避免在BeanPostProcessor內嵌入業務或者讓BeanPostProcessor依賴業務元件。

來一個例子演示一下。第一步,找到實現AOP功能的BeanPostProcessor,在容器完成BeanPostProcessor的建立後觀察它的位置:

這個類的繼承關係中存在Ordered介面,也就是說我們也實現一個Ordered,並且優先順序比它高,或者直接實現 PriorityOrdered 就好了。

準備以下程式碼:

/**
 * 這個註解只是標註一下要切入的類,介面或註解都可以
 * @author landscape
 * @date 2021-06-26
 */
public @interface BusinessAnnotation {
}

/**
 * @author landscape
 * @date 2021-06-26
 */
@Aspect
@Component
public class BusinessAspect {
	
    @Around("@within(com.landscape.demo.component.BusinessAnnotation)")
    public Object monitor(ProceedingJoinPoint joinPoint) {
        System.out.println("\n————————————————————————————————————————————————————");
        System.out.println(joinPoint.getTarget().getClass().getSimpleName() + ": 打工人開始工作");
        try {
            Object proceed = joinPoint.proceed();
            System.out.println(joinPoint.getTarget().getClass().getSimpleName() + ": 打工人結束工作");
            System.out.println("————————————————————————————————————————————————————");
            return proceed;
        } catch (Throwable throwable) {
            throwable.printStackTrace();
        }
        return null;
    }

}
//準備兩個打工人:
@Component
@BusinessAnnotation
public class Worker1 {

    public void work() {
        System.out.println("Worker1 working...");
    }
}

@Component
@BusinessAnnotation
public class Worker2 {

    public void work() {
        System.out.println("Worker2 working...");
    }
}

//準備兩個資本家:
@Component
public class Manager1 implements BeanPostProcessor, Ordered {
    @Autowired
    private Worker1 worker1;

    @Override
    public int getOrder() {
        //只要排在 AspectJAwareAdvisorAutoProxyCreator 之前就好了,設多少無所謂
        return Ordered.LOWEST_PRECEDENCE - 1;
    }
}

@Component
public class Manager2 implements BeanPostProcessor {
    
    @Autowired
    private Worker2 worker2;
    
}


//程式碼部分就完成啦!:-D

畫圖解釋一下上面的程式碼:

上面的程式碼共有三種角色:

  • Aspect,監視者切面
  • Manager,實現了BeanPostProcessor,內部依賴Worker
  • Worker,被切面增強

但是應該很快就能發現,圖中Manager1的優先順序比 AOP實現類的優先順序更高,而Manager1的初始化將導致 Worker1的例項化(原本Worker不應該在這個階段例項化),所以Worker1根本就不可能被切面監控。相對後面的Manager2和Worker2,他們例項化的時候已經存在AOP處理類了,所以可以被AOP切面監控。

執行容器程式碼:

@SpringBootApplication
public class SpringBootContainer {
    
    public static void main(String[] args) {
        ConfigurableApplicationContext context = SpringApplication.run(SpringBootContainer.class, args);
        context.getBean(Worker1.class).work();
        context.getBean(Worker2.class).work();
    }

}


  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::       (v2.3.10.RELEASE)

2021-06-26 15:30:29.750  INFO 13764 --- [           main] com.landscape.demo.SpringBootContainer   : No active profile set, falling back to default profiles: default
2021-06-26 15:30:30.164  INFO 13764 --- [           main] trationDelegate$BeanPostProcessorChecker : Bean 'worker1' of type [com.landscape.demo.component.Worker1] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
2021-06-26 15:30:30.225  INFO 13764 --- [           main] trationDelegate$BeanPostProcessorChecker : Bean 'worker2' of type [com.landscape.demo.component.Worker2] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
2021-06-26 15:30:30.647  INFO 13764 --- [           main] com.landscape.demo.SpringBootContainer   : Started SpringBootContainer in 1.14 seconds (JVM running for 2.145)
Worker1 working...

————————————————————————————————————————————————————
Worker2: 打工人開始工作
Worker2 working...
Worker2: 打工人結束工作
————————————————————————————————————————————————————

Process finished with exit code 0

Bean 'worker1' of type [com.landscape.demo.component.Worker1] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)

可以看到Worker1並沒有被切面切入,而Worker2的執行方法則成功的被切面增強。日誌中的這兩行也很好的說明了這種情況。

2. BeanFactoryPostProcessor

上一章的BeanPostProcessor是針對容器執行過程中例項化的Bean進行處理操作的擴充套件元件,而本章的BeanFactoryPostProcessor顧名思義,是對BeanFactory進行處理操作的元件。

BeanFactoryPostProcessor操作bean配置元資料。也就是說,SpringIoC容器允許BeanFactoryPostProcessor讀取配置元資料並可能對其進行更改以前容器例項化除BeanFactoryPostProcessor例項。

BeanFactoryPostProcessor例項的作用域為每個容器。這隻有在使用容器層次結構時才相關。如果您在一個容器中定義了BeanFactoryPostProcessor,那麼它只應用於該容器中的bean定義。一個容器中的Bean定義不會被另一個容器中的BeanFactoryPostProcessor例項進行後處理,即使兩個容器都屬於相同的層次結構。

在Spring框架中BeanFactoryPostProcessor的子介面只有一個(這裡不包含其他擴充套件框架,只針對Spring Framework 原始碼):

這裡以 ConfigurationClassPostProcessor為例子來幫助理解BeanFactoryPostProcessor介面。從它的實現關係上大致上就可以推測出它的特性、例項化時機、呼叫時機等資訊:

  1. 實現了 BeanFactoryPostProcessor,所以它可以對BeanFactory進行元資料配置
  2. 實現了 BeanDefinitionRegistryPostProcessor,用來對BeanDefinitionRegistry 做配置。
  3. 實現了 PriorityOrdered,在處理順序上較為優先。

按照執行順序來看,先看 ConfigurationClassPostProcessor 這個類對於 BeanDefinitionRegistryPostProcessor 介面的實現:

//org.springframework.context.annotation.ConfigurationClassPostProcessor#postProcessBeanDefinitionRegistry
/**
 * 從註冊中心中的配置類派生進一步的bean定義。
 * Derive further bean definitions from the configuration classes in the registry.
 */
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
    int registryId = System.identityHashCode(registry);
    if (this.registriesPostProcessed.contains(registryId)) {
        throw new IllegalStateException(
            "postProcessBeanDefinitionRegistry already called on this post-processor against " + registry);
    }
    if (this.factoriesPostProcessed.contains(registryId)) {
        throw new IllegalStateException(
            "postProcessBeanFactory already called on this post-processor against " + registry);
    }
    this.registriesPostProcessed.add(registryId);
	//對於 BeanDefinitionRegistryPostProcessor 介面的實現其實重點是下面呼叫的方法
    processConfigBeanDefinitions(registry);
}

//org.springframework.context.annotation.ConfigurationClassPostProcessor#processConfigBeanDefinitions
/**
 * Build and validate a configuration model based on the registry of
 * {@link Configuration} classes.
 */
public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
	//省略一段尋找候選的配置類、校驗、排序的過程

    // Parse each @Configuration class
    ConfigurationClassParser parser = new ConfigurationClassParser(
        this.metadataReaderFactory, this.problemReporter, this.environment,
        this.resourceLoader, this.componentScanBeanNameGenerator, registry);

    Set<BeanDefinitionHolder> candidates = new LinkedHashSet<>(configCandidates);
    Set<ConfigurationClass> alreadyParsed = new HashSet<>(configCandidates.size());

    do {
        //這裡的parse步驟做的事情非常多,處理了一個配置類中可能出現的配置元資料,例如@Import、@ComponentScan、內部配置類等很多事情
    	//但主題是BeanFactoryPostProcessor,這裡不做過多解釋
        parser.parse(candidates);
        parser.validate();

        Set<ConfigurationClass> configClasses = new LinkedHashSet<>(parser.getConfigurationClasses());
        configClasses.removeAll(alreadyParsed);

        // Read the model and create bean definitions based on its content
        if (this.reader == null) {
            this.reader = new ConfigurationClassBeanDefinitionReader(
                registry, this.sourceExtractor, this.resourceLoader, this.environment,
                this.importBeanNameGenerator, parser.getImportRegistry());
        }
        //這一步也非常重要,以眾多配置類為起點,載入路徑中所有的BeanDefinition。所以如果直接走過這一步會發現
        //BeanFactory中的 BeanDefinitionMap 中多了很多Bean,是 SpringBoot 非常重要的載入步驟
        this.reader.loadBeanDefinitions(configClasses);
        alreadyParsed.addAll(configClasses);

        candidates.clear();
        if (registry.getBeanDefinitionCount() > candidateNames.length) {
            String[] newCandidateNames = registry.getBeanDefinitionNames();
            Set<String> oldCandidateNames = new HashSet<>(Arrays.asList(candidateNames));
            Set<String> alreadyParsedClasses = new HashSet<>();
            for (ConfigurationClass configurationClass : alreadyParsed) {
                alreadyParsedClasses.add(configurationClass.getMetadata().getClassName());
            }
            for (String candidateName : newCandidateNames) {
                if (!oldCandidateNames.contains(candidateName)) {
                    BeanDefinition bd = registry.getBeanDefinition(candidateName);
                    if (ConfigurationClassUtils.checkConfigurationClassCandidate(bd, this.metadataReaderFactory) &&
                        !alreadyParsedClasses.contains(bd.getBeanClassName())) {
                        candidates.add(new BeanDefinitionHolder(bd, candidateName));
                    }
                }
            }
            candidateNames = newCandidateNames;
        }
    }
    while (!candidates.isEmpty());
	// 將ImportRegistry註冊為bean,以支援ImportAware @Configuration類
    // Register the ImportRegistry as a bean in order to support ImportAware @Configuration classes
    if (sbr != null && !sbr.containsSingleton(IMPORT_REGISTRY_BEAN_NAME)) {
        sbr.registerSingleton(IMPORT_REGISTRY_BEAN_NAME, parser.getImportRegistry());
    }

    if (this.metadataReaderFactory instanceof CachingMetadataReaderFactory) {
        // Clear cache in externally provided MetadataReaderFactory; this is a no-op
        // for a shared cache since it'll be cleared by the ApplicationContext.
        ((CachingMetadataReaderFactory) this.metadataReaderFactory).clearCache();
    }
}

以上核心邏輯已經新增到程式碼註釋中,省略了很多細節,從對BeanDefinitionRegistryPostProcessor實現的角度來看,只需要感受到它對BeanDefinitionRegistry的改動即可,也就是我們通過@Component、@Bean等方式定義的Bean都已經被讀入到容器中。

下面再來看ConfigurationClassPostProcessor 對於BeanFactoryPostProcessor 的實現部分:

//org.springframework.context.annotation.ConfigurationClassPostProcessor#postProcessBeanFactory
/**
 * 用cglib增強的子類替換Configuration類,以便在執行時為bean請求提供服務。
 * Prepare the Configuration classes for servicing bean requests at runtime
 * by replacing them with CGLIB-enhanced subclasses.
 */
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
    int factoryId = System.identityHashCode(beanFactory);
    if (this.factoriesPostProcessed.contains(factoryId)) {
        throw new IllegalStateException(
            "postProcessBeanFactory already called on this post-processor against " + beanFactory);
    }
    this.factoriesPostProcessed.add(factoryId);
    if (!this.registriesPostProcessed.contains(factoryId)) {
        // BeanDefinitionRegistryPostProcessor hook apparently not supported...
        // Simply call processConfigurationClasses lazily at this point then.
        processConfigBeanDefinitions((BeanDefinitionRegistry) beanFactory);
    }
    //這個實現裡最重要的部分在下面這行方法呼叫,也就是增強配置類
    enhanceConfigurationClasses(beanFactory);
    beanFactory.addBeanPostProcessor(new ImportAwareBeanPostProcessor(beanFactory));
}


增強的邏輯這裡就不貼程式碼了(數量很多),簡單的概括就是將@Bean註解標註的方法進行一次代理,只有真正需要構造Bean的時候才實際的呼叫方法,而後面的呼叫都將通過BeanName從BeanFactory中獲取。

由於主題是 BeanFactoryPostProcessor 而不是增強的邏輯所以不做過多解析,後面可能專門加一篇文章來解析這方面的邏輯,大致的內容可參考ConfigurationClassEnhancer 及幾個內部類的註釋。

從對 BeanFactoryPostProcessor 的實現的角度來看,只需要注意到相關的配置類成功的被修改了元資料,例項換成了被 CGLIB 增強的子類即可。

3. FactoryBean

摘抄一下來自Spring文件的翻譯:

FactoryBean介面是一個可插入到Spring IoC容器的例項化邏輯的點。如果您有複雜的初始化程式碼,可以用Java更好地表達,而不是(可能)冗長的XML,那麼您可以建立自己的FactoryBean,在該類中編寫複雜的初始化,然後將自定義的FactoryBean插入到容器中。

  • T getObject(): 返回該工廠建立的物件的一個例項。該例項可能被共享,這取決於該工廠返回的是單例還是原型。
  • boolean isSingleton(): 如果FactoryBean返回單例,則返回true,否則返回false。該方法的預設實現返回true。
  • Class <?> getObjectType() : 返回getObject()方法返回的物件型別,如果事先不知道該型別,則返回null。

另外,如果想要獲取FactoryBean本身,則需要在BeanName前面加上“&”,來自文件的翻譯:

當您需要向容器請求一個實際的FactoryBean例項本身,而不是它生成的bean時,在呼叫ApplicationContext的getBean()方法時,在bean的id前面加上&符號。因此,對於一個id為myBean的給定FactoryBean,在容器上呼叫getBean("myBean")將返回FactoryBean的產品,而呼叫getBean("&myBean")將返回FactoryBean例項本身。

在Spring的擴充套件點中,FactoryBean是一個相對簡單的概念,下面是一個簡單的小Demo,同時跟進原始碼加深理解:

public class Person {
    @Min(1)
    @NotNull
    private Integer id = 1;

    private String name ;

    private String address ;

    private LocalDateTime birthday;
    //省略Getter\Setter等方法
}

@Component
public class PersonFactory implements FactoryBean<Person> {
    @Override
    public boolean isSingleton() {
        return true;
    }

    @Override
    public Person getObject() throws Exception {
        Person person = new Person();
        person.setId(1)
                .setName("abc")
                .setAddress("南京")
                .setBirthday(LocalDateTime.now())
        ;
        return person;
    }

    @Override
    public Class<?> getObjectType() {
        return Person.class;
    }
}

執行啟動程式碼:

@SpringBootApplication
public class SpringBootContainer {

    public static void main(String[] args) {
        ConfigurableApplicationContext context = SpringApplication.run(SpringBootContainer.class, args);
        context.getBean(Person.class);
    }

}

Debug斷點打到Spring開始初始化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()) {
                //1. 因為我們定義的是FactoryBean,所以會進入到這個分支
				if (isFactoryBean(beanName)) {
                    //2. 這裡的getBean例項化的是工廠本身,也就是 PersonFactory,而不是目標物件Person
					Object bean = getBean(FACTORY_BEAN_PREFIX + beanName);
					if (bean instanceof FactoryBean) {
						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());
						}
                        //3. 這裡如果實現的是 SmartFactoryBean 且需要提前初始化目標物件才會進入分支
						if (isEagerInit) {
							getBean(beanName);
						}
					}
				}
				else {
					getBean(beanName);
				}
			}
		}

		// Trigger post-initialization callback for all applicable beans...
		//省略了一些後置處理器的觸發程式碼
	}

到容器初始化完成,進行例項化的也只是PersonFactory而已,而真正使FactoryBean開始例項化目標物件則是實際需要目標物件時,跟著原始碼可以走到下面這段核心程式碼:

//org.springframework.beans.factory.support.FactoryBeanRegistrySupport#getObjectFromFactoryBean
protected Object getObjectFromFactoryBean(FactoryBean<?> factory, String beanName, boolean shouldPostProcess) {
    //如果factory管理的物件是單例且beanName已經在該BeanFactory的單例物件的快取Map集合DefaultListableBeanFactory.singletonObjects中
    if (factory.isSingleton() && containsSingleton(beanName)) {
		//獲取執行緒互斥鎖定物件
        synchronized (getSingletonMutex()) {
            //如果是被建立過的物件則不會重複建立而是從快取中獲取
            Object object = this.factoryBeanObjectCache.get(beanName);
            if (object == null) {
                //這裡呼叫了實際的getObject方法,裡面的邏輯很簡單,除了一些許可權驗證和異常處理就是實際呼叫getObject
                //所以不貼跟緊程式碼了
                object = doGetObjectFromFactoryBean(factory, beanName);
                // Only post-process and store if not put there already during getObject() call above
                // (e.g. because of circular reference processing triggered by custom getBean calls)
                Object alreadyThere = this.factoryBeanObjectCache.get(beanName);
                if (alreadyThere != null) {
                    object = alreadyThere;
                }
                else {
                    if (shouldPostProcess) {
                        if (isSingletonCurrentlyInCreation(beanName)) {
                            // Temporarily return non-post-processed object, not storing it yet..
                            return object;
                        }
                        //模版回撥方法
                        beforeSingletonCreation(beanName);
                        try {
                            //這裡呼叫了BeanPostProcessor對目標物件進行處理
                            object = postProcessObjectFromFactoryBean(object, beanName);
                        }
                        catch (Throwable ex) {
                            throw new BeanCreationException(beanName,
                                                            "Post-processing of FactoryBean's singleton object failed", ex);
                        }
                        finally {
                            //模版回撥方法
                            afterSingletonCreation(beanName);
                        }
                    }
                    if (containsSingleton(beanName)) {
                        //單例物件放到快取裡去
                        this.factoryBeanObjectCache.put(beanName, object);
                    }
                }
            }
            return object;
        }
    }
    else {
        Object object = doGetObjectFromFactoryBean(factory, beanName);
        if (shouldPostProcess) {
            try {
                object = postProcessObjectFromFactoryBean(object, beanName);
            }
            catch (Throwable ex) {
                throw new BeanCreationException(beanName, "Post-processing of FactoryBean's object failed", ex);
            }
        }
        return object;
    }
}

到此為止,FactoryBean本身的例項化、目標物件的例項化流程就走完了。

希望這次對Spring知識點中擴充套件點的整理可以對自己和讀到這裡的同學有一點幫助。