Spring底层核心原理解析

113 阅读6分钟

一.识点串讲,后续章节进行深入分析

内容:

  1. Bean的生命周期底层原理
  2. 依赖注入底层实现原理
  3. 初始化底层原理
  4. 推断构造方法,底层实现原理
  5. AOP底层实现原理
  6. Spring事务底仓原理

1.1 Bean的生命周期

先看一段下面代码

ClassPathXmlApplication context  =  new ClassPathXmlApplicationContext("spring.xml");

UserService userService = context.getBean("userService");
userService.test();

这三行代码做了什么呢?

  1. 第一行代码,会构造一个ClassPathXmlApplicationContext对象, ClassPathXmlApplicationContext,调用该构造方法除开会实例化得到 一个对象,还会做哪些事情?
  2. 第二行代码,会调用ClassPathXmlApplicationContext的getBean方法,会得到 一个UserService对象,getBean()是如何实现的?返回的UserService对象和我们自 己直接new的UserService对象有区别吗?
  3. 第三行代码,就是简单的调用UserService的test()方法。

对于上的三行代码,如果要用的话就得这么写,如果你要用Spring,你就得这么写。就像你要用Mybatis,你就得写各种 Mapper接口。 但是用ClassPathXmlApplicationCotext其实已经过时了,在新版的Spring MVC和SpringBoot的底层主要用的都是AnnotationConfigAppilcationApplicationContext.

 AnnotationConfigApplicationContext  context = new  AnnotationConfigApplicationContext(AppConfig.class);
 UserService userservice = (UserService)context.getBean("userService");
 userServcie.test();
 

可以看到AnnotationConfigApplicationContext的用法和 ClassPathXmlApplicationContext是非常类似的,只不过需要传入的是一个class,而不是 一个xml文件。 而AppConfig.class和spring.xml一样,表示Spring的配置,比如可以指定扫描路径,可以 直接定义Bean,比如: spring.xml中的内容为:

<context:component-scan base-package = "com.ruozhuo"/>
<bean id = "userService" class = "com.ruozhuo.service.UserService"


AppConfig里面的内容为:

@ComponentScan("com.ruozhuo")
public class AppConfig{
@Bean
public UserService userService(){
return new UserService;
}

}

spring.xml和AppConfig的本质是一样的 目前我们基本很少使用上面两种方式来用spring,而是使用SpringMVC,或是SpringBoot,但是他们都是基于上面的方式进行的,都是需要在内部构建一个ApplicationContext的,只不过:

  1. Spring MVC创建的是XmlWebApplicationContext,和 ClassPathXmlApplicationContext类似,都是基于XML配置的
  2. Spring Boot创建的是AnnotationConfigApplicationContext 因为AnnotationConfigApplicationContext是比较重要的,并且 AnnotationConfigApplicationContext和ClassPathXmlApplicationContext大部分底层 都是共同的,后续会着重介绍AnnotationConfigApplicationContext的底层实现

二.Spring中如何创建一个对象

不管是AnnotationConfigApplicationContext还是ClassPathXmlAplicationContext,都可以简单的理解为就是用来创建Java对象的,比如调用getBean()就会去创建对象(此处不严谨,getBean可能也不会去创 建对象,后续详解)

在Java语言中,肯定是根据某个类来创建一个对象的。我们在看一下实例代码:


AnnotationConfigApplicationContext context =
new 
AnnotationConfigApplicationContext(AppConfig.class); 

UserService userService = (UserService) context.getBean("userService"); 

userService.test();

当我们调用context.getBean("userService")时,就会去创建一个对象,但是getBean方法 内部怎么知道"userService"对应的是UserService类呢? 所以,我们就可以分析出来,在调用AnnotationConfigApplicationContext的构造方法 时,也就是第一行代码,会去做一些事情:

1. 解析AppConfig.class,得到扫描路径

2. 遍历扫描路径下的所有Java类,如果发现某个类上存在@Component、 @Service等注解,那么Spring就把这个类记录下来,存在一个Map中,比如 Map<String,Class>(实际上,Spring源码中确实存在类似的这么一个Map,叫 做BeanDefinitionMap,后续会讲到)

3. Spring会根据某个规则生成当前类对应的beanName,作为key存入Map,当前 类作为value

总结: 扫描到注解:@Component注解、@Service注解、@Bean注解等,根据一定的规则生成当前类对应的beanName-----即对象名字---》作为key存入Map中,当前类作为value.这样在调用context.getBean("userService")时候,就可以根据userService(key)映射到(valeu)UserService类,-----紧接着根据UserService类去创建对象。

image.png

三.Bean的创建过程

那么Spring到底是如何来创建一个Bean的呢,这个就是Bean创建的生命周期,大致过程如下:

  1. 利用类的构造方法来实例化得到一个对象

    有多方法----spring会进行选择,这个叫做推断构造方法。

  2. 得到对象之后会判断是否有被@Autowired注解修饰的属性,这些属性找出来并由Spring进行行赋值-----这个过程叫做依赖注入。

  3. 依赖注入后spring会判断该对象是否实现了BeanNameAware接口、BeanClassLoaderAware接口、BeanFactoryAware接口,如果实现了,就表示当前对象必须分别实现接口中定义的方法

    • BeanNameAware接口-----setBeanName(设置对象名字)
    • BeanFactoryAware接口-----setBeanClassLoader(设置对象类加载器)
    • BeanFactoryAware------setBeanFactory(设置对象工厂)

    Spring会调用这些方法,并传入相应的参数**(Aware回调**

  4. Aware回调后----即设置完,Spring会判断该对象中是否存在某个方法被@PostConstruct注解了,如果存在Spring会调用当前对象的此方法**(初始化前)**

  5. 紧接着,Spring会判断该对象是否实现了InitiailizingBean接口,如果实现了就表示当前对象必须实现该接口中的“afterPropertiesSet()”方法,那Spring就会调用当 前对象中的afterPropertiesSet()方法(初始化)属性赋值之后。

  6. 最后,Spring会判断当前对象需不需要进行AOP,如果不需要那么Bean就创建完 了,如果需要进行AOP,则会进行动态代理并生成一个代理对象做为Bean(初始化 后) 通过最后一步,我们可以发现,当Spring根据UserService类来创建一个Bean时:

  1. 如果不用进行AOP,那么Bean就是UserService类的构造方法所得到的对象。

  2. 如果需要进行AOP,那么Bean就是UserService的代理类所实例化得到的对象,而 不是UserService本身所得到的对象。

Bean对象创建出来后: 1. 如果当前的Bean是单例Bean------会把Bean对象存入一个Map<String,Object>,Map的key为beanName,valu为Bean对象。这样下次getBean时就可 以直接从Map中拿到对应的Bean对象了。(实际上,在Spring源码中,这个Map就 是单例池) 2. 如果当前Bean是原型Bean,那么后续没有其他动作,不会存入一个Map,下次 getBean时会再次执行上述创建过程,得到一个新的Bean对象。

推断构造方法

Spring在基于某个类生成Bean的过程中,需要利用该类的构造方法来实例化得到一个对 象,但是如果一个类存在多个构造方法,Spring会使用哪个呢?

Spring的判断逻辑如下:

  1. 如果一个类只存在一个构造方法,不管该构造方法是无参构造方法,还是有参构造 方法,Spring都会用这个构造方法

  2. 如果一个类存在多个构造方法

    a. 这些构造方法中,存在一个无参的构造方法,那么Spring就会用 这个无参的构造方法

    b. 这些构造方法中,不存在一个无参的构造方法,那么Spring就会 报错 Spring的设计思想是这样的:

    1. 如果一个类只有一个构造方法,那么没得选择,只能用这个构造方法

    2. 如果一个类存在多个构造方法,Spring不知道如何选择,就会看是否有无参的构 造方法,因为无参构造方法本身表示了一种默认的意义

    3. 不过如果某个构造方法上加了@Autowired注解,那就表示程序员告诉Spring就用这个加了注解的方法,那Spring就会用这个加了@Autowired注解构造方法了 需要重视的是

    4. 如果Spring选择了一个有参的构造方法,Spring在调用这个有参构造方法 时,需要传入参数,那这个参数是怎么来的呢?

    Spring会根据入参的类型和入参的名字去Spring中找Bean对象(以单例Bean为例, Spring会从单例池那个Map中去找):

    a. 先根据入参类型找,如果只找到一个,那就直接用来作为入参

    b. 如果根据类型找到多个,则再根据入参名字来确定唯一一个

    c. 最终如果没有找到,则会报错,无法创建当前Bean对象 确定用哪个构造方法,确定入参的Bean对象,这个过程就叫做推断构造方法。

AOP的大致流程

AOP就是进行动态代理,在创建Bena的过程中,Spring会在最后一步判断当前正在创建的这个Bean是不是需要进行AOP,如果需要就会进行动态代理。

如何判断当前Bean是否需要动态代理:

1. 找出所有的切面Bean
2. 遍历切面中的每一个方法,看是否写了@Before,@After注解
3. 如果写了,则判断所对应的@PointCut是否和当前的Bean对象的类匹配
4. 如果匹配则表示当前Bean对象有匹配的PointCut,表示需要进行AOP