Spring底层原理

Spring底层原理

原理

Bean的创建过程

  1. 推断构造方法

    1. 选择使用的构造方法:

      • 如果只有无参的构造方法,就用无参的;

      • 如果只有一个有参的构造方法,就用这个有参的;

      • 如果有多个有参的构造方法,会使用构造上加了@Autowired的构造,没有则spring也不知道用哪个,会尝试找无参的,没有就报错。

    2. 对入参进行依赖注入

      会对有参的构造函数中的参数进行依赖注入,先通过类型去单例池中找,如果有多个则再通过参数名匹配,没有就报错。

  2. 依赖注入

    找加了响应注解(@Autowired、@Resource)的属性,先byType再byName

  3. 初始化

    • 初始化前:扫描对应注解(@PostConstruct),执行初始化前方法
    • 初始化中:扫描对应接口(afterPropertiesSet),执行初始化方法
    • 初始化后:AOP产生代理对象
      • cglib:代理对象继承原对象,原对象成为代理对象的一个属性。切面方法里通过joinPoint.getTarget()可以获取原对象,joinPoint.getThis()可以获取代理对象
  4. 放到Map(单例池)中

  5. Bean对象

事务

和AOP类似,生成代理对象来增强功能

开启事务:在数据库配置类上加@EnableTransactionManagement、@Configuration

  1. 事务管理器新建一个数据库连接conn
  2. conn.autocommit = false // 关闭自动提交
  3. 使用@Configuration把dataSource注入bean,使每次拿到的都是同一个dataSource

使用事务:@Transactional

:star:事务失效原理

必须是从获取到的代理对象来调用有事务的方法才会生效。

如果是从自己new的对象/其他方法调用事务方法等方式则不会经过切面,从而事务失效

:star:循环依赖

提前AOP

​ 成因:在创建一个bean时将其名字添加到一个set中,创建完后将其删除。如果此时另一个bean需要依赖注入的bean在单例池中找不到,而在这个set中,说明出现了循环依赖

​ 只有出现循环依赖时,将AOP的步骤提前到填充属性前,得到代理对象注入到二级缓存(earlySingletonObjects)中。此时的代理对象还不能放到单例池中,因为代理对象里的原对象还没创建出来,现在处于半成品状态

​ 后面再出现循环依赖,会先去二级缓存中找,没有再去提前AOP

​ 当当前对象创建完后,再从二级缓存中取出半成品对象继续初始化完再放进单例池中,半成品会被删掉

​ 而创建二级缓存中代理对象的方法,存在三级缓存(singletonFactories)中,在去二级缓存中找代理对象而没有找到时调用,调用完删掉。在之后正常流程的AOP时发现三级缓存里的被删除了的话,就不再AOP

@Lazy

​ 成因:@Async会生成代理对象,但和AOP是不同的逻辑,是在后处理器中做的代理。所以在提前AOP时放入二级缓存的是原始对象,注入的也是原始对象。而之后从二级缓存中取出继续初始化后,放入一级缓存的却是代理对象

​ 成因:在构造函数时出现循环依赖,即创建不出原始对象,如AService(Bservice b)、BService(AService a)

​ 解决:使用@Lazy会先用一个代理对象来占位,到之后用到时再注入对象,从而不会发生循环依赖

三级缓存

三级缓存就是三个Map,最终是为了保证单例

  • 第一级缓存:singletonObjects (单例池):存放创建好的bean
  • 第二级缓存:earlySingletonObjects:存放提前创建的代理对象
  • 第三级缓存:singletonFactories:存放创建代理对象的方法

源码

1.Bean的创建

分析过程

目标:通过debug来寻找在哪里创建了User对象,根据何时打印构造方法里的打印来寻找

1
2
3
4
5
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
<version>2.3.10.RELEASE</version>
</dependency>
  1. 通过配置文件方式将bean注入到容器中
1
2
3
4
5
6
7
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd ">
<bean class="com.xw.domain.User" id="user">
</bean>
</beans>
1
2
3
4
5
6
7
8
9
10
package com.xw.domain;

public class User {
private Integer id;
private String name;

public User() {
System.out.println("创建了User");
}
}
  1. 创建容器,通过debug发现实在创建容器时创建了user
1
2
3
4
5
6
7
8
9
10
11
12
package com.xw;

import com.xw.domain.User;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class SpringDemo {
public static void main(String[] args) {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
User user = (User) context.getBean("user");
System.out.println(user);
}
}
  1. 继续往里看,debug发现执行到refresh()时创建了user
1
2
3
4
5
6
7
8
9
10
public ClassPathXmlApplicationContext(
String[] configLocations, boolean refresh, @Nullable ApplicationContext parent)
throws BeansException {

super(parent);
setConfigLocations(configLocations);
if (refresh) {
refresh();
}
}
  1. 点入refresh(),发现创建容器的步骤,其中执行到第11步finishBeanFactoryInitialization(beanFactory);时创建了user

  1. 点入finishBeanFactoryInitialization()方法,发现调用了beanFactory.preInstantiateSingletons();后创建了user
  2. 继续往下点发现在方法里判断当前类是不是抽象、单例、懒惰,后调用了getBean(beanName)来创建或获取Bean
  3. getBean()调用doGetBean(),会先尝试从缓存中取,没有则先初始化依赖的Bean,然后调用createBean()来实例化和初始化Bean
    1. createBean()中调用了InstantiationAwareBeanPostProcessor()后置处理器来实现AOP,然后调用doCreateBean()反射实例化和初始化Bean

总结

​ 到这里发现是调用了beanFactory的doGetBean()方法创建了非懒加载的单例bean。beanFactory通过反射创建空参的单例对象,创建后将对象保存到了一个map集合中(singletonObjects),key为bean的名字,value为user的单例对象。

2.Bean的获取

context.getBean(),获取beanFactory,通过beanFactory的getBean方法,根据名称获取创建时放进map里的bean


Spring底层原理
http://xwww12.github.io/2023/03/19/spring/Spring底层原理/
作者
xw
发布于
2023年3月19日
许可协议