• Spring系列14:IoC容器的扩展点


    Spring系列14:IoC容器的扩展点

    回顾

    知识需要成体系地学习,本系列文章前后有关联,建议按照顺序阅读。上一篇我们详细介绍了Spring Bean的生命周期和丰富的扩展点,没有阅读的强烈建议先阅读。本篇来详细讲讲容器提供的扩展点,完整的生命周期图镇楼。

    image-20220120101935737

    本文内容

    1. 详解BeanPostProcessor
    2. 详解BeanFactoryPostProcessor
    3. 详解FactoryBean

    详解BeanPostProcessor

    作用和定义

    常规 BeanPostProcessor 的作用是提供自定义的实例化逻辑、初始化逻辑、依赖关系解析逻辑等,对bean进行增强。主要的作用阶段是初始阶段前后。该接口定义了2接口,分别是前置增强和后置增强。 Spring AOP 功能主要是通过 BeanPostProcessor 实现的。

    public interface BeanPostProcessor {
    	// 在任何 bean 初始化回调(如 InitializingBean 的 afterPropertiesSet 或自定义 init 方法)之前     // 将此 BeanPostProcessor 应用于给定的新 bean 实例。 bean 将已填充属性值。返回的 bean 实例可能是     // 原始的包装器。默认实现按原样返回给定的 bean
    	default Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
    		return bean;
    	}
        
        // 在任何 bean 初始化回调(如 InitializingBean 的 afterPropertiesSet 或自定义 init 方法)之   	   // 后,将此 BeanPostProcessor 应用于给定的新 bean 实例。 bean 将已填充属性值。返回的 bean 实例可     /// 能是原始的包装器。默认实现按原样返回给定的 bean
    	default Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
    		return bean;
    	}
    }
    

    自定义BeanPostProcessor

    自定义一个 BeanPostProcessor 实现,该实现调用每个 bean 的 toString() 方法打印内容输出到控制台。

    类定义如下

    /**
     * @author zfd
     * @version v1.0
     * @date 2022/1/20 11:21
     * @关于我 请关注公众号 螃蟹的Java笔记 获取更多技术系列
     */
    @Component
    public class MyBeanPostProcessor implements BeanPostProcessor {
        @Override
        public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
            // 返回原始bean
            return bean;
        }
    
        @Override
        public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
            System.out.println( bean + " 名称是: " + beanName);
            return bean;
        }
    }
    
    @Component("xxxBeanOne")
    public class BeanOne {
    }
    
    
    @Configuration
    @ComponentScan
    public class AppConfig {
    }
    

    测试程序及结果

    @org.junit.Test
    public void test1() {
        AnnotationConfigApplicationContext context =
                new AnnotationConfigApplicationContext(AppConfig.class);
        BeanOne bean = context.getBean(BeanOne.class);
        System.out.println(bean);
        context.close();
    }
    // 结果
    com.crab.spring.ioc.demo12.AppConfig$$EnhancerBySpringCGLIB$$da23c216@4445629 名称是: appConfig
    com.crab.spring.ioc.demo12.BeanOne@45b9a632 名称是: xxxBeanOne
    com.crab.spring.ioc.demo12.BeanOne@45b9a632
    

    从结果看,MyBeanPostProcessor#postProcessAfterInitialization 输出了容器内初始化bean的名称。

    使用 @Order 控制 BeanPostProcessor 执行顺序

    实际程序程序中肯定存在多个 BeanPostProcessor 通过 @Order 来指定顺序。

    增加 MyBeanPostProcessor2 指定顺序是 -2

    @Component
    @Order(-2)
    public class MyBeanPostProcessor2 implements BeanPostProcessor {
        @Override
        public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
            // 返回原始bean
            return bean;
        }
    
        @Override
        public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
            System.out.println("MyBeanPostProcessor2 输出:" +  bean + " 名称是: " + beanName);
            return bean;
        }
    }
    

    同样的测试程序,观察下结果

    com.crab.spring.ioc.demo12.AppConfig$$EnhancerBySpringCGLIB$$6fb51f6@54c562f7 名称是: appConfig
    MyBeanPostProcessor2 输出:com.crab.spring.ioc.demo12.AppConfig$$EnhancerBySpringCGLIB$$6fb51f6@54c562f7 名称是: appConfig
    com.crab.spring.ioc.demo12.BeanOne@318ba8c8 名称是: xxxBeanOne
    MyBeanPostProcessor2 输出:com.crab.spring.ioc.demo12.BeanOne@318ba8c8 名称是: xxxBeanOne
    com.crab.spring.ioc.demo12.BeanOne@318ba8c8
    

    MyBeanPostProcessor2 在 MyBeanPostProcessor 之前执行。

    通过源码了解下Spring是如何将多个 BeanPostProcessor 排序的,对应 PostProcessorRegistrationDelegate#registerBeanPostProcessors()。

    可以看出顺序是: PriorityOrdered > Ordered > 其它常规的。

    	public static void registerBeanPostProcessors(
    			ConfigurableListableBeanFactory beanFactory, AbstractApplicationContext applicationContext) {
    
    		String[] postProcessorNames = beanFactory.getBeanNamesForType(BeanPostProcessor.class, true, false);
    
    		// Separate between BeanPostProcessors that implement PriorityOrdered,
    		// Ordered, and the rest.
    		List<BeanPostProcessor> priorityOrderedPostProcessors = new ArrayList<>();
    		List<BeanPostProcessor> internalPostProcessors = new ArrayList<>();
    		List<String> orderedPostProcessorNames = new ArrayList<>();
    		List<String> nonOrderedPostProcessorNames = new ArrayList<>();
    		for (String ppName : postProcessorNames) {
    			if (beanFactory.isTypeMatch(ppName, PriorityOrdered.class)) {
    				BeanPostProcessor pp = beanFactory.getBean(ppName, BeanPostProcessor.class);
    				priorityOrderedPostProcessors.add(pp);
    				if (pp instanceof MergedBeanDefinitionPostProcessor) {
    					internalPostProcessors.add(pp);
    				}
    			}
    			else if (beanFactory.isTypeMatch(ppName, Ordered.class)) {
    				orderedPostProcessorNames.add(ppName);
    			}
    			else {
    				nonOrderedPostProcessorNames.add(ppName);
    			}
    		}
    
    		// First, register the BeanPostProcessors that implement PriorityOrdered.
    		sortPostProcessors(priorityOrderedPostProcessors, beanFactory);
    		registerBeanPostProcessors(beanFactory, priorityOrderedPostProcessors);
    
    		// Next, register the BeanPostProcessors that implement Ordered.
    		List<BeanPostProcessor> orderedPostProcessors = new ArrayList<>(orderedPostProcessorNames.size());
    		for (String ppName : orderedPostProcessorNames) {
    			BeanPostProcessor pp = beanFactory.getBean(ppName, BeanPostProcessor.class);
    			orderedPostProcessors.add(pp);
    			if (pp instanceof MergedBeanDefinitionPostProcessor) {
    				internalPostProcessors.add(pp);
    			}
    		}
    		sortPostProcessors(orderedPostProcessors, beanFactory);
    		registerBeanPostProcessors(beanFactory, orderedPostProcessors);
    
    		// Now, register all regular BeanPostProcessors.
    		List<BeanPostProcessor> nonOrderedPostProcessors = new ArrayList<>(nonOrderedPostProcessorNames.size());
    		for (String ppName : nonOrderedPostProcessorNames) {
    			BeanPostProcessor pp = beanFactory.getBean(ppName, BeanPostProcessor.class);
    			nonOrderedPostProcessors.add(pp);
    			if (pp instanceof MergedBeanDefinitionPostProcessor) {
    				internalPostProcessors.add(pp);
    			}
    		}
    		registerBeanPostProcessors(beanFactory, nonOrderedPostProcessors);
    
    		// Finally, re-register all internal BeanPostProcessors.
    		sortPostProcessors(internalPostProcessors, beanFactory);
    		registerBeanPostProcessors(beanFactory, internalPostProcessors);
    	}
    
    AutowiredAnnotationBeanPostProcessor 分析

    将回调接口或注释与自定义BeanPostProcessor 实现结合使用是扩展 Spring IoC 容器的常用方法。`AutowiredAnnotationBeanPostProcesso由 Spring 提供的实现,并自动注入依赖到带注解的字段、setter 方法和任意配置方法。

    源码头说明: 自动装配带注释的字段、设置方法和任意配置方法的 BeanPostProcessor 实现。此类要注入的成员是通过注解检测的:默认情况下,Spring 的@Autowired 和@Value 注解。还支持 JSR-330 的 @Inject 注解(如果可用)作为 Spring 自己的 @Autowired 的直接替代品。

    来看一下源码里面的关键方法

    public class AutowiredAnnotationBeanPostProcessor extends InstantiationAwareBeanPostProcessorAdapter
    		implements MergedBeanDefinitionPostProcessor, PriorityOrdered, BeanFactoryAware {
    	
        // 关键方法1: 默认构造方法 默认值支持注解 @Autowired @Value  @Inject
        public AutowiredAnnotationBeanPostProcessor() {
    		this.autowiredAnnotationTypes.add(Autowired.class);
    		this.autowiredAnnotationTypes.add(Value.class);
    		try {
    			this.autowiredAnnotationTypes.add((Class<? extends Annotation>)
    					ClassUtils.forName("javax.inject.Inject", AutowiredAnnotationBeanPostProcessor.class.getClassLoader()));
    			logger.trace("JSR-330 'javax.inject.Inject' annotation found and supported for autowiring");
    		}
    		catch (ClassNotFoundException ex) {
    			// JSR-330 API not available - simply skip.
    		}
    	}
        
        // 关键方法2: 将支持的注解标准的内容合并到 BeanDefinition 中为后续设置属性值做准备
        	@Override
    	public void postProcessMergedBeanDefinition(RootBeanDefinition beanDefinition, Class<?> beanType, String beanName) {
    		InjectionMetadata metadata = findAutowiringMetadata(beanName, beanType, null);
    		metadata.checkConfigMembers(beanDefinition);
    	}
        
        // 关键方法3: 自动注入依赖属性
        	@Override
    	public PropertyValues postProcessProperties(PropertyValues pvs, Object bean, String beanName) {
    		InjectionMetadata metadata = findAutowiringMetadata(beanName, bean.getClass(), pvs);
    		try {
    			metadata.inject(bean, beanName, pvs);
    		}
    		catch (BeanCreationException ex) {
    			throw ex;
    		}
    		catch (Throwable ex) {
    			throw new BeanCreationException(beanName, "Injection of autowired dependencies failed", ex);
    		}
    		return pvs;
    	}
        
    }
    

    既然简单提到源码了,深入一下,结合Spring的生命周期,看下对应的生效位置

    关键方法2: 将支持的注解标准的内容合并到 BeanDefinition,实例化之后属性填充之前。

    image-20220120144401684

    image-20220120144519534

    关键方法3: 自动注入依赖属性,发生在属性赋值阶段

    image-20220120145150125

    类似的 BeanPostProcessor 汇总下,可以对应看下源码

    名称 作用
    ConfigurationClassPostProcessor 处理@Configuration
    CommonAnnotationBeanPostProcessor 处理JSR 250 中的 @PostConstruct,@PreDestroy和@Resource
    AutowiredAnnotationBeanPostProcessor 处理@Autowired 、@Value 、 JSR-330 的 @Inject
    PersistenceAnnotationBeanPostProcessor 处理 PersistenceUnit 和 PersistenceContext 注解的 BeanPostProcessor,用于注入对应的 JPA 资源 EntityManagerFactory 和 EntityManager。

    详解BeanFactoryPostProcessor

    BeanPostProcessor 是对bean实例进行增强,类似地,BeanFactoryPostProcessor 对 bean 配置元数据也就是 BeanDefinition 进行操作。Spring IoC 容器允许BeanFactoryPostProcessor读取配置元数据并可能在容器实例化任何 bean之前BeanFactoryPostProcessor更改它,当然不包括 BeanFactoryPostProcessor实例。Spring 包括许多预定义的 BeanFactoryPostProcessor ,例如 PropertyOverrideConfigurer 和 PropertySourcesPlaceholderConfigurer。

    多个 BeanFactoryPostProcessor 的属性可以通过 @Ordered 来控制。

    PropertySourcesPlaceholderConfigurer 分析

    可以使用 PropertySourcesPlaceholderConfigurer 通过使用标准 Java 属性格式将 bean 定义中的属性值外部化到一个单独的文件中。针对当前 Spring 环境及其一组 PropertySource 解析 bean 定义属性值和 @Value 注释中的 ${...} 占位符。

    PropertySourcesPlaceholderConfigurer 不仅在指定的属性文件中寻找属性。默认情况下,如果它不能在指定的属性文件中找到属性,它将检查Spring Environment属性和常规Java System属性。

    来看一个实际的场景: 通常的我们的数据库的连接信息是配置在配置文件中的而不是固定写在程序中。

    数据库配置文件 jdbc.properties

    jdbc.driverClassName=org.hsqldb.jdbcDriver
    jdbc.url=jdbc:hsqldb:hsql://production:9002
    jdbc.username=sa
    jdbc.password=root
    

    通过 @Value 注入到我们数据库配置类中

    @Configuration("myDataSource")
    public class MyDataSource {
    
        @Value("${jdbc.driverClassName}")
        private String driverClassName;
    
        @Value("${jdbc.url}")
        private String url;
    
        @Value("${jdbc.username}")
        private String username;
    
        @Value("${jdbc.password}")
        private String password;
    
    	// ...
    }
    

    看下配置类,注入一个 PropertySourcesPlaceholderConfigurer

    @Configuration
    @ComponentScan
    // @PropertySource("classpath:demo12/jdbc.properties")
    public class AppConfig2 {
        // 自定义一个 PropertySourcesPlaceholderConfigurer
        @Bean
        public static PropertySourcesPlaceholderConfigurer placeholderConfigurer() {
            PropertySourcesPlaceholderConfigurer configurer =
                    new PropertySourcesPlaceholderConfigurer();
            // 配置配置文件的路径
            configurer.setLocation(new ClassPathResource("demo12/jdbc.properties"));
            return configurer;
    
        }
    }
    

    等价于下面的xml配置

    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    
        <bean class="org.springframework.context.support.PropertySourcesPlaceholderConfigurer" >
            <property name="locations" value="classpath:demo12/jdbc.properties"/>
        </bean>
    
        <bean class="com.crab.spring.ioc.demo12.MyDataSource">
            <property name="driverClassName" value="${jdbc.driverClassName}"/>
            <property name="url" value="${jdbc.url}"/>
            <property name="username" value="${jdbc.username}"/>
            <property name="password" value="${jdbc.password}"/>
        </bean>
    </beans>
    

    观察下输出结果

    MyDataSource{driverClassName='org.hsqldb.jdbcDriver', url='jdbc:hsqldb:hsql://production:9002', username='sa', password='root'}
    

    配置在 MyDataSource 的外部属性值已经成功注入了。

    过多关于 @Value 和外部配置属性的注入可以看下之前的文章 :Spring系列12: @Value @Resource @PostConstruct @PreDestroy 详解。

    扩展: 思考下Springboot 可以使用 ${xxx.xxx} 引用外部的 application.yml或是application.properties,是不是注入了一个自定义的 PropertySourcesPlaceholderConfigurer?

    PropertyOverrideConfigurer详解

    类似于 PropertySourcesPlaceholderConfigurer , PropertyOverrideConfigurer 用于覆盖bean定义中的属性值。如果覆盖属性文件没有特定 bean 属性值,则使用默认上下文定义的。覆盖属性文件的格式如下:

    # bean定义名称.属性名称=覆盖的属性值
    beanName.property=value
    

    来一个案例,在上面的案例的基础上,myDataSource.username替换为sa-override,
    myDataSource.password替换为root-override。

    配置文件如下

    myDataSource.username=sa-override
    myDataSource.password=root-override
    

    配置类注入一个 PropertySourcesPlaceholderConfigurer

    @Configuration
    @ComponentScan
    // @PropertySource("classpath:demo12/jdbc.properties")
    public class AppConfig2 {
        // 自定义一个 PropertySourcesPlaceholderConfigurer
        @Bean
        public static PropertySourcesPlaceholderConfigurer placeholderConfigurer() {
            PropertySourcesPlaceholderConfigurer configurer =
                    new PropertySourcesPlaceholderConfigurer();
            configurer.setLocation(new ClassPathResource("demo12/jdbc.properties"));
            return configurer;
    
        }
    	// 注入一个 PropertyOverrideConfigurer
        @Bean
        public static PropertyOverrideConfigurer propertyOverrideConfigurer() {
            PropertyOverrideConfigurer overrideConfigurer = new PropertyOverrideConfigurer();
            overrideConfigurer.setLocation(new ClassPathResource("demo12/jdbc-override.properties"));
            return overrideConfigurer;
        }
    
    }
    

    观察下输出结果

    MyDataSource{driverClassName='org.hsqldb.jdbcDriver', url='jdbc:hsqldb:hsql://production:9002', username='sa-override', password='root-override'}
    

    username 和 password 已经成功被替换了。

    详解FactoryBean

    FactoryBean 接口是Spring IoC容器实例化逻辑的可插点。如果您有复杂的初始化代码,用Java更好地表达,而不是(可能)冗长的XML,那么您可以创建自己的FactoryBean,在该类中编写复杂的初始化代码,然后将定制的FactoryBean 插入到容器中。

    FactoryBean 概念和接口在 Spring 框架中的许多地方都使用。 Spring 本身附带了 50 多个 FactoryBean 接口的实现。

    FactoryBean 接口的定义如下

    public interface FactoryBean<T> {
        // 返回此工厂创建的对象的实例。该实例可能会被共享,具体取决于该工厂是返回单例还是原型。
    	T getObject() throws Exception;
        
        // 返回由 getObject() 方法返回的对象类型,如果事先不知道该类型,则返回 null。
    	Class<?> getObjectType();
        
        // 如果此 FactoryBean 返回单例,则返回 true,否则返回 false。此方法的默认实现返回 true。
        default boolean isSingleton() {
    		return true;
    	}
    }
    

    FactoryBean注入到Spring容器中产生2个实例,FactoryBean实例和其生产处理的实例假设id是 myBean 。那么如何获取这个2个bean?

    • 获取FactoryBean实例: ApplicationContext#getBean("&myBean")
    • 获取生产出来的bean实例:ApplicationContext#getBean("myBean")

    来个实战案例

    定义一个 MyFactoryBean ,生产非单例,每次new一个

    @Component("myFactoryBean")
    public class MyFactoryBean implements FactoryBean<BeanOne> {
    
        @Override
        public BeanOne getObject() throws Exception {
            // 每次生成一个
            return new BeanOne();
        }
    
        @Override
        public Class<?> getObjectType() {
            return BeanOne.class;
        }
    
        @Override
        public boolean isSingleton() {
            // 非单例 每次生成一个
            return false;
        }
    }
    

    观察下输出

    MyFactoryBean 实例: com.crab.spring.ioc.demo12.MyFactoryBean@2ef3eef9
    true
    com.crab.spring.ioc.demo12.BeanOne@71809907 名称是: myFactoryBean
    MyBeanPostProcessor2 输出:com.crab.spring.ioc.demo12.BeanOne@71809907 名称是: myFactoryBean
    com.crab.spring.ioc.demo12.BeanOne@3ce1e309 名称是: myFactoryBean
    MyBeanPostProcessor2 输出:com.crab.spring.ioc.demo12.BeanOne@3ce1e309 名称是: myFactoryBean
    MyFactoryBean 生产的bean实例: com.crab.spring.ioc.demo12.BeanOne@71809907
    false
    

    细心的人会发现调用 FactoryBean#getObject() 生产bean 也是会走 BeanPostProcessor的增强流程的。

    在深入了解下使用 FactoryBean 的注意点:

    • FactoryBean 是一种程序化契约。实现不应该依赖注解驱动的注入或其他反射设施。 getObjectType() getObject() 可能会在Spring引导过程的早期调用,甚至在任何后处理器设置之前。如果需要访问其他 bean可以实现 BeanFactoryAware 并以编程方式获取它们。
    • 容器只负责管理FactoryBean 实例的生命周期,而不是FactoryBean 创建的对象的生命周期。因此,暴露的 bean 对象(例如 java.io.Closeable.close() 上的销毁方法不会被自动调用。相反,FactoryBean 应该实现 DisposableBean 并将任何此类关闭调用委托给底层对象。

    类似的接口还有 org.springframework.aop.framework.ProxyFactoryBean 、SmartFactoryBean 感兴趣的可以了解下

    总结

    本文详细分析了Spring 容器的扩展点,包括BeanPostProcessor、BeanFactoryPostProcessor、FactoryBean的原理和使用,结合上一篇Spring的生命周期和回调理解会更好。完全消化这2篇内容,Spring开发会更加顺手,离成为Spring高手更进一步。

    本篇源码地址: https://github.com/kongxubihai/pdf-spring-series/tree/main/spring-series-ioc/src/main/java/com/crab/spring/ioc/demo12
    知识分享,转载请注明出处。学无先后,达者为先!

  • 相关阅读:
    .NET MAUI – 一个代码库,多个平台
    从Linux的tty_struct指针获取驱动上下文
    科普达人丨漫画图解什么是 eRDMA?
    CSS笔记——浮动float与定位position及ClearFix解决方案
    【EdgeBox-8120AI-TX2】Ubuntu18.04 + ROS_ Melodic + 星秒PAVO2单线激光 雷达评测
    联华集团:IT团队如何实现从成本中心提升至价值中心|OceanBase 《DB大咖说》(十)
    打造高性能网站:使用 nginx、MySQL 和 PHP 编译,搭建 LNMP 环境并安装 WordPress实战
    设计模式行为型-状态模式
    jwt原理及使用
    使用html+css实现一个静态页面(含源码)
  • 原文地址:https://www.cnblogs.com/kongbubihai/p/15902837.html