面向面试的Spring全家桶

Spring

概述

Spring 是什么

Spring 框架是一个开放源代码的J2EE应用程序框架,是针对 bean 的生命周期进行管理的轻量级容器,其提供了功能强大 IOC、AOP 及Web MVC 等功能。Spring可以单独应用于构筑应用程序,也可以和其它框架组合使用。

Spring 的优点

  • 方便解耦,简化开发 ;
  • Spring 的 IOC(控制反转) 机制将对象之间的依赖关系交由框架处理,减低组件的耦合性;
  • Spring 提供了 AOP 技术,支持将一些通用任务,如安全,事务,日志,权限等进行集中式管理,从而提供更好的复用;
  • Spring 对于主流的应用框架提供了集成支持;

Spring 容器启动流程

  1. 在创建 Spring 容器,也就是启动 Spring 时:
  2. ⾸先会进⾏扫描,扫描得到所有的 BeanDefinition 对象,并存在⼀个 Map 中
  3. 然后筛选出⾮懒加载的单例 BeanDefinition 进⾏创建 Bean,对于多例 Bean 不需要在启动过程中去进⾏创建,对于多例 Bean 会在每次获取 Bean 时利⽤BeanDefinition 去创建
  4. 利⽤ BeanDefinition 创建 Bean 就是 Bean 的创建⽣命周期,这期间包括了合并 BeanDefinition 、推断构造⽅法、实例化、属性填充、初始化前、初始化、初始化后等步骤,其中AOP就是发⽣在初始化后这⼀步骤中
  5. 单例 Bean 创建完了之后,Spring 会发布⼀个容器启动事件
  6. Spring 启动结束

Spring 中的设计模式

image-20210912212656614

Spring Boot、Spring MVC 和 Spring 有什么区别

Spring 是⼀个 IOC 容器,⽤来管理 Bean,使⽤依赖注⼊实现控制反转,可以很⽅便的整合各种框架,提供 AOP 机制弥补 OOP 的代码重复问题、更⽅便将不同类不同⽅法中的共同处理抽取成切⾯、⾃动注⼊给⽅法执⾏,⽐如⽇志、异常等

SpringMvc 是 Spring 对 web 框架的⼀个解决⽅案,提供了⼀个总的前端控制器 Servlet,⽤来接收请求,然后定义了⼀套路由策略(url 到 handle 的映射)及适配执⾏ handle,将 handle 结果使⽤视图解析技术⽣成视图展现给前端

SpringBoot 是 Spring 提供的⼀个快速开发⼯具包,让程序员能更⽅便、更快速的开发 Spring+SpringMvc 应⽤,简化了配置(约定了默认配置),整合了⼀系列的解决⽅案,可以开箱即⽤


Spring 的 IOC

IoC 就是控制反转,是指创建对象的控制权的转移,以前创建对象的主动权和时机是由自己把控的,而现在这种权力转移到 Spring 容器中,并由容器根据配置文件去创建实例和管理各个实例之间的依赖关系,对象与对象之间松散耦合,也利于功能的复用。

DI(Dependency Injection) 依赖注入,和控制反转是同一个概念的不同角度的描述,即应用程序在运行时依赖 IoC 容器来动态注入对象需要的外部资源;也就是说,我们的程序在编写时,通过控制反转,把对象的创建交给了 Spring,但是代码中不可能出现没有依赖的情况,那这种依赖关系(如业务层和持久层直接的依赖关系),在使用 Spring 之后,就让 Spring 来维护了。


BeanFactory 和 ApplicationContext

BeanFactory 是 Spring 的“心脏”。它就是 Spring IoC 容器的真面目。Spring 使用 BeanFactory 来实例化、配置和管理 Bean。

如果说 BeanFactory 是 Spring 的心脏,那么 ApplicationContext 就是完整的躯体了, ApplicationContext 由 BeanFactory 派生而来,提供了更多面向实际应用的功能。在BeanFactory 中,很多功能需要以编程的方式实现,而在 ApplicationContext 中则可以通过配置实现。

  • BeanFactory

    • 提供了 IoC 容器应遵守的的最基本的接口;

    • 是 Spring 里面最底层的接口,包含了各种 bean 的定义,读取 bean 配置文档,管理 bean 的加载,实例化,控制 bean 的生命周期,维护 bean 之间的依赖关系 等等;

    • BeanFactroy 采用的是延迟加载形式来注入 Bean 的,即只有在使用到某个 Bean 时(调用getBean()),才对该 Bean 进行加载实例化;

      1. BeanFactory 作为一个主接口不继承任何接口,暂且称为一级接口。  

      2. 有 3 个子接口继承了它,进行功能上的增强。这 3 个子接口称为二级接口。 

      3. ConfigurableBeanFactory 可以被称为三级接口,对二级接口HierarchicalBeanFactory 进行了再次增强,它还继承了另一个外来的接口SingletonBeanRegistry  

      4. ConfigurableListableBeanFactory 是一个更强大的接口,继承了上述的所有接口,无所不包,称为四级接口。

        上面这 4 级接口是BeanFactory的基本接口体系。下面是继承关系的 2 个抽象类和 2 个实现类

      5. AbstractBeanFactory 作为一个抽象类,实现了三级接口ConfigurableBeanFactory 大部分功能。

      6. AbstractAutowireCapableBeanFactory 同样是抽象类,继承自AbstractBeanFactory,并额外实现了二级接口 AutowireCapableBeanFactory  

      7. DefaultListableBeanFactory 继承自 AbstractAutowireCapableBeanFactory,实现了最强大的四级接口 ConfigurableListableBeanFactory,并实现了一个外来接口 BeanDefinitionRegistry,它并非抽象类。

      8. 最后是最强大的 XmlBeanFactory,继承自 DefaultListableBeanFactory,重写了一些功能,使自己更强大。

  • ApplicationContext

    • ApplicationContext 是 BeanFactory 的子接口,除了提供 BeanFactory 所具有的功能外,还提供了更完整的框架功能;
    • BeanFactroy 采用的是延迟加载形式来注入 Bean 的,即只有在使用到某个 Bean 时(调用 getBean()),才对该 Bean 进行加载实例化,而ApplicationContext 则相反,它是在容器启动时,一次性创建了所有的 Bean。这样,在容器启动时,我们就可以发现 Spring 中存在的配置错误。
    • ApplicationContext 下主要有三个实现类:
      • ClassPathXmlApplicationContext(从类的根路径下加载配置文件)
      • FileSystemXmlApplicationContext(从磁盘路径上加载配置文件)
      • AnnotationConfigApplicationContext(使用注解配置容器对象时,需要使用此类来创建 Spring 容器)

Bean 的作用域

JavaBean、SpringBean、对象之间的区别?

JavaBean:JavaBean 是一种 Java 语言写的可重用组件。其实就是符合一定规范的 Java类,是一种规范。它的方法命名,构造以及行为必须符合特定的要求:

  1. 所有属性为 private
  2. 这个类必须具有一个公共的(public)无参构造函数
  3. private 属性必须提供 public 的 getter 和 setter 来给外部访问,并且方法的命名也
    必须遵循一定的命名规范
  4. 这个类是可序列化的,要实现 serializable 接口

SpringBean:SpringBean 是受 Spring 管理的对象,所有能受 Spring 容器管理的对象都可以成 SpringBean。Spring 中的 bean,是通过配置文件、javaconfig 等的设置,由 Spring 自动实例化,用完后自动销毁的对象。让我们只需要在用的时候使用对象就可以,不用考虑如果创建类对象(这就是 spring 的注入)。

FactoryBean:是一个 Java Bean,但是它是一个能生产对象的工厂 Bean,以 Bean 结尾,表示它是一个 Bean,不同于普通 Bean 的是:它是实现了FactoryBean 接口的 Bean,根据该 Bean 的 ID 从 BeanFactory 中获取的实际上是FactoryBean 的 getObject() 返回的对象,而不是 FactoryBean 本身,如果要获取 FactoryBean 对象,需要在id前面加一个&符号来获取。

使用:

@Configuration
public class AppConfig {
    @Bean
    public TransferService transferService() {
        return new TransferServiceImpl();
    }
}

Spring 容器中的 bean 可以分为5个范围:

  • singleton(单例):默认,每个容器中只有一个 bean 的实例,单例的模式由 BeanFactory 自身来维护;
  • prototype(原型):为每一个 bean 请求提供一个实例;
  • request(请求):为每一个网络请求创建一个实例,在请求完成以后,bean 会失效并被垃圾回收器回收;
  • session(会话):与 request 范围类似,确保每个 session 中有一个 bean 的实例,在 session 过期后,bean会随之失效;
  • global-session:全局作用域,WEB 项目中,应用在 Portlet 环境,如果没有 Portlet 环境那么 global Session 相当于 session;

Bean 的生命周期

实例化流程:

image-20210912165620572

主要就是加载 Bean 的 class 文件封装出 Bean 的各种定义信息以及单例、自动装配等信息,然后再利用反射进行实例化

生命周期流程:

img

首先是实例化:

  1. Spring 启动,查找并加载需要被 Spring 管理的 bean,注册定义信息 BeanDefinition,利用反射进行 Bean 的实例化

接下来依赖注入(包含BeanName、BeanFactory、ApplicationContext的配置):

  1. Bean 实例化后,进行依赖注入,根据定义信息配置 Bean 的所有属性
  2. 如果 Bean 实现了 BeanNameAware 接口,调用 Bean 的 SetBeanName() 方法传递 Bean 的 ID
  3. 如果 Bean 实现了 BeanFactoryAware 接口的话,Spring 调用 setBeanFactory()方法,将 BeanFactory 容器实例传入
  4. 如果 Bean 实现了 ApplicationContextAware 接口的话,Spring 将调用 Bean 的 setApplicationContext() 方法,将 bean 所在应用上下文引用传入进来

初始化以及后置处理器的初始化前后处理:

  1. 如果 Bean 实现了 BeanPostProcessor 接口(后置处理器),Spring 就将调用他们的 postProcessBeforeInitialization() 方法(初始化前)
  2. 如果 Bean 实现了 InitializingBean 接口,Spring 将调用他们的 afterPropertiesSet() 方法,在init-method 之前。类似的,如果 bean 使用 init-method 声明了初始化方法,该方法也会被调用(初始化)
  3. 如果 Bean 实现了 BeanPostProcessor 接口,Spring 就将调用他们的postProcessAfterInitialization() 方法(初始化后)

创建完成:

  1. 此时,Bean 已经准备就绪,可以被应用程序使用了。他们将一直驻留在应用上下文中,直到应用上下文被销毁。
  2. 如果 bean 实现了 DisposableBean 接口,Spring 将调用它的 destory() 接口方法,同样,如果 bean 使用了 destory-method 声明销毁方法,该方法也会被调用。

BeanFactory 和 FactoryBean

  • BeanFactory 是 Spring 中最底层的接口,它主要是用于 bean 实例的定义,读取 bean 的配置文件,管理 bean 实例和 bean 的生命周期,维护 bean 之间的依赖关系;
  • 而 FactoryBean 是一个能生产或修饰对象生成的工厂 bean ,用户可以通过实现该接口定制实例化 bean 的逻辑;

单例bean是否是线程安全的

Spring 框架并没有对单例 bean 进行任何多线程的封装处理;对于单例 bean,所有线程都共享一个单例实例 bean,因此是存在资源的竞争;

如果单例 bean 是一个无状态 bean(调用 bean 的方法不会使 bean 的字段属性发生改变),也就是线程中的操作不会对 bean 的成员执行查询以外的操作,那么这个单例 bean 是线程安全的,比如 SpringMVC 的 Controller,Service,Dao 等类,这些 bean 大多是无状态的,只关注于方法本身;

最浅显的解决办法就是将多态 bean 的作用域由 singleton 变更为 prototype;

对于原型(prototype) bean,每次创建一个新对象,也就是线程之间并不存在 bean 共享,自然是不会有线程安全的问题

注意:Spring 容器本身并没有提供线程安全的策略,因此是否线程安全完全取决于 Bean 本身的特性!


循环依赖及如何解决

循环依赖

循环依赖其实就是循环引用,也就是两个或则两个以上的 bean 互相持有对方,最终形成闭环。比如 A 依赖于 B,B 依赖于 C,C 又依赖于 A;如下图所示:

![在这里插入图片描述]( https://img-blog.csdnimg.cn/20200721111017108.png?x-oss-process=image/watermark ,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQzODE2NDA5,size_16,color_FFFFFF,t_70)

Spring 中循环依赖的场景有:

  • 全部的构造器循环依赖(无法解决)
  • 属性的循环依赖

什么情况下循环依赖可以被处理?

  • 出现循环依赖的 Bean 必须要是单例;
  • 依赖注入的方式不能全是构造器注入的方式;

注意:“全部的”构造器的循环依赖是无法被解决的,只能拋出 BeanCurrentlyInCreationException 异常;


假设,有类 A 和 B,A 中通过 setter 注入了 B,而 B 中通过 setter 注入了 A

首先,创建 bean 实例主要经过以下三步:

  1. 实例化 bean: AbstractAutowireCapableBeanFactory 中的 createBeanInstance 方法;
  2. 属性注入:AbstractAutowireCapableBeanFactory 中的 populateBean 方法;
  3. 初始化:AbstractAutowireCapableBeanFactory 中的 initializeBean 方法;

Spring 为了解决单例的循环依赖问题,使用了三级缓存,并且这三级缓存是在方法 getBean 中的:

  • 一级缓存:存储单例 Bean,即 Map<String, Object> singletonObjects
  • 二级缓存:存储提前暴露的 Bean,真正的解决循环依赖是靠二级缓存的。Map<String, Object> earlySingletonObjects
  • 三级缓存:存储 Bean 和其要加强的 AOP 代理,如果需要 AOP 增强的 Bean 遇到了循环依赖,则使用该缓存中的 AOP 代理增强 Bean,二级缓存中存储的就是从这个工厂中获取到的对象

Spring 在对象 getBean() 时,先从一级缓存拿,拿到直接返回,拿不到就去二级缓存拿,再拿不到就去三级缓存拿 ObjectFactory,拿到了就调用 getObject 创建对象,拿不到就返回 null

所谓的半成品对象,例如我们 A 类的属性中需要注入 B 类,此时去 BeanFactory 中发现并没有 B 类实例,因此我们又来创建 B 类的实例到容器中,然而我的 B 类中也要注入 A 类实例,没关系,我们现在的 A B 两个半成品实例都在第二级缓存中,这个时候 A 类就可以注入这个半成品实例 B 了(尽管 B 还没有完全创建成功)

第三级缓存可以存放 AOP 代理对象,如果不使用 AOP 代理的话,两级缓存也能解决循环依赖问题了,例如当 A 与 B 循环依赖,并且 A 被 AOP 代理的情况下

img

因为 A 需要 B,所以 A 停留在依赖注入的阶段,等待 B 创建完成,但是 B 也需要 A,并且 A 是被代理了的,因此 B 的创建过程中依赖注入阶段需要创建 A 的代理类,那么代理类在哪?当然是在第三级缓存里面,可是这样好像还是没有看出来三级缓存有何高明之处,毕竟这种场景使用二级缓存也可以解决,因此,第三级缓存本身就不是特地为了循环依赖问题而设计的,而是一种保证没有出现循环依赖的情况下满足初始化完成之后生成代理的设计思想


依赖注入流程

创建 A 实例时,调用 getBean 方法,最终回调用到 doGetBean 方法中:

protected <T> T doGetBean(final String name, @Nullable final Class<T> requiredType,
                          @Nullable final Object[] args, boolean typeCheckOnly) throws BeansException {
    final String beanName = transformedBeanName(name);
    Object bean;
    // 方法1)从三个map中获取单例类
    Object sharedInstance = getSingleton(beanName);
    // 省略无关代码
}
else {
    // 如果是多例的循环引用,则直接报错
    if (isPrototypeCurrentlyInCreation(beanName)) {
        throw new BeanCurrentlyInCreationException(beanName);
    }
    // 省略若干无关代码
    try {
        // Create bean instance.
        if (mbd.isSingleton()) {
            // 方法2) 获取单例对象
            sharedInstance = getSingleton(beanName, () -> {
                try { //方法3) 创建ObjectFactory中getObject方法的返回值
                    return createBean(beanName, mbd, args);
                }
                catch (BeansException ex) {
                    destroySingleton(beanName);
                    throw ex;
                }
            });
            bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
        }
    }
    // 省略若干无关代码
    return (T) bean;
}

第一次就会在标注的 方法1 中进行实例的获取(该方法是一个重载方法),如下代码:

protected Object getSingleton(String beanName, boolean allowEarlyReference) {
    Object singletonObject = this.singletonObjects.get(beanName);//尝试获取一级缓存
    if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
        synchronized (this.singletonObjects) {
            singletonObject = this.earlySingletonObjects.get(beanName);//尝试获取二级缓存
            if (singletonObject == null && allowEarlyReference) {
                ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);//尝试在三级缓存中获取,也就是在对象工厂中获取
                if (singletonFactory != null) {
                    singletonObject = singletonFactory.getObject();
                    //将三级缓存对象移动到二级缓存之中,因此二级缓存earlySingletonObjects存放的有可能是经过AOP增强的代理对像
                    this.earlySingletonObjects.put(beanName, singletonObject);
                    this.singletonFactories.remove(beanName);
                }
            }
        }
    }
    return singletonObject;
}

由于开始时实例 A 并未创建,那么就会继续执行方法 doGetBean 的逻辑,然后就会执行标注的 方法3(一个函数式接口方法 createBean):

protected Object createBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)
    throws BeanCreationException {

    // 省略无关代码
    try {
        //获取对象
        Object beanInstance = doCreateBean(beanName, mbdToUse, args);
        return beanInstance;
    }
    // 省略无关代码
}
protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args)
    throws BeanCreationException {

    BeanWrapper instanceWrapper = null;
    // 省略代码
    if (instanceWrapper == null) {
        // 实例化bean
        instanceWrapper = createBeanInstance(beanName, mbd, args);
    }
    boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
                                      isSingletonCurrentlyInCreation(beanName));
    if (earlySingletonExposure) {
        // 重点!!!将实例化的对象添加到singletonFactories中(暴露在三级缓存中)
        addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
    }
    // 初始化bean
    Object exposedObject = bean;
    try {
        //为当前的实例对象进行属性的注入
        populateBean(beanName, mbd, instanceWrapper);
        exposedObject = initializeBean(beanName, exposedObject, mbd);
    }
    // 省略无关代码
    return exposedObject;
}

该方法执行完毕后就会将未完全初始化好该 bean 的情况下直接暴露在三级缓存 singletonFactories 之中;

addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));

当 A 完成了实例化并添加进了三级缓存后,就要开始为 A 进行属性注入了,在注入时发现 A 依赖了 B,那么这个时候 Spring 又会去 getBean(b),然后反射调用 setter 方法完成属性注入;

因为 B 需要注入 A,所以在创建 B 的时候,又会去调用 getBean(a),这个时候就又回到之前的流程了,但是不同的是,之前的 getBean 是为了创建 Bean,而此时再调用 getBean 不是为了创建了,而是要从缓存中获取(方法1),由于之前 A 在实例化后已经将其放入了三级缓存 singletonFactories 中,所以此时 doGetBean 方法时就会在标注的 方法1 中进行获取,第一步,先获取到三级缓存中的工厂,第二步,调用对象工厂的 getObject 方法来获取到对应的对象,此时会将实例对象 A 移动到二级缓存之中,最终得到这个对象后将其注入到 B 中;

B 拿到 A 对象实例后顺利完成了创建对象实例后的属性注入以及初始化过程,并且完全初始化之后将自己放入到一级缓存 singletonObjects 中。

此时再返回 A 的属性注入的方法中,A 就能够顺利的拿到 B 的实例并且完成 A 的属性注入以及初始化等方法。接着通过标注的 方法2 继续进行实例的获取,就会调用重载的方法 getSingletion,最终 A 也存入了一级缓存 singletonObjects 中:

public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
    Assert.notNull(beanName, "Bean name must not be null");
    synchronized (this.singletonObjects) {
        Object singletonObject = this.singletonObjects.get(beanName);
        if (singletonObject == null) {
            // 省略无关代码
            beforeSingletonCreation(beanName);
            boolean newSingleton = false;
            // 省略无关代码
            try {
                //通过方法3后然后在该方法中直接获取已经创建的对象实例即可。
                singletonObject = singletonFactory.getObject();
                newSingleton = true;
            }
            // 省略无关代码
            finally {
                if (recordSuppressedExceptions) {
                    this.suppressedExceptions = null;
                }
                afterSingletonCreation(beanName);
            }
            if (newSingleton) {
                //步骤A
                addSingleton(beanName, singletonObject);
            }
        }
        return singletonObject;
    }
}

步骤A:将实例添加到一级缓存中。

protected void addSingleton(String beanName, Object singletonObject) {
    synchronized (this.singletonObjects) {
        //添加单例对象到map中
        this.singletonObjects.put(beanName, singletonObject);
        //从早期暴露的工厂中移除,此map在解决循环依赖中发挥了关键的作用
        this.singletonFactories.remove(beanName);
        //从早期暴露的对象map中移除
        this.earlySingletonObjects.remove(beanName);
        //添加到已注册的单例名字集合中
        this.registeredSingletons.add(beanName);
    }
}

在整个过程中可以看出缓存(map)存放时的优先顺序。其中 singletonObjects 里面存放的是初始化之后的单例对象;earlySingletonObjects 中存放的是一个已完成实例化未完成初始化的早期单例对象;而 singletonFactories 中存放的是 ObjectFactory 对象,此对象的 getObject 方法返回值即刚完成实例化还未开始初始化的单例对象。所以先后顺序是,单例对象先存在于 singletonFactories 中,后存在于 earlySingletonObjects 中,最后初始化完成后放入 singletonObjects 中。


知道这些流程之后,我们就能够知道,A 的构造方法中依赖了 B 的实例对象,同时 B 的构造方法中依赖了 A 的实例对象的这种情况下是无法完成依赖注入的,这就是因为,加入 singletonFactories 三级缓存的前提是使用 createBeanInstance 方法调用构造器创建了实例对象!!

因此,Spring 通过将实例化后的对象提前暴露给 Spring 容器中的 singletonFactories,解决了循环依赖的问题。


AOP 下循环依赖

在普通的循环依赖的情况下,三级缓存没有任何作用,三级缓存实际上跟 Spring 中的 AOP 相关,在 AOP 下继续观察方法 getEarlyBeanReference:

	protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
		Object exposedObject = bean;
		if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
			for (BeanPostProcessor bp : getBeanPostProcessors()) {
				if (bp instanceof SmartInstantiationAwareBeanPostProcessor) {
					SmartInstantiationAwareBeanPostProcessor ibp = (SmartInstantiationAwareBeanPostProcessor) bp;
					exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName);
				}
			}
		}
		return exposedObject;
	}
123456789101112

如果在开启 AOP 的情况下,那么就是调用到 AnnotationAwareAspectJAutoProxyCreator 的 getEarlyBeanReference 方法,对应的源码如下:

	public Object getEarlyBeanReference(Object bean, String beanName) {		Object cacheKey = getCacheKey(bean.getClass(), beanName);		this.earlyProxyReferences.put(cacheKey, bean);		return wrapIfNecessary(bean, beanName, cacheKey);	}12345

这样就可以看出对 A 进行了 AOP 代理的话,那么此时 getEarlyBeanReference 将返回一个代理后的对象,而不是实例化阶段创建的对象,这样就意味着 B 中注入的 A 将是一个代理对象而不是 A 的实例化阶段创建后的对象;


Spring AOP

AOP:面向切面编程(Aspect-Oriented Programming),可以说是对 OOP 的一种补充,专门用于处理一些具有横切性质的服务。简单的说就是把我们重复的代码抽取出来,在需要执行的时候,使用动态代理技术,在不修改源码的基础上,对我们已有的方法进行增强。即在程序运行期间动态的将某段代码切入到指定方法指定位置并进行运行的编程方式;

动态代理:利用反射机制在运行时创建代理类。

静态代理:运行前创建代理类

用于将那些与业务无关,但却对多个对象产生影响的公共行为和逻辑,抽取并封装为一个可重用的模块,这个模块被命名为“切面”(Aspect);


概念

  • 切面(Aspect):被抽取的公共模块,可能会横切多个对象。 在 Spring AOP 中,切面可以使用通用类(基于模式的风格) 或者在普通类中以 @Aspect(告诉Spring该类是一个切面类) 注解来实现;
  • 连接点(Join point):指目标类中的方法,在 Spring AOP 中,一个连接点总是代表一个方法的执行;
  • 通知(Advice):在切面的某个特定的连接点(Join point)上执行的动作。在 Spring 中,是以拦截器做通知模型, 并维护一个以连接点为中心的拦截器链;
  • 切入点(Pointcut):切入点是指我们要对哪些 Join point 进行拦截的定义。**通过切入点表达式,指定拦截的方法,比如指定拦截 add * ,search *** ;
  • 目标对象(Target Object): 被一个或者多个切面(Aspect)所通知(Advice)的对象。也有人把它叫做被通知(Adviced) 对象。 既然 Spring AOP 是通过运行时代理实现的,这个对象永远是一个被代理(proxied) 对象;
  • 织入(Weaving):指把增强(可以理解为Advice)应用到目标对象来创建新的代理对象的过程。Spring 是通过实现后置处理器 BeanPostProcessor 接口来实现织入的。也就是在 Bean 完成初始化之后,通过给目标对象生成代理对象,并交由 Spring IoC 容器来接管,这样再去容器中获取到的目标对象就是已经增强过的代理对象。

通知类型

  • 前置通知(@Before):在某连接点(join point)之前执行的通知
  • 后置通知(@After):当某连接点退出的时候执行的通知
  • 返回通知(@AfterReturning):在某连接点(join point)正常完成后执行的通知
  • 异常通知(@AfterThrowing):在方法抛出异常退出时执行的通知;
  • 环绕通知(@Around):包围一个连接点(join point)的通知

Spring AOP 原理

如果使用接口,则用 JDK 的动态代理实现;如果没有实现接口,则使用 CGLIB 通过字节码技术来实现

创建代理对象流程:

image-20210912175608515

简要流程(后置处理器的初始化后):

img

完整流程:

  1. AnnotationAwareAspectJAutoProxyCreator 是 AOP 核心处理类
  2. AnnotationAwareAspectJAutoProxyCreator 实现了 BeanProcessor,其中 postProcessAfterInitialization 是核心方法,该方法在 Bean 的初始化后执行。
  3. 核心实现分为 2 步 getAdvicesAndAdvisorsForBean 获取当前 bean 匹配的增强器 createProxy 为当前 bean 创建代理
  4. getAdvicesAndAdvisorsForBean 核心逻辑如下
    1. 找所有增强器,也就是所有 @Aspect 注解的 Bean
    2. 找匹配的增强器,也就是根据 @Before,@After 等注解上的表达式,与当前 bean 进行匹配,暴露匹配上的。
    3. 对匹配的增强器进行扩展和排序,就是按照 @Order 或者 PriorityOrdered 的 getOrder 的数据值进行排序,越小的越靠前。
  5. createProxy 有 2 种创建方法,JDK 代理或 CGLIB
    1. 如果设置了 proxyTargetClass=true,一定是 CGLIB 代理
    2. 如果 proxyTargetClass=false,目标对象实现了接口,走 JDK 代理
    3. 如果没有实现接口,走 CGLIB 代理

Spring的事务

Spring 事务的本质其实就是数据库对事务的支持,没有数据库的事务支持,Spring 是无法提供事务功能的;

Spring事务实现

  1. Spring 事务底层是基于数据库事务和 AOP 机制的
  2. ⾸先对于使⽤了 @Transactional 注解的 Bean,Spring 会创建⼀个代理对象作为Bean
  3. 当调⽤代理对象的⽅法时,会先判断该⽅法上是否加了 @Transactional 注解
  4. 如果加了,那么则利⽤事务管理器创建⼀个数据库连接
  5. 并且修改数据库连接的 autocommit 属性为 false,禁⽌此连接的⾃动提交,这是实现 Spring 事务⾮常重要的⼀步
  6. 然后执⾏当前⽅法,⽅法中会执⾏ sql
  7. 执⾏完当前⽅法后,如果没有出现异常就直接提交事务
  8. 如果出现了异常,并且这个异常是需要回滚的就会回滚事务,否则仍然提交事务
  9. Spring 事务的隔离级别对应的就是数据库的隔离级别
  10. Spring 事务的传播机制是 Spring 事务⾃⼰实现的,也是 Spring 事务中最复杂的
  11. Spring 事务的传播机制是基于数据库连接来做的,⼀个数据库连接⼀个事务,如果传播机制配置为需要新开⼀个事务,那么实际上就是新建⽴⼀个数据库连接,在此新数据库连接上执⾏ sql

配置类:数据源,JDBCTemplate 以及 配置事务管理器 TransactionManager

@Configuration@EnableTransactionManagement@ComponentScan(value = "com.qixia.test")public class TxConfig {    @Bean    public DataSource dataSource(){        DruidDataSource druidDataSource = new DruidDataSource();        druidDataSource.setUsername("root");        druidDataSource.setPassword("song1202S");        druidDataSource.setUrl("jdbc:mysql:///test");        druidDataSource.setDriverClassName("com.mysql.jdbc.Driver");        return druidDataSource;    }    @Bean    public JdbcTemplate jdbcTemplate(){        return new JdbcTemplate(dataSource());    }    @Bean    public PlatformTransactionManager platformTransactionManager(){        return new DataSourceTransactionManager(dataSource());    }}Dao 类@Repositorypublic class UserDao {    @Autowired    JdbcTemplate jdbcTemplate;    @Transactional    public void insert() {        String sql = "insert into user(name,password,address,phone) values(?,?,?,?)";        jdbcTemplate.update(sql, "七夏", "123456", "岐山", "110120119");        //会进行回滚        int i = 10 / 0;    }}测试类public class TxTest {    @Test    public void test(){        AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(TxConfig.class);        UserDao userDao = ac.getBean(UserDao.class);        userDao.insert();    }}

Spring 的事物种类

Spring 支持编程式事务管理和声明式事务管理两种方式:

  1. 编程式事务管理使用 TransactionTemplate ;

  2. 声明式事务管理建立在 AOP 之上,其本质是通过 AOP 功能,对方法前后进行拦截,将事务处理的功能编织到拦截的方法中,也就是在目标方法开始之前加入一个事务,在执行完目标方法之后根据执行情况提交或者回滚事务;

    声明式事务最大的优点就是不需要在业务逻辑代码中掺杂事务管理的代码,只需在配置文件或配置类中做相关的事务规则声明或通过 @Transactional 注解的方式,便可以将事务规则应用到业务逻辑中;

    声明式事务管理要优于编程式事务管理,这正是 Spring 倡导的非侵入式的开发方式,使业务代码不受污染,只要加上注解就可以获得完全的事务支持。唯一不足地方是,最细粒度只能作用到方法级别,无法做到像编程式事务那样可以作用到代码块级别。

Spring 中什么时候 @Transactional 会失效

因为 Spring 事务是基于代理来实现的,所以某个加了 @Transactional 的⽅法只有是被代理对象调⽤时,那么这个注解才会⽣效,所以如果不是被代理对象来调⽤这个⽅法,那么@Transactional 是不会失效的。

因此同一个类的方法调用会导致事务无效,例如类中有 A B 两个方法,都声明了事务注解,在 A 中直接调用了 B 方法,会导致事务失效,因为相当于 A 中直接调用了 this.B() 方法,我们说过事务需要走代理类执行,直接使用 this 会不走代理对象

同时如果某个⽅法是 private 的,那么 @Transactional 也会失效,因为底层 cglib 是基于⽗⼦类来实现的,⼦类是不能重载⽗类的 private ⽅法的,所以⽆法很好的利⽤代理,也会导致@Transactianal 失效


Spring 的事物传播行为

在注解 @Transactional 注解中使用 propagation 属性进行设置;

事务传播行为 解释
PROPAGATION_REQUIRED 如果有事务在运行就加入该事务,否则,就启动一个新的事务,并在自己的事务内运行
PROPAGATION_REQUIRED_NEW 不管当前有没有事物都新建一个事物
PROPAGATION_SUPPORTS 如果当前存在事物就加入该事务,否则就以非事物方式运行
PROPAGATION_NOT_SUPPORTED 以非事物方式运行,如果当前存在事物则将当前事物挂起
PROPAGATION_MANDATORY 当前存在事物时就加入该事务,否则就抛出异常
PROPAGATION_NEVER 以非事物方式运行,如果存在当前存在事物则抛出异常
PROPAGATION_NESTED 如果当前存在事务,则在嵌套事务内执行,否则新建一个事物

Spring 的隔离级别

MySQL 中事物的默认隔离级别是 REPEATABLE_READ,可重复读;

  • 脏读:对于两个事务 T1 和 T2,当 T1 读取了已经被 T2 更新但还没有被提交的字段,之后,若 T2 回滚,T1 读取的内容就是临时且无效的;
  • 不可重复读:对于两个事务 T1 和 T2,当 T1 读取了一个字段,然后 T2 更新并提交了该字段,之后,T1 再次读取同一个字段,值就不同了;
  • 幻读:对于两个事务 T1 和 T2,当 T1 从一个表中读取了一个字段,然后 T2 在该表中插入或除了一些行数据,之后,如果 T1 再次读取同一个表,就会多出或少了几行;
隔离级别 解释
ISOLATION_DEFAULT 是 PlatformTransactionManager 默认的隔离级别,使用数据库默认的事务隔离级别
ISOLATION_READ_UNCOMMITTED 读未提交,允许另外一个事务可以看到这个事务未提交的数据
ISOLATION_READ_COMMITTED 读已提交,保证一个事务修改的数据提交后才能被另一事务读取,而且能看到该事务对已有记录的更新
ISOLATION_REPEATABLE_READ 可重复读,保证一个事务修改的数据提交后才能被另一事务读取,但是不能看到该事务对已有记录的更新
ISOLATION_SERIALIZABLE 序列化,一个事务在执行的过程中完全看不到其他事务对数据库所做的更新

SpringMvc

概述

SpringMvc 工作流程

  1. ⽤户发送请求⾄前端控制器 DispatcherServlet
  2. DispatcherServlet 收到请求调⽤ HandlerMapping 处理器映射器
  3. 处理器映射器找到具体的处理器(可以根据 xml 配置、注解进⾏查找),⽣成处理器及处理器拦截器(如果有则⽣成)⼀并返回给 DispatcherServlet
  4. DispatcherServlet 调⽤ HandlerAdapter 处理器适配器
  5. HandlerAdapter 经过适配调⽤具体的处理器(Controller,也叫后端控制器)
  6. Controller 执⾏完成返回 ModelAndView
  7. HandlerAdapter 将 controller 执⾏结果 ModelAndView 返回给 DispatcherServlet
  8. DispatcherServlet 将 ModelAndView 传给 ViewReslover 视图解析器
  9. ViewReslover 解析后返回具体 View
  10. DispatcherServlet 根据 View 进⾏渲染视图(即将模型数据填充至视图中)
  11. DispatcherServlet 响应⽤户

SpringBoot

概述

SpringBoot 介绍

Spring Boot 的设计目的是用来简化新 Spring 应用的初始搭建以及开发过程。该框架使用了特定的方式来进行配置,从而使开发人员不再需要定义样板化的配置。通过这种方式,Spring Boot 致力于在蓬勃发展的快速应用开发领域成为领导者。

SpringBoot所具备的特征有:

  • 可以创建独立的 Spring 应用程序,并且基于其 Maven 或 Gradle 插件,可以创建可执行的 JARs 和 WARs;
  • 内嵌 Tomcat 或 Jetty 等 Servlet 容器;
  • 提供自动配置的 “starter” 项目对象模型(POMS)以简化 Maven 配置;
  • 尽可能自动配置 Spring 容器;

核心设计

SpringBoot 之所以可以做到简化配置文件直接启动,无外乎是其内部的两种设计策略:开箱即用和约定大于配置。

开箱即用:在开发过程中,通过 maven 项目的 pom 文件中添加相关依赖包,然后通过相应的注解来代替繁琐的 XML 配置以管理对象的生命周期。

约定大于配置:由 SpringBoot 本身来配置目标结构,由开发者在结构中添加信息的软件设计范式。这一特点虽降低了部分灵活性,增加了BUG定位的复杂性,但减少了开发人员需要做出决定的数量,同时减少了大量的XML配置,并且可以将代码编译、测试和打包等工作自动化。

自动装配

  • 我们的 SpringBoot 项目的入口是 SpringBootApplication 注解上的类,该注解内部其实对应三个注解

    • @SpringBootConfiguration

      继承自 Configuration ,也就是可以使用 @Bean 注解将方法返回值注入容器

    • @EnableAutoConfiguration

      一旦加上此注解,那么将会开启自动装配功能,简单点讲,Spring 会试图在你的 classpath 下找到所有配置的 Bean 然后进行装配

    • @ComponentScan

      扫描当前包及其子包被 Service Component Controller 等注解标注的类并放入容器中

  • 我们的自动装配重点就在于注解 @EnableAutoConfiguration,其内部也是包含两个注解

    • @AutoConfigurationPackage //自动配置包

    • @Import(AutoConfigurationImportSelector.class)//自动配置导入选择

      该注解帮我们导入了 AutoConfigurationImportSelector,这个类中存在一个方法可以帮我们获取所有的配置,其一路找啊找,最终找到了 spring.factories 文件,其内部包含了很多自动配置属性

    当我们的SpringBoot项目启动的时候,会先导入AutoConfigurationImportSelector,这个类会帮我们选择所有候选的配置,我们需要导入的配置都是SpringBoot帮我们写好的一个一个的配置类,那么这些配置类的位置,存在与META-INF/spring.factories文件中,通过这个文件,Spring可以找到这些配置类的位置,于是去加载其中的配置。

  • 当然我们不是每次启动都加载 spring.factories 文件中的所有配置并装配,而是根据 @ConditionalOnClass 注解进行判断条件是否成立(只要导入相应的 stater,条件就能成立),如果条件成立则加载配置类,否则不加载该配置类。

    这就是为什么我们每次导入 redis、mysql 等的 starter,就可以直接在 yml 文件中设置端口、地址进行使用了

posted @ 2021-09-19 23:17  他是医你的药  阅读(103)  评论(0)    收藏  举报