Spring 源码分析

image-20230814230734308

1、启动 spring 容器的方式

创建一个 AnnotationConfigApplicationContext 类,并指定配置类

public class Test { 
    public static void main(String[] args) { 
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConf.class);
    } 
} 

而在 AppConf 配置类中就可以添加对应的注解配置

@ComponentScan("com.demo")
public class AppConf { 
} 

此时通过 context 变量就可以获取扫描路径中生成的 bean

image-20230813222129188

2、bean 的生命周期

1、bean 对象

在Spring框架中,Bean对象是指由Spring容器管理的对象,可以是任意类型的Java类的实例,如数据库连接、业务逻辑类、控制器等

Bean实例的创建和管理由Spring容器负责,而不是由应用程序本身负责。Bean的主要优势是可以将对象的创建和管理与业务逻辑分离,使应用程序更加灵活和易于维护

图片

2、bean 创建过程

类 –> 无参构造方法 –> 普通对象 –> 依赖注入 –> init(执行初始化方法)–> 初始化后(AOP)–> 代理对象 –> Map<beanName, Bean对象> 单例池

其中依赖注入的过程如下

image-20230813223156467

所以只需要将定义好的普通对象存入到单例池中,也可成为 bean 对象,但是不同的是 spring 会自动创建对象并进行赋值

image-20230813223817574

对应源码

image-20230813224749909

3、初始化方法

方式一:

实现 InitializingBean 接口,并重写 afterPropertiesSet 方法

image-20230813224140043

对应的源码如下

image-20230813224543381

源码中执行了创建 bean 示例、填充 bean 和 初始化 bean 的过程,其中执行方法对应了初始化 bean

image-20230813225506438

在初始化 bean 中可以找到对应的这个方法

image-20230813225621906

该方法中,先判断这个 bean 是否实现了 InitializingBean 接口,并调用 afterPropertiesSet() 方法

image-20230813225724325

方式二:

使用 @PostConstruct 注解修饰方法

4、bean 构造方法

  • 没有构造方法:如果 bean 对象中没有显示写出构造方法的时候,在创建 bean 时会使用默认的无参构造方法
  • 存在无参构造方法:直接使用无参构造方法
  • 不存无参构造方法,但存在一个有参构造方法:直接执行有参构造方法
  • 不存无参构造方法,但存在多个有参构造方法:直接报错

如果存在多个构造方法时,可以直接 @Autowired 注解指定 spring 使用某个构造方法,如果指定了多个构造方法也会报错

image-20230813231415923

以上过程为 spring 的推断构造方法

其中构造方法中的参数,都会从 spring 单例池中获取,如果不存在这个 bean 则会先创建这个 bean,并且这个对象必须为一个 bean,不然也会报错

例如将 OrderService 上面的注解注释掉的话,调用 UserService 的构造方法也会报错

image-20230813231906497

image-20230813232401605

5、bean 获取

在 spring 中可以注册多个相同类型的 bean,如下又多注册了两个 OrderService 类型的 bean

image-20230813232514117

此时获取到的 bean 并不相同,所以单例 bean 的意思是只存在一个相同名字的 bean

image-20230813232640516

所以在这种情况下,执行这个构造方法就会报错了,找不到对应的 bean,而且通过类型查找的话会匹配到三个

所以只能把 service 改成 正确的 bean 名称,或者只注册一个 OrderService 类型的 bean

image-20230813232847644

同时,也可以把参数改成 List 这样可以获取所有的 OrderService 类型的 bean

image-20230813233127736

6、依赖注入

@Autowired 先 byType 再 byName,加了这个注解默认是必须注入的,注入失败则报错,但可以通过 required 属性设置

image-20230813233547783

image-20230813233658186

7、AOP 代理对象

如果使用了 aop 之后,存在 beanMap 单例池的 bean 对象将会是对应的代理对象,此后调用 bean 的 test 方法,都会先执行代理中的代码

测试

image-20230813234003004

开启 aop 需要增加 @EnableAspectJAutoProxy 注解

image-20230813234147513

此时获取到的 userService 这个 bean,发现是代理对象

image-20230813234329850

如果将 aop 代码注释掉,可以看到获取到的就是普通对象了

image-20230813234526169

分析

使用 sop 的时候都会生成代理对象,代理对象中添加普通对象,并重写 test() 方法,先执行切面逻辑代码,再调用普通对象的 test() 方法

image-20230813234927053

并且这个代理对象中 target 是经过依赖注入对象,其中还包含 orderService这个注册进来的 bean,而代理对象不会进行依赖注入

image-20230813235756225

所以,代理对象就是对 普通对象 进行了修饰,在调用指定方法的时候进行其他逻辑

通过 JoinPoint 属性就可以获取到对应的 普通对象 和 当前代理对象

image-20230814000041240

8、spring 事务

初始化

定义 jdbc,并开启事务

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>8.0.26</version>
</dependency>

在配置类上进行定义,并开启事务

@Configuration
@EnableTransactionManagement
public class TransactionConf { 
    @Bean
    public DataSource dataSource() { 
        DriverManagerDataSource dataSource = new DriverManagerDataSource();
        dataSource.setUrl("jdbc:mysql://127.0.0.1:3306/test?serverTimezone=GMT&createDatabaseIfNotExist=true&useUnicode=true&characterEncoding=utf-8&autoReconnect=true");
        dataSource.setUsername("root");
        dataSource.setPassword("123456");
        return dataSource;
    } 

    @Bean
    public JdbcTemplate jdbcTemplate() { 
        return new JdbcTemplate(dataSource());
    } 

    @Bean
    public PlatformTransactionManager platformTransactionManager() { 
        DataSourceTransactionManager transactionManager = new DataSourceTransactionManager();
        transactionManager.setDataSource(dataSource());
        return transactionManager;
    } 
} 

使用

通过以下代码即可使用基本的事务功能

@Component
public class UserService { 
    @Autowired
    private JdbcTemplate jdbcTemplate;

    @Transactional
    public void test() { 
        jdbcTemplate.execute("insert students(id, name, age) values(1, '晓江', 23)");
        throw new RuntimeException();
    } 
} 

原理

通过开启事务的方式,也会生成一个 UserService 的代理对象,在执行 test() 方法的之前事务切面逻辑

image-20230814224640713

事务注解失效情况

增加方法 test2(),并使用 @Transactional(propagation = Propagation.NEVER) 注解进行修饰,但发现并不会生效

因为这个时候调用 test2() 方法的是 userService 普通对象,而不是 代理对象,所以配置的切面逻辑不会生效

image-20230814225651281

同理,以下方案也会使得事务失效,通过普通对象调用有注解的方法都会失效

image-20230814230253410

不过以下这种情况则不会导致事务失效

image-20230814230521004

解决方案

需要使得 test2() 方法上的注解生效,所以必须使用代理对象进行调用,可以直接使用依赖注入自身,然后再调用

image-20230814230101425

9、@Configuration 注解作用

用于修饰配置类,并生成该配置类的代理对象,对于上述事务配置类中,这个注解使得 jdbcTemplateplatformTransactionManager 使用了相同的 dataSource 数据源

image-20230814232147604

然后 platformTransactionManager 事务管理器将为这个 dataSource 创建对应的连接 conn,并将这两个对象存入 Map<dataSource, conn> 集合中,所以在后续的业务代码中开启事务时,都会从这个 Map 中获取连接

如果没有 @Configuration 注解的话,就算开启了事务,但是获取不到对应的数据库连接,无法正确的关闭 自动提交,所以事务一样不会正确回滚

并且可以通过 platformTransactionManager 配置多数据源

注意:虽然可以在 @Configuration 修饰的配置类中注册 bean,但即使不使用这个注解,还是一样可以通过 @Bean 注解去注册bean

10、循环依赖

在创建 bean 的过程中,出现两个 bean 之间依赖注入的相互引用

image-20230814233717576

image-20230815204214444

自我依赖

image-20230815204302058

11、三级缓存

简介

一级缓存 singletonObjects:存储最终的对象,主要存放的是已经完成实例化、属性填充和初始化所有步骤的单例Bean实例,这样的Bean能够直接提供给用户使用,我们称之为终态Bean或叫成熟Bean

二级缓存 earlySingletonObjects: 可用来存储半成品Bean(或代理对象),主要存放的已经完成初始化但属性还没自动赋值的Bean,这些Bean还不能提供用户使用,只是用于提前暴露的Bean实例,我们把这样的Bean称之为临时Bean或早期的Bean(半成品Bean)

三级缓存 singletonFactories: 存储最早的普通对象的 lambda 表达式,调用ObjectFactory.getObject()最终会调用getEarlyBeanReference方法,获取提前暴露的单例bean引用(或代理对象)

image-20230815204535055

原理

1、三级缓存用于实例化完成的普通对象的 lambda 表达式

2、二级缓存用于填充 bean 的过程中,如果出现了循环依赖,则会提前进行 AOP,先去查找二级缓存是否存在代理对象(如果不需要进行 AOP,二级缓存则保存 普通对象)

  • 如果不存在,则调用三级缓存中的 lambda 表达式,生成代理对象(或普通对象)
  • 如果存在,则直接使用该代理对象

3、一级缓存则用于保存最后生成的 bean

image-20230815224044828

对应源码

对应于 AbstractAutowireCapableBeanFactory 类中的 doCreateBean 方法

在实例化完成之后,就会出现以下代码,用于解决循环依赖问题,将 beanName 和 lambda 表达式存入到三级缓存中

image-20230815213126539

在添加的过程中,还会将二级缓存对应的 bean 清除,并标记为正在创建

image-20230815213340770

而在下面的填充 bean 的过程中,就需要进行依赖注入,可能就会出现循环依赖

image-20230815213500676

在填充 bean 的过程中,会判断是通过 name 还是通过 type 进行依赖注入

image-20230815214822264

而在依赖注入的过程中会去获取 bean

image-20230815214853434

getBean 方法会调用到 doGetBean 方法,其中就会去单例池中去获取 bean

image-20230815215048194

最后这个才是 bean 的获取过程

  1. 先判断一级缓存是否存在
  2. 一级缓存获取不到,且这个 bean 正在创建过程中(出现循环依赖)
  3. 查询二级缓存是否存在
  4. 二级缓存获取不到且支持循环依赖
  5. 加锁后再次尝试获取
  6. 如果一二级缓存仍获取不到,则查询三级缓存(这是最先设置的)
  7. 三级缓存存在,则调用三级缓存的 lambda 表达式,生成代理对象(提前 AOP)
  8. 将代理对象存入二级缓存中,并清除三级缓存
  9. 返回代理对象

image-20230815215431210

查看后置 AOP 操作

点击进入初始化方法

image-20230815221807773

进入方法后,在最下面可以看到应用 bean 的后置初始化方法

image-20230815221929355

并进入 postProcessAfterInitialization 方法查看

image-20230815222757753

这个接口方法具有多个实现类,但我们只需要关心 aop 自动代理的实现方法

image-20230815222905801

在 aop 自动代理的过程中,会先判断二级缓存是否存在对应当前 bean 的代理对象,如果存在则不再生成代理 bean

image-20230815222955212

至此就完成了创建 bean 的过程,这个 doCreateBean 方法最终就会把二级缓存的代理对象返回出去

12、@Async 循环依赖

将 AOP 代理去掉之后,在 userService 的 test() 方法上增加 @Async 注解

image-20230815224556186

image-20230815224659634

image-20230815225755775

再次启动代码,则会发现居然不会报错了!!!

但是也可以发现,单例池中的 userService 存在的也是代理对象了,这是由 Async 注解生成的代理对象

image-20230815225853962

算了,测试不出来,可能这个 spring 源码已经解决了这个问题

13、@Lazy解决循环依赖

@Lazy注解可以用于Spring框架中的Bean。当一个Bean被注解为@Lazy时,Spring会在需要时延迟初始化这个Bean,从而避免在循环依赖中过早地初始化Bean。

例如以下代码,在 spring 中不会一开始就初始化这个 bean,只会进行 userService 的实例化,不会进行 orderService 的依赖注入,所以不会出现循环依赖

直到调用 userSerivce.test() 方法的时候才会进行依赖注入

image-20230815233032460

3、FactoryBean

普通生成 bean

1、实现 FactoryBean 接口,然后重写方法

@Component
public class TestFactoryBean implements FactoryBean<UserService> { 
    @Override
    public UserService getObject() throws Exception { 
        return new UserService();
    } 

    @Override
    public Class<?> getObjectType() { 
        return UserService.class;
    } 
} 

2、测试获取 bean

public class Test { 
    public static void main(String[] args) { 
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConf.class);
        System.out.println(context.getBean("testFactoryBean"));
        System.out.println(context.getBean("&testFactoryBean"));
        System.out.println(context.getBean("userService"));
    } 
} 

3、发现通过这种方式,实现一个接口并增加 @Component 注解,实际上注册了两个 bean:

  • 通过 testFactoryBean 获取到的是 FactoryBean 实现类中注册的 bean
  • 通过 &testFactoryBean 获取到的则是 实现类 本身
  • 且无法通过 userService 这个注册 bean 的名称去获取

image-20230817223054791

生成代理对象 bean

1、创建 userMapper 接口

image-20230817224341844

2、修改 TestFactoryBean 类的方法

@Component
public class TestFactoryBean implements FactoryBean { 
    @Override
    public Object getObject() throws Exception { 
        return Proxy.newProxyInstance(TestFactoryBean.class.getClassLoader(), new Class[]{ UserMapper.class} , (proxy, method, args) -> new UserMapper() { 
            @Override
            public int select() { 
                return 1;
            } 
            @Override
            public int update() { 
                return 2;
            } 
        } );
    } 

    @Override
    public Class<?> getObjectType() { 
        return UserMapper.class;
    } 
} 

3、启动并测试

public class Test { 
    public static void main(String[] args) { 
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConf.class);
        UserMapper userMapper = (UserMapper) context.getBean("testFactoryBean");
        System.out.println(userMapper.select());
        System.out.println(userMapper.update());
    } 
} 

4、好吧,测试失败,看来跟我理解的不太一样

image-20230817225358141

5、然后把接口的返回类型修改成 Object 就可以了,但是输出内容和我想的也不一样

image-20230817225542875

image-20230817225628412

4、实现 mybatis 的 bean 注册

1、导入 mybatis 依赖,因为这里是要试着自己实现 mybatis 和 spring 整合的,所以就不直接导入 mybatis-spring 的依赖了

<dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis</artifactId>
    <version>3.5.9</version>
</dependency>

image-20230817231040747

2、注册一个 SqlSessionFactory 的 bean,等会用于创建 sqlSession 来生成 mapper

注册bean

image-20230817233111770

配置 xml 文件

image-20230817231349230

对应文档位置

image-20230817231101445

3、创建 MybatisFactoryBean 用于生成 bean

image-20230817233133419

4、修改 userMapper 的内容

image-20230817233159129

5、启动测试,成功查询数据库并返回内容

public class Test { 
    public static void main(String[] args) { 
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConf.class);
        UserService userService = (UserService) context.getBean("userService");
        userService.test();
    } 
} 

image-20230817233238118

5、通过 BeanDefinition 注册 bean

1、修改 MybatisFactoryBean 的内容,并去掉 component 注解

image-20230817234338484

2、修改启动类,注册 BeanDefinition

public class Test { 
    public static void main(String[] args) { 
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
        context.register(AppConf.class);

        // 注册 bean
        AbstractBeanDefinition beanDefinition1 = BeanDefinitionBuilder.genericBeanDefinition().getBeanDefinition();
        beanDefinition1.setBeanClass(MybatisFactoryBean.class);
        beanDefinition1.getConstructorArgumentValues().addGenericArgumentValue(UserMapper.class);
        context.registerBeanDefinition("userMapper", beanDefinition1);

        context.refresh();

        // 调用 bean
        UserService userService = (UserService) context.getBean("userService");
        userService.test();
    } 
} 

3、测试启动发现能够达到相同的效果

image-20230817234802152

新增其他 mapper

1、新增 OrderMapper 接口

public interface OrderMapper { 
    @Select("select age from students where id = 1")
    String select();
} 

2、修改 UserService

image-20230817234938713

3、修改启动类,也注册 orderMapper 这个 bean

image-20230817235046954

4、启动测试,一切正常

image-20230817235126747

但是这样子对于注册 mapper 来说还是太多于麻烦,没新增一个 mapper 都需要修改主函数去注册多一个 bean,所以需要考虑使用扫描的方法,批量注册 bean

6、ImportBeanDefinitionRegistrar

通过以上方式可以实现用 BeanDefinition 注册 bean,但是代码都存放在主函数中,影响美观和可读性,所以可以通过实现 ImportBeanDefinitionRegistrar 接口来解决这个问题

1、实现 ImportBeanDefinitionRegistrar 接口并重写方法 registerBeanDefinitions

public class MybatisImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar { 
    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry, BeanNameGenerator importBeanNameGenerator) { 
        // 注册 userMapper
        AbstractBeanDefinition beanDefinition1 = BeanDefinitionBuilder.genericBeanDefinition().getBeanDefinition();
        beanDefinition1.setBeanClass(MybatisFactoryBean.class);
        beanDefinition1.getConstructorArgumentValues().addGenericArgumentValue(UserMapper.class);
        registry.registerBeanDefinition("userMapper", beanDefinition1);

        // 注册 orderMapper
        AbstractBeanDefinition beanDefinition2 = BeanDefinitionBuilder.genericBeanDefinition().getBeanDefinition();
        beanDefinition2.setBeanClass(MybatisFactoryBean.class);
        beanDefinition2.getConstructorArgumentValues().addGenericArgumentValue(OrderMapper.class);
        registry.registerBeanDefinition("orderMapper", beanDefinition2);
    } 
} 

2、将注册的代码迁移到上方,然后删除主函数的代码

image-20230817235820858

3、在配置类中导入这个实现类

image-20230817235948664

4、再次启动主函数,mapper 同样也可以被注册进来

image-20230818000004840

7、配置扫描

自定义扫描注解

1、自定义扫描注解,并将 Import 注解的内容移入到这个注解中

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Import(MybatisImportBeanDefinitionRegistrar.class)
public @interface TestScan { 
    String value() default "";
} 

2、在 AppConf 中使用这个注解,并配置扫描路径

image-20230820151054122

3、此时在我们 import 进来的这个 ImportBeanDefinitionRegistrar 实现类中就可以获取到加了 import 注解的类的信息了

public class MybatisImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar { 
    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry, BeanNameGenerator importBeanNameGenerator) { 
        Map<String, Object> testScan = importingClassMetadata.getAnnotationAttributes(TestScan.class.getName());
        testScan.get("value");
        ……
    } 
} 

image-20230820151502244

配置扫描器

1、创建扫描器,继承 ClassPathBeanDefinitionScanner 类

public class TestScanner extends ClassPathBeanDefinitionScanner { 

    public TestScanner(BeanDefinitionRegistry registry) { 
        super(registry);
    } 

    // 配置只扫描接口(默认不扫描)
    @Override
    protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) { 
        return beanDefinition.getMetadata().isInterface();
    } 

    // 配置扫描方式
    @Override
    protected Set<BeanDefinitionHolder> doScan(String... basePackages) { 
        // 配置扫描全部
        super.addIncludeFilter((var1, var2) -> true);
        Set<BeanDefinitionHolder> beanDefinitionHolders = super.doScan(basePackages);
        return beanDefinitionHolders;
    } 
} 

2、在 ImportBeanDefinitionRegistrar 实现类中引用这个扫描器

image-20230820153502640

3、测试查看扫描结果,可以将两个接口描出来,但生成的 bean 不是我们想要的结果,应该是这个接口的实现类或者自定义的 MybatisFactoryBean 才对

image-20230820160656816

4、修改扫描结果 TestScanner 的 doScan 方法

@Override
protected Set<BeanDefinitionHolder> doScan(String... basePackages) { 
    // 配置扫描全部
    super.addIncludeFilter((var1, var2) -> true);
    Set<BeanDefinitionHolder> beanDefinitionHolders = super.doScan(basePackages);
    // 修改 bean 的 getBeanDefinition
    for (BeanDefinitionHolder beanDefinitionHolder : beanDefinitionHolders) { 
        BeanDefinition beanDefinition = beanDefinitionHolder.getBeanDefinition();
        beanDefinition.getConstructorArgumentValues()
            .addGenericArgumentValue(Objects.requireNonNull(beanDefinition.getBeanClassName()));
        // 修改 bean 的实现类 FactoryBean
        beanDefinition.setBeanClassName(MybatisFactoryBean.class.getName());
    } 
    return beanDefinitionHolders;
} 

5、而对于 MybatisImportBeanDefinitionRegistrar 类,现在只需配置如下代码即可

public class MybatisImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar { 
    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry, BeanNameGenerator importBeanNameGenerator) { 
        // 获取扫描路径
        Map<String, Object> testScan = importingClassMetadata.getAnnotationAttributes(TestScan.class.getName());
        String path = testScan.get("value").toString();

        // 开始扫描,调用 scan 方法
        TestScanner scanner = new TestScanner(registry);
        scanner.scan(path);
    } 
} 

5、再次启动测试,发现能够正常调用 mapper 的方法

image-20230820155607053

新增 mapper 测试

public interface NumberMapper { 
    @Select("select 'number'")
    String select();
} 

在 userService 中使用

@Component
public class UserService { 
    @Autowired
    private UserMapper userMapper;
    @Autowired
    private OrderMapper orderMapper;
    @Autowired
    private NumberMapper numberMapper;

    public void test() { 
        System.out.println(userMapper.select());
        System.out.println(orderMapper.select());
        System.out.println(numberMapper.select());
    } 
} 

发现同样能够正常使用

image-20230820155844499

8、Spring 扫描流程

1、点击 AnnotationConfigApplicationContext

image-20230820162146493

2、查看 refresh 方法

image-20230820162216230

3、在 invokeBeanFactoryPostProcessors 方法中就定义了扫描

接下来就一路往下查看

image-20230820163857630

发现了一个 BeanDefinition 后置处理器

image-20230820163931830

查看配置类的实现方式

image-20230820164020785

image-20230820164038965

找到下面的解析方法

image-20230820164647980

image-20230820164917287

image-20230820164930632

在 processConfigurationClass 方法中的 configClass 就是我们传进来的配置类

image-20230820165014014

在 doProcessConfigurationClass 方法中就会识别对应的配置类注解

image-20230820165106504

9、ComponentScan 注解

通过上方代码分析,查看 doProcessConfigurationClass 方法下面的 @ComponentScan 注解的处理,可以看到 ComponentScans 和 ComponentScan 都可以用来配置扫描路径

image-20230820165347814

发现源码中也是使用 ClassPathBeanDefinitionScanner 扫描 componentScan 配置的路径

image-20230821215856055

而在 ComponentScan 注解上面加了 @Repeatable 注解,所以可以重复使用

image-20230820165808628

配置扫描多路径

方式一:通过使用多个 ComponentScan 注解

@ComponentScan("com.example.web_test")
@ComponentScan("com.example.web_test2")
@ComponentScan({ "com.example.web_test3", "com.example.web_test4"} )
public class AppConf { 
} 

方式二:使用 ComponentScans 注解

@ComponentScans({ @ComponentScan("com.example.web_test"), @ComponentScan("com.example.web_test2")} )
public class AppConf { 
} 

扫描过程

1、扫描 component 注解中配置的路径,然后依次获取扫描出来的 BeanDefinition,并判断扫描出来的是否为配置类

image-20230821221229486

2、checkConfigurationClassCandidate 方法判断这个 bean 中是否存在 Configuration 注解,存在则作为配置类进行解析

image-20230821221416440

3、如果不存在 Configuration 注解,则会调用 isConfigurationCandidate 方法进行判断:

  • 判断是否为接口
  • 判断是否存在部分注解
  • 判断是否有带 Bean 注解的方法

image-20230821222356523

4、candidateIndicators 是一个 Set,所以只要一个类存在以下注解便是配置类,需要在进行一次解析

image-20230821222513693

10、BeanName 生成机制

1、回到刚才的扫描注解解析方法

image-20230821223217232

2、解析代码中获取 componentScan 注解中配置的 beanName 生成器 nameGenerator

image-20230821223315185

3、自定义生成器,创建 BeanNameGenerator 实现类

public class TestBeanNameGenerator implements BeanNameGenerator { 
    @Override
    public String generateBeanName(BeanDefinition beanDefinition, BeanDefinitionRegistry beanDefinitionRegistry) { 
        return "xiaojiang" + ClassUtils.getShortName(Objects.requireNonNull(beanDefinition.getBeanClassName()));
    } 
} 

4、修改 AppConfig 类上的扫描注解

@ComponentScan(value = "com.example.web_test", nameGenerator = TestBeanNameGenerator.class)
public class AppConf { 
} 

5、启动测试,成功获取到 bean

image-20230821225050654

查看默认 beanName 生成策略

1、获取注解上的 value 值作为 beanName

image-20230821225720837

2、先获取路径的 ShortName,也就是类名,然后再进行处理

image-20230821225159638

3、然后判断类名:

  • 如果类名为空或长度为 0 则返回原名
  • 如果类名首两个字母都是大写,也返回原名
  • 正常类名则直接首字母小写作为 beanName

image-20230821225209853

11、 Scope 注解

1、加在实体类上,用于标识作用域

@Component
@Scope(WebApplicationContext.SCOPE_REQUEST)
public class UserService { 
} 

2、可以通过以上配置,设置当发送 request 请求的时候才会创建这个 bean

image-20230821230054346

3、如果此时另一个 bean 需要依赖到 userService

@Component
public class OrderService { 
    @Autowired
    private UserService userService;
} 

4、这个时候就会创建一个 userService 的代理对象用于依赖注入

5、而生成代理对象的代理机制也是可以在 scope 注解中配置 proxyMode 属性

image-20230821230509317

12、扫描文件类型配置

1、继续查看 componentScan 解析代码

image-20230821230905280

2、查看 ComponentScan 注解中配置的默认值

image-20230821231312729

3、配置任意目录下的所有 .class 文件

image-20230821231351646

13、扫描路径解析

1、查看扫描路径的获取方式

  • 获取注解中 basePackages 的属性(跟 value 属性一样),这个路径可以是多个
  • 获取注解中 basePackageClasses 的属性(一个类),然后获取这个类的包路径
  • 如果注解中未配置以上两个属性,则使用当前这个类的包路径进行扫描

image-20230821232405909

2、并且在扫描代码的最后,配置在扫描到这身这个 bean(AppConfig)的时候直接过滤掉,因为一开始就已经注册到了

image-20230821232851167

14、bean 重复注册

1、接着上方读取完路径之后,就开始正式扫描路径内容了

image-20230823215647392

2、在 doscan 方法中,会校验这个 bean 是否已经注册了

image-20230823220621030

3、以下是校验方式,如果已经存在且类型相同,则不再进行注册

image-20230823220758708

15、ASM 扫描技术

1、查看 doscan 的扫描路径内容方法

image-20230823221315719

2、对于 bean 的扫描有两种方式

image-20230823221415413

3、而对应的默认方式就是 ASM 技术,查看 scanCandidateComponents 方法

  • 先拼接获取扫描路径,然后获取各个 class 文件
  • 利用 asm 技术获取各个 class 文件的元数据 meta(在不使用反射和实例化的情况下获取类的信息)

image-20230823221918937

16、过滤和扫描配置

1、接着上续方法,对元数据进行判断

image-20230823223229891

2、校验是否被过滤或包含,而在我们的配置中并没有手动设置 includeFilters 内容

image-20230823223301147

3、回到一开始扫描 Component 注解的时候

image-20230823224006103

4、在创建扫描器的时候会查看是否使用默认过滤器

image-20230823224109349

5、没有配置的话是使用默认的

image-20230823224200559

6、这个就会为扫描器配置 includeFilters,过滤加有 ComponentManagedBeanNamed 注解的类

image-20230823224310194

7、所以回到刚才的扫描位置

image-20230823225350836

8、如果经过了过滤器,则会判断是否存在 @Conditional 注解,并根据条件判断是否创建

image-20230823225454261

9、获取条件内容并进行判断

image-20230823231209893

10、以下是对配置类注解的判断

image-20230823231346783

image-20230823231615930

11、回到 addCandidateComponentsFromIndex 方法,如果类存在 component 等注解后,就需要进行第二次判断

image-20230823233409536

12、进行第二次判断

  • 这个类是否独立(不是普通内部类)
  • 不是接口或抽象类
  • 是抽象类时,是否存在 Lookup 注解修饰方法

image-20230823233506279

image-20230823233633559

17、Lookup 注解

根据上方扫描判断可知,当一个类是抽象类时,无法创建为 bean,但是如果方法上加了 Lookup 注解,则可以生成这个抽象类的代理对象作为 bean

用法

1、在抽象类的方法上加 Lookup 注解

@Component
public abstract class UserService { 

    // 这个方法仅用于获取 bean
    @Lookup("orderService")
    public OrderService test() { 
        // 实际上,以下代码并不会执行
        System.out.println(11111111);
        return null;
    } 
} 

2、启动测试,发现只输出了 orderService 信息,并不会执行 test 方法内部代码

image-20230823234517751

作用

1、例如当 orderService 是多例 bean 时

@Component
@Scope("prototype")
public class OrderService { 
} 

2、使用依赖注入的方式获取

@Component
public abstract class UserService { 
    @Autowired
    private OrderService orderService;

    public void test() { 
        System.out.println(orderService);
    } 
} 

3、启动测试发现,获取到的都是同一个 bean

image-20230823234948477

4、改用成 Lookup 注解获取 bean

@Component
public abstract class UserService { 

    public void test() { 
        System.out.println(orderService());
    } 

    @Lookup("orderService")
    public OrderService orderService() { 
        System.out.println(11111111);
        return null;
    } 
} 

5、启动发现,获取到的都是不同的bean

image-20230823235106822

18、componentsIndex

1、查看bean 扫描的优化机制

image-20230824000901269

1、而这个 componentsIndex 的配置,同样是在创建 scanner 的时候配置的

image-20230824001244597

image-20230824001323487

2、接着查看 loadIndex 方法

image-20230824001357947

3、在这里根据文件构建 index

image-20230824001418287

4、而在 doLoadIndex 方法中,则加载这个路径 META-INF/spring.components

image-20230824001513295

image-20230824001539653

5、所以只需要在这个文件中配置内容即可生成 componentsIndex

com.example.web_test.service.UserService=org.springframework.stereotype.Component

6、再次启动,发现成功读取到配置文件的内容,左边为 bean 内容,右边为注解类型

image-20230824002628462

7、并且配置之后只会加载配置文件中的 bean,其他 bean 即使加了 component 注解也不会被加载进来

image-20230824002738486