锐单电子商城 , 一站式电子元器件采购平台!
  • 电话:400-990-0325

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

时间:2023-12-23 18:07:01 ba附带连接器

回顾

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

在这里插入图片描述

本文内容

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

一、详解BeanPostProcessor

作用和定义

常规 BeanPostProcessor 对bean增强。主要阶段是初始阶段前后。界面定义了前增强和后增强两个界面。 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) thows 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,实例化之后属性填充之前。


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

类似的 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 { 
        
        // 每次生成一个
         

相关文章