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处理器也要执行

相关推荐
特立独行的猫a2 分钟前
11款常用C++在线编译与运行平台推荐与对比
java·开发语言·c++
louisgeek12 分钟前
Java 位运算
java
hweiyu001 小时前
Maven 私库
java·maven
Boilermaker19921 小时前
【Java EE】SpringIoC
前端·数据库·spring
Super Rookie1 小时前
Spring Boot 企业项目技术选型
java·spring boot·后端
写不出来就跑路1 小时前
Spring Security架构与实战全解析
java·spring·架构
ZeroNews内网穿透2 小时前
服装零售企业跨区域运营难题破解方案
java·大数据·运维·服务器·数据库·tcp/ip·零售
sleepcattt2 小时前
Spring中Bean的实例化(xml)
xml·java·spring
lzzy_lx_20892 小时前
Spring Boot登录认证实现学习心得:从皮肤信息系统项目中学到的经验
java·spring boot·后端
Dcs2 小时前
立即卸载这些插件,别让它们偷你的资产!
java