Spring 底层原理整体脉络

Spring 基础回顾

入门使用

// 创建一个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 容器的方式,代码如下:

// 构造Spring容器
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);

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

问题思考

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

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

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

Spring 中对象的创建

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

// 构造Spring容器
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
  1. 首先看下 AppConfig.class 文件中的内容:
@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:
首先验证只有一个无参构造方法的情况:

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

}

输出结果:
在这里插入图片描述

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

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

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

输出结果如下:
在这里插入图片描述

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

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

@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:存在多个构造函数,且没有无参构造。

@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 使用哪一个构造方法进行对象的创建。
指定默认构造方法:

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

  • 19
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值