【本文内容】
- 介绍了
BeanFactoryPostProcessor
。 -
BeanFactoryPostProcessor
在Bean生命周期中何时被调用。 -
BeanFactoryPostProcessor
在Spring中的运用场景:类PropertyResourceConfigurer
和类ServletComponentRegisteringPostProcessor
- 自定义场景:【启动时打印bean的一些信息】,【在启动的时候把Spring的某个原生Bean,替换为自己的Bean。】
1. BeanFactoryPostProcessor
介绍
工厂级别的勾子,用来允许修改application context中的bean的definition,作用于bean实例化前。
这个接口就一个方法:
public interface BeanFactoryPostProcessor {
void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException;
}
2. 何时被调用
从图中可以看到实现了接口BeanFactoryPostProcessor
的类会在Bean实例化以及bean内部的一些依赖注入之前被调用。
即在bean定义被加载后,就开始BeanFactoryPostProcessor
的调用了(毕竟这个接口主要就是为了修改bean的definition,如果在bean实例化后被调用,那么修改的definition就没有意义了!)。
3. 在Spring中的运用场景
3.1 抽象类 PropertyResourceConfigurer
(官方文档)
这个抽象类主要是为了让bean的property values可以从配置文件中读取。它有两个实现类:
-
PropertyOverrideConfigurer
:比如配置为"dataSource.driverClassName=com.mysql.jdbc.Driver",这个类负责将这个value从配置文件中(比如叫datasource.properties)推到相应的bean定义(bean definition)中。 -
PropertyPlaceholderConfigurer
,这个类可以将代码中定义的"${...}"替换为配置文件中的实际的值。(注:这个类在5.2版本后被淘汰了,后续使用的是PropertySourcesPlaceholderConfigurer
。
具体来看抽象类PropertyResourceConfigurer
的源码:
- 可以看到首先它实现了Ordered接口(这是必须的),因为需要控制各个BeanFactoryPostProcessor的执行顺序。而
Ordered.LOWEST_PRECEDENCE
即Integer.MAX_VALUE
,表示顺序无限延后,也就是最后执行。 - 另外这个抽象类实现了
BeanFactoryPostProcessor
接口,所以需要实现postProcessBeanFactory
方法。可以看到在这个方法里主要做的就是读取properties文件,做一些必要的convert,并process这些properties到bean Definition中。
public abstract class PropertyResourceConfigurer extends PropertiesLoaderSupport
implements BeanFactoryPostProcessor, PriorityOrdered {
private int order = Ordered.LOWEST_PRECEDENCE; // default: same as non-Ordered
// 略:serOrder / getOrder
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
try {
Properties mergedProps = mergeProperties();
// Convert the merged properties, if necessary.
convertProperties(mergedProps);
// Let the subclass process the properties.
processProperties(beanFactory, mergedProps);
}
catch (IOException ex) {
throw new BeanInitializationException("Could not load properties", ex);
}
}
}
具体看如何process到beanDefinition中:
在实现类PropertyOverrideConfigurer
中有个方法:可以看到从factory中拿到beanDefinition后,将通过读取到的property/value,组合成新的PropertyValue对象,放回BeanDefinition中。
这就是整个BeanFactoryPostProcessor接口修改BeanDefinition的过程。
protected void applyPropertyValue(
ConfigurableListableBeanFactory factory, String beanName, String property, String value) {
BeanDefinition bd = factory.getBeanDefinition(beanName);
BeanDefinition bdToUse = bd;
while (bd != null) {
bdToUse = bd;
bd = bd.getOriginatingBeanDefinition();
}
PropertyValue pv = new PropertyValue(property, value);
pv.setOptional(this.ignoreInvalidKeys);
bdToUse.getPropertyValues().addPropertyValue(pv);
}
3.2 ServletComponentRegisteringPostProcessor
(源码)
ServletComponentRegisteringPostProcessor
位于是Spring Boot中的类,主要的作用是配置注解@ServletComponentScan
,可以扫描出标注在类上的@WebServlet
、@WebFilter
以及@WebListener
,并进行解析。
- 可以看到
ServletComponentRegisteringPostProcessor
实现了BeanFactoryPostProcessor
接口,并实现了postProcessBeanFactory
方法。 - 在
postProcessBeanFactory
中,主要是基于@ServletComponentScan
注解的包,进行扫描。 -
scanPackage()
方法即拿到上述介绍的被标记的三个注解(@WebServlet
、@WebFilter
以及@WebListener
)的类,然后调用各自的handler(三个注解的handler不一样)的handle方法,handle方法下文有介绍。
class ServletComponentRegisteringPostProcessor implements BeanFactoryPostProcessor, ApplicationContextAware {
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
if (isRunningInEmbeddedWebServer()) {
ClassPathScanningCandidateComponentProvider componentProvider = createComponentProvider();
for (String packageToScan : this.packagesToScan) {
scanPackage(componentProvider, packageToScan);
}
}
}
private void scanPackage(ClassPathScanningCandidateComponentProvider componentProvider, String packageToScan) {
for (BeanDefinition candidate : componentProvider.findCandidateComponents(packageToScan)) {
if (candidate instanceof AnnotatedBeanDefinition) {
for (ServletComponentHandler handler : HANDLERS) {
handler.handle(((AnnotatedBeanDefinition) candidate),
(BeanDefinitionRegistry) this.applicationContext);
}
}
}
}
}
下述的源码来自处理@WebServlet
注解的handler,类WebServletHandler
:
看到ServletRegistrationBean
是不是很熟悉?因为在Spring中我们通常定义一个Serlvet,可以在Configuration中加个@Bean,然后手动new出ServletRegistrationBean
,所以的doHandle也在做这件事。
@Override
public void doHandle(Map<String, Object> attributes, AnnotatedBeanDefinition beanDefinition,
BeanDefinitionRegistry registry) {
BeanDefinitionBuilder builder = BeanDefinitionBuilder.rootBeanDefinition(ServletRegistrationBean.class);
builder.addPropertyValue("asyncSupported", attributes.get("asyncSupported"));
builder.addPropertyValue("initParameters", extractInitParameters(attributes));
builder.addPropertyValue("loadOnStartup", attributes.get("loadOnStartup"));
String name = determineName(attributes, beanDefinition);
builder.addPropertyValue("name", name);
builder.addPropertyValue("servlet", beanDefinition);
builder.addPropertyValue("urlMappings", extractUrlPatterns(attributes));
builder.addPropertyValue("multipartConfig", determineMultipartConfig(beanDefinition));
registry.registerBeanDefinition(name, builder.getBeanDefinition());
}
【总结】
我们自定义的Servlet类,注入方式有两种:
- 第一种即Spring中定义@Bean:
@Bean
public ServletRegistrationBean myServlet(){
MyServlet myServlet = new MyServlet();
return new ServletRegistrationBean(myServlet,"/my","/my02");
}
- 第二种(适用Spring Boot),即在
MyServlet
类上加上注解@WebServlet
,并在启动Application类上加@ServletComponentScan
。
@WebServlet(urlPatterns = "/myUrl")
public class MyServlet extends HttpServlet {
}
而第二种方式,恰恰是通过BeanFactoryPostProcessor
接口来实现的!
4. 自定义场景
4.1 在Spring启动时打印以下信息:
- a. bean的个数。
- b. 所有bean类名中包含
auto
,打印前两个bean name。 - c. 实现了
JpaRepository
接口的bean的名字。
为此,我们定义了类PrintBeanFactoryPostProcessor
,在方法中通过beanFactory
就能拿到上述需要打印的信息。
@Component
public class PrintBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
int count = beanFactory.getBeanDefinitionCount();
System.out.println("Get " + count + " beans...");
System.out.println("--------------------------");
Arrays.stream(beanFactory.getBeanDefinitionNames())
.filter(name -> name.toLowerCase().contains("auto")).limit(2).forEach(System.out::println);
System.out.println("--------------------------");
Arrays.stream(beanFactory.getBeanNamesForType(JpaRepository.class)).forEach(System.out::println);
}
}
【打印的结果如下】
image.png
4.2 在启动的时候把Spring的某个原生Bean,替换为自己的Bean
比如在事务invoke之前和之后打印以下信息:a. 打印出何时开始何时结束。b. 统计时间。
那么,可以自定义一个类(比如叫MyTransactionInterceptor),然后继承TransactionInterceptor
,在我们自己的类中重写方法invoke(MethodInvocation invocation)
(比如前后打印,中间可以调用父类即真正的TransactionInterceptor
的方法:super.invoke(invocation)
)。
我们用以下代码来模拟整个替换Bean的过程:
- 类:Apple.java (代表Spring中的某个原生Bean,如
TransactionInterceptor
)。
@Component
public class Apple {
public String getName() {
return "apple";
}
}
我们写一个Controller取读getName()方法:
@RestController
@RequestMapping("apple")
public class AppleController {
@Autowired
private Apple apple;
@GetMapping
public String apple() {
return apple.getName();
}
}
结果:
image.png
由于Apple类是Spring自已的Bean(假设),那么我们希望用自定义的定去替换它。首先,新建一个Peach.java类:
public class Peach extends Apple {
public String getName() {
return "peach";
}
}
利用本文介绍的BeanFactoryPostProcessor
,在启动的时候,用Peach类替换掉原先的Apple类:
@Configuration
public class AppleFactoryPostProcessor implements BeanFactoryPostProcessor, PriorityOrdered {
@Override
public int getOrder() {
return Ordered.LOWEST_PRECEDENCE;
}
public void postProcessBeanFactory(@NonNull ConfigurableListableBeanFactory beanFactory) throws BeansException {
String[] beanNames = beanFactory.getBeanNamesForType(Apple.class);
for (String beanName : beanNames) {
BeanDefinition beanDefinition = beanFactory.getBeanDefinition(beanName);
beanDefinition.setBeanClassName(Peach.class.getName());
beanDefinition.setFactoryBeanName(null);
beanDefinition.setFactoryMethodName(null);
}
}
}
上述的Controller不变,继续访问:
image.png
可以看到Apple已经被替换成我们自己的Peach类了。
网友评论