【探索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;
    }
}

这里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 管理啦");
    }
}
@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 管理啦");
    }
}
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);
        }

    }
}

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

在这里插入图片描述

context.registerBean(ConfigurationClassPostProcessor.class); 

接着将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");
});

规定包扫描的路径

这时候运行程序,就可以发现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();
        }
    }
}

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

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

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

    @Bean
    public SqlSessionFactoryBean sqlSessionFactoryBean(DataSource dataSource) {
        SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
        sqlSessionFactoryBean.setDataSource(dataSource);
        return sqlSessionFactoryBean;
    }
    
  • 接着拿到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();
        }
    }
}

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;
}

但是这里是一个个地添加,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
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

起名方面没有灵感

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

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

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

打赏作者

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

抵扣说明:

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

余额充值