Spring原理学习(三)BeanFactory后处理器

学习目标:

  1. BeanFactory后处理器的作用:为BeanFactory提供扩展
  2. 常见的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 

 总结

  1. @ComponentScan, @Bean, @Mapper 等注解的解析属于核心容器(即 BeanFactory)的扩展功能

  2. 这些扩展功能由不同的 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没有

实现步骤:

  1. 查找 Config类 里是否有 @ComponentScan 注解,如果有的话获取到 basePackages 属性的值,将包路径转换为类路径。
  2. 通过PathMatchingResourcePatternResolver获取到路径上的资源
  3. 通过CachingMetadataReaderFactory对资源读取,读取注解的元信息,查看是否添加@Component及其派生注解
  4. 最后将其注册到容器
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

  总结:

  1. Spring 操作元数据的工具类 CachingMetadataReaderFactory

  2. 通过注解元数据(AnnotationMetadata)获取直接或间接标注的注解信息

  3. 通过类元数据(ClassMetadata)获取类名,AnnotationBeanNameGenerator 生成 bean 名

  4. 解析元数据是基于 ASM 技术

4、模拟解析 @Bean

实现步骤:

  1. 通过CachingMetadataReaderFactory对指定类路径的资源进行读取,获取到类中标注着@Bean的方法
  2. 通过BeanDefinitionBuilder生成对应的Bean
  3. 注册到容器中
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。

实现步骤:

  1. 通过PathMatchingResourcePatternResolver获取mapper包下的资源
  2. 通过CachingMetadataReaderFactory读取类的元信息,对标有@Mapper的接口生成MapperFactoryBean对象。
  3. 注册到容器
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底层也是这样实现)

总结:

  1. Mapper 接口被 Spring 管理的本质:实际是被作为 MapperFactoryBean 注册到容器中

  2. Spring 的诡异做法,根据接口生成的 BeanDefinition 仅为根据接口名生成 bean 名

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小鲁蛋儿

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值