Spring中常见的BeanFactory后处理器

常见的BeanFacatory后处理器

先给出没有添加任何BeanFactory后处理器的测试代码

java 复制代码
public class TestBeanFactoryPostProcessor {
    public static void main(String[] args) {
        GenericApplicationContext context = new GenericApplicationContext();
        context.registerBean("config", Config.class);

        context.refresh();

        for (String beanDefinitionName : context.getBeanDefinitionNames()) {
            System.out.println(beanDefinitionName);
        }

        context.close();
    }
}

在配置类中我们编写了如下信息

java 复制代码
@Configuration
@ComponentScan("com.zmt.test5")
public class Config {

    @Bean
    public Bean2 bean2(){
        return new Bean2();
    }

    @Bean
    public SqlSessionFactoryBean sqlSessionFactoryBean(DataSource dataSource){
        SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
        sqlSessionFactoryBean.setDataSource(dataSource);
        return sqlSessionFactoryBean;
    }

    @Bean
    public DruidDataSource dataSource(){
        DruidDataSource dataSource = new DruidDataSource();
        dataSource.setUrl("jdbc:mysql://localhost:3306/test");
        dataSource.setUsername("root");
        dataSource.setPassword("123456");
        return dataSource;
    }
}

同时还有一个Bean1添加了@Component注解并且能够被扫描到,所以理论上来讲,我们可以观察到五个beanName,那么执行测试代码观察输出结果

可以看到,这里只输出了一个beanName,我们可以推测出其他注解没有生效,那么接下来我们将常用的BeanFactory后处理器也注册到BeanFactory后,观察输出结果

java 复制代码
public class TestBeanFactoryPostProcessor {
    public static void main(String[] args) {
        GenericApplicationContext context = new GenericApplicationContext();
        context.registerBean("config", Config.class);
        //添加BeanFactory后处理器
        context.registerBean(ConfigurationClassPostProcessor.class);//用来解析 @ComponentScan @Bean @Import @ImportResource注解
        context.registerBean(MapperScannerConfigurer.class,bd -> {
            bd.getPropertyValues().add("basePackage","com.zmt.test.mapper");
        });//扫描Mapper,相当于@MapperScan注解

        context.refresh();

        for (String beanDefinitionName : context.getBeanDefinitionNames()) {
            System.out.println(beanDefinitionName);
        }

        context.close();
    }
}

添加Spring提供的BeanFactory后处理器之后,可以正常将Bean对象添加到context容器当中了,执行结果如下

接下来我们模拟实现在BeanFactory后处理器当中具体如何解析这些注解。

模拟实现组件扫描

首先是模拟实现@ComponentScan注解是如何扫描包,获取类资源的

java 复制代码
public class TestBeanFactoryPostProcessor {
    public static void main(String[] args) throws IOException {
        GenericApplicationContext context = new GenericApplicationContext();
        context.registerBean("config", Config.class);
        //模拟ConfigurationPostProcessor处理器中如何解析@ComponentScan注解
        ComponentScan componentScan = AnnotationUtils.findAnnotation(Config.class, ComponentScan.class);
        if (componentScan != null) {
            for (String s : componentScan.basePackages()) {
                //将com.zmt.test5.bean转化格式为classpath*:com/zmt/test5/bean/**/*.class
                String path = "classpath*:"+s.replace(".", "/")+"/**/*.class";
                //创建出一个元数据读取工厂,用来读取类资源信息
                CachingMetadataReaderFactory factory = new CachingMetadataReaderFactory();
                //通过getResource方法获取到path中的所有类资源
                Resource[] resources = context.getResources(path);
                for (Resource resource : resources) {
                    //读取类资源信息
                    MetadataReader reader = factory.getMetadataReader(resource);
                    System.out.println("类名:"+reader.getClassMetadata());
                    System.out.println("是否添加了@Component注解:"+reader.getAnnotationMetadata().hasAnnotation(Component.class.getName()));
                    System.out.println("是否添加了@Component的派生注解:"+reader.getAnnotationMetadata().hasMetaAnnotation(Component.class.getName()));
                }
            }
        }

        context.refresh();

        for (String beanDefinitionName : context.getBeanDefinitionNames()) {
            System.out.println(beanDefinitionName);
        }

        context.close();
    }
}

运行结果如下,可以正常识别类上是否添加了@Component注解或是派生注解

能够扫描到类上添加的注解之后,我们是需要将注解添加到BeanDefinitionMap当中去的,那么继续完善我们的测试方法

java 复制代码
public class TestBeanFactoryPostProcessor {
    public static void main(String[] args) throws IOException {
        GenericApplicationContext context = new GenericApplicationContext();
        context.registerBean("config", Config.class);

        //模拟ConfigurationPostProcessor处理器中如何解析@ComponentScan注解
        ComponentScan componentScan = AnnotationUtils.findAnnotation(Config.class, ComponentScan.class);
        if (componentScan != null) {
            for (String s : componentScan.basePackages()) {
                //将com.zmt.test5转化格式为classpath*:com/zmt/test5/**/*.class
                String path = "classpath*:" + s.replace(".", "/") + "/**/*.class";
                //创建出一个元数据读取工厂,用来读取类资源信息
                CachingMetadataReaderFactory factory = new CachingMetadataReaderFactory();
                //通过getResource方法获取到path中的所有类资源
                Resource[] resources = context.getResources(path);
                //创建Bean名称生成器
                AnnotationBeanNameGenerator generator = new AnnotationBeanNameGenerator();
                for (Resource resource : resources) {
                    //读取类资源信息
                    MetadataReader reader = factory.getMetadataReader(resource);
                    System.out.println("类名1:" + reader.getClassMetadata());
                    AnnotationMetadata annotationMetadata = reader.getAnnotationMetadata();
                    System.out.println("是否添加了@Component注解:" + annotationMetadata.hasAnnotation(Component.class.getName()));
                    System.out.println("是否添加了@Component的派生注解:" + annotationMetadata.hasMetaAnnotation(Component.class.getName()));

                    if (annotationMetadata.hasAnnotation(Component.class.getName()) ||
                            annotationMetadata.hasMetaAnnotation(Component.class.getName())) {
                        //如果该类添加了注解,需要添加到BeanDefinitionMap当中去,生成BeanDefinition对象
                        AbstractBeanDefinition bd = BeanDefinitionBuilder.genericBeanDefinition(reader.getClassMetadata().getClassName())
                                .getBeanDefinition();
                        DefaultListableBeanFactory beanFactory = context.getDefaultListableBeanFactory();
                        //生成Bean名称
                        String name = generator.generateBeanName(bd,beanFactory);
                        System.out.println("name:"+name);
                        //将BeanDefinition注册到beanFactory
                        beanFactory.registerBeanDefinition(name,bd);
                    }
                }
            }
        }

        context.refresh();

        for (String beanDefinitionName : context.getBeanDefinitionNames()) {
            System.out.println(beanDefinitionName);
        }

        context.close();
    }
}

我们通过打断点查看beanFactory中是否将BeanDefinition信息注册到BeanDefinitionMap当中去,结果如下

至此已经实现了组件扫描,但是目前我们的实现是在容器初始化 [refresh()方法] 之前就做好了,我们应该将这些实现抽取到一个BeanFactory后处理器当中,等待refresh()方法回调,因此我们将这些实现代码放入一个组件扫描后处理器。

java 复制代码
public class ComponentScanPostProcessor implements BeanFactoryPostProcessor {
    @Override //在执行context.refresh()方法时回调该方法
    public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException {
        try {
            ComponentScan componentScan = AnnotationUtils.findAnnotation(Config.class, ComponentScan.class);
            if (componentScan != null) {
                for (String s : componentScan.basePackages()) {
                    //将com.zmt.test5转化格式为classpath*:com/zmt/test5/**/*.class
                    String path = "classpath*:" + s.replace(".", "/") + "/**/*.class";
                    //创建出一个元数据读取工厂,用来读取类资源信息
                    CachingMetadataReaderFactory factory = new CachingMetadataReaderFactory();
                    //通过getResource方法获取到path中的所有类资源
                    Resource[] resources = new PathMatchingResourcePatternResolver().getResources(path);
                    //创建Bean名称生成器
                    AnnotationBeanNameGenerator generator = new AnnotationBeanNameGenerator();
                    for (Resource resource : resources) {
                        //读取类资源信息
                        MetadataReader reader = factory.getMetadataReader(resource);
                        System.out.println("类名1:" + reader.getClassMetadata());
                        AnnotationMetadata annotationMetadata = reader.getAnnotationMetadata();
                        System.out.println("是否添加了@Component注解:" + annotationMetadata.hasAnnotation(Component.class.getName()));
                        System.out.println("是否添加了@Component的派生注解:" + annotationMetadata.hasMetaAnnotation(Component.class.getName()));

                        if (annotationMetadata.hasAnnotation(Component.class.getName()) ||
                                annotationMetadata.hasMetaAnnotation(Component.class.getName())) {
                            //如果该类添加了注解,需要添加到BeanDefinitionMap当中去,生成BeanDefinition对象
                            AbstractBeanDefinition bd = BeanDefinitionBuilder.genericBeanDefinition(reader.getClassMetadata().getClassName())
                                    .getBeanDefinition();
                            if (configurableListableBeanFactory instanceof DefaultListableBeanFactory){
                                DefaultListableBeanFactory beanFactory = (DefaultListableBeanFactory) configurableListableBeanFactory;
                                String name = generator.generateBeanName(bd,beanFactory);
                                System.out.println("name:"+name);
                                beanFactory.registerBeanDefinition(name,bd);
                            }
                        }
                    }
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

修改此时的测试代码,将我们自定义的BeanFactory后处理器注册到context当中

java 复制代码
public class TestBeanFactoryPostProcessor {
    public static void main(String[] args) throws IOException {
        GenericApplicationContext context = new GenericApplicationContext();
        context.registerBean("config", Config.class);
        //模拟ConfigurationPostProcessor处理器中如何解析@ComponentScan注解
        context.registerBean(ComponentScanPostProcessor.class);

        context.refresh();

        for (String beanDefinitionName : context.getBeanDefinitionNames()) {
            System.out.println(beanDefinitionName);
        }

        context.close();
    }
}

模拟实现@Bean注册

java 复制代码
public class TestBeanFactoryPostProcessor {
    public static void main(String[] args) throws IOException {
        GenericApplicationContext context = new GenericApplicationContext();
        context.registerBean("config", Config.class);
        //模拟@Bean注解解析
        //@Bean注解的解析与组件扫描不太一样,@Bean的解析是将配置类当作工厂类,而被Bean注解修饰的方法被当作工厂方法
        //用来读取配置类信息
        CachingMetadataReaderFactory factory = new CachingMetadataReaderFactory();
        MetadataReader reader = factory.getMetadataReader(Config.class.getName());
        //获取被注解修饰的方法中被Bean注解修饰的方法
        Set<MethodMetadata> methods = reader.getAnnotationMetadata().getAnnotatedMethods(Bean.class.getName());
        for (MethodMetadata method : methods) {
            System.out.println(method);
            //因为是通过Config调用那些方法,因此这里不用指定类的名称,而是通过我们手动set设置bd的名称
            BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition();
            //指定工厂类Config通过该类获取工厂方法,这里给工厂类的beanName即可
            builder.setFactoryMethodOnBean(method.getMethodName(),"config");
            AbstractBeanDefinition bd = builder.getBeanDefinition();
            //将BeanDefinition加入到BeanFactory
            context.registerBeanDefinition(method.getMethodName(),bd);

        }

        context.refresh();

        for (String beanDefinitionName : context.getBeanDefinitionNames()) {
            System.out.println(beanDefinitionName);
        }

        context.close();
    }
}

表面上以上代码就可以实现@Bean注解的解析,但实际上运行会出现如下错误

在创建sqlSessionFactoryBean时失败,这是因为在创建该对象时,需要注入一个参数,那么我们需要在创建BeanDefinition时需要指定Bean的装配模式

但是还是存在一个问题,那就是目前无法解析@Bean注解中的属性,比如说dataSource方法上的Bean注解中指定的初始化方法我们没有办法解析执行,因此,我们接着完善上面的代码,同时我们将这些实现代码同样抽取到一个BeanFactory后处理当中,那么具体的实现代码如下

java 复制代码
public class AtBeanPostProcessor implements BeanFactoryPostProcessor {
    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        try {
            CachingMetadataReaderFactory factory = new CachingMetadataReaderFactory();
            MetadataReader reader = factory.getMetadataReader(Config.class.getName());
            //获取被注解修饰的方法中被Bean注解修饰的方法
            Set<MethodMetadata> methods = reader.getAnnotationMetadata().getAnnotatedMethods(Bean.class.getName());
            for (MethodMetadata method : methods) {
                System.out.println(method);
                //获取方法上的注解属性信息,得到map集合后通过get方法获取初始化方法的方法名
                String initMethod = method.getAnnotationAttributes(Bean.class.getName()).get("initMethod").toString();
                //因为是通过Config调用那些方法,因此这里不用指定类的名称,而是通过我们手动set设置bd的名称
                BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition();
                //指定工厂类Config通过该类获取工厂方法,这里给工厂类的beanName即可
                builder.setFactoryMethodOnBean(method.getMethodName(),"config");
                //如果是工厂方法的参数需要自动装配那么需要指定装配模式
                builder.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_CONSTRUCTOR);
                if (initMethod.length()>0){
                    //说明当前@Bean注解添加了initMethod属性
                    builder.setInitMethodName(initMethod);
                }
                AbstractBeanDefinition bd = builder.getBeanDefinition();
                //将BeanDefinition加入到BeanFactory
                DefaultListableBeanFactory context = (DefaultListableBeanFactory) beanFactory;
                context.registerBeanDefinition(method.getMethodName(),bd);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

模拟实现MapperScan

在实现MapperScan之前,我们先通过配置类了解一下Spring底层是如何将接口创建对象并加入beanFactory当中的

java 复制代码
@Configuration
@ComponentScan("com.zmt.test5.bean")
public class Config {
    //...上面已经展示过的内容
    
    @Bean
    public MapperFactoryBean<Mapper1> mapper1(SqlSessionFactory sqlSessionFactory){
        MapperFactoryBean<Mapper1> factoryBean = new MapperFactoryBean<>(Mapper1.class);
        factoryBean.setSqlSessionFactory(sqlSessionFactory);
        return factoryBean;
    }

    @Bean
    public MapperFactoryBean<Mapper2> mapper2(SqlSessionFactory sqlSessionFactory){
        MapperFactoryBean<Mapper2> factoryBean = new MapperFactoryBean<>(Mapper2.class);
        factoryBean.setSqlSessionFactory(sqlSessionFactory);
        return factoryBean;
    }
}

这样添加Mapper存在一个问题,就是无法批量添加,只要存在一个Mapper接口就需要编写一个@Bean注解。因此我们需要实现类似于ComponentScan的注解一样,实现Mapper注解的扫描,这里我们就直接将实现代码写在BeanFactory后处理器当中了

java 复制代码
public class MapperPostProcessor implements BeanDefinitionRegistryPostProcessor {
    @Override
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry beanFactory) throws BeansException {
        try {
            //首先实现扫描包
            PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
            Resource[] resources = resolver.getResources("classpath*:com/zmt/test5/mapper/*.class");
            //获取类信息
            CachingMetadataReaderFactory factory = new CachingMetadataReaderFactory();
            AnnotationBeanNameGenerator generator = new AnnotationBeanNameGenerator();
            for (Resource resource : resources) {
                MetadataReader reader = factory.getMetadataReader(resource);
                ClassMetadata classMetadata = reader.getClassMetadata();
                if (classMetadata.isInterface()) {
                    //判断该类是否是接口,如果是接口那么再进行下一步
                    //生成bd,我们是通过MapperFactoryBean获取接口对象的,因此这里指定MapperFactoryBean类名
                    AbstractBeanDefinition bdFactoryBean = BeanDefinitionBuilder.genericBeanDefinition(MapperFactoryBean.class)
                            //设置MapperFactoryBean构造方法中的需要生成的bean对象
                            .addConstructorArgValue(classMetadata.getClassName())
                            //需要注入sqlSessionFactory对象,这里选择根据类型注入
                            .setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE)
                            .getBeanDefinition();
                    //生成bean名称需要依赖genericBeanDefinition中的指定的通用Bean的className
                    //因为是根据MapperFactoryBean一个类生成所有的Mapper对象,因此采用MapperFactoryBean的bd生成beanName时
                    //会导致生成的都是同一个名称,从而导致后者bd覆盖前者bd,所以我们再次生成一个bd,这里保存的ClassName是各个Mapper类名
                    //在生成beanName时,bd选择各个Mapper接口生成的bd
                    AbstractBeanDefinition bd = BeanDefinitionBuilder.genericBeanDefinition(classMetadata.getClassName()).getBeanDefinition();
                    String name = generator.generateBeanName(bd, beanFactory);
                    //注册到beanFactory中的还是bdFactoryBean
                    beanFactory.registerBeanDefinition(name,bdFactoryBean);
                }
            }

        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {

    }
}

通过断点观察,我们可以知道beanName是各个mapper接口类名,但是beanClass还是MapperFactoryBean

需要注意的是,我们在创建Mapper的bd时,需要注入sqlSessionFactory对象,因此,我们需要确保Spring中存在该对象,因此,我们需要将上文中的AtBeanPostProcessor处理器也要执行

相关推荐
测开小菜鸟29 分钟前
使用python向钉钉群聊发送消息
java·python·钉钉
P.H. Infinity1 小时前
【RabbitMQ】04-发送者可靠性
java·rabbitmq·java-rabbitmq
生命几十年3万天1 小时前
java的threadlocal为何内存泄漏
java
caridle2 小时前
教程:使用 InterBase Express 访问数据库(五):TIBTransaction
java·数据库·express
^velpro^2 小时前
数据库连接池的创建
java·开发语言·数据库
苹果醋32 小时前
Java8->Java19的初步探索
java·运维·spring boot·mysql·nginx
秋の花2 小时前
【JAVA基础】Java集合基础
java·开发语言·windows
小松学前端2 小时前
第六章 7.0 LinkList
java·开发语言·网络
Wx-bishekaifayuan2 小时前
django电商易购系统-计算机设计毕业源码61059
java·spring boot·spring·spring cloud·django·sqlite·guava
customer082 小时前
【开源免费】基于SpringBoot+Vue.JS周边产品销售网站(JAVA毕业设计)
java·vue.js·spring boot·后端·spring cloud·java-ee·开源