• 【探索Spring底层】4.BeanFactory后处理器


    【探索Spring底层】BeanFactory后处理器

    1.常见的工厂后处理器

    常见的工厂后处理器有

    • ConfigurationClassPostProcessor
    • MapperScannerConfigurer

    1.1 ConfigurationClassPostProcessor

    ConfigurationClassPostProcessor是Spring中最重要的后置处理器,没有之一

    它的作用是解析@ComponentScan @Bean @Import @ImportResource

    环境准备

    @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;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    这里com.itheima.a05.component包里面有三个Bean

    分别是Bean2、Bean3、Bean4

    @Component
    public class Bean2 {
    
        private static final Logger log = LoggerFactory.getLogger(Bean2.class);
    
        public Bean2() {
            log.debug("我被 Spring 管理啦");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    @Controller
    public class Bean3 {
    
        private static final Logger log = LoggerFactory.getLogger(Bean3.class);
    
        public Bean3() {
            log.debug("我被 Spring 管理啦");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    public class Bean4 {
    
        private static final Logger log = LoggerFactory.getLogger(Bean4.class);
    
        public Bean4() {
            log.debug("我被 Spring 管理啦");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    public class A05 {
        private static final Logger log = LoggerFactory.getLogger(A05.class);
    
        public static void main(String[] args) throws IOException {
    
            // ⬇️GenericApplicationContext 是一个【干净】的容器
            GenericApplicationContext context = new GenericApplicationContext();
            context.registerBean("config", Config.class);
    		
    		// ⬇️初始化容器
            context.refresh();
    
            for (String name : context.getBeanDefinitionNames()) {
                System.out.println(name);
            }
    
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    运行程序,这里只有config这个Bean

    在这里插入图片描述

    context.registerBean(ConfigurationClassPostProcessor.class); 
    
    • 1

    接着将ConfigurationClassPostProcessor这个BeanFactory后处理器注册到容器中

    再次运行程序

    在这里插入图片描述

    容器中多了Bean2和Bean3,说明@ComponentScan注解生效

    并且Config里面得三个@Bean注解也生效了


    1.2 MapperScannerConfigurer

    MapperScannerConfigurer这个作用是用来扫描Mybatis的@Mapper

    也就是解析@MapperScanner(但是这种用法已经少用了)

    这里准备了两个Mapper

    在这里插入图片描述

    context.registerBean(MapperScannerConfigurer.class, bd -> {
        bd.getPropertyValues().add("basePackage", "com.itheima.a05.mapper");
    });
    
    • 1
    • 2
    • 3

    规定包扫描的路径

    这时候运行程序,就可以发现Mapper1和Mapper2也被注册到容器中了

    在这里插入图片描述

    而下面的那些Bean后处理器则是MapperScannerConfigurer给我们添加的(间接)


    2. 手撕两个BeanFactory后处理器

    2.1 手撕ConfigurationClassPostProcessor

    首先创建一个类,实现BeanDefinitionRegistryPostProcessor接口,这里需要重写两个方法

    1. postProcessBeanFactory:是在context.refresh,也就是初始化容器的时候被调用
    2. postProcessBeanDefinitionRegistry:这个方法是在Bean定义信息即将被加载,但是Bean实例并未创建的时候执行的,在这个时机,就可以添加解析@ComponentScan注解的功能

    首先,第一步利用AnnotationUtils的findAnnotation在Config类中寻找注解@ComponentScan

    接着,第二步根据ComponentScan里面的basePackages,也就是包名,比如com.itheima.a05.component,拼接成路径,因为可能有很多个,所以需要用循环来遍历一下

    **然后,第三步new一个PathMatchingResourcePatternResolver对象,利用getResources获取上面拼接好的路径的资源,接着遍历这些资源(class文件)。**PathMatchingResourcePatternResolver是实现了ResourcePatternResolver接口的一个类,所以有getResources方法

    而第四步就是,利用CachingMetadataReaderFactory对象(也就是factory,前面已经new出来了,利用他读取class文件),利用它的getMetadataReader方法读取每一个class文件

    接下来第五步,利用前面getMetadataReader读取的每一个类源文件返回的MetadataReader对象中的getAnnotationMetadata方法读取这个类中的注解信息

    第六步则是判断这些注解信息里面是否有@Component注解(hasAnnotation方法)或者@Component的派生注解(hasMetaAnnotation方法,比如@Controller)

    第七步,如果有这些注解,就利用BeanDefinitionBuilder的genericBeanDefinition方法创建这个Bean,创建的时候需要指定类名,可以利用前面第四步获取的MetadataReader对象获取到类名(reader.getClassMetadata().getClassName()),最后getBeanDefinition就拿到Bean的定义了

    **第八步,利用AnnotationBeanNameGenerator工具(这个继承了BeanNameGenerator),分析@Component注解,然后给生成对应的Bean名字 **

    第九步,利用beanFactory将Bean注册到容器中

    public class ComponentScanPostProcessor implements BeanDefinitionRegistryPostProcessor {
        @Override // context.refresh
        public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException {
    
        }
    
        @Override
        public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry beanFactory) throws BeansException {
            try {
                //第一步
                ComponentScan componentScan = AnnotationUtils.findAnnotation(Config.class, ComponentScan.class);
                if (componentScan != null) {
                    //第二步
                    for (String p : componentScan.basePackages()) {
                        System.out.println(p);
                        String path = "classpath*:" + p.replace(".", "/") + "/**/*.class";
                        System.out.println(path);
                        //Spring的一个工具。也用来读取类的源文件
                        CachingMetadataReaderFactory factory = new CachingMetadataReaderFactory();
                        //第三步
                        Resource[] resources = new PathMatchingResourcePatternResolver().getResources(path);
                        AnnotationBeanNameGenerator generator = new AnnotationBeanNameGenerator();
                        for (Resource resource : resources) {
                            //第四步
                            MetadataReader reader = factory.getMetadataReader(resource);
                            //第五步
                            AnnotationMetadata annotationMetadata = reader.getAnnotationMetadata();
                            //第六步
                            if (annotationMetadata.hasAnnotation(Component.class.getName())
                                || annotationMetadata.hasMetaAnnotation(Component.class.getName())) {
                                //第七步
                                AbstractBeanDefinition bd = BeanDefinitionBuilder
                                    .genericBeanDefinition(reader.getClassMetadata().getClassName())
                                        .getBeanDefinition();
                                //第八步
                                String name = generator.generateBeanName(bd, beanFactory);
                                //第九步
                                beanFactory.registerBeanDefinition(name, bd);
                            }
                        }
                    }
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
    
    • 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

    2.2 模拟@Bean标注的工厂方法的解析

    想要解析@Bean注解标注的工厂方法,与解析@ComponentScan有点类似

    首先实现了BeanDefinitionRegistryPostProcessor接口,重写了postProcessBeanFactory方法和postProcessBeanDefinitionRegistry方法

    首先,新建一个CachingMetadataReaderFactory,这个类是用来读取源文件的,然后用getAnnotationMetadata拿到所有跟注解相关的源数据,这里返回结果是一个Set集合

    接着遍历这个结合,对里面的方法进行一系列的操作

    • 获取注解的属性,像上面的DruidDataSource就会有这个initMethod属性,可以借助get方法获取

      @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;
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
    • 创建BeanDefinitionBuilder,在容器的实现也用到了BeanDefinitionBuilder,那时候需要将指定类名或class文件,但是这里是利用工厂方式来创建Bean,所以不需要指定类名

    • 利用BeanDefinitionBuilder对象的setFactoryMethodOnBean设置工厂方法,第一个参数是参数为方法名字,既然是工厂方法,当然是要被调用才能创建出一个对象,所以需要new出这个工厂,然后利用这个工厂调用这个工厂方法,这样才能得到这个工厂方法生产出来的对象,所以第二个参数就是这个工厂config

    • 并且如果工厂方法有参数的话,例如需要开启自动装配才可以,默认是不开启自动装配的,所以参数的对象默认为null

      @Bean
      public SqlSessionFactoryBean sqlSessionFactoryBean(DataSource dataSource) {
          SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
          sqlSessionFactoryBean.setDataSource(dataSource);
          return sqlSessionFactoryBean;
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
    • 接着拿到AbstractBeanDefinition(利用BeanDefinitionBuilder对象的getBeanDefinition方法),将这个方法注入到beanFactory中即可

    public class AtBeanPostProcessor implements BeanDefinitionRegistryPostProcessor {
        @Override
        public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException {
    
        }
    
        @Override
        public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry beanFactory) throws BeansException {
            try {
                CachingMetadataReaderFactory factory = new CachingMetadataReaderFactory();
                //读取源文件,不走类加载,效率高
                MetadataReader reader = factory.getMetadataReader(new ClassPathResource("com/itheima/a05/Config.class"));
                //拿到相关getAnnotationMetadata
                //getAnnotationMetadata拿到所有跟注解相关的源数据
                Set<MethodMetadata> methods = reader.getAnnotationMetadata().getAnnotatedMethods(Bean.class.getName());
                for (MethodMetadata method : methods) {
                    System.out.println(method);
                    //获取注解属性
                    String initMethod = method.getAnnotationAttributes(Bean.class.getName()).get("initMethod").toString();
                    BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition();
                    //设置工厂方法
                    builder.setFactoryMethodOnBean(method.getMethodName(), "config");
                    //假如工厂方法有参数的时候,需要开启自动装配,默认是不开启自动装配的
                    builder.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_CONSTRUCTOR);
                    if (initMethod.length() > 0) {
                        builder.setInitMethodName(initMethod);
                    }
                    //注册工厂方法
                    //名字一般是这个工厂方法的名字
                    AbstractBeanDefinition bd = builder.getBeanDefinition();
                    beanFactory.registerBeanDefinition(method.getMethodName(), bd);
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
    
    • 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

    2.3 @Mapper注解的底层

    在Spring底层,@Mapper是这样实现的

    • new一个MapperFactoryBean,构造参数为添加了@Mapper注解的class
    • 给MapperFactoryBean设置SqlSessionFactory对象
    @Bean
    public MapperFactoryBean<Mapper1> mapper1(SqlSessionFactory sqlSessionFactory) {
        MapperFactoryBean<Mapper1> factory = new MapperFactoryBean<>(Mapper1.class);
        factory.setSqlSessionFactory(sqlSessionFactory);
        return factory;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    但是这里是一个个地添加,Spring底层也是这样的,不过比较麻烦。


    2.4 手撕MapperFactoryBean

    MapperFactoryBean的构建和前面@Bean这些的实现也是十分类似的

    • 首先获取class文件

    • 然后遍历这些文件

    • 利用CachingMetadataReaderFactory对象读class文件的信息

    • 接着读取类相关的信息

    • 判断类是否是接口

    • 是接口的话,使用BeanDefinitionBuilder的genericBeanDefinition方法将MapperFactoryBean定义Bean

    • 接着需要获取这个类的类名,作为构造参数;并且开启自动装配,因为这个工厂方法需要一个参数SqlSessionFactory对象

      在这里插入图片描述

    • 然后就利用根据接口的名字来生成一个AbstractBeanDefinition对象,但是这个AbstractBeanDefinition对象并不是添加到容器中的,只是为了生成名字,然后将这个名字作为beab的名字将bean注入到容器中

      • 注意:这里并不像前面那种利用AnnotationBeanNameGenerator对象来为bean构建一个bean名字,因为每个接口构建的BeanDefinition都是MapperFactoryBean,利用AnnotationBeanNameGenerator生成的bean名字都会是mapperFactoryBean,因此会导致其他接口无法注入到容器中。
    public class MapperPostProcessor implements BeanDefinitionRegistryPostProcessor {
    
        @Override
        public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry beanFactory) throws BeansException {
            try {
                PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
                //获取class文件
                Resource[] resources = resolver.getResources("classpath:com/itheima/a05/mapper/**/*.class");
                AnnotationBeanNameGenerator generator = new AnnotationBeanNameGenerator();
                CachingMetadataReaderFactory factory = new CachingMetadataReaderFactory();
                //遍历文件
                for (Resource resource : resources) {
                    //读取class信息
                    MetadataReader reader = factory.getMetadataReader(resource);
                    //获取类的信息
                    ClassMetadata classMetadata = reader.getClassMetadata();
                    //是否是接口
                    if (classMetadata.isInterface()) {
                        AbstractBeanDefinition bd = BeanDefinitionBuilder.genericBeanDefinition(MapperFactoryBean.class)
                                //获取类名,作为参数
                                .addConstructorArgValue(classMetadata.getClassName())
                                .setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE)
                                .getBeanDefinition();
                        AbstractBeanDefinition bd2 = BeanDefinitionBuilder.genericBeanDefinition(classMetadata.getClassName()).getBeanDefinition();
                        String name = generator.generateBeanName(bd2, beanFactory);
                        beanFactory.registerBeanDefinition(name, bd);
                    }
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
    
        }
    
        @Override
        public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) 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
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39

  • 相关阅读:
    深入理解 Document Load 和 Document Ready 的区别
    了解5个区别,FPmarkets用烛台和Renko图实现交易翻倍
    上周热点回顾(9.12-9.18)
    redis数据库
    Linux-组管理和权限管理
    ESB+MDM预置样例测试总结
    B - 缺失的数据范围
    【input 身份证号】星号 代替,input 切割成 多个 小格格(类似)
    Node学习四 —— 函数执行规划
    cf #833 Div.2(A-D)
  • 原文地址:https://blog.csdn.net/weixin_51146329/article/details/126647690