【Spring源码】- 04 扩展点之BeanFactoryPostProcessor

211 阅读10分钟
原文链接: mp.weixin.qq.com

之前分析IoC容器的启动流程时,夹杂在启动流程中我们发现 Spring给我们提供了大量的扩展点,基于这些扩展点我们就可以实现很多灵活的功能定制需求。这篇我们首先来看下BeanFactoryPostProcessor这个扩展点,它是非常重要的一个扩展点,面向 IoC容器进行扩展。

类结构

BeanFactoryPostProcessorBeanFactory的后置处理器,针对BeanFactory实现各种功能扩展。 BeanFactoryPostProcessor又分为两类:BeanFactoryPostProcessorBeanDefinitionRegistryPostProcessorBeanDefinitionRegistryPostProcessor继承了 BeanFactoryPostProcessor接口,对其进行了扩展。

执行时机

前面分析IoC容器启动流程中, refresh()方法中invokeBeanFactoryPostProcessors(beanFactory)处触发 BeanFactoryPostProcessor扩展类的执行。

执行逻辑总结:	1、先执行BeanDefinitionRegistryPostProcessor#postProcessBeanDefinitionRegistry方法,其中BeanDefinitionRegistryPostProcessor执行优先级如下:		a、addBeanFactoryPostProcessor()传入到优先级最高,因为不需要实例化,直接可以获取到对象进行执行;	 	b、然后从IoC容器中获取PriorityOrdered接口的BeanDefinitionRegistryPostProcessor,实例化并排序后执行postProcessBeanDefinitionRegistry方法	 	c、然后从IoC容器中获取Ordered接口的BeanDefinitionRegistryPostProcessor,实例化并排序后执行postProcessBeanDefinitionRegistry方法	 	d、然后从IoC容器中获取剩余的BeanDefinitionRegistryPostProcessor,实例化后执行postProcessBeanDefinitionRegistry方法;注意这个处理步骤存在一个循环,主要是存在执行前面的BeanDefinitionRegistryPostProcessor#postProcessBeanDefinitionRegistry方法时,存在可能会向IoC容器中注册新的BeanDefinitionRegistryPostProcessor,通过循环保证都会被执行;	2、然后执行BeanDefinitionRegistryPostProcessor#postProcessBeanFactory方法,执行顺序参照步骤1中执行顺序;	3、最后才会执行BeanFactoryPostProcessor#postProcessBeanFactory,执行优先级和BeanDefinitionRegistryPostProcessor一致:	 	a、addBeanFactoryPostProcessor()传入到优先级最高,因为不需要实例化,直接可以获取到对象进行执行;	 	b、然后从IoC容器中获取PriorityOrdered接口的BeanFactoryPostProcessor,实例化并排序后执行postProcessBeanFactory方法	 	c、然后从IoC容器中获取Ordered接口的BeanFactoryPostProcessor,实例化并排序后执行postProcessBeanFactory方法	 	d、然后从IoC容器中获取剩余的BeanFactoryPostProcessor,实例化后执行postProcessBeanFactory方法

总结两句话:

  • 1、不同方法执行优先级:`BeanDefinitionRegistryPostProcessor#postProcessBeanDefinitionRegistry` > `BeanDefinitionRegistryPostProcessor#postProcessBeanFactory` > `BeanFactoryPostProcessor#postProcessBeanFactory`;
  • 2、同方法执行优先级:`addBeanFactoryPostProcessor` > `PriorityOrdered` > `Ordered` > 非排序

使用场景

这两个接口都比较简单,都只定义一个方法。首先看下BeanDefinitionRegistryPostProcessor#postProcessBeanDefinitionRegistry这个方法,主要使用场景是:向 IoC容器中注册BeanDefinition。这里面有个非常重要的实现类: ConfigurationClassPostProcessor,完成各种将Bean解析成 BeanDefinition注入到IoC容器功能。

BeanDefinitionRegistryPostProcessor

案例一

首先,我们来看个例子,自定义BeanDefinition方式注册到 IoC容器中。

1、定义一个Bean


                                public class TestService02 { 
                                    private TestService01 testService01; @Autowired private TestService03 testService03; 
                                        private String name;     //省略setter、toString方法}
                                            

2、由于该Bean没有 @Component等注解,所以我们可以定义一个BeanDefinitionRegistryPostProcessor扩展注册到 IoC容器中:

@Componentpublic class MyBeanFactoryPostProcessor implements BeanDefinitionRegistryPostProcessor { @Override public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {  System.out.println("===MyBeanFactoryPostProcessor#postProcessBeanDefinitionRegistry===");  //创建一个BeanDefinition  RootBeanDefinition beanDefinition = new RootBeanDefinition(TestService02.class);  //获取MutablePropertyValues,用于添加依赖注入  MutablePropertyValues pvs = beanDefinition.getPropertyValues();  //为依赖注入指定固定值方式  pvs.addPropertyValue("name", "张三");  //为依赖注入指定beanName,自动将beanName指向IoC中的Bean注入进去  pvs.addPropertyValue("testService01", new RuntimeBeanReference("testService01"));  //将生成的BeanDefinition注册到IoC容器中  registry.registerBeanDefinition("testService02", beanDefinition); } @Override public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {  System.out.println("===MyBeanFactoryPostProcessor#postProcessBeanFactory==="); }}

3、输出结果:

myConfigtestService01testService03myBeanFactoryPostProcessortestService02===TestService02属性信息===TestService02{testService01=xxx.bean.TestService01@56ef9176, testService03=xxx.bean.TestService03@4566e5bd, name='张三'}

从输出结果可以看到,TestService02已被注册到 IoC容器中,且其属性字段都已成功进行依赖注入。通过这种方式,我们就可以更加灵活的向IoC容器注册 Bean,实现各种功能的扩展。

案例二

我们可以模拟@ComponentScan@Component注解方式实现:将指定路径下的带有指定注解的Bean注册到IoC容器中。

1、首先,模仿@ComponentScan@Component定义两个注解:

@Target(ElementType.TYPE)@Retention(RetentionPolicy.RUNTIME)@Documented@Indexedpublic @interface MyComponent { String value() default "";}
@Retention(RetentionPolicy.RUNTIME)@Target(ElementType.TYPE)@Documentedpublic @interface MyComponentScan { @AliasFor("basePackages") String[] value() default {}; @AliasFor("value") String[] basePackages() default {}; Class<?>[] basePackageClasses() default {};}

2、定义一个BeanDefinitionRegistryPostProcessor实现类:


                                                @Componentpublic class 
                                                    MyBeanFactoryPostProcessor implements BeanDefinitionRegistryPostProcessor { 
                                                        private List<String> basePackageList = new ArrayList<>(); @Override 
                                                            public void postProcessBeanFactory
                                                                (ConfigurableListableBeanFactory beanFactory) throws BeansException {  System.out.println(
                                                                    "===MyBeanFactoryPostProcessor#postProcessBeanFactory==="); } @Override public 
                                                                        void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) 
                                                                            throws BeansException {  Arrays.stream(registry.getBeanDefinitionNames()).forEach(x -> parseMyComponentScan(registry.getBeanDefinition(x)));  
                                                                                if(basePackageList.isEmpty()){//没有扫描路径情况   return;  }  
                                                                                    //定义一个BeanDefinitionScanner,扫描指定路径下Bean  ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(registry, 
                                                                                        false);  scanner.addIncludeFilter(new AnnotationTypeFilter(MyComponent.class));  scanner.setIncludeAnnotationConfig(
                                                                                            false);  String[] basePackages = basePackageList.stream().toArray(String[]::new);  
                                                                                                int beanCount = scanner.scan(basePackages);  System.out.println("注册成功:"+beanCount); } 
                                                                                                    /**  * 遍历所有的BeanDefinition,然后解析出有@MyComponentScan注解信息,并提取basePackageClasses属性值,  * 解析成扫描路径存储到basePackageList集合中  * 
                                                                                                        @param beanDefinition  */ private 
                                                                                                            void parseMyComponentScan(BeanDefinition beanDefinition){  
                                                                                                                if(AnnotatedBeanDefinition.class.isAssignableFrom(beanDefinition.getClass())){   AnnotatedBeanDefinition bdf = (AnnotatedBeanDefinition) beanDefinition;   Class<?> beanClass;   
                                                                                                                    try {    beanClass = Class.forName(bdf.getBeanClassName());    AnnotationMetadata metadata = AnnotationMetadata.introspect(beanClass);    Map<String, Object> config = metadata.getAnnotationAttributes(MyComponentScan.class.getName());    
                                                                                                                        if(config != null && config.containsKey(
                                                                                                                            "basePackageClasses")){     Object obj = config.get("basePackageClasses");     Class clz = ((Class[]) obj)[
                                                                                                                                0];     basePackageList.add(clz.getPackage().getName());    }   } 
                                                                                                                                    catch (Throwable e) {    e.printStackTrace();   }  } }}
                                                                                                                                        

3、测试:

@Configuration@ComponentScan(basePackageClasses = MyConfig.class)@MyComponentScan(basePackageClasses = MyConfig.class)public class MyConfig {}

@ComponentScan可以把常规的 @Component注册到IoC容器中, @MyComponentScan可以把指定包路径下带有@MyComponent注解的 Bean注册到IoC容器中。

BeanFactoryPostProcessor

上面我们利用BeanDefinitionRegistryPostProcessor#postProcessBeanDefinitionRegistry扩展方法实现了 @MyComponentScan@MyComponent注解功能,实现将指定包路径下带有 @MyComponent注解的Bean注册到IoC容器中。 BeanDefinitionRegistryPostProcessor接口中还有一个继承父接口BeanFactoryPostProcessor中的方法: postProcessBeanFactory

上一个案例实现了@MyComponent注解的 Bean注册到IoC功能,现在我们来实现对所有 @MyComponent注解的类进行AOP增强。

1、定义一个增强类:

public class MyMethodInterceptor implements MethodInterceptor {    @Override    public Object intercept(Object obj, Method method, Object[] args, MethodProxy methodProxy)            throws Throwable {        try {            before(method);//前置通知            Object ret = methodProxy.invokeSuper(obj, args);//目标方法执行            after(method, ret);//后置通知            return ret;        } catch (Exception e) {            exception();//异常通知        } finally {            afterReturning();//方法返回通知        }        return null;    } //前置增强    private void before(Method method) {        System.out.printf("before execute:%s\r\n", method.getName());    } //后置增强    private void after(Method method, Object ret) {        System.out.printf("after execute:%s, ret:%s\r\n", method.getName(), ret);    } //异常增强    private void exception() {        System.out.println("execute failure");    } //after返回增强    private void afterReturning() {        System.out.println("execute finish");    }}

2、基于Spring Enhancer工具类,实现一个传入 Class,创建一个带有增强逻辑的Class返回出来工具类:


                                                                                                                                                                        public class 
                                                                                                                                                                            EnhancerUtil  { //定义增强实例 
                                                                                                                                                                                private static 
                                                                                                                                                                                    final Callback[] CALLBACKS = new Callback[] {   
                                                                                                                                                                                        new MyMethodInterceptor(),   NoOp.INSTANCE }; 
                                                                                                                                                                                            private static 
                                                                                                                                                                                                final Class[] CALLBACKTYPES = 
                                                                                                                                                                                                    new Class[] {   
                                                                                                                                                                                                        new MyMethodInterceptor().getClass(),   NoOp.INSTANCE.getClass() }; 
                                                                                                                                                                                                            public 
                                                                                                                                                                                                                static Class createProxyClass
                                                                                                                                                                                                                    (Class targetClz)
                                                                                                                                                                                                                        {  Enhancer enhancer =  
                                                                                                                                                                                                                        new Enhancer();  
                                                                                                                                                                                                                            //基于继承方式代理:设置代理类的父类,就是目标对象,创建出来的对象就是目标对象的子类  enhancer.setSuperclass(targetClz);  
                                                                                                                                                                                                                                //设置回调过滤器,返回值是callbacks数组的下标  enhancer.setCallbackFilter(
                                                                                                                                                                                                                                    new CallbackFilter() {   
                                                                                                                                                                                                                                        @Override   
                                                                                                                                                                                                                                            public 
                                                                                                                                                                                                                                                int 
                                                                                                                                                                                                                                                    accept
                                                                                                                                                                                                                                                        (Method method) {    
                                                                                                                                                                                                                                                            /**     * 屏蔽掉Object类中定义方法的拦截     */    
                                                                                                                                                                                                                                                                if (method.getDeclaringClass() == targetClz) {     
                                                                                                                                                                                                                                                                    return 
                                                                                                                                                                                                                                                                        0;    }    
                                                                                                                                                                                                                                                                            return 
                                                                                                                                                                                                                                                                                1;   }  });  
                                                                                                                                                                                                                                                                                    /**   * NoOp回调把对方法调用直接委派给这个方法在父类中的实现,即可理解为真实对象直接调用方法   */  enhancer.setCallbackTypes(CALLBACKTYPES);  
                                                                                                                                                                                                                                                                                        //设置类加载器  enhancer.setClassLoader(targetClz.getClassLoader());  
                                                                                                                                                                                                                                                                                            //创建代理对象  Class clz = enhancer.createClass();  Enhancer.registerStaticCallbacks(clz, CALLBACKS);  
                                                                                                                                                                                                                                                                                                return clz; }}
                                                                                                                                                                                                                                                                                                    

3、下面我们来实现 BeanFactoryPostProcessor#postProcessBeanFactory方法:

                                                                                                                                                                                                                                                                                                                
                                                                                                                                                                                                                                                                                                                    
                                                                                                                                                                                                                                                                                                                        @Component
                                                                                                                                                                                                                                                                                                                            public 
                                                                                                                                                                                                                                                                                                                                
                                                                                                                                                                                                                                                                                                                                    class 
                                                                                                                                                                                                                                                                                                                                        MyBeanFactoryPostProcessor 
                                                                                                                                                                                                                                                                                                                                            implements 
                                                                                                                                                                                                                                                                                                                                                BeanDefinitionRegistryPostProcessor { 
                                                                                                                                                                                                                                                                                                                                                    private List<String> basePackageList = 
                                                                                                                                                                                                                                                                                                                                                        new ArrayList<>(); 
                                                                                                                                                                                                                                                                                                                                                            /**  * 基于该扩展实现AOP增强  * 
                                                                                                                                                                                                                                                                                                                                                                @param beanFactory  * 
                                                                                                                                                                                                                                                                                                                                                                    @throws BeansException  */ 
                                                                                                                                                                                                                                                                                                                                                                        @Override 
                                                                                                                                                                                                                                                                                                                                                                            
                                                                                                                                                                                                                                                                                                                                                                                public 
                                                                                                                                                                                                                                                                                                                                                                                    void 
                                                                                                                                                                                                                                                                                                                                                                                        postProcessBeanFactory
                                                                                                                                                                                                                                                                                                                                                                                            (ConfigurableListableBeanFactory beanFactory) 
                                                                                                                                                                                                                                                                                                                                                                                                throws BeansException {        
                                                                                                                                                                                                                                                                                                                                                                                                    //遍历IoC中BeanDefinition  Arrays.stream(beanFactory.getBeanDefinitionNames())    .forEach(x -> enhancer(beanFactory.getBeanDefinition(x))); }    
                                                                                                                                                                                                                                                                                                                                                                                                        /**  * 判断beanDefinition是否有
                                                                                                                                                                                                                                                                                                                                                                                                            @MyComponent注解,如果有则调用EnhancerUtil.createProxyClass创建一个增强后的类,  * 并替换到BeanDefinition中的beanClass  * 
                                                                                                                                                                                                                                                                                                                                                                                                                @param beanDefinition  */ 
                                                                                                                                                                                                                                                                                                                                                                                                                    
                                                                                                                                                                                                                                                                                                                                                                                                                        private 
                                                                                                                                                                                                                                                                                                                                                                                                                            void 
                                                                                                                                                                                                                                                                                                                                                                                                                                enhancer
                                                                                                                                                                                                                                                                                                                                                                                                                                    (BeanDefinition beanDefinition)
                                                                                                                                                                                                                                                                                                                                                                                                                                        {  
                                                                                                                                                                                                                                                                                                                                                                                                                                        if(ScannedGenericBeanDefinition.class.isAssignableFrom(beanDefinition.getClass())){   ScannedGenericBeanDefinition bdf = (ScannedGenericBeanDefinition) beanDefinition;   
                                                                                                                                                                                                                                                                                                                                                                                                                                            try {    String beanClassName = bdf.getBeanClassName();    Class beanClass = Class.forName(beanClassName);    
                                                                                                                                                                                                                                                                                                                                                                                                                                                if (beanClass != 
                                                                                                                                                                                                                                                                                                                                                                                                                                                    null){     AnnotationMetadata metadata = AnnotationMetadata.introspect(beanClass);     
                                                                                                                                                                                                                                                                                                                                                                                                                                                        if(metadata.hasAnnotation(MyComponent.class.getName())){      Class enhancedClass = EnhancerUtil.createProxyClass(beanClass);      
                                                                                                                                                                                                                                                                                                                                                                                                                                                            if(enhancedClass != beanClass){       bdf.setBeanClass(enhancedClass);      }     }    }   } 
                                                                                                                                                                                                                                                                                                                                                                                                                                                                catch (Throwable e) {    e.printStackTrace();   }  } }}
                                                                                                                                                                                                                                                                                                                                                                                                                                                                    

4、定义Bean

                                                                                                                                                                                                                                                                                                                                                                                                                                                                            
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    @MyComponent
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        public 
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                class 
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    TestService02 { 
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            public String 
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                hi
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    (String name)
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        {  System.out.println(
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        "===TestService02#hi===");  
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            return 
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                "hello,"+name; }}
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    

5、 context.getBean(TestService02.class).testService02.hi("zhangsan")调用方法时,会发现方法前后都已被加入增强逻辑:

                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    before
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        execute:hi===TestService02#hi===after
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        execute:hi,
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        ret:hello,zhangsanexecute
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        finish返回结果:hello,zhangsan
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        

总结

通过使用场景案例分析,我们对 BeanFactoryPostProcessor扩展点的使用大致情况应该有了一些了解:

  • BeanDefinitionRegistryPostProcessor#postProcessBeanDefinitionRegistry这个扩展点主要用于实现向 IoC容器中注册 Bean相关的功能扩展。比如上面案例中我们九利用该扩展点实现了自定义扫描包路径下自定义注解,并向 IoC容器中注册 Bean
  • BeanFactoryPostProcessor#postProcessBeanFactory这个扩展点主要用于实现将注册到 IoC容器中BeanDefinition进行相关操作。以为这个方法执行时机是在 postProcessBeanDefinitionRegistry之后,这时Bean都已经以 BeanDefinition样式被注册到IoC容器中,这时就可以对这些 BeanDefinition进行操作。比如案例中我们获取到BeanDefinition中的 beanClass属性,并使用Enhancer工具类创建 AOP代理类并设置进去,就实现了Spring中的 AOP功能。