记一种 spring 框架的想当然但错误的用法

用户头像
小明同学
关注
发布于: 2020 年 09 月 11 日

Talk is cheap. Show me the code. —— Torvalds, Linus (2000-08-25).



The Case



废话不多说,直接上代码:



代码示例



package autoinject;
public class BusinessService {
}



package autoinject;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
public class SampleBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
@Autowired
private BusinessService businessService;
public BusinessService getBusinessService() {
return businessService;
}
public void setBusinessService(BusinessService businessService) {
this.businessService = businessService;
}
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException {
}
}



<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">

<context:component-scan base-package="com.sogou.bizdev.iflow" use-default-filters="false">
<context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>

<bean class="autoinject.SampleBeanFactoryPostProcessor" id="sampleBeanFactoryPostProcessor"/>

<bean class="autoinject.BusinessService" id="businessService"/>

</beans>



package autoinject;
import org.junit.Assert;
import org.junit.Test;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class AutoInjectTest {
@Test
public void testAutoInject() {
ConfigurableApplicationContext appContext = new ClassPathXmlApplicationContext("classpath:autoinject.xml");
SampleBeanFactoryPostProcessor beanFactoryPostProcessor = (SampleBeanFactoryPostProcessor) appContext.getBean("sampleBeanFactoryPostProcessor");
Assert.assertNotNull(beanFactoryPostProcessor.getBusinessService());
}
}



示例代码到这里全部写完,大家猜测一下这个这个 test case 的结果会是什么?



Spring 容器加载过程



Spring 容器加载过程大体如下:



  1. 解析 xml 文件,获取 bean 定义

  2. bean 定义的后置处理

  3. bean 实例化的后置处理



在解析 xml 文件注册 BeanDefinition 的阶段,标签 context:component-scan 的使用有两个作用:

1. 扫描指定路径下被 @Component, @Repository, @Service, and @Controller 注解的类,注册对应的 BeanDefinition

2. 通过将 @Required, @Autowired, @PostConstruct, @PreDestroy, @Resource, @PersistenceContext and @PersistenceUnit 注解对应的 BeanPostProcessor 实现类的 BeanDefinition 注册到 BeanDefinitionRegistry (之后过程会实例化这些 BeanPostProcessor),激活注解功能。



在 bean 定义的后置处理过程,主要是实例化 BeanFactoryPostProcessor 的实现类,并调用其中的 postProcessBeanFactory 方法,在实例化 BeanFactoryPostProcessor 的过程中,如果其存在依赖,也会遍历实例化其依赖,这和其他 bean 的实例化过程没有区别。

现在重点关注下被 @Autowired@Resource 注解的属性的自动注入问题,这部分逻辑请查阅 关键代码 ,即获取当前 getBeanPostProcessorCache().instantiationAware 已经注册的 BeanPostProcessorAutowiredAnnotationBeanPostProcessorCommonAnnotationBeanPostProcessor 分别处理 @Autowired@Resource 注解的自动注入问题,都实现了 InstantiationAwareBeanPostProcessor 接口,先后执行 postProcessPropertiespostProcessPropertyValues 获取需要注入的属性和要注入的值信息等。



回过头来看,getBeanPostProcessorCache().instantiationAware 中的 BeanPostProcessor是什么时候注入进入的呢?查看 getBeanPostProcessors() 源码逻辑 ,是获取 beanPostProcessors 集合中成员,那何时往 beanPostProcessors 添加新的元素呢?存在两个入口 addBeanPostProcessoraddBeanPostProcessors ,在 源码 处调用,往上层看最开始在 源码 处开始注册 BeanPostProcessor 的逻辑。



在整体看下顺序,发现 BeanFactoryPostProcessor 实例化(invokeBeanFactoryPostProcessors)时, BeanPostProcessor 还没有注册(registerBeanPostProcessors),因此找不到 AutowiredAnnotationBeanPostProcessor 去处理 sampleBeanFactoryPostProcessor 中被 @Autowired 注解的属性,导致 businessService 属性无法注入。



@Override
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
// Prepare this context for refreshing.
prepareRefresh();
// Tell the subclass to refresh the internal bean factory.
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
// Prepare the bean factory for use in this context.
prepareBeanFactory(beanFactory);
try {
// Allows post-processing of the bean factory in context subclasses.
postProcessBeanFactory(beanFactory);
// Invoke factory processors registered as beans in the context.
invokeBeanFactoryPostProcessors(beanFactory);
// Register bean processors that intercept bean creation.
registerBeanPostProcessors(beanFactory);
// Initialize message source for this context.
initMessageSource();
// Initialize event multicaster for this context.
initApplicationEventMulticaster();
// Initialize other special beans in specific context subclasses.
onRefresh();
// Check for listener beans and register them.
registerListeners();
// Instantiate all remaining (non-lazy-init) singletons.
finishBeanFactoryInitialization(beanFactory);
// Last step: publish corresponding event.
finishRefresh();
}
catch (BeansException ex) {
if (logger.isWarnEnabled()) {
logger.warn("Exception encountered during context initialization - " +
"cancelling refresh attempt: " + ex);
}
// Destroy already created singletons to avoid dangling resources.
destroyBeans();
// Reset 'active' flag.
cancelRefresh(ex);
// Propagate exception to caller.
throw ex;
}
finally {
// Reset common introspection caches in Spring's core, since we
// might not ever need metadata for singleton beans anymore...
resetCommonCaches();
}
}
}



写到最后



最后,我们再看下最开始抛出的那个问题的答案,当然是注入失败,断言不通过。



最初发现这个问题是,感觉这个是不是 Spring 的 bug,但是想到这么明显的问题,Spring 应该不会出现这种低级的犯错,于是在 Github 上 创建了 issue,得到了 Spring 开发者的回复:



This is by design: BeanFactoryPostProcessor is a very low-level callback and comes before any annotation processing (which is implemented in subsequent BeanPostProcessor callbacks). Either wire up your post-processor explicitly (e.g. using <property>) or use programmatic BeanFactory.getBean calls within the post-processor implementation. Alternatively, consider putting your business-dependent logic into some other location; BeanFactoryPostProcessor is really meant to be an infrastructure callback.



FWIW, a corresponding note can be found in our core beans chapter in the reference documentation.



大意是:有意设计成这样的,BeanFactoryPostProcessor 作为一种非常基础组件,在处理注解的逻辑之前发挥作用。如果我们一定需要在 BeanFactoryPostProcessor 中注入依赖,可以通过在 xml 显示配置 <property></property> 的方式 或 BeanFactory.getBean 获取依赖的 bean 实例。



原 issue 地址贴这里:framework#25754



发布于: 2020 年 09 月 11 日 阅读数: 61
用户头像

小明同学

关注

https://github.com/liuming-dev 2020.08.27 加入

开源爱好者,关注中间件和云原生技术~

评论

发布
暂无评论
记一种spring框架的想当然但错误的用法