• Spring(六)- BeanFactoryPostProcessor 与 BeanPostProcessor 后处理器


    一、Spring 的后处理器

    Spring的后处理器是Spring对外开发的重要扩展点,允许我们介入到Bean的整个实例化流程中来,以达到动态注册BeanDefinition,动态修改BeanDefinition,以及动态修改Bean的作用。Spring主要有两种后处理器:
    ⚫ BeanFactoryPostProcessor:Bean工厂后处理器,在BeanDefinitionMap填充完毕,Bean实例化之前执行;
    BeanPostProcessor:Bean后处理器,一般在Bean实例化之后,填充到单例池singletonObjects之前执行。
    BeanFactoryPostProcessor只执行一次,是在所有BeanDefinition填充到BeanDefinitionMap时;
    BeanPostProcessor会执行多次,每个bean实例化之后都会执行;

    1. Bean工厂后处理器 – BeanFactoryPostProcessor

    BeanFactoryPostProcessor是一个接口规范,实现了该接口的类只要交由Spring容器管理的话,那么Spring就会回调该接口的方法,用于对BeanDefinition注册和修改的功能。

    BeanFactoryPostProcessor 定义如下:

    @FunctionalInterface
    public interface BeanFactoryPostProcessor {
    	void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory);
    }
    
    • 1
    • 2
    • 3
    • 4

    编写BeanFactoryPostProcessor实现类:

    public class MyBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
    	@Override
    	public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException{
    		System.out.println("BeanDefinitionMap填充完毕后回调该方法,MyBeanFactoryPostProcessor执行了...");
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    配置BeanFactoryPostProcessor实现类:

    <bean class="com.itheima.processor.MyBeanFactoryPostProcessor"/>
    
    • 1

    postProcessBeanFactory 参数本质就是 DefaultListableBeanFactory(DefaultListableBeanFactory是ConfigurableListableBeanFactory的实现类),拿到BeanFactory的引用,自然就可以对beanDefinitionMap中的BeanDefinition进行操作了 ,例如对UserDaoImpl的BeanDefinition进行修改操作

    public class MyBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
    	public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
    	// userDao初始的bean的定义,类型为UserDaoImpl
    	BeanDefinition userDaoBD = beanFactory.getBeanDefinition("userDao");//获得UserDao定义对象
    	// 修改userDao的类型为UserDaoImpl2
    	userDaoBD.setBeanClassName("com.itheima.dao.impl.UserDaoImpl2"); //修改class
    	// userDaoBD.setInitMethodName(methodName); //修改初始化方法
    	// userDaoBD.setLazyInit(true); //修改是否懒加载
    	// ... 省略其他的设置方式 ...
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    上面已经对指定的BeanDefinition进行了修改操作,下面对BeanDefiition进行注册操作

    // 自定义类MyBeanFactoryPostProcessor还需要注册到容器中
    public class MyBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
    	@Override
    	public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException {
    	// 强转成子类DefaultListableBeanFactory
    	if(configurableListableBeanFactory instanceof DefaultListableBeanFactory){
    			// DefaultListableBeanFactory有注册BeanDefinition的方法registerBeanDefinition,所以需要将configurableListableBeanFactory强转
    			DefaultListableBeanFactory beanFactory = (DefaultListableBeanFactory) configurableListableBeanFactory;
    			// 使用RootBeanDefinition对BeanDefinition进行操作
    			BeanDefinition beanDefinition = new RootBeanDefinition();
    			// 注册一个UserDaoImpl2类型的bean
    			beanDefinition.setBeanClassName("com.itheima.dao.UserDaoImpl2");
    			// 进行注册操作
    			beanFactory.registerBeanDefinition("userDao2", beanDefinition);
    		}
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    Spring 提供了一个BeanFactoryPostProcessor的子接口BeanDefinitionRegistryPostProcessor专门用于注册BeanDefinition操作

    // 自定义类MyBeanFactoryPostProcessor2还需要注册到容器中
    public class MyBeanFactoryPostProcessor2 implements BeanDefinitionRegistryPostProcessor {
    	// postProcessBeanFactory是顶级接口BeanFactoryPostProcessor中的方法,所以这里需要继承下来
    	@Override
    	public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException {}
    	@Override
    	public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry beanDefinitionRegistry) throws BeansException {
    		BeanDefinition beanDefinition = new RootBeanDefinition();
    		// 注册一个UserDaoImpl2类型的bean
    		beanDefinition.setBeanClassName("com.itheima.dao.UserDaoImpl2");
    		// 进行注册操作
    		beanDefinitionRegistry.registerBeanDefinition("userDao2", beanDefinition);
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    BeanFactoryPostProcessor 与 BeanDefinitionRegistryPostProcessor的执行顺序是,先调用子类接口BeanDefinitionRegistryPostProcessor中的postProcessBeanDefinitionRegistry、postProcessBeanFactory方法,再调用父类接口BeanFactoryPostProcessor中的postProcessBeanFactory方法

    BeanFactoryPostProcessor 在SpringBean的实例化过程中的体现:

    在这里插入图片描述

    (1)应用:使用Spring的BeanFactoryPostProcessor扩展点完成自定义注解扫描

    要求如下:
    ⚫ 自定义@MyComponent注解,使用在类上;
    ⚫ 使用包扫描器工具BaseClassScanUtils完成指定包的类扫描;
    ⚫ 自定义BeanFactoryPostProcessor完成注解@MyComponent的解析,解析后最终被Spring管理。

    自定义@MyComponent注解,使用在类上

    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    public @interface MyComponent {
    	// 显示的指定Bean的beanName
    	String value() default ""; 
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    在类上使用@MyComponent

    @MyComponent("otherBean")
    public class OtherBean {
    }
    
    • 1
    • 2
    • 3

    自定义BeanFactoryPostProcessor完成注解解析

    public class MyComponentBeanFactoryPostProcessor implements BeanDefinitionRegistryPostProcessor {
        @Override
        public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry beanDefinitionRegistry) throws BeansException {
            // 通过扫描工具去扫描指定包及其子包下的所有类,收集使用@Mycomponent的注解的类
            Map<String, Class> myComponentAnnotationMap = BaseClassScanUtils.scanMyComponentAnnotation("com.itheima");
            // 遍历Map,组装BeanDefinition进行注册
            myComponentAnnotationMap.forEach((beanName,clazz)->{
                // 获得beanClassName
                String beanClassName = clazz.getName();//com.itheima.beans.OtherBean
                // 创建BeanDefinition
                BeanDefinition beanDefinition = new RootBeanDefinition();
                beanDefinition.setBeanClassName(beanClassName);
                // 注册
                beanDefinitionRegistry.registerBeanDefinition(beanName,beanDefinition);
            });
    
        }
    
        @Override
        public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException {
    
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    将自定义类MyComponentBeanFactoryPostProcessor注册到容器中

    <bean class="com.itheima.processor.MyComponentBeanFactoryPostProcessor"/>
    
    • 1

    包扫描器工具BaseClassScanUtils

    package com.itheima.utils;
    
    import org.springframework.core.io.Resource;
    import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
    import org.springframework.core.io.support.ResourcePatternResolver;
    import org.springframework.core.type.classreading.CachingMetadataReaderFactory;
    import org.springframework.core.type.classreading.MetadataReader;
    import org.springframework.core.type.classreading.MetadataReaderFactory;
    import org.springframework.util.ClassUtils;
    
    import java.lang.annotation.Annotation;
    import java.util.HashMap;
    import java.util.List;
    import java.util.Map;
    
    public class BaseClassScanUtils {
    
        // 设置资源规则
        private static final String RESOURCE_PATTERN = "/**/*.class";
    
        public static Map<String, Class> scanMyComponentAnnotation(String basePackage) {
    
            // 创建容器存储使用了指定注解的Bean字节码对象
            Map<String, Class> annotationClassMap = new HashMap<String, Class>();
    
            // spring工具类,可以获取指定路径下的全部类
            ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver();
            try {
                String pattern = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX + ClassUtils.convertClassNameToResourcePath(basePackage) + RESOURCE_PATTERN;
                Resource[] resources = resourcePatternResolver.getResources(pattern);
                // MetadataReader 的工厂类
                MetadataReaderFactory refractory = new CachingMetadataReaderFactory(resourcePatternResolver);
                for (Resource resource : resources) {
                    // 用于读取类信息
                    MetadataReader reader = refractory.getMetadataReader(resource);
                    // 扫描到的class
                    String classname = reader.getClassMetadata().getClassName();
                    Class<?> clazz = Class.forName(classname);
                    // 判断是否属于指定的注解类型
                    if(clazz.isAnnotationPresent(MyComponent.class)){
                        // 获得注解对象
                        MyComponent annotation = clazz.getAnnotation(MyComponent.class);
                        // 获得value属性值
                        String beanName = annotation.value();
                        // 判断是否为""
                        if(beanName != null && !beanName.equals("")){
                            // 存储到Map中去
                            annotationClassMap.put(beanName,clazz);
                            continue;
                        }
    
                        //如果没有为"",那就把当前类的类名作为beanName
                        annotationClassMap.put(clazz.getSimpleName(), clazz);
    
                    }
                }
           } catch (Exception exception) {
            }
    
            return annotationClassMap;
        }
    	// 测试
        public static void main(String[] args) {
            Map<String, Class> stringClassMap = scanMyComponentAnnotation("com.itheima");
            System.out.println(stringClassMap);
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68

    2. Bean后处理器 – BeanPostProcessor

    Bean被实例化后,到最终缓存到名为singletonObjects单例池之前,中间会经过Bean的初始化过程,例如:属性的填充、初始方法init的执行等,其中有一个对外进行扩展的点BeanPostProcessor,我们称为Bean后处理。跟上面的Bean工厂后处理器相似,它也是一个接口,实现了该接口并被容器管理的BeanPostProcessor,会在流程节点上被Spring自动调用。

    BeanPostProcessor的接口定义如下:

    public interface BeanPostProcessor {
    	@Nullable
    	// 在属性注入完毕,init初始化方法执行之前被回调
    	default Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
    		return bean;
    	}
    	@Nullable
    	// 在初始化方法执行之后,被添加到单例池singletonObjects之前被回调
    	default Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
    		return bean;
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    自定义MyBeanPostProcessor

    public class MyBeanPostProcessor implements BeanPostProcessor {
    	/* 参数: bean是当前被实例化的Bean,beanName是当前Bean实例在容器中的名称
    	  返回值:当前Bean实例对象 */
    	public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
    		// 在这里还可以对bean进行设置值,这里只是演示作用,实际情况灵活变通
    		if(bean instanceof UserDaoImpl) {
    			UserDaoImpl userDaoImpl = (UserDaoImpl) bean;
    			userDaoImpl.setUserName("zhangsan");
    		}
    		System.out.println("BeanPostProcessor的before方法...");
    		return bean;
    	}
    	/* 参数: bean是当前被实例化的Bean,beanName是当前Bean实例在容器中的名称
    	  返回值:当前Bean实例对象 */
    	public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
    		System.out.println("BeanPostProcessor的after方法...");
    		return bean;
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    配置MyBeanPostProcessor

    <bean class="com.itheima.processors.MyBeanPostProcessor">bean>
    
    • 1

    执行顺序:
    UserDaoImpl创建了…
    UserDaoImpl属性填充…
    BeanPostProcessor的before方法…
    InitializingBean的afterpropertiesset方法
    UserDaoImpl自定义的初始化init方法执行…
    BeanPostProcessor的after方法…

    (1)应用:对Bean方法进行执行时间日志增强

    要求如下:
    ⚫ Bean的方法执行之前控制台打印当前时间;
    ⚫ Bean的方法执行之后控制台打印当前时间。
    分析:
    ⚫ 对方法进行增强主要就是代理设计模式和包装设计模式;
    ⚫ 由于Bean方法不确定,所以使用动态代理在运行期间执行增强操作;
    ⚫ 在Bean实例创建完毕后,进入到单例池之前,使用Proxy代替真实的目标Bean

    编写BeanPostProcessor,增强逻辑编写在 after方法中

    public class TimeLogBeanPostProcessor implements BeanPostProcessor {
    	public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
    		// 对Bean进行动态代理,返回的是Proxy代理对象
    		Object proxyBean = Proxy.newProxyInstance(bean.getClass().getClassLoader(),
    												  bean.getClass().getInterfaces(),
    								(Object proxy, Method method, Object[] args) -> {
    									long start = System.currentTimeMillis();
    									System.out.println("开始时间:" + new Date(start));
    									// 执行目标方法,这里演示的是所有bean的方法进行增强,实际情况灵活变通
    									Object result = method.invoke(bean, args);
    									long end = System.currentTimeMillis();
    									System.out.println("结束时间:" + new Date(end));
    									return result;
    								});
    		// 返回代理对象,存入单例池
    		return proxyBean;
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    <bean class="com.itheima.processor.TimeLogBeanPostProcessor">bean>
    
    • 1

    BeanPostProcessor 在 SpringBean的实例化过程中的体现

    在这里插入图片描述

  • 相关阅读:
    导数专题解题技巧
    notepad++官网地址 https://notepad-plus-plus.org,notepad++使用教程
    shell入门运算符操作、条件判断
    学习C++第二十四课--成员函数模板,模板显示实例化与声明笔记
    hyperf 前置中间件 后置中间件
    【动态规划】123. 买卖股票的最佳时机 III、188. 买卖股票的最佳时机 IV
    【Windows Docker:安装nginx】
    欧科云链携手上海数据交易所,关于未来的“超前实践”正在发生...
    在BAT大厂和小公司做开发,会有哪些体验上的区别?
    国家读写作业台灯标准是什么?护眼灯显色指数多少比较合适
  • 原文地址:https://blog.csdn.net/qq_36602071/article/details/127690796