美文网首页
【每天学点Spring】BeanFactoryPostProce

【每天学点Spring】BeanFactoryPostProce

作者: 伊丽莎白2015 | 来源:发表于2022-12-10 21:13 被阅读0次

    【本文内容】

    • 介绍了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类了。

    相关文章

      网友评论

          本文标题:【每天学点Spring】BeanFactoryPostProce

          本文链接:https://www.haomeiwen.com/subject/xhwzfdtx.html