Spring 源码分析

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

2、bean 的生命周期
1、bean 对象
在Spring框架中,Bean对象是指由Spring容器管理的对象,可以是任意类型的Java类的实例,如数据库连接、业务逻辑类、控制器等
Bean实例的创建和管理由Spring容器负责,而不是由应用程序本身负责。Bean的主要优势是可以将对象的创建和管理与业务逻辑分离,使应用程序更加灵活和易于维护

2、bean 创建过程
类 –> 无参构造方法 –> 普通对象 –> 依赖注入 –> init(执行初始化方法)–> 初始化后(AOP)–> 代理对象 –> Map<beanName, Bean对象> 单例池
其中依赖注入的过程如下

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

对应源码

3、初始化方法
方式一:
实现 InitializingBean 接口,并重写 afterPropertiesSet 方法

对应的源码如下

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

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

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

方式二:
使用 @PostConstruct 注解修饰方法
4、bean 构造方法
- 没有构造方法:如果 bean 对象中没有显示写出构造方法的时候,在创建 bean 时会使用默认的无参构造方法
- 存在无参构造方法:直接使用无参构造方法
- 不存无参构造方法,但存在一个有参构造方法:直接执行有参构造方法
- 不存无参构造方法,但存在多个有参构造方法:直接报错
如果存在多个构造方法时,可以直接 @Autowired 注解指定 spring 使用某个构造方法,如果指定了多个构造方法也会报错

以上过程为 spring 的推断构造方法
其中构造方法中的参数,都会从 spring 单例池中获取,如果不存在这个 bean 则会先创建这个 bean,并且这个对象必须为一个 bean,不然也会报错
例如将 OrderService 上面的注解注释掉的话,调用 UserService 的构造方法也会报错


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

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

所以在这种情况下,执行这个构造方法就会报错了,找不到对应的 bean,而且通过类型查找的话会匹配到三个
所以只能把 service 改成 正确的 bean 名称,或者只注册一个 OrderService 类型的 bean

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

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


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

开启 aop 需要增加 @EnableAspectJAutoProxy 注解

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

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

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

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

所以,代理对象就是对 普通对象 进行了修饰,在调用指定方法的时候进行其他逻辑
通过 JoinPoint 属性就可以获取到对应的 普通对象 和 当前代理对象

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() 方法的之前事务切面逻辑

事务注解失效情况
增加方法 test2(),并使用 @Transactional(propagation = Propagation.NEVER) 注解进行修饰,但发现并不会生效
因为这个时候调用 test2() 方法的是 userService 普通对象,而不是 代理对象,所以配置的切面逻辑不会生效

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

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

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

9、@Configuration 注解作用
用于修饰配置类,并生成该配置类的代理对象,对于上述事务配置类中,这个注解使得 jdbcTemplate 和 platformTransactionManager 使用了相同的 dataSource 数据源

然后 platformTransactionManager 事务管理器将为这个 dataSource 创建对应的连接 conn,并将这两个对象存入 Map<dataSource, conn> 集合中,所以在后续的业务代码中开启事务时,都会从这个 Map 中获取连接
如果没有 @Configuration 注解的话,就算开启了事务,但是获取不到对应的数据库连接,无法正确的关闭 自动提交,所以事务一样不会正确回滚
并且可以通过 platformTransactionManager 配置多数据源
注意:虽然可以在 @Configuration 修饰的配置类中注册 bean,但即使不使用这个注解,还是一样可以通过 @Bean 注解去注册bean
10、循环依赖
在创建 bean 的过程中,出现两个 bean 之间依赖注入的相互引用


自我依赖

11、三级缓存
简介
一级缓存 singletonObjects:存储最终的对象,主要存放的是已经完成实例化、属性填充和初始化所有步骤的单例Bean实例,这样的Bean能够直接提供给用户使用,我们称之为终态Bean或叫成熟Bean
二级缓存 earlySingletonObjects: 可用来存储半成品Bean(或代理对象),主要存放的已经完成初始化但属性还没自动赋值的Bean,这些Bean还不能提供用户使用,只是用于提前暴露的Bean实例,我们把这样的Bean称之为临时Bean或早期的Bean(半成品Bean)
三级缓存 singletonFactories: 存储最早的普通对象的 lambda 表达式,调用ObjectFactory.getObject()最终会调用getEarlyBeanReference方法,获取提前暴露的单例bean引用(或代理对象)

原理
1、三级缓存用于实例化完成的普通对象的 lambda 表达式
2、二级缓存用于填充 bean 的过程中,如果出现了循环依赖,则会提前进行 AOP,先去查找二级缓存是否存在代理对象(如果不需要进行 AOP,二级缓存则保存 普通对象)
- 如果不存在,则调用三级缓存中的 lambda 表达式,生成代理对象(或普通对象)
- 如果存在,则直接使用该代理对象
3、一级缓存则用于保存最后生成的 bean

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

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

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

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

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

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

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

查看后置 AOP 操作
点击进入初始化方法

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

并进入 postProcessAfterInitialization 方法查看

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

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

至此就完成了创建 bean 的过程,这个 doCreateBean 方法最终就会把二级缓存的代理对象返回出去
12、@Async 循环依赖
将 AOP 代理去掉之后,在 userService 的 test() 方法上增加 @Async 注解



再次启动代码,则会发现居然不会报错了!!!
但是也可以发现,单例池中的 userService 存在的也是代理对象了,这是由 Async 注解生成的代理对象

算了,测试不出来,可能这个 spring 源码已经解决了这个问题
13、@Lazy解决循环依赖
@Lazy注解可以用于Spring框架中的Bean。当一个Bean被注解为@Lazy时,Spring会在需要时延迟初始化这个Bean,从而避免在循环依赖中过早地初始化Bean。
例如以下代码,在 spring 中不会一开始就初始化这个 bean,只会进行 userService 的实例化,不会进行 orderService 的依赖注入,所以不会出现循环依赖
直到调用 userSerivce.test() 方法的时候才会进行依赖注入

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 的名称去获取

生成代理对象 bean
1、创建 userMapper 接口

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、好吧,测试失败,看来跟我理解的不太一样

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


4、实现 mybatis 的 bean 注册
1、导入 mybatis 依赖,因为这里是要试着自己实现 mybatis 和 spring 整合的,所以就不直接导入 mybatis-spring 的依赖了
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.9</version>
</dependency>

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

配置 xml 文件

对应文档位置

3、创建 MybatisFactoryBean 用于生成 bean

4、修改 userMapper 的内容

5、启动测试,成功查询数据库并返回内容
public class Test {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConf.class);
UserService userService = (UserService) context.getBean("userService");
userService.test();
}
}

5、通过 BeanDefinition 注册 bean
1、修改 MybatisFactoryBean 的内容,并去掉 component 注解

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、测试启动发现能够达到相同的效果

新增其他 mapper
1、新增 OrderMapper 接口
public interface OrderMapper {
@Select("select age from students where id = 1")
String select();
}
2、修改 UserService

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

4、启动测试,一切正常

但是这样子对于注册 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、将注册的代码迁移到上方,然后删除主函数的代码

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

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

7、配置扫描
自定义扫描注解
1、自定义扫描注解,并将 Import 注解的内容移入到这个注解中
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Import(MybatisImportBeanDefinitionRegistrar.class)
public @interface TestScan {
String value() default "";
}
2、在 AppConf 中使用这个注解,并配置扫描路径

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");
……
}
}

配置扫描器
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 实现类中引用这个扫描器

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

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 的方法

新增 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());
}
}
发现同样能够正常使用

8、Spring 扫描流程
1、点击 AnnotationConfigApplicationContext

2、查看 refresh 方法

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

接下来就一路往下查看

发现了一个 BeanDefinition 后置处理器

查看配置类的实现方式


找到下面的解析方法



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

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

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

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

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

配置扫描多路径
方式一:通过使用多个 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,并判断扫描出来的是否为配置类

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

3、如果不存在 Configuration 注解,则会调用 isConfigurationCandidate 方法进行判断:
- 判断是否为接口
- 判断是否存在部分注解
- 判断是否有带 Bean 注解的方法

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

10、BeanName 生成机制
1、回到刚才的扫描注解解析方法

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

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

查看默认 beanName 生成策略
1、获取注解上的 value 值作为 beanName

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

3、然后判断类名:
- 如果类名为空或长度为 0 则返回原名
- 如果类名首两个字母都是大写,也返回原名
- 正常类名则直接首字母小写作为 beanName

11、 Scope 注解
1、加在实体类上,用于标识作用域
@Component
@Scope(WebApplicationContext.SCOPE_REQUEST)
public class UserService {
}
2、可以通过以上配置,设置当发送 request 请求的时候才会创建这个 bean

3、如果此时另一个 bean 需要依赖到 userService
@Component
public class OrderService {
@Autowired
private UserService userService;
}
4、这个时候就会创建一个 userService 的代理对象用于依赖注入
5、而生成代理对象的代理机制也是可以在 scope 注解中配置 proxyMode 属性

12、扫描文件类型配置
1、继续查看 componentScan 解析代码

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

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

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

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

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

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

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

15、ASM 扫描技术
1、查看 doscan 的扫描路径内容方法

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

3、而对应的默认方式就是 ASM 技术,查看 scanCandidateComponents 方法
- 先拼接获取扫描路径,然后获取各个 class 文件
- 利用 asm 技术获取各个 class 文件的元数据 meta(在不使用反射和实例化的情况下获取类的信息)

16、过滤和扫描配置
1、接着上续方法,对元数据进行判断

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

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

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

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

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

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

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

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

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


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

12、进行第二次判断
- 这个类是否独立(不是普通内部类)
- 不是接口或抽象类
- 是抽象类时,是否存在 Lookup 注解修饰方法


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 方法内部代码

作用
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

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

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

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


2、接着查看 loadIndex 方法

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

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


5、所以只需要在这个文件中配置内容即可生成 componentsIndex
com.example.web_test.service.UserService=org.springframework.stereotype.Component
6、再次启动,发现成功读取到配置文件的内容,左边为 bean 内容,右边为注解类型

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

- 本文链接:https://lxjblog.gitee.io/2023/08/24/spring%E6%BA%90%E7%A0%81/
- 版权声明:本博客所有文章除特别声明外,均默认采用 许可协议。