关于Spring底层原理整体脉络

关于Spring底层原理整体脉络

    • 一、Spring创建一个对象
    • 二、Bean的创建过程
    • 三、推断构造方法
    • 四、AOP大致流程
    • 五、Spring事务

一、Spring创建一个对象

Spring 入门代码:

ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("spring.xml");
UserService userService = (UserService) context.getBean("userService");
userService.test();

ClassPathXmlApplicationContext 已过时,在新版的 Spring MVC 和 Spring Boot 的底层主要使用 AnnotationConfigApplicationContext

AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
UserService userService = (UserService) context.getBean("userService");
userService.test();
  • AnnotationConfigApplicationContext 的用法和 ClassPathXmlApplicationContext 非常类似,只是需要传入的是一个 class,而不是一个 xml 文件。
  • AppConfig.class 和 spring.xml 一样,表示 Spring 的配置,例如可以指定扫描路径,可以直接定义 Bean。

spring.xml:

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

AppConfig:

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

spring.xml 和 AppConfig.class 本质上是一样的。

目前很少直接使用上述方式来使用 Spring,而是使用 Spring MVC 或 Spring Boot,其底层都基于上述方式,均需在内部创建一个 ApplicationContext:

  1. Spring MVC 创建:XmlWebApplicationContext,与 **ClassPathXmlApplicationContext **类似,都是基于 XML 配置。
  2. Spring Boot 创建:AnnotationConfigApplicationContext。

AnnotationConfigApplicationContext 比较重要,并且和 ClassPathXmlApplicationContext 大部分底层都是共同的。

AnnotationConfigApplicationContext 与 ClassPathXmlApplicationContext 可以简单理解为都是用来创建 Java 对象的,例如调用 getBean() 可能就会去创建对象。

根据某个类创建一个对象:

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

在调用 AnnotationConfigApplicationContext 的构造方法时:

  1. 解析 AppConfig.class,得到扫描路径。
  2. 遍历扫描路径下的所有 Java 类,若发现某个类上存在 @Component、@Service 等注解,Spring 则将此类记录,存于一个 Map 中,在 Spring 源码中叫做 BeanDefinitionMap
  3. Spring 会根据某个规则生成当前类对应的 beanName,作为 key 存入 Map,当前类作为 value。

因此调用 context.getBean(“userService”) 时,便可根据 “userService” 找到 UserService 类,从而去创建对象。

二、Bean的创建过程

Bean 创建的生命周期:

关于Spring底层原理整体脉络_第1张图片

  1. 利用该类的构造方法来实例化得到一个对象(若一个类中有多个构造方法,Spring 则会进行选择,此过程即推断构造方法
  2. 得到一个对象后,Spring 会判断该对象中是否存在被 @Autowired 注解了的属性,将这些属性找出来并由 Spring 进行赋值(依赖注入
  3. 依赖注入后,Spring 会判断该对象是否实现了 BeanNameAware 接口、BeanClassLoaderAware 接口、BeanFactoryAware 接口,若实现则表示当前对象必须实现该接口中所定义的 setBeanName()、setBeanClassLoader()、setBeanFactory() ,Spring 则会调用这些方法并传入相应的参数(Aware回调
  4. Aware 回调后,Spring 会判断该对象中是否存在某个方法被 @PostConstruct 注解,若存在,Spring 则会调用当前对象的此方法(初始化前
  5. Spring 接着会判断该对象是否实现了 InitializingBean 接口,若实现则表示当前对象必须实现该接口中的 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 中,Map 的 key 为 beanName,value 为 Bean对象。之后在 getBean 时即可直接从 Map 中拿到对应的 Bean 对象,在 Spring 源码中,此 Map 即为单例池
  2. 若当前 Bean 是原型 Bean,则后续没有其他动作,不会存入一个 Map,下次 getBean 时会再次执行上述创建过程,得到一个新的 Bean 对象。

三、推断构造方法

Spring 在基于某个类生成 Bean 的过程中,需要利用该类的构造方法来实例化得到一个对象,但是若一个类存在多个构造方法,Spring 将进行推断。

Spring 的判断逻辑:

  1. 若一个类只存在一个构造方法,则不管该构造方法是无参还是有参,均会被 Spring 选择
  2. 若一个类存在多个构造方法,则进行进一步判断:
    1. 这些构造方法中若存在一个无参构造方法,Spring 则直接选择此无参构造方法
    2. 这些构造方法中若不存在一个无参构造方法,Spring 则会报错

Spring 设计思想:

  1. 若一个类只有一个构造方法,则直接选择
  2. 若一个类存在多个构造方法,Spring 则会优先选择无参构造方法,因为无参构造方法本身即为一种默认含义
  3. 但若某个构造方法上加了 @Autowired 注解,则表示程序员让 Spring 优先使用此构造方法,Spring 则会选择此加了 @Autowired 注解的构造方法

若 Spring 选择了一个有参的构造方法,则 Spring 在调用此构造方法时,对传参的选择:

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

  1. 先根据入参类型找,如果只找到一个,那就直接用来作为入参
  2. 如果根据类型找到多个,则再根据入参名字来确定唯一一个
  3. 最终如果没有找到,则会报错,无法创建当前Bean对象

确定用哪个构造方法,确定入参的 Bean 对象,此过程即为推断构造方法

若一个对象分别使用 @Bean 和 @Component 定义,则 @Bean 覆盖 @Component

四、AOP大致流程

AOP 即为进行动态代理,在创建一个 Bean 的过程中,Spring 在最后一步会去判断当前正在创建的这个 Bean 是否需要进行AOP,若需要则会进行动态代理。

判断当前 Bean 对象是否需要进行 AOP:

  1. 找出所有的切面 Bean
  2. 遍历切面中的每个方法,看是否有 @Before、@After 等注解
  3. 若有,则将方法缓存于一个 map 里,接着判断所对应的 Pointcut 是否和当前 Bean 对象的类匹配
  4. 若匹配,则表示当前 Bean 对象需要进行 AOP,待到执行时直接从缓存中调用对应的切面方法

cglib 进行 AOP 的大致流程:

  1. 生成代理类 UserServiceProxy,代理类继承 UserService
  2. 代理类中重写了父类的方法,例如 UserService#test()
  3. 代理类中还有一个 target 属性,该属性的值即为被代理对象(即原来的通过 UserService 类进行推断构造方法实例化出来的对象,进行了依赖注入、初始化等步骤的对象)
  4. 若类中含有 test 方法,则代理类中的 test 方法被执行时:
    1. 执行切面逻辑(@Before)
    2. 调用 target.test()

因此从 Spring 容器得到 UserService 的 Bean 对象时,实际上得到的是 UserServiceProxy 所生成的对象,即代理对象。

伪代码:

class UserServiceProxy extend Userservice {
	UserService target;// 通过传参赋值为普通对象

    public void test() {
    	// 执行切面注解的方法 @Before
        target.test();
        // 执行切面注解的方法 @After
    }
}
  • 若使用代理对象调用方法的话,由于 AOP 是在创建 Bean 的最后一步进行,之后并没有再次进行依赖注入,因此代理对象中的其他属性是没有值的,无法直接使用。
  • 而使用 target 进行调用的话,即使用普通对象进行调用方法,在之前已经进行过依赖注入,因此对象中的其他属性是有值的,可直接使用。

target 对象不是代理对象,而是被代理对象。

五、Spring事务

当某个方法上加 @Transactional 注解后,就表示该方法在调用时会开启 Spring 事务,而此方法所在的类所对应的 Bean 对象会是该类的代理对象。

Spring 事务的代理对象执行某个方法时:

  1. 判断当前执行的方法是否存在 @Transactional 注解
  2. 若存在,则利用事务管理器(TransactionMananger)新建一个数据库连接
  3. 修改数据库连接的 autocommit 为 false(不自动提交)
  4. 执行 target.test(),执行程序员所写的业务逻辑代码,即执行 sql
  5. 执行完成之后若没有出现异常,则提交,否则回滚

Spring 事务是否会失效的判断标准:
某个加了 @Transactional 注解的方法被调用时,需判断是否是直接被代理对象调用的,若是则事务生效,若不是则事务失效。

你可能感兴趣的:(Spring源码专栏,spring,java,后端)