Spring技术

5 篇文章 0 订阅
2 篇文章 0 订阅

Spring 概述

Spring 的特性

Spring 基于 J2EE 技术实现了一套轻量级的 Java Web Service 系统应用框架,旨在提高开发人员的开发效率以及系统的可维护性。Spring 的特性包括:

  • 轻量级

    从 Jar 包的大小上来说,核心 Jar 包 spring-web-5.2.0.RELEASE.jarspring-core-5.2.0.RELEASE.jar均为 1.4 M 左右;

    从系统的资源使用上来说,Spring 运行期间只需要少量的操作系统资源(内存和 CPU)便能稳定运行。

  • 面向容器

    Spring 实现了对象的配置化生成和对象的生命周期管理,所以是面向容器的。

  • 控制反转

  • 面向切面

  • 框架灵活

Spring 的模块

Spring 为企业应用程序提供一站式服务。Spring 模块提供的常用模块有核心容器层(Core Container)、数据访问层(Data Access)、Web 应用层(Web Access)。

核心容器层

核心容器层包括 Spring-Beans、Spring-Core、Spring-Context 等模块。

  • Spring-Beans

    基于工厂模式实现对象的创建。Spring-Beans 通过 xml 配置文件实现了声明式的对象管理,将对象之间复杂的依赖关系从实际编码逻辑中解耦出来。

  • Spring-Core

    Spring 的核心功能实现,提供 IoC 依赖注入功能的支持。

  • Spring-Context

    在 Spring-Beans 和 Spring-Core 模块的基础上构建起来的。Spring-Context 模块继承自 Spring-Beans 模块,并且添加了国际化、事件传播、资源加载和透明地创建上下文等功能。

数据访问层

数据访问层包括:JDBC、ORM、OXM、JMS 和 TX 模块。

  • JDBC (Java Data Base Connectivity)

    提供了对数据库访问的抽象 JDBC。不同的数据库都有自己独立的 API 用于操作数据库,而 Java 程序只需要和 JDBC API 交互,这样就屏蔽了数据库的影响。

  • ORM (Object Relational Mapping)

    提供对 Hibernate 等 ORM 框架的支持。

  • OXM (Object XML Mapping)

    提供对 Castor 等 OXM 框架的支持。

  • JMS (Java Message Service)

    JMS 模块包括消息的生产和消费功能。从 Spring 4.1 开始,Spring 集成了 Spring-Messaging 模块,用于实现对消息队列的支持。

  • TX

    提供对事务的支持。

Web 应用层

Web 应用层主要包括 Web 交互和数据传输等相关功能,包括 Web、Web-MVC、Web-Socket 和 Web-Flux。

  • Web

    提供了面向 Web 应用的基本功能。

  • Web-MVC

    提供对 Spring MVC 的实现。

  • Web-Socket

    提供了对 WebSocket 的支持,WebSocket 可以让客户端和服务端进行双向通信。

  • Web-Flux

    提供对 WebFlux 的支持。

    目前最新的 5.x 版本中 Web 模块的 Portlet 组件已经被废弃掉,同时增加了用于异步响应式处理的 WebFlux 组件。

其他重要模块

  • Spring AOP

    提供了面向切面的编程实现,允许应用程序通过定义方法拦截器和切入点来实现系统功能和业务功能之间的解耦。

  • Spring Aspects

    提供了 Spring 与 AspectJ 的集成,是一个面向切面编程的模块。

Spring 的注解

@Contoller

SpringMVC 中,控制器 Controller 负责处理 DispatcherServlet 分发的请求,它把用户请求的数据经过业务处理层处理之后封装成一个 Model,然后再把该 Model 返回给对应的 View 进行展示。

SpringMVC 提供了一个非常简便的定义 Controller 的方法,无需继承特定的类或者接口,只需使用 @Controller 标记一个类是 Contoller。

@RequestMapping

使用 @RequestMapping 来映射 URL 到 Controller,或者到 Controller 的处理方法上。method 的值一旦指定,则处理方法只对指定的 HTTP method 类型请求处理。

可以为多个方法映射相同的 URL 和不同的 HTTP method 类型,Spring MVC 根据请求的 method 类型是可以区分开这些方法的。

@RequestParam & @PathVariable

在 SpringMVC 中,两者的作用都是将 request 里的参数值绑定到 Controller 里的方法参数中,区别在于 URL 的写法不同。

  • 使用 @RequestParam 时,URL 是这样的:
http://host:port/path?参数名=参数值
  • 使用 @PathVariable 时,URL 是这样的:
http://host:port/path/参数值

@ResponseBody

该注解用于将 Controller 中方法返回的对象,通过适当的 HttpMessageConverter 转换为指定的格式后,写入到 Response 对象的 bodys 数据区。

@Service & @Controller & @Repository & @Component

@Service、 @Contrller、 @Repository 其实这 3 个注解和 @Component 是等效的,用在实现类上:

  • @Service 用于标注业务层组件
  • @Controller 用于标注控制层组件
  • @Repository 用于编著数据访问组件
  • @Component 泛指组件,当组件不好归类时,可以使用这个注解进行标注

@Value

在 Spring 3.0 中,可以通过使用 @Value,对一些如 xxx.properties 文件中的文件,进行键值对的注入。

@Autowired

@Autowired 可以对成员变量、成员方法和构造函数进行标注,来完成自动装配工作。

@Autowired & @Resource

@Autowired 是 Spring 提供的注解,采用的策略是按照类型注入的:

public class UserService{
    @Autowired
    userDao; // 在 Spring 容器中找到类型为 UserDao 的对象,将其注入
}

存在问题:同一类型有多个 Bean,可以使用 @Qualifier 具体去装配哪个对象。

public class UserService{
    @Autowired
    @Qualifier(name="userDao")
    userDao; // 在 Spring 容器中找到类型为 UserDao,名称为 userDao 的对象,将其注入
}

@Resource 是 J2EE 提供的注解,默认是按照名称注入的:

Service{
    @Resource
    userDao; // 自动按名称进行装配
    
    @Resource(name="studentDao")
    studentDao; // 按名称进行装配,找不到会抛出异常
    
    @Resource(type="TeacherDao")
    teacherDao; // 按类型进行装配,找不到或者找到多个都会抛出异常
    
    @Resource(name="manDao",type="ManDao")
    manDao;// 找唯一匹配的 Bean 进行装配,如果找不到则会抛出异常
}

2_SpringIoC原理

Spring IoC 原理

IoC (Inverse of Control)

IOC,即控制反转(Inverse of Control)是一种设计思想,并不是一个具体的技术实现。

  • 控制:控制对象的创建及销毁(生命周期)。
  • 反转:将对象的控制权交给 IoC 容器。

所有类的创建、销毁都由 Spring 来控制,也就是说控制对象生命周期的不是引用它的对象,而是 Spring。对于某个具体对象而言,以前是它控制其他对象,现在所有对象都被 Spring 控制。

依赖注入 (Dependency Injection)

依赖注入就是将底层类作为参数传递给上层类,实现上层对下层的控制依赖注入实现控制反转

举例说明依赖注入:以生产行李箱为例。

传统设计思路:先设计轮子,然后根据轮子 size 来设计底盘,再根据底盘来设计箱体,最后设计好行李箱。

size 是固定值,可以进行相应的改进:

依赖注入设计思路

先设计行李箱的大概样子,再根据行李箱的样子设计箱体,根据箱体去设计底盘,然后去设计轮子。

不难理解,依赖注入就是将底层类作为参数传递给上层类,实现上层对下层的控制

Spring 支持 4 种依赖注入:setter 注入、构造器注入、注解注入和接口注入。

setter 注入

<bean id="exampleBean" class="examples.ExampleBean">
    <!-- setter injection using the nested ref element -->
    <property name="beanOne">
        <ref bean="anotherExampleBean"/>
    </property>

    <!-- setter injection using the neater ref attribute -->
    <property name="beanTwo" ref="yetAnotherBean"/>
    <property name="integerProperty" value="1"/>
</bean>

<bean id="anotherExampleBean" class="examples.AnotherBean"/>
<bean id="yetAnotherBean" class="examples.YetAnotherBean"/>
public class ExampleBean {

    private AnotherBean beanOne;

    private YetAnotherBean beanTwo;

    private int i;

    public void setBeanOne(AnotherBean beanOne) {
        this.beanOne = beanOne;
    }

    public void setBeanTwo(YetAnotherBean beanTwo) {
        this.beanTwo = beanTwo;
    }

    public void setIntegerProperty(int i) {
        this.i = i;
    }
}

构造器注入

<bean id="exampleBean" class="examples.ExampleBean" factory-method="createInstance">
    <constructor-arg ref="anotherExampleBean"/>
    <constructor-arg ref="yetAnotherBean"/>
    <constructor-arg value="1"/>
</bean>

<bean id="anotherExampleBean" class="examples.AnotherBean"/>
<bean id="yetAnotherBean" class="examples.YetAnotherBean"/>
public class ExampleBean {

    private AnotherBean beanOne;

    private YetAnotherBean beanTwo;

    private int i;

    public ExampleBean(
        AnotherBean anotherBean, YetAnotherBean yetAnotherBean, int i) {
        this.beanOne = anotherBean;
        this.beanTwo = yetAnotherBean;
        this.i = i;
    }
}

注解注入

public class ExampleBean {

    @Autowired
    @Qualifier("anotherExampleBean")
    private AnotherBean beanOne;
    
    @Autowired
    @Qualifier("yetAnotherBean")
    private YetAnotherBean beanTwo;

    @Value("1")
    private int i;
}

接口注入

接口注入模式因为历史较为悠久,在很多容器中都已经得到应用。但由于其在灵活性、易用性上不如其他注入模式,因而在 IOC 的专题世界内并不被看好。

IoC 和 DI 的关系

依赖注入实现控制反转。

依赖倒置原则、IoC、DI 和 IoC 容器的关系:

IoC 容器

IoC 容器指具有依赖注入功能的容器。

IoC 容器负责创建对象,将对象连接在一起,配置这些对象,并从创建中处理这些对象的整个生命周期,直到它们被完全销毁。

Spring 通过配置文件描述 IoC 容器管理的对象。Spring IoC 容器通过读取配置文件中的配置元数据,通过元数据对应用中的各个对象进行实例化及装配。一般使用基于 xml 配置文件进行配置元数据,而且 Spring 与配置文件完全解耦的,可以使用其他任何可能的方式进行配置元数据,比如注解、基于 Java 文件的、基于属性文件的配置都可以。

Spring IoC 容器的代表就是 org.springframework.beans 包下的 BeanFactory 接口:

  • IoC 容器要实现的最基础的接口
  • 采用延迟初始化策略(容器初始化完成后并不会创建 Bean 对象,只有当收到初始化请求时才进行初始化)
  • 由于是延迟初始化策略,因此启动速度较快,占用资源较少

org.springframework.context 包下的 ApplicationContext 接口扩展了 BeanFactory:

  • 在 BeanFactory 基础上,增加了更为高级的特性:事件发布、国际化等。
  • 在容器启动时,完成所有 Bean 的创建
  • 启动时间较长,占用资源较多

IoC 容器初始化过程

  • Resource 定位:即 BeanDefinition 的资源定位,Resource 为各种形式的 BeanDefinition 的使用都提供了统一的接口
  • BeanDefinition 的载入
  • 向 IoC 容器中注册 BeanDefinition:实际上 IoC 容器内部维护一个 HashMap,注册过程就是将 BeanDefinition 添加至 HashMap 中。

Spring Bean 的装配流程

IoC 容器其实就是一个大工厂,它用来管理我们所有的对象以及依赖关系:

  • Spring 在启动时会从 xml 配置文件/注解中读取应用程序提供的 Bean 配置信息,并在 Spring 容器中生成一份相应的 Bean 定义注册表
  • 根据注册表实例化 Bean,装配好 Bean 之间的依赖关系
  • 将 Bean 实例放入 Spring IoC 容器中,等待应用程序调用

getBean 方法

ApplicationContext 接口获取 Bean 的方法:

方法说明
Object getBean(String name)根据名称返回一个 Bean,客户端需要自己进行类型转换
T getBean(String name, Class requiredType)根据名称和指定的类型返回一个Bean,客户端无需自己进行类型转换,如果类型转换失败,容器抛出异常
T getBean(Class requiredType)根据指定的类型返回一个Bean,客户端无需自己进行类型转换,如果没有或有多于一个Bean存在容器将抛出异常
Map<String, T> getBeansOfType(Class type)根据指定的类型返回一个键值为名字和值为 Bean 对象的Map,如果没有Bean对象存在则返回空的 Map

getBean(name) 代码逻辑:

  • 获取参数 name 转化为 beanName
  • 从缓存中加载实例
  • 实例化 Bean
  • 检测 parentBeanFactory(若无缓存数据,直接到 parentBeanFactory 中去加载)
  • 初始化依赖的 Bean
  • 返回 Bean

注意:BeanFactory 和 FactoryBean 的区别

  • BeanFactory 是 IoC 最基本的容器,负责生产和管理 Bean,为其他具体的 IoC 容器提供了最基本的规范。
  • FactoryBean 是一个 Bean,是一个接口,当 IoC 容器中的 Bean 实现了 FactoryBean 后,通过 getBean(String beanName) 获取到的 Bean 对象并不是 FactoryBean 的实现类对象,而是这个实现类中的 getObject() 方法返回的对象。要想获取 FactoryBean 的实现类对象,就是在 beanName 前面加上 “&”

Spring 中 Bean 的作用域

singleton

singleton 即单例模式。singleton 作用域是 Spring 中的缺省作用域

当一个 Bean 的作用域为 singleton,那么 Spring IoC 容器中只会存在一个共享的 Bean 实例, 并且所有对 Bean 的请求,只要 id 与该 Bean 定义相匹配,则只会返回 Bean 的同一实例

可以指定 Bean 节点的 lazy-init=“true” 来延迟初始化 Bean, 此时只有在第一次获取 Bean 时才会初始化 Bean,即第一次请求该 Bean 时才初始化。 每次获取到的对象都是同一个对象。

配置文件 XML 中将 Bean 定义成 singleton :

<bean id="ServiceImpl" class="com.southeast.service.ServiceImpl" scope="singleton">

@Scope 注解的方式:

@Service
@Scope("singleton")
public class ServiceImpl{
}

prototype

prototype 即原型模式。当一个 Bean 的作用域为 prototype,表示一个 Bean 定义对应多个对象实例。 prototype 作用域的 Bean 会导致在每次对该 Bean 请求(将其注入到另一个 Bean 中,或者以程序的方式调用容器的 getBean() 方法)时都会创建一个新的 Bean 实例。

在创建容器的时候并没有实例化, 而是当我们获取 Bean 的时候才会去创建一个对象,而且我们每次获取到的对象都不是同一个对象。

配置文件 XML 中将 Bean 定义成 prototype :

<bean id="ServiceImpl" class="com.southeast.service.ServiceImpl" scope="prototype">

或者

<bean id="ServiceImpl" class="com.southeast.service.ServiceImpl" singleton="false"/>

@Scope 注解的方式:

@Service
@Scope("prototype")
public class ServiceImpl{
}

Spring 中线程安全问题

有状态 Bean & 无状态 Bean:

  • 有状态 Bean

    对象中有实例变量(成员变量),可保存数据;

    非线程安全

  • 无状态 Bean

    对象中无实例变量,不能保存数据,可在多线程环境下共享;

    线程安全

Spring 采用两种方式保证线程安全:

  • 采用 ThreadLocal 进行处理
  • 采用原型模式,每次有 Bean 请求时,都会创建一个新的 Bean 实例

所以根据经验,对有状态的 Bean 应该使用 prototype 作用域,而对无状态的 Bean 则应该使用 singleton 作用域。

request

request 只适用于Web程序,每一次 HTTP 请求都会产生一个新的 Bean , 同时该 Bean 仅在当前HTTP request 内有效,当请求结束后,该对象的生命周期即告结束。

在 XML 中将 Bean 定义成 request ,可以这样配置:

<bean id="ServiceImpl" class="com.southeast.service.ServiceImpl" scope="request">

session

session 只适用于Web程序, session 作用域表示该针对每一次 HTTP 请求都会产生一个新的 Bean, 同时该 Bean 仅在当前 HTTP session 内有效。 与 request 作用域一样,可以根据需要放心的更改所创建实例的内部状态, 而别的 HTTP session 中根据 userPreferences 创建的实例, 将不会看到这些特定于某个 HTTP session 的状态变化。 当HTTP session最终被废弃的时候,在该 HTTP session 作用域内的 Bean 也会被废弃掉。

<bean id="userPreferences" class="com.foo.UserPreferences" scope="session"/>

globalSession

globalSession 作用域类似于标准的 HTTP session 作用域, 不过仅仅在基于 portlet 的 Web 应用中才有意义。 Portlet 规范定义了全局 Session 的概念, 它被所有构成某个 portlet web 应用的各种不同的 portlet所共享。 在 globalSession 作用域中定义的 Bean 被限定于全局 portlet Session 的生命周期范围内。

<bean id="user" class="com.foo.Preferences "scope="globalSession"/>

注意:五种作用域中,request、session 和 globalSession 三种作用域仅在基于 Web 的应用中使用(不必关心你所采用的是什么 Web 应用框架),只能用在基于 Web 的 Spring ApplicationContext 环境。

Spring 中 Bean 的生命周期

Spring bean 的生命周期执行:

1、Spring 对 Bean 进行实例化。

2、Spring 将值和 Bean 的引用注入到 Bean 对应的属性中。

3、如果 Bean 实现了 BeanNameAware 接口,则会调用其实现的 setBeanName() 方法,

Spring 会将 bean 的 id 传递给 setBeanName() 接口方法。

4、如果 Bean 实现了 BeanFactoryAware 接口,则会调用其实现的 setBeanFactory() 方法,将 BeanFactory 容器实例作为传入参数。

5、如果 Bean 实现了 ApplicationContextAware 接口,则会调用其实现的 setApplicationContext() 方法,将应用上下文的引用作为传入参数。

6、如果 Bean 实现了 BeanPostProcessor 接口,Spring 将调用 postProcessBeforeInitialization() 接口方法。

7、如果 Bean 实现了InitializingBean 接口,Spring 将调用 afterPropertiesSet() 接口方法。

8、如果Bean 实现了 init-method 声明了初始化方法,该方法也会被调用。

9、如果 Bean 实现了 BeanPostProcessor 接口,Spring 将调用 postProcessAfterInitialization() 接口方法。

10、此时 Bean 已经准备就绪,可以被应用程序使用了,他们将会一直驻留在应用上下文中,一直到该应用上下文被销毁。

11、如果 Bean 实现了 DisposableBean 接口,Spring 将调用它的 destroy() 接口方法。

12、如果 Bean 使用 destroy-method 声明了销毁方法,方法也会被调用。

循环依赖问题

循环依赖指的是若 A 中有 B 的属性,B 中有 A 的属性,则当进行依赖注入时,就会产生 A 还未创建完,因为对 B 的创建再次返回创建 A。

类的实例化 & 类的初始化

类的实例化是指创建一个类的实例(对象)的过程。

类的初始化是指为类中各个类成员(被 static 修饰的成员变量)赋初始值的过程,是类生命周期中的一个阶段

Spring 中类的实例化 & 类的初始化

Spring 中所有 Bean 默认都是单例模式,所以 Bean 的初始化和实例化都是在加载进入 Bean 容器时进行的。如果想使用时再初始化,那么可以把类定义为原型模式。

三级缓存

单例对象,在 Spring IoC 容器中,有且仅有一个对象,将对象放入缓存中。

Spring 中使用 “三级缓存”:

// 一级缓存:单例对象的缓存(存储实例化完成的 Bean)
/** Cache of singleton objects: bean name --> bean instance */
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<String, Object>(256);
 
// 三级缓存:单例 ObjectFactory 的缓存
/** Cache of singleton factories: bean name --> ObjectFactory */
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<String, ObjectFactory<?>>(16);

// 二级缓存:提前曝光的单例对象的缓存(存储正在实例化的 Bean)
/** Cache of early singleton objects: bean name --> bean instance */
private final Map<String, Object> earlySingletonObjects = new HashMap<String, Object>(16);

举例说明解决循环依赖(A 中有B,B 中有 A)的具体过程:

Spring 中单例对象的初始化主要分为 3 步:

  • 第一步:createBeanInstance
  • 第二步:populateBean 填充属性
  • 第三步:intializeBean 初始化

在进行 createBeanInstance 后,该单例对象此时已被创建,Spring 将该对象提前曝光到 singeltonFacoties 中

  • A 完成 createBeanInstance ,并且提前曝光到 singeltonFacoties 中
  • A 进行第二步,发现需要依赖 B,尝试获取 B
  • B 开始创建,B 完成 createBeanInstance,发现需要依赖 A,尝试获取 A:先尝试从 singletonObjects 中获取,发现不存在,因为 A 未初始化完全;再尝试从 earlySingletonObjects 中获取;再去 singletonFactories 中获取,此时 B 获取 A,并将 A 放入 earlySingletonObjects 中,再删除 A 在singletonFactories 中对应的 ObjectFactory。
  • B 获取 A,顺利完成第二、三步,将初始化完成的 B 放入 singletonObjects 中。
  • 此时返回创建 A,A 可获取 B,顺利完成第二、三步,A 初始化完成, 将 A 放入 singletonObjects 中。

Spring 2 种循环依赖:构造器循环依赖 & setter 循环依赖

  • 构造器循环依赖:因为提前曝光到 singletonFactories 中的前提是需要执行构造方法,所以使用 “三级缓存” 无法解决该种循环依赖。
  • setter 循环依赖

所以在使用 Spring 框架进行开发时:

  • 尽量不要使用基于构造器的依赖注入方式,使用基于 setter 的依赖注入方式
  • 使用 @Autowired 注解,让 Spring 决定合适的时机

补充

3_SpringAOP原理

Spring AOP 原理

AOP 即面向切面编程(Aspect Oriented Programing),实际上是将一些通用的功能横向抽取出来:

  • 一方面,减少系统的重复代码
  • 另一方面,降低模块间的耦合度,比较好维护和扩展

Spring AOP 将应用分为核心关注点和横切关注点。业务处理流程为核心关注点,被业务所依赖的公共部分为横切关注点。横切关注点的特点是其行为经常发生在核心关注点多出,而多处操作基本相似,比如权限认证、日志、事务等。AOP 的核心思想是将核心关注点和横切关注点分离开来,以降低模块耦合度。

Spring AOP 的应用场景主要有:

  • 权限统一管理授权
  • 缓存统一维护
  • 数据懒加载
  • 资源池统一申请和管理
  • 统一事务管理

AOP 相关术语

术语解释描述
Joinpoint连接点所谓连接点是指那些被拦截到的点。在 Spring 中,这些点指的是方法,因为 Spring 只支持方法类型的连接点
Pointcut切入点所谓切入点是指我们要对哪些 Joinpoint 进行拦截的定义。
Advice通知/增强所谓通知是指拦截到 Joinpoint 之后要执行的具体操作。通知分为前置通知,后置通知,异常通知,最终通知,环绕通知(切面要完成的功能)
Introduction引介引介是一种特殊的通知。在不修改类代码的前提下,可以在运行期为类动态地添加一些方法或字段
Target目标对象代理的目标对象
Weaving织入是指把增强应用到目标对象来创建新的代理对象的过程。 有三种织入方式:Spring 采用动态代理织入,而 AspectJ 采用编译期织入类装载期织入
Proxy代理一个类被 AOP 织入增强后,就产生一个结果代理类
Aspect切面是切入点和通知(/引介)的结合

例如在 IUserDao 接口中:

public interface IUserDao {
    void add();
    void delete();
    void update();
    void search();
}
  • IUserDao 被增强的对象,就是 Target(目标对象)
  • add()、delete()、update() 和 search() 都是 JoinPoint(连接点)
  • 这里要对 add() 和 update() JoinPoint 进行拦截,则 add() 和 update() 就是 Pointcut(切入点)
  • Advice 指的是要增强的代码,也就是代码的增强
  • Weaving:指的是把增强(Advice)应用到目标对象(Target)创建新的代理对象的过程
  • Aspect:是切入点和通知的结合,在 add 或 update 方法上应用增强

AOP 底层原理

AOP 的底层原理是动态代理机制:

  • 类实现了接口,JDK 动态代理
  • 类未实现任何接口,Cglib 动态代理

AOP 的 5 种通知类型

  • 前置通知

    在连接点前面执行,前置通知不会影响连接点的执行,除非此处抛出异常。

  • 后置通知

    在连接点执行完成后执行,不管是正常执行完成,还是抛出异常,都会执行返回通知中的内容。

  • 成功通知

    在连接点正常执行完成后执行,如果连接点抛出异常,则不会执行。

  • 异常通知

    在连接点抛出异常后执行。

  • 环绕通知

    环绕通知围绕在连接点前后,比如一个方法调用的前后。这是最强大的通知类型,能在方法调用前后自定义一些操作。环绕通知还需要负责决定是继续处理连接点(调用ProceedingJoinPoint 的 proceed 方法)还是中断执行。

五种通知的执行顺序为:前置通知、环绕通知、成功通知/异常通知、后置通知。

Spring AOP & AspectJ AOP

  • 增强时机

    Spring AOP 是运行时增强;AspectJ 是编译时增强

  • 底层原理

    Spring AOP 基于代理;AspectJ 基于字节码操作 (Bytecode Manipulation)

  • 性能

    AspectJ 相比于 Spring AOP 功能更加强大,AspectJ 应该算的上是 Java 生态系统中最完整的 AOP 框架了。Spring AOP 相对来说更简单,并且 Spring AOP 已集成了 AspectJ,如果切面较少,那么两者性能差异不大,当切面较多时,最好选择 AspectJ ,它比 Spring AOP 快很多。

4_SpringMVC原理

Spring MVC 原理

MVC (Model-View-Controller)

MVC 即 Model-View-Controller,是一种复合模式。

  • Model:模型。封装数据源和所有基于这些数据的操作
  • View:视图。用来显现模型
  • Controller:控制器。封装外界作用于模型的操作

模型利用 “观察者模式” 让控制器和视图随最新的状态改变而更新;

视图和控制器实现 “策略模式”,控制器是视图的行为,若希望有不同的行为,可直接换一个控制器;

视图内部则是利用 “组合模式”。

MVC 具有以下优点:

  • 三个模块可共享一个模型,大大提高代码的可重用性
  • 三个模块相互独立,耦合性较低
  • Controller 提高了应用程序的灵活性,使用 Controller 可连接不同模型和视图去满足用户的需求

Spring MVC

SpringMVC 是一种基于 Java,实现了 Web MVC 设计模式,请求驱动类型的轻量级 Web 框架。优点如下:

  • 基于组件技术。全部的应用对象,无论是控制器、视图,还是业务对象之类都是 Java 组件。并且和 Spring 提供的其他基础结构紧密集成;
  • 不依赖于 Servlert API;
  • 可以任意使用各种视图技术,而不仅仅局限于 jspl;
  • 支持各种请求资源的映射策略;
  • 易扩展。

原理

Spring MVC 框架是以请求为驱动,围绕 DispatcherServlet 设计,将请求发给控制器,然后通过模型对象,分派器来展示请求结果视图。

  • 客户端发起 HTTP 请求,将请求提交到 DispatcherServlet。
  • 寻找处理器:由 DispatcherServlet 查询一个或多个 HandlerMapping,找到处理该请求的 Contoller。
  • 调用处理器:DispatcherServlet 将请求提交到 Controller。
  • 调用业务处理逻辑并返回结果:Controller 调用业务处理逻辑后,返回 ModelAndView。
  • 处理视图映射并返回模型:DispatcherServlet 查询一个或多个 ViewResolver 视图解析器,找到 ModelAndView 指定的视图。
  • HTTP 响应:视图负责将结果在客户端浏览器上渲染和展示。

重要组件

DispatcherServlet
  • 说明:前端控制器,不需要工程师开发,由 SpringMVC 框架提供。
  • 作用:Spring MVC 的入口。接收请求,响应结果,相当于转发器,中央处理器。DispatcherServlet是整个流程控制的中心,由它调用其它组件处理用户的请求,DispatcherServlet 降低了组件之间的耦合度
HandlerMapping
  • 说明:处理器映射器,不需要工程师开发,由 Spring MVC 框架提供。
  • 作用:根据请求的 url 查找 Handler。Spring MVC 提供了不同的映射器实现不同的映射方式,例如:配置文件方式,实现接口方式,注解方式等。
HandlerAdapter
  • 说明:处理器适配器。
  • 作用:按照特定规则(HandlerAdapter要求的规则)执行 Handler。通过 HandlerAdapter 对处理器进行执行,这是适配器模式的应用,通过扩展适配器可以对更多类型的处理器进行执行。
Handler
  • 说明:处理器,需要工程师开发。
  • 注意:编写 Handler 时按照 HandlerAdapter 的要求的规则去做,这样适配器才可以去正确执行 Handler, Handler 是后端控制器,在 DispatcherServlet 的控制下 Handler 对具体的用户请求进行处理。 由于 Handler 涉及到具体的用户业务请求,所以一般情况需要工程师根据业务需求开发 Handler。
ViewResolver
  • 说明:视图解析器,不需要工程师开发,由 Spring MVC 框架提供。

  • 作用:进行视图解析,根据逻辑视图名解析成真正的视图。ViewResolver 负责将处理结果生成 View 视图, ViewResolver 首先根据逻辑视图名解析成物理视图名即具体的页面地址,再生成 View 视图对象

    Spring MVC 框架提供了很多的 View 视图类型,包括:jstlView、freemarkerView、pdfView等。 一般情况下需要通过页面标签或页面模版技术将模型数据通过页面展示给用户,需要工程师根据业务需求开发具体的页面。

View
  • 说明:视图 View,需要工程师开发。
  • 作用:View 是一个接口,实现类支持不同的 View 类型(jsp、freemarker、pdf…)

5_Spring事务管理

Spring 事务管理

编程式事务 & 声明式事务

编程式事务

编程式事务指的是通过编码方式实现事务,类似 JDBC 编程实现事务管理,比如 TransactionalTemplate 或者 TransactionManager。

@Autowired
private TransactionTemplate transactionTemplate;

public void testTransaction() {

    transactionTemplate.execute(new TransactionCallbackWithoutResult() {
        @Override
        protected void doInTransactionWithoutResult(TransactionStatus transactionStatus) {

            try {
                // 业务代码
            } catch (Exception e){
                // 回滚
                transactionStatus.setRollbackOnly();
            }
        }
    });
}
@Autowired
private PlatformTransactionManager transactionManager;

public void testTransaction() {

    TransactionStatus status = transactionManager.getTransaction(new DefaultTransactionDefinition());
    try {
        // 业务代码
        transactionManager.commit(status);
    } catch (Exception e) {
        // 回滚
        transactionManager.rollback(status);
    }
}

声明式事务

声明式事务在 XML 配置文件中配置或者直接基于注解,其中基于@Transactional 的全注解方式使用最多。

事务方式优点缺点
编程式事务显示调用,不易出错侵入式代码,编码量大
声明式事务简单,对代码侵入小隐藏实现细节,出错不易定位

Spring 事务管理接口

Spring 框架中,事务管理相关最重要的 3 个接口如下:

  • PlatformTransactionManager:(平台)事务管理器
  • TransactionDefinition:事务定义信息(隔离级别、传播行为、超时、只读、回滚)
  • TransactionStatus:事务运行状态

所谓事务管理,实质上就是按照给定的事务规则来执行提交或者回滚操作。其中,“给定的事务规则”是用 TransactionDefinition 表示的,“按照……来执行提交或者回滚操作”是用 PlatformTransactionManager 表示的,而 TransactionStatus 可以看作代表事务本身。

PlatformTransactionManager

Spring 并不直接管理事务,而是提供了多种事务管理器 。Spring 事务管理是通过 PlatformTransactionManager 接口体现的,该接口是 Spring事务策略的核心。通过该接口,Spring 为各个平台如 JDBC (DataSourceTransactionManager)、Hibernate (HibernateTransactionManager)、JPA (JpaTransactionManager) 等都提供了对应的事务管理器,但是具体的实现就是各个平台自己的事情了。

public interface PlatformTransactionManager {

    //平台无关的获得事务的方法
    TransactionStatus getTransaction(TransactionDefinition definition) throws TransactionException;

    //平台无关的事务提交方法
    void commit(TransactionStatus status) throws TransactionException;

    //平台无关的事务回滚方法
    void rollback(TransactionStatus status) throws TransactionException;
}

比如我们在使用 JDBC 进行数据持久化操作时,进行如下配置:

<!-- 事务管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <!-- 数据源 -->
    <property name="dataSource" ref="dataSource" />
</bean>

TransactionDefinition

TransactionDefinition 接口用于定义事务属性,事务属性可以理解成事务的一些基本配置,描述了事务策略如何应用到方法上。事务属性包含了以下 5 个方面:

  • 事务隔离级别
  • 事务传播行为
  • 回滚规则
  • 是否只读
  • 事务超时

TransactionStatus

TransactionStatus 接口用来记录事务的状态,该接口定义了一组方法,用来获取或判断事务的相应状态信息。

public interface TransactionStatus{
    boolean isNewTransaction(); // 是否是新的事务
    boolean hasSavepoint(); // 是否有恢复点
    void setRollbackOnly();  // 设置为只回滚
    boolean isRollbackOnly(); // 是否为只回滚
    boolean isCompleted; // 是否已完成
}

Spring 事务属性

事务隔离级别

事务隔离级别是指多个事务之间的隔离程度。

TransactionDefinition 接口中定义了 5 个表示隔离级别的常量:

public interface TransactionDefinition {
    ...
    int ISOLATION_DEFAULT = -1;
    int ISOLATION_READ_UNCOMMITTED = 1;
    int ISOLATION_READ_COMMITTED = 2;
    int ISOLATION_REPEATABLE_READ = 4;
    int ISOLATION_SERIALIZABLE = 8;
    ...
}
// 事务隔离级别对应的枚举
public enum Isolation {
    DEFAULT(TransactionDefinition.ISOLATION_DEFAULT),    READ_UNCOMMITTED(TransactionDefinition.ISOLATION_READ_UNCOMMITTED),
    READ_COMMITTED(TransactionDefinition.ISOLATION_READ_COMMITTED),
    REPEATABLE_READ(TransactionDefinition.ISOLATION_REPEATABLE_READ),
    SERIALIZABLE(TransactionDefinition.ISOLATION_SERIALIZABLE);

    private final int value;

    Isolation(int value) {
        this.value = value;
    }

    public int value() {
        return this.value;
    }

}
隔离级别解释说明
ISOLATION_DEFAULT默认这是默认值,表示使用底层数据库的默认隔离级别。
ISOLATION_READ_UNCOMMITTED读未提交该隔离级别表示一个事务可以读取另一个事务修改但还没有提交的数据,该级别不能防止脏读和不可重复读,因此很少使用该隔离级别
ISOLATION_READ_COMMITTED读可提交该隔离级别表示一个事务只能读取另一个事务已经提交的数据。该级别可以防止脏读,这也是大多数情况下推荐的隔离级别
ISOLATION_REPEATABLE_READ可重复读该隔离级别表示一个事务在整个过程中可以多次重复执行某个查询,并且每次返回的记录都相同。即使在多次查询之间有新增的数据满足该查询,这些新增的记录也会被忽略。该级别可以防止脏读和不可重复读,但幻读仍有可能发生
ISOLATION_SERIALIZABLE可串行化所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,该级别可以防止脏读、不可重复读以及幻读。但是,这将严重影响程序的性能,通常情况下也不会用到该级别

事务传播行为

事务传播行为是为了解决业务层方法之间互相调用的事务问题

当事务方法被另一个事务方法调用时,必须指定事务应该如何传播。例如:方法可能继续在现有事务中运行,也可能开启一个新事务,并在自己的事务中运行。

TransactionDefinition 接口中定义了 5t个表示传播行为的常量:

public interface TransactionDefinition {
    int PROPAGATION_REQUIRED = 0;
    int PROPAGATION_SUPPORTS = 1;
    int PROPAGATION_MANDATORY = 2;
    int PROPAGATION_REQUIRES_NEW = 3;
    int PROPAGATION_NOT_SUPPORTED = 4;
    int PROPAGATION_NEVER = 5;
    int PROPAGATION_NESTED = 6;
    ...
}
//  事务传播行为对应的枚举
public enum Propagation {
    REQUIRED(TransactionDefinition.PROPAGATION_REQUIRED),
    SUPPORTS(TransactionDefinition.PROPAGATION_SUPPORTS),
    MANDATORY(TransactionDefinition.PROPAGATION_MANDATORY),
    REQUIRES_NEW(TransactionDefinition.PROPAGATION_REQUIRES_NEW),
    NOT_SUPPORTED(TransactionDefinition.PROPAGATION_NOT_SUPPORTED),
    NEVER(TransactionDefinition.PROPAGATION_NEVER),
    NESTED(TransactionDefinition.PROPAGATION_NESTED);

    private final int value;

    Propagation(int value) {
        this.value = value;
    }

    public int value() {
        return this.value;
    }

}
传播行为简写说明
PROPAGATION_REQUIREDrequired如果当前事务存在,方法将会在该事务中运行。否则,会启动一个新的事务。
PROPAGATION_SUPPORTSsupports表示支持当前事务,如果当前没有事务,就以无事务方式执行。
PROPAGATION_MANDATORYmandatory表示使用当前的事务,如果当前没有事务,就抛出异常。
PROPAGATION_REQUIRES_NEWrequired_new表示新建事务,如果当前存在事务,把当前事务挂起。
PROPAGATION_NOT_SUPPORTEDnot_supported表示以无事务方式执行操作,如果当前存在事务,就把当前事务挂起。
PROPAGATION_NEVERnever表示以无事务方式执行,如果当前存在事务,则抛出异常。
PROPAGATION_NESTEDnested如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与PROPAGATION_REQUIRED类似的操作。

举例说明事务传播类型:

StudentServiceImplA implements StudentService{
    @Autowired
    studentDao;
    
    @Autowired
    studentService;
    
    @Transactional(required) // 事务传播特性为 requried: 如果当前事务存在,方法将会在该事务中运行。否则,会启动一个新的事务。
    insertA(){
        // 对数据库操作,插入 A 数据
        studentService.insertB(); // 插入 B 数据
        // I/O 异常
    }
}
StudentServiceImplB implements StudentService{
    @Autowired
    studentDao;
    
    @Transactional
    insertB(){
        // 对数据库操作,插入 B 数据
    }
}

分以下几种情况讨论:

  • 若 StudentServiceImplB 中 insertB() 中传播类型是 required,数据库中既没有 A 数据,也没有 B 数据,则 insertA() 和 insertB() 属于同一个事务,发生异常,事务回滚即可。
  • 若 StudentServiceImplB 中 insertB() 中传播类型是 required_new,数据库中没有 A 数据,但是有 B 数据,则 insertB() 创建了一个新事务,insertA() 中发生异常,该事务回滚。
  • 若 StudentServiceImplB 中 insertB() 中传播类型是 not_supported,数据库中没有 A 数据,但是有 B 数据,则 insertB() 以非事务方式执行,执行 insertA() 的事务回滚,insertB() 中不会发生回滚

注意:insertB() 为何要放入 StudentServiceImplB 中?

Spring 的事务机制是基于 AOP 代理实现的。如果在 StudentServiceImplA 中使用 insertB() ,insertA() 中在调用 insertB() 是通过当前对象来调用 insertB() 的,而不是通过代理来调用 insertB() 的,此时 insertB() 上加事务注解就失效了

事务超时属性

事务超时指一个事务所允许执行的最长时间,如果超过该时间限制但事务还没有完成,则自动回滚事务。在 TransactionDefinition 中以 int 的值来表示超时时间,其单位是秒,默认值为 -1。

事务只读属性

对于只有读取数据查询的事务,可以指定事务类型为 readonly,即只读事务。

只读事务不涉及数据的修改,数据库会提供一些优化手段,适合用在有多条数据库查询操作的方法中。

事务回滚规则

默认情况下,事务只有遇到运行期异常时才会回滚,Error 也会导致事务回滚,但遇到检查型异常时不会回滚。

@Transactional 注解

@Transactional 注解可以使用在类上,表明该注解对该类中所有的 public 方法都生效。也可以使用在方法上,但是注解只能应用到 public 方法上,否则不生效。

源码解析

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Transactional {

    @AliasFor("transactionManager")
    String value() default "";

    @AliasFor("value")
    String transactionManager() default "";

    // 事务的传播行为,默认值为 REQUIRED
    Propagation propagation() default Propagation.REQUIRED;

    // 事务隔离级别,默认值为 DEFAULT
    Isolation isolation() default Isolation.DEFAULT;

    // 事务的超时时间,默认值为-1,即不会超时
    int timeout() default TransactionDefinition.TIMEOUT_DEFAULT;

    // 指定事务是否为只读事务,默认值为 false
    boolean readOnly() default false;

    // 用于指定能够触发事务回滚的异常类型
    // Class<? extends Throwable>[] 说明可以指定多个异常类型
    Class<? extends Throwable>[] rollbackFor() default {};

    String[] rollbackForClassName() default {};

    Class<? extends Throwable>[] noRollbackFor() default {};

    String[] noRollbackForClassName() default {};

}

工作机制

@Transactional 的工作机制是基于 Spring AOP 实现的。

如果一个类或者一个类中的 public 方法被标注 @Transactional 注解的话,Spring 容器就会在启动的时候为其创建一个代理类,在调用被 @Transactional 注解的 public 方法的时,实际调用的是 TransactionInterceptor 类中的 invoke() 方法。invoke() 方法会在目标方法之前开启事务,方法执行过程中如果遇到异常的时候回滚事务,方法调用完成后提交事务。

事务失效问题

1. 同一个类中方法调用

在同一个类中的其他没有被 @Transactional 注解的方法内部调用 @Transactional 注解的方法,则 @Transactional 注解的方法的事务会失效。

这是因为Spring AOP 代理造成的,因为只有当 @Transactional`注解的方法在类以外被调用的时候,Spring 事务管理才生效。

@Service
public class StudentService {

    private void insertA() {
        // insertA() 中调用 insertB() 是通过当前对象来调用 insertB() 的,而不是通过代理来调用 insertB() 的,此时insertB()上加事务注解就失效了。
        insertB();
        // ...
    }

    // 事务注解失效
    @Transactional
    public void insertB() {
        // ... 
    }
}Copy to clipboardErrorCopied

可以采用以下 2 种方式解决事务失效问题:

  • 避免同一类中自调用
  • 使用 AspectJ 取代 Spring AOP 代理
2. try-catch 导致事务失效
StudentServiceImplA implements StudentService{
    @Autowired
    studentDao;

    @Autowired
    studentService;

    @Transactional
    insertA(){
        try {
           // 对数据库操作,插入 A 数据
           studentService.insertB(); // 插入 B 数据
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

如果 insertB() 内部抛了异常,被 insertA() 方法 catch 了该异常,则该事务不能正常回滚。

这是因为当 ServiceB 中抛出异常后,ServiceB 标识当前事务需要 rollback 。但是ServiceA 中由于捕获这个异常并进行处理,ServiceA 认为当前事务应该正常commit ,会抛出UnexpectedRollbackException 异常。

Spring 的事务是在调用业务方法之前开始的,业务方法执行完毕之后才执行 commit 或者 rollback,事务是否执行取决于是否抛出运行时异常。如果抛出运行时异常并在业务方法中没有 catch 到的话,事务会回滚。

解决该事务失效问题:

在业务方法中一般不需要 catch 异常,如果非要 catch 则一定要抛出运行时异常,或者注解中指定抛异常类 @Transactional(rollbackFor=Exception.class)。

3. 数据库引擎不支持事务

事务能否生效数据库引擎是否支持事务是关键。

常用的 MySQL 数据库默认使用支持事务的InnoDB 存储引擎。如果数据库引擎切换成不支持事务的 MyIsam,那么事务就从根本上失效了。

使用注意事项

  • 正确的设置 @Transactional 的 rollbackFor 和 propagation 属性,否则事务可能会回滚失败
  • @Transactional 注解只有作用到 public 方法上事务才生效,不推荐在接口上使用
  • 避免同一个类中调用 @Transactional 注解的方法,这样会导致事务失效

6_Spring中用到的设计模式

Spring 中涉及到的设计模式

工厂模式

Spring 使用工厂模式可以通过 BeanFactory 或 ApplicationContext 创建 Bean。

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.FileSystemXmlApplicationContext;
 
public class App {
    public static void main(String[] args) {
        ApplicationContext context = new FileSystemXmlApplicationContext(
                "xxxx");
 
        context.getBean("xxx"); //  使用 ApplicationContext 创建 Bean
    }
}

单例模式

Spring 通过 ConcurrentHashMap 实现单例注册表的特殊方式实现单例模式:

// 通过 ConcurrentHashMap(线程安全) 实现单例注册表
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<String, Object>(64);

public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
        Assert.notNull(beanName, "'beanName' must not be null");
        synchronized (this.singletonObjects) {
            // 检查缓存中是否存在实例  
            Object singletonObject = this.singletonObjects.get(beanName);
            if (singletonObject == null) {
                //...
                try {
                    singletonObject = singletonFactory.getObject();
                }
                //... 
                // 如果实例对象不不存在,那么注册到单例注册表中。
                addSingleton(beanName, singletonObject);
            }
            return (singletonObject != NULL_OBJECT ? singletonObject : null);
        }
    }
    // 将对象添加到单例注册表
    protected void addSingleton(String beanName, Object singletonObject) {
            synchronized (this.singletonObjects) {
                this.singletonObjects.put(beanName, (singletonObject != null ? singletonObject : NULL_OBJECT));

            }
        }
}

Spring 实现单例的方式:

  • XML 配置方式:

    <bean id="testService" class="xxxx.TestServiceImpl" scope="singleton"/>
    
  • 注解方式:

    @Service
    @Scope("singleton")
    public class TestServiceImpl{
    }
    

原型模式

Spring 实现原型方式:

Spring 实现单例的方式:

  • XML 配置方式:

    <bean id="testService" class="xxxx.TestServiceImpl" scope="prototype"/>
    
  • 注解方式:

    @Service
    @Scope("prototype")
    public class TestServiceImpl{
    }
    

代理模式

Spring AOP、Spring 事务管理都大量运用了代理模式。

适配器模式

Spring AOP 的增强或通知 (Advice) 使用到了适配器模式,与之相关的接口是 AdvisorAdapter 。Advice 常用的类型有:BeforeAdvice(前置通知)、AfterAdvice(后置通知)等,每个类型 Advice 都有对应的拦截器:MethodBeforeAdviceInterceptor、AfterReturningAdviceAdapter。Spring 预定义的 Advice 要通过对应的适配器,适配成 MethodInterceptor 接口(方法拦截器)类型的对象,例如 MethodBeforeAdviceInterceptor 负责适配 MethodBeforeAdvice。

Spring MVC 中的 DispatcherServlet 根据请求信息调用 HandlerMapping,解析请求对应的 Handler。解析到对应的 Handler 后,开始由 HandlerAdapter 处理。HandlerAdapter 作为期望接口,具体的适配器实现类用于对目标类进行适配,Controller 作为需要适配的类。

**

7_MyBatis

**

Mybatis 基本原理

MyBatis 是 Apache 的一个 Java 开源项目,是一款优秀的持久层框架。

支持定制化 SQL、存储过程以及高级映射。Mybatis 可将 SQL 语句配置在 XML 文件中,避免将 SQL 语句硬编码在 Java 类中。MyBatis 具有如下特点:

  • 通过参数映射方式,可以将参数灵活的配置在 SQL 语句中的配置文件中,避免在 Java 类中配置参数(JDBC)
  • 通过输出映射机制,将结果集的检索自动映射成相应的 Java 对象,避免对结果集手工检索(JDBC)
  • Mybatis 通过 XML 配置文件对数据库连接进行管理

核心类

SqlSession

SqlSession 相当于一个会话,每次访问数据库都需要这样一个会话,类似 JDBC 中的 Connection,但是现在几乎所有的连接都是使用的连接池技术,用完后直接归还而不会像 SqlSession 一样销毁。

注意 SqlSession 不是线程安全的,应该设置为线程私有。每次创建的 SqlSession 都必须及时关闭它,如果 SQLSession 长期存在就会使数据库连接池的活动资源减少,对系统性能的影响很大,可以在 finally 代码块中将其关闭。此外 SqlSession 存活于一个应用的请求和操作,可以执行多条 SQL语句,保证事务的一致性。

SqlSession 在执行过程中,包括以下对象:

  • Executor

    即执行器,负责调度 StatementHandler、ParameterHandler、ResultSetHandler 等来执行对应的 SQL 语句。

  • StatementHandler

    使用数据库的 Statement(PreparedStatement)执行操作。

  • ParamentHandler

    来处理 SQL 参数。

  • ResultSetHandler

    封装并处理数据集。

SqlSessionFactory

SqlSessionFactory 用于创建 SqlSession。每次应用程序访问数据库时, 需要通过 SqlSessionFactory 创建 SqlSession,显然 SqlSessionFactory 和整个 Mybatis 的生命周期是相同的。

SqlSessionFactory 是线程安全的,应用运行迁建不要重复创建多次,因为创建多个可能为消耗尽数据库的连接资源,建议使用单例模式。

SqlSessionaFactoryBuilder

SqlSessionaFactoryBuilder 用于创建 SqlSessionFactory,该类可以被实例化、使用和丢弃,一旦创建了 SqlSessionFactory,就不再需要它了。 因此 SqlSessionFactoryBuilder 实例的最佳作用域是方法作用域。

Mapper

Mapper 接口的实例是从 SqlSession 中获取,作用是发送 SQL,然后返回我们需要的结果,或者执行 SQL 从而更改数据库的数据,因此它应该在 SqlSession 的事务方法之内。

在 Spring 管理的 Bean 中, Mapper 是单例的。

功能架构

接口层

提供给外部使用的接口 API,开发人员通过这些本地 API 来操作数据库。

接口层一接收到调用请求就会调用数据处理层来完成具体的数据处理。

数据处理层

负责具体的SQL查找、SQL解析、SQL执行和执行结果映射处理等。它主要功能是根据调用的请求完成一次数据库操作。

  • 加载配置:配置来源有配置文件或者注解,将 SQL 的配置信息加载成为一个个 MappedStatement 对象(包括了传入参数映射配置、执行的 SQL 语句、结果映射配置),存储在内存中。
  • SQL解析:当 API 接口层接收到调用请求时,会接收到传入 SQL 的 ID 和传入对象(可以是 Map、JavaBean 或者基本数据类型),Mybatis 会根据 SQL 的 ID 找到对应的 MappedStatement,然后根据传入参数对象对MappedStatement 进行解析,解析后可以得到最终要执行的 SQL 语句和参数。
  • SQL执行:将最终得到的 SQL 和参数拿到数据库进行执行,得到操作数据库的结果。
  • 结果映射:将操作数据库的结果按照映射的配置进行转换,可以转换成HashMap、JavaBean 或者基本数据类型,并将最终结果返回。

基础支撑层

负责最基础的功能支撑,包括连接管理、事务管理、配置加载和缓存处理,这些都是共用的东西,将他们抽取出来作为最基础的组件。为上层的数据处理层提供最基础的支撑。

执行流程

  • Mybatis 配置

    sqlMapConfig.xml 文件是 Mybatis 的全局配置文件,配置了 Mybatis的运行环境等信息。Mapper.xml 文件即 SQL 映射文件,文件中配置了操作数据库的 SQL 语句,需要在 sqlMapConfig.xml 中加载。

  • 通过 Mybatis 环境等配置信息构造 SqlSessionFactory

  • 通过 SqlSessionFactory 创建 SqlSession,操作数据库需要通过 SqlSession进行。

  • Mybatis 自定义了 Executor 执行器操作数据库,Executor 接口有两个实现,一个是基本执行器、一个是缓存执行器。

  • MappedStatement 是 Mybatis 一个底层封装对象,包装 Mybatis 配置信息及 SQL 映射信息等。Mapper.xml 文件中一个 SQL 对应一个 MappedStatement对象,SQL 的 id 即 MappedStatement 的 id。

  • MappedStatement 对 SQL 执行输入参数进行定义,包括 HashMap、基本类型、pojo,Executor 通过MappedStatement 在执行 SQL 前将输入的 Java 对象映射至 SQL 中,输入参数映射就是 jdbc 编程中对preparedStatement 设置参数。

  • MappedStatement 对 SQL 执行输出结果进行定义,包括 HashMap、基本类型、pojo,Executor 通过MappedStatement 在执行 SQL 后将输出结果映射至 Java对象中,输出结果映射过程相当于 jdbc 编程中对结果的解析处理过程。

缓存机制

MyBatis 的缓存分为一级缓存和二级缓存。默认情况下一级缓存是开启的。

一级缓存

一级缓存指 SqlSession 级别的缓存,在同一个 SqlSession 中执行相同的 SQL 语句查询时将查询结果集缓存。一级缓存最多能缓存 1024 条 SQL 语句。

当客户端第一次发出一个 SQL 查询语句时,MyBatis 执行 SQL 查询并将查询结果写入 SqlSession 的一级缓存,当第二次有相同的 SQL 查询语句时,则直接从缓存中获取。当同一个 SqlSession 多次发出相同的 SQL 查询语句时,MyBatis 直接从缓存中获取数据。如果两次查询中出现 commit 操作(新增、删除、修改),则认为数据发生了变化,Mybaits 会把该 SqlSession 中的一级缓存区域全部清空,当下次再到缓存中查找时将找不到对应的缓存数据,因此需要再次从数据库中查询数据并将查询的结果写入缓存。

二级缓存

二级缓存指跨 SqlSession 的缓存,即 Mapper 级别的缓存。在 Mapper 级别的缓存内,不同的 SqlSession 缓存可以共享。

Mapper 以命名空间为单位创建缓存数据结构,数据结构是 Map 类型,Map 中 Key 为MapperId + Offset + Limit + SQL + 所有入参。开启二级缓存后,会使用 CachingExecutor 装饰 Executor ,在进入一级缓存的查询流程前,先在 CachingExecutor 进行二级缓存的查询。

开启二级缓存需要做一下配置:

  • 在全局配置中启用二级缓存配置。
  • 在对应的 Mappper.xml 中配置 Cache 节点。
  • 在对应的 select 查询节点中添加 userCache=true。

MyBatis 面试题

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值