【每天学点Spring】BeanFactoryPostProcessor介绍以及场景运用

【本文内容】

  • 介绍了BeanFactoryPostProcessor
  • BeanFactoryPostProcessor在Bean生命周期中何时被调用。
  • BeanFactoryPostProcessor在Spring中的运用场景:类PropertyResourceConfigurer和类ServletComponentRegisteringPostProcessor
  • 自定义场景:【启动时打印bean的一些信息】,【在启动的时候把Spring的某个原生Bean,替换为自己的Bean。】

1. BeanFactoryPostProcessor 介绍

官方文档:https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/beans/factory/config/BeanFactoryPostProcessor.html

工厂级别的勾子,用来允许修改application context中的bean的definition,作用于bean实例化前。

这个接口就一个方法:

public interface BeanFactoryPostProcessor {

    void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException;

}

2. 何时被调用

参考:https://stackoverflow.com/questions/30455536/beanfactorypostprocessor-and-beanpostprocessor-in-lifecycle-events

从图中可以看到实现了接口BeanFactoryPostProcessor的类会在Bean实例化以及bean内部的一些依赖注入之前被调用

即在bean定义被加载后,就开始BeanFactoryPostProcessor的调用了(毕竟这个接口主要就是为了修改bean的definition,如果在bean实例化后被调用,那么修改的definition就没有意义了!)。

书:《Spring 5 Design Patterns》

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_PRECEDENCEInteger.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. 统计时间。

参考:https://www.folkstalk.com/tech/overriding-transaction-propagation-levels-for-methods-having-springs-transactional-with-code-solutions/

那么,可以自定义一个类(比如叫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类了。

©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容