学习目标:
- BeanFactory后处理器的作用:为BeanFactory提供扩展
- 常见的BeanFactory后处理器
1、测试BeanFactory 后处理器的作用
Config类
@Configuration
@ComponentScan("com.itheima.a05.component")
public class Config {
@Bean
public Bean1 bean1() {
return new Bean1();
}
@Bean
public SqlSessionFactoryBean sqlSessionFactoryBean(DataSource dataSource) {
SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
sqlSessionFactoryBean.setDataSource(dataSource);
return sqlSessionFactoryBean;
}
@Bean(initMethod = "init")
public DruidDataSource dataSource() {
DruidDataSource dataSource = new DruidDataSource();
dataSource.setUrl("jdbc:mysql://localhost:3306/test");
dataSource.setUsername("root");
dataSource.setPassword("root");
return dataSource;
}
}
Bean1类
public class Bean1 {
private static final Logger log = LoggerFactory.getLogger(Bean1.class);
public Bean1() {
log.debug("我被 Spring 管理啦");
}
}
component包下的Bean2,Bean3,Bean4类
@Component
public class Bean2 {
private static final Logger log = LoggerFactory.getLogger(Bean2.class);
public Bean2() {
log.debug("我被 Spring 管理啦");
}
}
@Controller
public class Bean3 {
private static final Logger log = LoggerFactory.getLogger(Bean3.class);
public Bean3() {
log.debug("我被 Spring 管理啦");
}
}
public class Bean4 {
private static final Logger log = LoggerFactory.getLogger(Bean4.class);
public Bean4() {
log.debug("我被 Spring 管理啦");
}
}
mapper包下的Mapper1,Mapper2,Mapper3类
@Mapper
public interface Mapper1 {
}
@Mapper
public interface Mapper2 {
}
public class Mapper3 {
}
主程序类:
public class A05Application {
public static void main(String[] args) throws IOException {
// GenericApplicationContext 是一个【干净】的容器
GenericApplicationContext context = new GenericApplicationContext();
context.registerBean("config", Config.class);
// 初始化容器
context.refresh();// 执行beanFactory后处理器, 添加bean后处理器, 初始化所有单例
for (String name : context.getBeanDefinitionNames()) {
System.out.println(name);
}
// 销毁容器
context.close();
}
}
结果:
config
1)添加ConfigurationClassPostProcessor
ConfigurationClassPostProcessor 可以解析
-
@ComponentScan
-
@Bean
-
@Import
-
@ImportResource
context.registerBean(ConfigurationClassPostProcessor.class);
结果:@ComponentScan注解被解析
[main] com.itheima.a05.component.Bean2 - 我被 Spring 管理啦
[main] com.itheima.a05.component.Bean3 - 我被 Spring 管理啦
[main] com.itheima.a05.Bean1 - 我被 Spring 管理啦
[main] c.a.druid.pool.DruidDataSource - {dataSource-1} inited
config
org.springframework.context.annotation.ConfigurationClassPostProcessor
bean2
bean3
bean1
sqlSessionFactoryBean
dataSource
[main] c.a.druid.pool.DruidDataSource - {dataSource-1} closing ...
[main] c.a.druid.pool.DruidDataSource - {dataSource-1} closed
2)添加MapperScannerConfigurer
MapperScannerConfigurer可以解析
- Mapper 接口
context.registerBean(MapperScannerConfigurer.class, beanDefinition -> {
//设置包扫描路径
beanDefinition.getPropertyValues().add("basePackage", "com.itheima.a05.mapper");
});//@MapperScanner
结果:Mapper接口被解析
[main] com.itheima.a05.component.Bean2 - 我被 Spring 管理啦
[main] com.itheima.a05.component.Bean3 - 我被 Spring 管理啦
[main] com.itheima.a05.Bean1 - 我被 Spring 管理啦
[main] c.a.druid.pool.DruidDataSource - {dataSource-1} inited
config
org.springframework.context.annotation.ConfigurationClassPostProcessor
org.mybatis.spring.mapper.MapperScannerConfigurer
bean2
bean3
bean1
sqlSessionFactoryBean
dataSource
mapper1
mapper2
org.springframework.context.annotation.internalConfigurationAnnotationProcessor
org.springframework.context.annotation.internalAutowiredAnnotationProcessor
org.springframework.context.annotation.internalCommonAnnotationProcessor
org.springframework.context.event.internalEventListenerProcessor
org.springframework.context.event.internalEventListenerFactory
[main] c.a.druid.pool.DruidDataSource - {dataSource-1} closing ...
[main] c.a.druid.pool.DruidDataSource - {dataSource-1} closed
总结
-
@ComponentScan, @Bean, @Mapper 等注解的解析属于核心容器(即 BeanFactory)的扩展功能
-
这些扩展功能由不同的 BeanFactory 后处理器来完成,其实主要就是补充了一些 bean 定义
2、BeanFactoryPostProcessor接口与BeanDefinitionRegistryPostProcessor接口
BeanFactoryPostProcessor接口:实现该接口,可以在spring的bean创建之前,修改bean的定义属性。也就是说,Spring允许BeanFactoryPostProcessor在容器实例化任何其它bean之前读取配置元数据,并可以根据需要进行修改,例如可以把bean的scope从singleton改为prototype,也可以把property的值给修改掉。可以同时配置多个BeanFactoryPostProcessor,并通过设置’order’属性来控制各个BeanFactoryPostProcessor的执行次序。
注意:BeanFactoryPostProcessor是在spring容器加载了bean的定义文件之后,在bean实例化之前执行的。接口方法的入参是ConfigurrableListableBeanFactory,使用该参数,可以获取到相关bean的定义信息。
BeanFactoryPostProcessor和BeanDefinitionRegistryPostProcessor 都是spring的后置处理器,但是两个类的侧重点不一样,BeanDefinitionRegistryPostProcessor 侧重于创建自定义的beanDefinition,而 BeanFactoryPostProcessor侧重于对已有beanDefinition属性的修改。
执行顺序:BeanDefinitionRegistryPostProcessor 先于 BeanFactoryPostProcessor 执行。
可以查看源码的这个方法:invokeBeanFactoryPostProcessors
再说明一下BeanPostProcessor与BeanFactoryPostProcessor的区别:
BeanPostProcessor,可以在spring容器实例化bean之后,在执行bean的初始化方法前后,添加一些自己的处理逻辑。
注意:BeanPostProcessor是在spring容器加载了bean的定义文件并且实例化bean之后执行的。BeanPostProcessor的执行顺序是在BeanFactoryPostProcessor之后。
参考文章:BeanDefinitionRegistryPostProcessor的作用及和 BeanFactoryPostProcessor 的区别
BeanDefinitionRegistryPostProcessor是BeanFactoryPostProcessor的子接口
public interface BeanDefinitionRegistryPostProcessor extends BeanFactoryPostProcessor {
void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry var1) throws BeansException;
}
@FunctionalInterface
public interface BeanFactoryPostProcessor {
void postProcessBeanFactory(ConfigurableListableBeanFactory var1) throws BeansException;
}
3、模拟解析 @ComponentScan
实现BeanDefinitionRegistryPostProcessor接口的原因:提供的BeanDefinitionRegistry有注册bean的方法,而BeanFactoryPostProcessor提供的ConfigurableListableBeanFactory没有
实现步骤:
- 查找 Config类 里是否有 @ComponentScan 注解,如果有的话获取到 basePackages 属性的值,将包路径转换为类路径。
- 通过PathMatchingResourcePatternResolver获取到路径上的资源
- 通过CachingMetadataReaderFactory对资源读取,读取注解的元信息,查看是否添加@Component及其派生注解
- 最后将其注册到容器
public class ComponentScanPostProcessor implements BeanDefinitionRegistryPostProcessor {
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry beanFactory) throws BeansException {
MetadataReader reader = null;
try {
//Spring提供的工具类,查找 Config类 里是否有 @ComponentScan 注解
ComponentScan componentScan = AnnotationUtils.findAnnotation(Config.class, ComponentScan.class);
if (componentScan != null) {
for (String p : componentScan.basePackages()) {
System.out.println("basePackages: " + p);//com.itheima.a05.component
//如何扫描组件,路径转换
//com.itheima.a05.component -> classpath*:com/itheima/a05/component/**/*.class
String path = "classpath*:" + p.replace(".", "/") + "/**/*.class";
//Spring 操作元数据的工具类
CachingMetadataReaderFactory factory = new CachingMetadataReaderFactory();
//获取指定路径下的资源
Resource[] resources = new PathMatchingResourcePatternResolver().getResources(path);
//BeanNameGenerator接口是bean名字生成器的入口
//AnnotationBeanNameGenerator是注解方式的bean生成名字
AnnotationBeanNameGenerator generator = new AnnotationBeanNameGenerator();
for (Resource resource : resources) {
System.out.println("扫描到的类: " + resource);
//获取元数据读取器
reader = factory.getMetadataReader(resource);
System.out.println("类名:" + reader.getClassMetadata().getClassName());
//读取注解原信息
AnnotationMetadata annotationMetadata = reader.getAnnotationMetadata();
//hasAnnotation方法:是否包含指定注解,加了派生注解是扫描不到的
//hasMetaAnnotation:用于判断注解类型自己是否被某个元注解类型所标注
System.out.println("是否加了 @Component:" + annotationMetadata.hasMetaAnnotation(Component.class.getName()));
System.out.println("是否是 @Component 的派生注解:" + annotationMetadata.hasMetaAnnotation(Component.class.getName()));
if (annotationMetadata.hasMetaAnnotation(Component.class.getName())
|| annotationMetadata.hasAnnotation(Component.class.getName())) {
//根据类名生成bean
AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder
.genericBeanDefinition(reader.getClassMetadata().getClassName())
.getBeanDefinition();
//根据 beanDefinition 和 beanFactory 生成 beanName
String beanName = generator.generateBeanName(beanDefinition, beanFactory);
//向容器注册bean
beanFactory.registerBeanDefinition(beanName, beanDefinition);
}
}
}
}
}catch (IOException e) {
e.printStackTrace();
}
}
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException {
}
}
主启动类
public class A05Application {
public static void main(String[] args) throws IOException {
// GenericApplicationContext 是一个【干净】的容器
GenericApplicationContext context = new GenericApplicationContext();
context.registerBean("config", Config.class);
context.registerBean(ComponentScanPostProcessor.class);
// 初始化容器
context.refresh();
for (String name : context.getBeanDefinitionNames()) {
System.out.println(name);
}
// 销毁容器
context.close();
}
}
结果:
basePackages: com.itheima.a05.component
扫描到的类: file [E:\spring源码\代码\show\target\classes\com\itheima\a05\component\Bean2.class]
类名:com.itheima.a05.component.Bean2
是否加了 @Component:true
是否是 @Component 的派生注解:false
扫描到的类: file [E:\spring源码\代码\show\target\classes\com\itheima\a05\component\Bean3.class]
类名:com.itheima.a05.component.Bean3
是否加了 @Component:false
是否是 @Component 的派生注解:true
扫描到的类: file [E:\spring源码\代码\show\target\classes\com\itheima\a05\component\Bean4.class]
类名:com.itheima.a05.component.Bean4
是否加了 @Component:false
是否是 @Component 的派生注解:false
[DEBUG] 11:16:12.621 [main] com.itheima.a05.component.Bean2 - 我被 Spring 管理啦
[DEBUG] 11:16:12.626 [main] com.itheima.a05.component.Bean3 - 我被 Spring 管理啦
config
com.itheima.a05.ComponentScanPostProcessor
bean2
bean3
总结:
-
Spring 操作元数据的工具类 CachingMetadataReaderFactory
-
通过注解元数据(AnnotationMetadata)获取直接或间接标注的注解信息
-
通过类元数据(ClassMetadata)获取类名,AnnotationBeanNameGenerator 生成 bean 名
-
解析元数据是基于 ASM 技术
4、模拟解析 @Bean
实现步骤:
- 通过CachingMetadataReaderFactory对指定类路径的资源进行读取,获取到类中标注着@Bean的方法
- 通过BeanDefinitionBuilder生成对应的Bean
- 注册到容器中
public class AtBeanPostProcessor implements BeanDefinitionRegistryPostProcessor {
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry beanFactory) throws BeansException {
MetadataReader reader = null;
try {
//Spring 操作元数据的工具类
CachingMetadataReaderFactory factory = new CachingMetadataReaderFactory();
//获取元数据读取器
reader = factory.getMetadataReader(new ClassPathResource("com/itheima/a05/Config.class"));
//获取类中标注着 @Bean 的方法
Set<MethodMetadata> methods = reader.getAnnotationMetadata().getAnnotatedMethods(Bean.class.getName());
for (MethodMetadata method : methods) {
System.out.println("加了 @Bean 的方法: " + method);
//获取 @Bean 的属性 initMethod 的值
String initMethod = method.getAnnotationAttributes(Bean.class.getName()).get("initMethod").toString();
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition();
//定义config的工厂方法
// 参数一:工厂方法名字 参数二:工厂对象对应的bean的名字,先有工厂对象才能进一步调用工厂方法
builder.setFactoryMethodOnBean(method.getMethodName(), "config");
//指定自动装配模式,默认不自动装配,即遇到工厂方法参数就跳过去了
//AUTOWIRE_CONSTRUCTOR:自动装配构造函数的形参,完成对应属性的自动装配,
builder.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_CONSTRUCTOR);
if (initMethod.length() > 0) {
//设置初始化方法的名字
builder.setInitMethodName(initMethod);
}
//生成bean
AbstractBeanDefinition beanDefinition = builder.getBeanDefinition();
//在容器中注册bean
beanFactory.registerBeanDefinition(method.getMethodName(), beanDefinition);
}
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException {
}
}
主启动类
public class A05Application {
public static void main(String[] args) throws IOException {
// GenericApplicationContext 是一个【干净】的容器
GenericApplicationContext context = new GenericApplicationContext();
context.registerBean("config", Config.class);
context.registerBean(AtBeanPostProcessor.class);
// 初始化容器
context.refresh();
for (String name : context.getBeanDefinitionNames()) {
System.out.println(name);
}
// 销毁容器
context.close();
}
}
结果:
加了 @Bean 的方法: com.itheima.a05.Config.bean1()
加了 @Bean 的方法: com.itheima.a05.Config.sqlSessionFactoryBean(javax.sql.DataSource)
加了 @Bean 的方法: com.itheima.a05.Config.dataSource()
[main] com.itheima.a05.Bean1 - 我被 Spring 管理啦
[main] c.a.druid.pool.DruidDataSource - {dataSource-1} inited
config
com.itheima.a05.AtBeanPostProcessor
bean1
sqlSessionFactoryBean
dataSource
[main] c.a.druid.pool.DruidDataSource - {dataSource-1} closing ...
[main] c.a.druid.pool.DruidDataSource - {dataSource-1} closed
5、模拟解析 Mapper 接口
首先明确一个问题:Spring如何管理Mapper接口?
Spring并不能管理接口,最终管理的还是对象,MapperFactoryBean可以生产出Mapper对象,我们创建MapperFactoryBean就可以了。
//生产Mapper1接口的对象
@Bean
public MapperFactoryBean<Mapper1> mapper1(SqlSessionFactory sqlSessionFactory) {
MapperFactoryBean<Mapper1> factory = new MapperFactoryBean<>(Mapper1.class);
factory.setSqlSessionFactory(sqlSessionFactory);
return factory;
}
//生产Mapper2接口的对象
@Bean
public MapperFactoryBean<Mapper2> mapper2(SqlSessionFactory sqlSessionFactory) {
MapperFactoryBean<Mapper2> factory = new MapperFactoryBean<>(Mapper2.class);
factory.setSqlSessionFactory(sqlSessionFactory);
return factory;
}
但是这样比较麻烦,一个Mapper接口对应一个MapperFactoryBean,我们可以通过扫描mapper包,批量生成MapperFactoryBean。
实现步骤:
- 通过PathMatchingResourcePatternResolver获取mapper包下的资源
- 通过CachingMetadataReaderFactory读取类的元信息,对标有@Mapper的接口生成MapperFactoryBean对象。
- 注册到容器
public class MapperPostProcessor implements BeanDefinitionRegistryPostProcessor {
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry beanFactory) throws BeansException {
try {
PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
//获取指定路径下的资源
Resource[] resources = resolver.getResources("classpath:com/itheima/a05/mapper/**/*.class");
//Spring 操作元数据的工具类
CachingMetadataReaderFactory factory = new CachingMetadataReaderFactory();
//AnnotationBeanNameGenerator是注解方式的bean生成名字
AnnotationBeanNameGenerator generator = new AnnotationBeanNameGenerator();
for (Resource resource : resources) {
//获取元数据读取器
MetadataReader reader = factory.getMetadataReader(resource);
ClassMetadata classMetadata = reader.getClassMetadata();
//是否是接口
if (classMetadata.isInterface() && reader.getAnnotationMetadata().hasAnnotation(Mapper.class.getName())){
//生成MapperFactoryBean对象
AbstractBeanDefinition beanDefinition1 = BeanDefinitionBuilder.genericBeanDefinition(MapperFactoryBean.class)
//添加构造参数,Mapper接口名字
.addConstructorArgValue(classMetadata.getClassName())
//设置自动注入方式为根据类型注入
.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE)
.getBeanDefinition();
//根据Mapper接口生成beanName
AbstractBeanDefinition beanDefinition2 = BeanDefinitionBuilder.genericBeanDefinition(classMetadata.getClassName())
.getBeanDefinition();
String beanName = generator.generateBeanName(beanDefinition2, beanFactory);
//将bean注册到容器
beanFactory.registerBeanDefinition(beanName, beanDefinition1);
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException {
}
}
注意:不能通过beanDefinition1去生成beanName,beanDefinition1类型为MapperFactoryBean,由于得到的名字一样,都为mapperFactoryBean,for循环第二次会将第一次注册到容器的bean覆盖掉,所以这里根据Mapper接口生成beanName。(Spring底层也是这样实现)
总结:
-
Mapper 接口被 Spring 管理的本质:实际是被作为 MapperFactoryBean 注册到容器中
-
Spring 的诡异做法,根据接口生成的 BeanDefinition 仅为根据接口名生成 bean 名