Spring 底层原理整体脉络

Spring 基础回顾

入门使用

java 复制代码
// 创建一个Spring容器
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("spring.xml");
// 从容器中获取Bean
UserService userService = (UserService) context.getBean("userService");
// 调用userService的test方法
userService.test();

以上三行代码为 Spring 学习过程中的入门代码。大体来说,上面代码的意思是:

  1. 构造一个 Spring 容器,用于管理各个 bean,一旦容器被创建,就可以从容器中获取这 bean,并且利用它们来构建应用程序。
  2. 从构建的 Spring 容器中,获取名称为 userService 的 bean。
  3. 通过从 Spring 中获取的对象,调用对象的方法。

下面介绍另一种初始化 Spring 容器的方式,代码如下:

java 复制代码
// 构造Spring容器
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);

该方法与上一种方法初始化 Spring 容器的使用方式一样,只不过创建时,传入的参数不同,一个是传入 xml 文件,另一个是传入class文件。

问题思考

通过以上三行代码,可以总结出三个问题

  • Spring 容器是怎么初始化的?
  • 通过 Spring 容器获取的对象,和直接 new 出来的对象有什么区别呢?
  • 使用 Spring 中获取的对象调用该方法和通过 new 出来的对象调用该方法,有什么区别吗?

后续通过对 Spring 中 bean 的初始化流程,对以上问题进行解答。

Spring 中对象的创建

在 Spring 中,对象的创建大部分都是在创建 Spring 容器的时候进行创建的,也就是该行代码所运行的时机。

java 复制代码
// 构造Spring容器
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
  1. 首先看下 AppConfig.class 文件中的内容:
java 复制代码
@ComponentScan("com.lingxi")  
public class AppConfig {

	/**
		往Spring中注册对象,在此处Spring中有两个OrderService对象,
		一个名为orderService,一个名为orderService1
		第一个对象是Spring进行包扫描时,自动注册的对象,第二个是该方法注册的对象。
	*/
    @Bean
    public OrderService orderService1() {
        return new OrderService();
    }
}

在该文件配置中,主要代码就是@ComponentScan("com.lingxi") ,它告诉 Spring 要去扫描哪个包下的文件,去获取扫描路径。该注解的 ==value== 参数是个数组类型,可以指定多个包。

  1. 根据从注解中获取的扫描路径,进行包扫码 Spring 根据从第一步获取到的扫描路径,对该路径下的所有类进行扫描,若某个类中含有@Component@Service@mapper等注解,Spring 会将它们记录下来,放到一个Map中,该Map的结构为 Map<String, BeanDefinition>,key 为对象名称,val 可以理解为该类的信息。
  2. 当调用 getBean()方法时,会从该 Map 中获取对象,并返回。

Bean 的创建过程

上图为 Spring 创建 bean 的大体流程。

对象创建:利用该类的构造方法,去实例化对象。

依赖注入:创建完对象之后,会判断该对象是否有需要注入的属性值,若存在需要注入的属性值,则对这些属性进行注入。

Aware回调:判断该对象是否实现了Aware回调接口,若实现了相应接口,那Spring就会调用这些方法并传入相应的参数。

初始化前 :判断该对象是否有被@PostConstruct注解修饰的方法,若存在,则去执行该方法。

初始化 :判断该对象是否实现了InitializingBean接口,若实现了该接口,则去执行afterPropertiesSet方法。

初始化后 :判断该对象是否需要进行 AOP,若需要进行 AOP,则会生成该对象的代理对象,放进beanDefinitionMap中;若不需要进行AOP,则生成原始对象,放进该beanDefinitionMap中。

构造方法的推断

在 Spring 创建对象的过程中,具体选择哪一个构造方法去创建对象,也是有规律的。

  • 当构造方法只有一个时,不论该构造方法是有参构造还是无参构造,都会选择该构造方法
  • 当构造方法有多个时
    • 若存在无参构造,则使用无参构造去创建对象。
    • 若不存在无参构造,则会报错 ,若要指定默认构造方法,则需要在构造方法上添加@Autowired注解,指定默认构造方法。

验证如下: 验证1: 首先验证只有一个无参构造方法的情况:

java 复制代码
@Component
public class OrderService {
    // 无参构造
    public OrderService() {
        System.out.println("无参构造 ====》orderService init ...");
    }
    
    public void test() {
        System.out.println("orderService test ...");
    }

}

输出结果:

然后验证只有一个有参构造的情况

java 复制代码
@Component
public class OrderService {
    // 有参构造
    public OrderService(UserService userService) {
        System.out.println("有参构造 ====》orderService init ...");
    }

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

输出结果如下:

即当对象只有一个构造方法时,不论该方法为有参构造还是无参构造,Spring 都会使用这个构造方法去构造对象。

验证2:存在多个构造函数,且存在一个无参构造

java 复制代码
@Component
public class OrderService {

    public OrderService() {
        System.out.println("无参构造 ====》orderService init ...");
    }

    public OrderService(UserService userService) {
        System.out.println("有参构造 ====》orderService init ...");
    }

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

当存在多个构造函数,且存在一个无参构造,会使用无参构造去创建对象。

验证3:存在多个构造函数,且没有无参构造。

java 复制代码
@Component
public class OrderService {

    public OrderService(UserService userService) {
        System.out.println("有参构造1 ====》orderService init ...");
    }

    public OrderService(UserService userService, UserService userService1) {
        System.out.println("有参构造2 ====》orderService init ...");
    }

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

这里报错 No default constructor found; nested exception is java.lang.NoSuchMethodException 说明在创建对象时,未找到默认构造方法,此时需要告诉 Spring 使用哪一个构造方法进行对象的创建。 指定默认构造方法:

java 复制代码
@Component
public class OrderService {

	// 指定该构造方法为默认构造方法,也可指定另一个
	@Autowired
    public OrderService(UserService userService) {
        System.out.println("有参构造1 ====》orderService init ...");
    }
	
    public OrderService(UserService userService, UserService userService1) {
        System.out.println("有参构造2 ====》orderService init ...");
    }

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

当存在多个构造方法时,且不存在无参构造,Spring 会抛出异常,找不到默认构造方法,需在想要的构造方法上,添加 @Autowired 注解,指定构造方法。
注意: Spring会根据入参的类型和入参的名字去Spring中找Bean对象: 1.先根据入参类型找,如果只找到一个,那就直接用来作为入参 2.如果根据类型找到多个,则再根据入参名字来确定唯一一个 3.最终如果没有找到,则会报错,无法创建当前Bean对象

AOP 的大体流程

AOP 是 Spring 的一个重要模块,其核心就是动态代理。 在 Spring 创建 Bean 的过程中,会判断该对象是否需要进行 AOP,若需要进行 AOP,则会进行动态代理,产生代理对象。

如何判断对象是否需要进行动态代理

  1. 找出所有的切面 Bean, 即被 @Aspect 注解标记的类。
  2. 遍历切面中的每个方法,看是否写了@Before@After等注解。
  3. 如果写了,则判断所对应的Pointcut(切点表达式)是否和当前 Bean 对象的类是否匹配。
  4. 如果匹配则表示当前Bean对象有匹配的的Pointcut,表示需要进行 AOP,则需要产生代理对象。

AOP 的大体流程

  1. 通过 cglib 动态代理,由被代理类 UserService, 生成代理类 UserServiceProxy
  2. 代理类中重写了父类的方法,比如 UserService 中的test()方法
  3. 代理类中有一个 target 属性,该属性的值为被代理对象(也就是由 Spring 创建的普通对象)
  4. 代理类中的test()方法被执行时的逻辑如下: a. 执行切面逻辑(@Before) b. 调用target.test()

即通过原始对象调用test()方法时,不会获得 AOP 增强的功能,只有原始功能;而通过代理对象去调用test()方法时,能够根据在 Spring 中配置的相应方法,对原始功能进行增强。

相关推荐
幽络源小助理20 小时前
springboot校园车辆管理系统源码 – SpringBoot+Vue项目免费下载 | 幽络源
vue.js·spring boot·后端
刀法如飞20 小时前
一款开箱即用的Spring Boot 4 DDD工程脚手架
java·后端·架构
uzong20 小时前
后端系统设计文档模板
后端
幽络源小助理21 小时前
SpringBoot+Vue车票管理系统源码下载 – 幽络源免费项目实战代码
vue.js·spring boot·后端
uzong21 小时前
软件架构指南 Software Architecture Guide
后端
又是忙碌的一天21 小时前
SpringBoot 创建及登录、拦截器
java·spring boot·后端
勇哥java实战分享1 天前
短信平台 Pro 版本 ,比开源版本更强大
后端
学历真的很重要1 天前
LangChain V1.0 Context Engineering(上下文工程)详细指南
人工智能·后端·学习·语言模型·面试·职场和发展·langchain
计算机毕设VX:Fegn08951 天前
计算机毕业设计|基于springboot + vue二手家电管理系统(源码+数据库+文档)
vue.js·spring boot·后端·课程设计
上进小菜猪1 天前
基于 YOLOv8 的智能杂草检测识别实战 [目标检测完整源码]
后端