Spring框架擴展點詳解(BeanPostProcessor等)


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

1. BeanPostProcessor

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

ApplicationContext可以在其 beanDefinitions 中自動檢測框架中預置和我們自行擴展的BeanPostProcessor,並將這些后處理器應用於隨后創建的任何 bean。
在ApplicationContext中自動檢測的BeanPostProcessor bean 將根據PriorityOrdered和Ordered語義進行排序。 相比之下,以編程方式注冊到BeanFactory BeanPostProcessor bean 將按注冊順序應用; 對於以編程方式注冊的后處理器,通過實現PriorityOrdered或Ordered接口表達的任何排序語義都將被忽略。

所謂的編程方式就是說通過手動調用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知識點中擴展點的整理可以對自己和讀到這里的同學有一點幫助。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM