gpt4 book ai didi

不一样的视角来学习Spring源码之容器与Bean---下

转载 作者:知者 更新时间:2024-03-13 03:27:47 24 4
gpt4 key购买 nike

不一样的视角来学习Spring源码之容器与Bean—上

BeanFactory 后处理器

BeanFactory 后处理器的作用

工厂Bean后置处理器,主要是用来向容器中添加一些Bean的定义信息

下面给出的是演示过程中,可能会用到的依赖:

<dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.1.4</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jdbc</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.1.17</version>
        </dependency>

例子:

@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 Bean1 {

    private static final Logger log = LoggerFactory.getLogger(Bean1.class);

    public Bean1() {
        log.debug("我被 Spring 管理啦");
    }
}
@Mapper
public interface Mapper1 {
}

@Mapper
public interface Mapper2 {
}

public class Mapper3 {
}
@Configuration
@ComponentScan("com.two")
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;
    }
}
/*
    BeanFactory 后处理器的作用
 */
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);
        }

        // ⬇️销毁容器
        context.close();

    }
}

此时容器中只有一个Bean,显然config配置类的注解都没有被解析,此时就需要添加工厂后置处理器来解析这些注解了,从而可以往容器中添加更多的bean的定义信息

ConfigurationClassPostProcessor负责解析@ComponentScan @Bean @Import @ImportResource

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

        // @ComponentScan @Bean @Import @ImportResource
        context.registerBean(ConfigurationClassPostProcessor.class);

        // ⬇️初始化容器
        context.refresh();

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

        // ⬇️销毁容器
        context.close();

    }
}

可以看出ConfigurationClassPostProcessor还能把@Component以及其衍生注解标注的类,如果被扫描到后,都会被放入容器中

MapperScannerConfigurer负责扫描mapperScan注解

/*
    BeanFactory 后处理器的作用
 */
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);

        // @ComponentScan @Bean @Import @ImportResource
        context.registerBean(ConfigurationClassPostProcessor.class);
        // @MapperScan注解
        context.registerBean(MapperScannerConfigurer.class, bd -> {
            //指定扫描的包名
            bd.getPropertyValues().add("basePackage", "com.two.mapper");
        });

        // ⬇️初始化容器
        context.refresh();

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

        // ⬇️销毁容器
        context.close();

    }
}

MapperScannerConfigurer还会额外干些事情

收获💡

  • ConfigurationClassPostProcessor 可以解析

  • @ComponentScan

  • @Bean

  • @Import

  • @ImportResource

  • MapperScannerConfigurer 可以解析

  • Mapper 接口

  1. @ComponentScan, @Bean, @Mapper 等注解的解析属于核心容器(即 BeanFactory)的扩展功能
  2. 这些扩展功能由不同的 BeanFactory 后处理器来完成,其实主要就是补充了一些 bean 定义

模拟解析 @ComponentScan

这里不会去直接翻阅其源码,而是通过引用spring源码中一些类的方法,来基本还原大致的解析过程

  • 自定义一个ComponentScanPostProcessor负责解析@ComponentScan注解,而不使用spring提供的ConfigurationClassPostProcessor工厂bean后置处理器进行解析

public class ComponentScanPostProcessor implements BeanDefinitionRegistryPostProcessor {
    /**
     * 该方法会在context.refresh中被调用
     */
    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException {

    }

    @Override
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry beanFactory) throws BeansException {
        try {
            //这里按理来说,应该从context中或取到外部传入的config配置类对象才对
            //AnnotationUtils获取Config配置类上的ComponentScan注解,如果没有标注范围null
            ComponentScan componentScan = AnnotationUtils.findAnnotation(Config.class, ComponentScan.class);
            //如果配置类标注了该注解
            if (componentScan != null) {
                //获取注解中标注的basePackages属性标注扫描的包路径
                for (String p : componentScan.basePackages()) {
                    //输出需要扫描的包路径
                    System.out.println("需要扫描的包路径为: "+p);
                    // com.dhy.component -> classpath*:com/dhy/component/**/*.class
                    // /**/*.class表示当前包和子包下面所有的类
                    String path = "classpath*:" + p.replace(".", "/") + "/**/*.class";
                    //输出该转换后的资源路径
                    System.out.println("转换后的资源路径: "+path);
                    //读取类的原信息工厂---可以从二进制字节码文件中读取出相关信息---底层使用了asm字节码重组技术
                    CachingMetadataReaderFactory factory = new CachingMetadataReaderFactory();
                    //通过PathMatchingResourcePatternResolver去定位符合这些路径的资源集合
                    Resource[] resources = new PathMatchingResourcePatternResolver().getResources(path);
                    //负责解析@Component注解中标注的beanName
                    AnnotationBeanNameGenerator generator = new AnnotationBeanNameGenerator();
                    for (Resource resource : resources) {
                        //System.out.println(resource);
                        //当前resource是一个class文件
                        //通过原信息工厂获取到原信息读取器
                        MetadataReader reader = factory.getMetadataReader(resource);
                        System.out.println("类名:" + reader.getClassMetadata().getClassName());
                        //从二进制class文件中读取出当前类上的注解信息
                        AnnotationMetadata annotationMetadata = reader.getAnnotationMetadata();
                        System.out.println("是否加了 @Component:" + annotationMetadata.hasAnnotation(Component.class.getName()));
                        //例如:@Controller注解上加了@Component注解
                        System.out.println("是否加了 @Component 派生:" + annotationMetadata.hasMetaAnnotation(Component.class.getName()));
                        //只选择加了Component或者其派生注解
                        if (annotationMetadata.hasAnnotation(Component.class.getName())
                            || annotationMetadata.hasMetaAnnotation(Component.class.getName())) {
                            //将扫描到的符合条件的class包装成一个BeanDefinition
                            AbstractBeanDefinition bd = BeanDefinitionBuilder
                                    //传入class类名
                                    .genericBeanDefinition(reader.getClassMetadata().getClassName())
                                    .getBeanDefinition();
                            //通过传入的BeanDefinition生成一个beanName
                            String name = generator.generateBeanName(bd, beanFactory);
                            //注册BeanDefinition进BeanFactory--传入BeanName,BeanDefinition
                            beanFactory.registerBeanDefinition(name, bd);
                        }
                    }
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

将我们自定义的ComponentScanPostProcessor注册到Bean工厂中:

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

        // 解析 @ComponentScan
        //自定义的ComponentScanPostProcessor,专门解析ComponentScan注解
        context.registerBean(ComponentScanPostProcessor.class);

        // ⬇️初始化容器
        context.refresh();

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

        // ⬇️销毁容器
        context.close();

    }
}

输出:

13:06:16.413 [main] DEBUG org.springframework.context.support.GenericApplicationContext - Refreshing org.springframework.context.support.GenericApplicationContext@2d363fb3
13:06:16.442 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'com.two.ComponentScanPostProcessor'
需要扫描的包路径为: com.two
转换后的资源路径: classpath*:com/two/**/*.class
类名:com.two.A05
是否加了 @Component:false
是否加了 @Component 派生:false
类名:com.two.AtBeanPostProcessor
是否加了 @Component:false
是否加了 @Component 派生:false
类名:com.two.Bean1
是否加了 @Component:false
是否加了 @Component 派生:false
类名:com.two.ComponentScanPostProcessor
是否加了 @Component:false
是否加了 @Component 派生:false
类名:com.two.Config
是否加了 @Component:false
是否加了 @Component 派生:true
13:06:16.586 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Overriding bean definition for bean 'config' with a different definition: replacing [Root bean: class [com.two.Config]; scope=; abstract=false; lazyInit=null; autowireMode=0; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=null; factoryMethodName=null; initMethodName=null; destroyMethodName=null] with [Generic bean: class [com.two.Config]; scope=; abstract=false; lazyInit=null; autowireMode=0; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=null; factoryMethodName=null; initMethodName=null; destroyMethodName=null]
类名:com.two.MapperPostProcessor
是否加了 @Component:false
是否加了 @Component 派生:false
类名:com.two.component.Bean2
是否加了 @Component:true
是否加了 @Component 派生:false
类名:com.two.component.Bean3
是否加了 @Component:false
是否加了 @Component 派生:true
类名:com.two.component.Bean4
是否加了 @Component:false
是否加了 @Component 派生:false
类名:com.two.mapper.Mapper1
是否加了 @Component:false
是否加了 @Component 派生:false
类名:com.two.mapper.Mapper2
是否加了 @Component:false
是否加了 @Component 派生:false
类名:com.two.mapper.Mapper3
是否加了 @Component:false
是否加了 @Component 派生:false
13:06:16.607 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'config'
13:06:16.607 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'bean2'
13:06:16.607 [main] DEBUG com.two.component.Bean2 - 我被 Spring 管理啦
13:06:16.608 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'bean3'
13:06:16.608 [main] DEBUG com.two.component.Bean3 - 我被 Spring 管理啦
config
com.two.ComponentScanPostProcessor
//这里看出我们自定义的ComponentScanPostProcessor生效了
bean2
bean3
13:06:16.634 [main] DEBUG org.springframework.context.support.GenericApplicationContext - Closing org.springframework.context.support.GenericApplicationContext@2d363fb3, started on Sun Mar 27 13:06:16 CST 2022

Process finished with exit code 0

收获💡

  1. Spring 操作元数据的工具类 CachingMetadataReaderFactory
  2. 通过注解元数据(AnnotationMetadata)获取直接或间接标注的注解信息
  3. 通过类元数据(ClassMetadata)获取类名,AnnotationBeanNameGenerator 生成 bean 名
  4. 解析元数据是基于 ASM 技术

工厂Bean后置处理器是在refresh方法中,每个节点被调用的,而Bean的后置处理器是在getBean方法执行Bean生命周期过程中被调用的,前者主要是为了向容器中增加Bean的定义信息,后者是为了干预Bean生命周期中各个阶段的行为。

模拟解析 @Bean

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();
            //推荐使用这种方式读取,因为他是直接通过asm读取字节码,不会走反射过程和类加载过程
            //这里直接写死配置类的路径了
               MetadataReader reader = factory.getMetadataReader(new ClassPathResource("com/two/Config.class"));
            //获取当前配置类中被@Bean注解标注的方法
            Set<MethodMetadata> methods = reader.getAnnotationMetadata().getAnnotatedMethods(Bean.class.getName());
            //遍历这些方法
            for (MethodMetadata method : methods) {
                System.out.println("当前被@Bean注解标注的方法名为: "+method);
                //获取@Bean注解中的属性
                Map<String, Object> annotationAttributes = method.getAnnotationAttributes(Bean.class.getName());
                //找到initMethod属性对应的值
                String initMethod = annotationAttributes.get("initMethod").toString();
                //下面针对每个一个方法生成一个BeanDefinition
                BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition();
                //我们需要把这些被@Bean标注的方法变成工厂方法---工厂方法负责生产产品
                //设置工厂方法名和当前工厂方法属于的对象
                builder.setFactoryMethodOnBean(method.getMethodName(), "config");
                //设置自动装配模式,默认为AbstractBeanDefinition.AUTOWIRE_NO
                //如果工厂方法上有参数,那么参数值是否需要自动注入? ---这就是自动装配模式需要管的事情
                //对于工厂方法的参数而言,自动装配模式应该是构造方法的自动注入
                builder.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_CONSTRUCTOR);
                //设置初始化方法名称
                if (initMethod.length() > 0) {
                    builder.setInitMethodName(initMethod);
                }
                //生成BeanDefinition
                AbstractBeanDefinition bd = builder.getBeanDefinition();
                //注册BeanDefinition到BeanFactory
                beanFactory.registerBeanDefinition(method.getMethodName(), bd);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

将自定义的AtBeanPostProcessor注册到BeanFactory中

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);
        //注册自定义的工厂Bean后置处理器
        context.registerBean(AtBeanPostProcessor.class);
        // ⬇️初始化容器
        context.refresh();

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

        // ⬇️销毁容器
        context.close();

    }
}

输出:

13:27:15.227 [main] DEBUG org.springframework.context.support.GenericApplicationContext - Refreshing org.springframework.context.support.GenericApplicationContext@2d363fb3
13:27:15.257 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'com.two.AtBeanPostProcessor'
当前被@Bean注解标注的方法名为: com.two.Config.bean1()
当前被@Bean注解标注的方法名为: com.two.Config.sqlSessionFactoryBean(javax.sql.DataSource)
当前被@Bean注解标注的方法名为: com.two.Config.dataSource()
13:27:15.365 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'config'
13:27:15.365 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'bean1'
13:27:15.366 [main] DEBUG com.two.Bean1 - 我被 Spring 管理啦
13:27:15.366 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'sqlSessionFactoryBean'
13:27:15.372 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'dataSource'
13:27:15.488 [main] INFO com.alibaba.druid.pool.DruidDataSource - {dataSource-1} inited
13:27:15.491 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Autowiring by type from bean name 'sqlSessionFactoryBean' via factory method to bean named 'dataSource'
13:27:15.497 [main] DEBUG org.apache.ibatis.logging.LogFactory - Logging initialized using 'class org.apache.ibatis.logging.slf4j.Slf4jImpl' adapter.
13:27:15.502 [main] DEBUG org.mybatis.spring.SqlSessionFactoryBean - Property 'configuration' or 'configLocation' not specified, using default MyBatis Configuration
13:27:15.545 [main] DEBUG org.mybatis.spring.SqlSessionFactoryBean - Property 'mapperLocations' was not specified.
//自定义的工厂Bean后置处理器生效了
config
com.two.AtBeanPostProcessor
bean1
sqlSessionFactoryBean
dataSource
13:27:15.571 [main] DEBUG org.springframework.context.support.GenericApplicationContext - Closing org.springframework.context.support.GenericApplicationContext@2d363fb3, started on Sun Mar 27 13:27:15 CST 2022
13:27:15.572 [main] INFO com.alibaba.druid.pool.DruidDataSource - {dataSource-1} closing ...
13:27:15.572 [main] INFO com.alibaba.druid.pool.DruidDataSource - {dataSource-1} closed

收获💡

  1. 进一步熟悉注解元数据(AnnotationMetadata)获取方法上注解信息

模拟解析 Mapper 接口

我们先看一下mybaits是如何处理这个mapper接口的

@Configuration
@ComponentScan(basePackages = "com.two")
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;
    }

    @Bean
    public MapperFactoryBean<Mapper1> mapper1(SqlSessionFactory sqlSessionFactory) {
        MapperFactoryBean<Mapper1> factory = new MapperFactoryBean<>(Mapper1.class);
        factory.setSqlSessionFactory(sqlSessionFactory);
        return factory;
    }

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

Mybaits对于每个mapper接口,会使用一个MapperFactoryBean来包装该mapper接口,其实我们可以猜到以后要注入该mapper接口时,就是从MapperFactoryBean获取mapper接口对应的实现类,其实MapperFactoryBean就是一个工厂类

@Bean
    public MapperFactoryBean<Mapper1> mapper1(SqlSessionFactory sqlSessionFactory) {
        MapperFactoryBean<Mapper1> factory = new MapperFactoryBean<>(Mapper1.class);
        factory.setSqlSessionFactory(sqlSessionFactory);
        return factory;
    }

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

这里是手动通过@Bean完成的关联注入功能,现在我们要实现通过@Mapper或者@MapperScan接口扫描自动完成这样的功能

public class MapperPostProcessor implements BeanDefinitionRegistryPostProcessor {

    @Override
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry beanFactory) throws BeansException {
        try {
            //定位资源,PathMatchingResourcePatternResolver可以通过模糊匹配定位资源,并且可以一次性定位多个资源
            PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
            Resource[] resources = resolver.getResources("classpath:com/two/mapper/**/*.class");
            //beanName生成器
            AnnotationBeanNameGenerator generator = new AnnotationBeanNameGenerator();
            //缓存元数据读取器工厂---生产读取并解析class文件的reader
            CachingMetadataReaderFactory factory = new CachingMetadataReaderFactory();
            //遍历资源
            for (Resource resource : resources) {
                //读取并解析class文件的reader
                MetadataReader reader = factory.getMetadataReader(resource);
                //class文件解析完后得到的类元数据信息
                ClassMetadata classMetadata = reader.getClassMetadata();
                //当前class上注解的元数据信息
                AnnotationMetadata annotationMetadata = reader.getAnnotationMetadata();
                //是否是一个接口并且标注了@Mapper注解
                if (classMetadata.isInterface()&&annotationMetadata.hasAnnotation(Mapper.class.getName())) {
                    //生成一个BeanDefinition
                    AbstractBeanDefinition bd = BeanDefinitionBuilder
                            //Bean的类型是MapperFactoryBean
                            .genericBeanDefinition(MapperFactoryBean.class)
                            //联系这@Bean原生注入中的这一行代码: MapperFactoryBean<Mapper1> factory = new MapperFactoryBean<>(Mapper1.class);
                            //添加构造方法参数
                            .addConstructorArgValue(classMetadata.getClassName())
                            //设置自动注入模式---按照类型注入
                            //联系这一行: factory.setSqlSessionFactory(sqlSessionFactory);
                            .setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE)
                            .getBeanDefinition();
                    //AnnotationMetadata默认会根据BeanDefinition来中的传入的当前BeanDefinition中对应的Bean.class类名首字母小写即bean作为beanName
                    //但是这里因为所有接口都要被MapperFactoryBean接管,因此这会导致所生成的BeanName都是一样的
                    //对于一样的BeanName,IOC的选择是新的覆盖旧的
                    String name = generator.generateBeanName(bd, beanFactory);
                    //注册Bean
                    beanFactory.registerBeanDefinition(name, bd);
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }

    }

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

    }
}

针对这里说的问题,我来演示一下:

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);
        //注册一个ConfigurationClassPostProcessor负责解析@ComponentScan @Bean @Import @ImportResource
        context.registerBean(ConfigurationClassPostProcessor.class);
        //注册我们写的Bean工厂后置处理器
        context.registerBean(MapperPostProcessor.class);
        // ⬇️初始化容器
        context.refresh();

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

        // ⬇️销毁容器
        context.close();

    }
}

mapper包下面有这三个类:

@Mapper
public interface Mapper1 {
}

@Mapper
public interface Mapper2 {
}

public class Mapper3 {
}

Spring团队的解决思路类似下面这样:

public class MapperPostProcessor implements BeanDefinitionRegistryPostProcessor {

    @Override
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry beanFactory) throws BeansException {
        try {
            //定位资源,PathMatchingResourcePatternResolver可以通过模糊匹配定位资源,并且可以一次性定位多个资源
            PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
            Resource[] resources = resolver.getResources("classpath:com/two/mapper/**/*.class");
            //beanName生成器
            AnnotationBeanNameGenerator generator = new AnnotationBeanNameGenerator();
            //缓存元数据读取器工厂---生产读取并解析class文件的reader
            CachingMetadataReaderFactory factory = new CachingMetadataReaderFactory();
            //遍历资源
            for (Resource resource : resources) {
                //读取并解析class文件的reader
                MetadataReader reader = factory.getMetadataReader(resource);
                //class文件解析完后得到的类元数据信息
                ClassMetadata classMetadata = reader.getClassMetadata();
                //当前class上注解的元数据信息
                AnnotationMetadata annotationMetadata = reader.getAnnotationMetadata();
                //是否是一个接口并且标注了@Mapper注解
                if (classMetadata.isInterface()&&annotationMetadata.hasAnnotation(Mapper.class.getName())) {
                    //生成一个BeanDefinition
                    AbstractBeanDefinition bd = BeanDefinitionBuilder
                            //Bean的类型是MapperFactoryBean
                            .genericBeanDefinition(MapperFactoryBean.class)
                            //联系这@Bean原生注入中的这一行代码: MapperFactoryBean<Mapper1> factory = new MapperFactoryBean<>(Mapper1.class);
                            //添加构造方法参数
                            .addConstructorArgValue(classMetadata.getClassName())
                            //设置自动注入模式---按照类型注入
                            //联系这一行: factory.setSqlSessionFactory(sqlSessionFactory);
                            .setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE)
                            .getBeanDefinition();
                     //为每个接口生成一个BeanDefinition---但是该BeanDefinition作用仅仅是来生成一个BeanName
                    AbstractBeanDefinition bd2 = BeanDefinitionBuilder.genericBeanDefinition(classMetadata.getClassName()).getBeanDefinition();
                    //这样每次通过bd2生成的BeanName就会不同了
                    String name = generator.generateBeanName(bd2, beanFactory);
                    //注册Bean
                    beanFactory.registerBeanDefinition(name, bd);
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }

    }

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

    }
}

再测试:

收获💡

  1. Mapper 接口被 Spring 管理的本质:实际是被作为 MapperFactoryBean 注册到容器中
  2. Spring 的诡异做法,根据接口生成的 BeanDefinition 仅为根据接口名生成 bean 名

6) Aware 接口

Aware 接口及 InitializingBean 接口

  1. Aware 接口用于注入一些与容器相关信息, 例如
    a. BeanNameAware 注入 bean 的名字
    b. BeanFactoryAware 注入 BeanFactory 容器
    c. ApplicationContextAware 注入 ApplicationContext 容器
    d. EmbeddedValueResolverAware ${}

举例:

public class MyBean implements BeanNameAware, ApplicationContextAware, InitializingBean {

    private static final Logger log = LoggerFactory.getLogger(MyBean.class);

    @Override
    public void setBeanName(String name) {
        log.debug("当前bean " + this + " 名字叫:" + name);
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        log.debug("当前bean " + this + " 容器是:" + applicationContext);
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        log.debug("当前bean " + this + " 初始化");
    }
}

主启动类:

/*
    Aware 接口及 InitializingBean 接口
 */
public class A06 {
    private static final Logger log = LoggerFactory.getLogger(A06.class);

    public static void main(String[] args) {
        GenericApplicationContext context = new GenericApplicationContext();
        context.registerBean("myBean", MyBean.class);
        context.refresh(); 
        context.close();
    }
}

结果:

收获💡
  • Aware 接口提供了一种【内置】 的注入手段,例如

  • BeanNameAware 注入 bean 的名字

  • BeanFactoryAware 注入 BeanFactory 容器

  • ApplicationContextAware 注入 ApplicationContext 容器

  • EmbeddedValueResolverAware 注入 ${} 解析器

  • InitializingBean 接口提供了一种【内置】的初始化手段

  • 对比

  • 内置的注入和初始化不受扩展功能的影响,总会被执行

  • 而扩展功能受某些情况影响可能会失效

  • 因此 Spring 框架内部的类常用内置注入和初始化

疑问?

2. 有同学说: b、c、d 的功能用 @Autowired 就能实现啊, 为啥还要用 Aware 接口呢
            简单地说:
                a. @Autowired 的解析需要用到 bean 后处理器, 属于扩展功能
                b. 而 Aware 接口属于内置功能, 不加任何扩展, Spring 就能识别
            某些情况下, 扩展功能会失效, 而内置功能不会失效

            例1: 你会发现用 Aware 注入 ApplicationContext 成功, 而 @Autowired 注入 ApplicationContext 失败

例子1:

public class MyBean implements BeanNameAware, ApplicationContextAware, InitializingBean {

    private static final Logger log = LoggerFactory.getLogger(MyBean.class);

    @Override
    public void setBeanName(String name) {
        log.debug("当前bean " + this + " 名字叫:" + name);
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        log.debug("当前bean " + this + " 容器是:" + applicationContext);
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        log.debug("当前bean " + this + " 初始化");
    }

    @Autowired
    public void aaa(ApplicationContext applicationContext) {
        log.debug("当前bean " + this + " 使用@Autowired 容器是:" + applicationContext);
    }

    @PostConstruct
    public void init() {
        log.debug("当前bean " + this + " 使用@PostConstruct 初始化");
    }
}

再次执行程序,查看@Autowired注解和@PostConstruct注解是否生效了

显然并没有生效,我们只有加入相应的工厂Bean后置处理器,才能生效,下面验证:

public class A06 {
    private static final Logger log = LoggerFactory.getLogger(A06.class);

    public static void main(String[] args) {
        GenericApplicationContext context = new GenericApplicationContext();
        context.registerBean("myBean", MyBean.class);
        //解析Autowire注解的
        context.registerBean(AutowiredAnnotationBeanPostProcessor.class);
        //解析Resource,PostConstrcut和PreDestory注解的
        context.registerBean(CommonAnnotationBeanPostProcessor.class);

        context.refresh(); 
        context.close();
    }
}

@Autowired失效了?

我们增加一个配置类Config1

@Configuration
public class MyConfig1 {

    private static final Logger log = LoggerFactory.getLogger(MyConfig1.class);

    @Autowired
    public void setApplicationContext(ApplicationContext applicationContext) {
        log.debug("注入 ApplicationContext");
    }

    @PostConstruct
    public void init() {
        log.debug("初始化");
    }

    @Bean //  beanFactory 后处理器------->关键点
    public BeanFactoryPostProcessor processor1() {
        return beanFactory -> {
            log.debug("执行 processor1");
        };
    }
}

我们需要增加ConfigurationClassPostProcessor工厂Bean后置处理器来处理@Configuration和@Bean注解

/*
    Aware 接口及 InitializingBean 接口
 */
public class A06 {
    private static final Logger log = LoggerFactory.getLogger(A06.class);

    public static void main(String[] args) {
        GenericApplicationContext context = new GenericApplicationContext();
        context.registerBean("myConfig1", MyConfig1.class);
        context.registerBean(AutowiredAnnotationBeanPostProcessor.class);
        context.registerBean(CommonAnnotationBeanPostProcessor.class);
        context.registerBean(ConfigurationClassPostProcessor.class);
        
        context.refresh(); 
        context.close();
    }
}

问题来了,@Autowired注解和@PostConstruct注解凉凉了

疑问解答:

我们先来看一下Bean的生命周期流程图:

下面再来进一步分析一下@Autowired注解失效的原因:
Java 配置类不包含 BeanFactoryPostProcessor 的情况

Java 配置类包含 BeanFactoryPostProcessor 的情况,因此要创建其中的 BeanFactoryPostProcessor 必须提前创建 Java 配置类,而此时的 BeanPostProcessor 还未准备好,导致 @Autowired 等注解失效

对应代码

@Configuration
public class MyConfig1 {

    private static final Logger log = LoggerFactory.getLogger(MyConfig1.class);

    @Autowired
    public void setApplicationContext(ApplicationContext applicationContext) {
        log.debug("注入 ApplicationContext");
    }

    @PostConstruct
    public void init() {
        log.debug("初始化");
    }

    @Bean //  ⬅️ 注释或添加 beanFactory 后处理器对应上方两种情况
    public BeanFactoryPostProcessor processor1() {
        return beanFactory -> {
            log.debug("执行 processor1");
        };
    }

}
解决方案

注意

解决方法:

  • 用内置依赖注入和初始化取代扩展依赖注入和初始化
@Configuration
public class MyConfig2 implements InitializingBean, ApplicationContextAware {

    private static final Logger log = LoggerFactory.getLogger(MyConfig2.class);

    @Override
    public void afterPropertiesSet() throws Exception {
        log.debug("初始化");
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        log.debug("注入 ApplicationContext");
    }

    @Bean //  beanFactory 后处理器
    public BeanFactoryPostProcessor processor2() {
        return beanFactory -> {
            log.debug("执行 processor2");
        };
    }
}
  • 用静态工厂方法代替实例工厂方法,避免工厂对象提前被创建

小结

a. Aware 接口提供了一种【内置】 的注入手段, 可以注入 BeanFactory, ApplicationContext
            b. InitializingBean 接口提供了一种【内置】的初始化手段
            c. 内置的注入和初始化不受扩展功能的影响, 总会被执行, 因此 Spring 框架内部的类常用它们

7) 初始化与销毁

初始化销毁顺序

演示:

public class Bean1 implements InitializingBean {
    private static final Logger log = LoggerFactory.getLogger(Bean1.class);

    @PostConstruct
    public void init1() {
        log.debug("初始化1");
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        log.debug("初始化2");
    }

    public void init3() {
        log.debug("初始化3");
    }
}
public class Bean2 implements DisposableBean {
    private static final Logger log = LoggerFactory.getLogger(Bean2.class);

    @PreDestroy
    public void destroy1() {
        log.debug("销毁1");
    }

    @Override
    public void destroy() throws Exception {
        log.debug("销毁2");
    }

    public void destroy3() {
        log.debug("销毁3");
    }
}
@SpringBootApplication
public class A07_1 {

    public static void main(String[] args) {
        ConfigurableApplicationContext context = SpringApplication.run(A07_1.class, args);
        context.close();
    }

    @Bean(initMethod = "init3")
    public Bean1 bean1() {
        return new Bean1();
    }

    @Bean(destroyMethod = "destroy3")
    public Bean2 bean2() {
        return new Bean2();
    }
}

测试:

测试2:

public class A07_2 {

    public static void main(String[] args) {
        DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
        beanFactory.registerBeanDefinition(
                "myBean",
                BeanDefinitionBuilder.genericBeanDefinition(MyBean.class)
                        .setDestroyMethodName("destroy")
                        .getBeanDefinition()
        );

        System.out.println(beanFactory.getBean(MyBean.class));
        beanFactory.destroySingletons(); // 销毁之后, 仍可创建新的单例
        System.out.println(beanFactory.getBean(MyBean.class));

    }

    static class MyBean {
        public MyBean() {
            System.out.println("MyBean()");
        }

        public void destroy() {
            System.out.println("destroy()");
        }
    }
}

可以看出,对于beanFactory 来说,只有手动调用destroySingletons方法,才会去销毁这些单例Bean,销毁完后,可创建新的Bean

收获💡

Spring 提供了多种初始化手段,除了 @PostConstruct,@Bean(initMethod) 之外,还可以实现 InitializingBean 接口来进行初始化,如果同一个 bean 用了以上手段声明了 3 个初始化方法,那么它们的执行顺序是

  1. @PostConstruct 标注的初始化方法
  2. InitializingBean 接口的初始化方法
  3. @Bean(initMethod) 指定的初始化方法

与初始化类似,Spring 也提供了多种销毁手段,执行顺序为

  1. @PreDestroy 标注的销毁方法
  2. DisposableBean 接口的销毁方法
  3. @Bean(destroyMethod) 指定的销毁方法

8) Scope

在当前版本的 Spring 和 Spring Boot 程序中,支持五种 Scope

  • singleton,容器启动时创建(未设置延迟),容器关闭时销毁
  • prototype,每次使用时创建,不会自动销毁,需要调用 DefaultListableBeanFactory.destroyBean(bean) 销毁
  • request,每次请求用到此 bean 时创建,请求结束时销毁
  • session,每个会话用到此 bean 时创建,会话结束时销毁
  • application,web 容器用到此 bean 时创建,容器停止时销毁

有些文章提到有 globalSession 这一 Scope,也是陈旧的说法,目前 Spring 中已废弃

但要注意,如果在 singleton 注入其它 scope 都会有问题,解决方法有

  • @Lazy
  • @Scope(proxyMode = ScopedProxyMode.TARGET_CLASS)
  • ObjectFactory
  • ApplicationContext.getBean
@Scope("session")
@Component
public class BeanForSession {
    private static final Logger log = LoggerFactory.getLogger(BeanForSession.class);

    @PreDestroy
    public void destroy() {
        log.info("destroy");
    }
}
@Scope("request")
@Component
public class BeanForRequest {
    private static final Logger log = LoggerFactory.getLogger(BeanForRequest.class);

    @PreDestroy
    public void destroy() {
        log.info("destroy");
    }

}
@Scope("application")
@Component
public class BeanForApplication {
    private static final Logger log = LoggerFactory.getLogger(BeanForApplication.class);

    @PreDestroy
    public void destroy() {
        log.info("destroy");
    }
}

测试:

@RestController
public class MyController {

    @Lazy
    @Autowired
    private BeanForRequest beanForRequest;

    @Lazy
    @Autowired
    private BeanForSession beanForSession;

    @Lazy
    @Autowired
    private BeanForApplication beanForApplication;

    @GetMapping(value = "/test", produces = "text/html")
    public String test(HttpServletRequest request, HttpSession session) {
        ServletContext sc = request.getServletContext();
        String sb = "<ul>" +
                    "<li>" + "request scope:" + beanForRequest + "</li>" +
                    "<li>" + "session scope:" + beanForSession + "</li>" +
                    "<li>" + "application scope:" + beanForApplication + "</li>" +
                    "</ul>";
        return sb;
    }

}
/*
    singleton, prototype, request, session, application

    jdk >= 9 如果反射调用 jdk 中方法,会报访问异常
    jdk <= 8 不会有问题

    演示 request, session, application 作用域
    打开不同的浏览器, 刷新 http://localhost:8080/test 即可查看效果
    如果 jdk > 8, 运行时请添加 --add-opens java.base/java.lang=ALL-UNNAMED
 */
@SpringBootApplication
public class A08 {
    public static void main(String[] args) {
        SpringApplication.run(A08.class, args);
        /*
            学到了什么
                a. 有几种 scope
                b. 在 singleton 中使用其它几种 scope 的方法
                c. 其它 scope 的销毁
                    1. 可以将通过 server.servlet.session.timeout=10s 观察 session bean 的销毁---默认30分钟失效
                    2. ServletContextScope 销毁机制疑似实现有误
         */
    }
}

Tomcat的Session实现原理,这里说明了为什么至少需要

收获💡

  1. 有几种 scope
  2. 在 singleton 中使用其它几种 scope 的方法
  • 其它 scope 的销毁时机

  • 可以将通过 server.servlet.session.timeout=30s 观察 session bean 的销毁

  • ServletContextScope 销毁机制疑似实现有误,,待定

分析 - singleton 注入其它 scope 失效

以单例注入多例为例

@Scope("prototype")
@Component
public class F1 {
}
@Scope(value = "prototype", proxyMode = ScopedProxyMode.TARGET_CLASS)
@Component
public class F2 {
}
@Scope("prototype")
@Component
public class F3 {
}
@Scope("prototype")
@Component
public class F4 {
}

有一个单例对象 E

@Component
public class E {
    private static final Logger log = LoggerFactory.getLogger(E.class);

    private F f;

    public E() {
        log.info("E()");
    }

    @Autowired
    public void setF(F f) {
        this.f = f;
        log.info("setF(F f) {}", f.getClass());
    }

    public F getF() {
        return f;
    }
}

测试

E e = context.getBean(E.class);
F f1 = e.getF();
F f2 = e.getF();
System.out.println(f1);
System.out.println(f2);

输出

com.dhy.demo.cycle.F@6622fc65
com.dhy.demo.cycle.F@6622fc65

发现它们是同一个对象,而不是期望的多例对象

对于单例对象来讲,依赖注入仅发生了一次,后续再没有用到多例的 F,因此 E 用的始终是第一次依赖注入的 F

解决

  • 仍然使用 @Lazy 生成代理
  • 代理对象虽然还是同一个,但当每次使用代理对象的任意方法时,由代理创建新的 f 对象

@Component
public class E {
    @Autowired
    @Lazy
    public void setF(F f) {
        this.f = f;
        log.info("setF(F f) {}", f.getClass());
    }
    // ...
}

注意

  • @Lazy 加在也可以加在成员变量上,但加在 set 方法上的目的是可以观察输出,加在成员变量上就不行了
  • @Autowired 加在 set 方法的目的类似

输出

E: setF(F f) class com.itheima.demo.cycle.F$$EnhancerBySpringCGLIB$$8b54f2bc
F: F()
com.dhy.demo.cycle.F@3a6f2de3
F: F()
com.dhy.demo.cycle.F@56303b57

从输出日志可以看到调用 setF 方法时,f 对象的类型是代理类型

4种解决方法
如果 jdk > 8, 运行时请添加 --add-opens java.base/java.lang=ALL-UNNAMED,否则spring可能会反射调用jdk内部方法,造成访问权限异常被拒

@Component
public class E {

    @Lazy
    @Autowired
    private F1 f1;

    @Autowired
    private F2 f2;

    @Autowired
    private ObjectFactory<F3> f3;

    @Autowired
    private ApplicationContext context;

    public F1 getF1() {
        return f1;
    }

    public F2 getF2() {
        return f2;
    }

    public F3 getF3() {
        return f3.getObject();
    }

    public F4 getF4() {
        return context.getBean(F4.class);
    }
}

对于f2的设置如下:

@Scope(value = "prototype", proxyMode = ScopedProxyMode.TARGET_CLASS)
@Component
public class F2 {
}

本质也是生成一个代理对象,每次使用代理对象的任意方法时,由代理创建新的 F2 对象

剩余两种方法,都是从IOC容器中获取bean对象

小结

出现上面scope失效的原因在于在依赖注入阶段就去获取了对应scope的值,然后后续所有使用到该scope类型的对象都是直接返回一开始注入好的值,而不是每次都向容器去获取一下。

因此想要解决这个办法,就必须要推迟socpe bean的获取,方法有上面说的四种

更多细节可以参考本文

24 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com