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 中配置的相应方法,对原始功能进行增强。

相关推荐
黄俊懿1 分钟前
【深入理解SpringCloud微服务】Sentinel功能详解
后端·spring·spring cloud·微服务·中间件·架构·sentinel
运维&陈同学5 分钟前
【zookeeper04】消息队列与微服务之zookeeper客户端访问
linux·后端·微服务·zookeeper·云原生·消息队列·云计算
2401_854391081 小时前
企业OA管理系统:Spring Boot技术架构与应用
spring boot·后端·架构
潜洋1 小时前
Spring Boot教程之七: Spring Boot –注释
java·spring boot·后端·注释
不能只会打代码1 小时前
深入讲解Spring Boot和Spring Cloud,外加图书管理系统实战!
spring boot·后端·spring cloud
一见1 小时前
go编程中yaml的inline应用
开发语言·后端·golang
程序猿进阶1 小时前
Otter 安装流程
java·数据库·后端·mysql·数据同步·db·otter
水w2 小时前
详细介绍HTTP与RPC:为什么有了HTTP,还需要RPC?
java·开发语言·后端·http·rpc·1024程序员节
我的运维人生2 小时前
Spring Boot应用开发深度解析与实践案例
java·spring boot·后端·运维开发·技术共享
Peter_chq2 小时前
【计算机网络】数据链路层
linux·c语言·开发语言·网络·c++·后端·网络协议