目录

Life in Flow

知不知,尚矣;不知知,病矣。
不知不知,殆矣。

X

Spring Framework5.X

Spring 框架,使用的好处

  • 什么是 Spring:轻量级的 DI / IoC 和 AOP 容器的开源框架

  • 有啥好处:

    • 管理创建和组装对象之间的依赖关系 使用前:手工创建

      1UserControoler
      2private UserService userService = new UserService();
      

      使用后:Spring 创建,自动注入

    • 面向切面编程(AOP)可以解耦核心业务和边缘业务的关系
      场景:用户调用下单购买视频接口,需要判断登录,拦截器是 AOP 思想的一种实现
      使用前:代码写逻辑,每次下单都调用方法判断,多个方法需要判断登录则都需要 登录方法判断
      使用后:根据一定的方法或者路径规则进行判断是否要调用,降低代码耦合度

    • 包含 Java 大型项目里面常见解决方案 Web 层、业务层、数据访问层等

    • 极其便利的整合其他主流技术栈,比如 Redis、mq、MyBatis、jpa

    • 社区庞大和活跃,在微服务、大数据、云计算都有对应的组件

  • 为什么要学?(SpringBoot 帮我们简化了很多配置)

    • 使用 springboot2.x 后,很少会接触到各种细化的 bean 配置,但是底层实现流程和原理是必须掌握的。

构建 Spring5.X 项目

  • 创建 maven 项目

  • 添加依赖

     1<!-- https://mvnrepository.com/artifact/org.springframework/spring-context -->
     2        <dependency>
     3            <groupId>org.springframework</groupId>
     4            <artifactId>spring-context</artifactId>
     5            <version>5.2.5.RELEASE</version>
     6        </dependency>
     7        <!-- https://mvnrepository.com/artifact/org.springframework/spring-core -->
     8        <dependency>
     9            <groupId>org.springframework</groupId>
    10            <artifactId>spring-core</artifactId>
    11            <version>5.2.5.RELEASE</version>
    12        </dependency>
    13        <!-- https://mvnrepository.com/artifact/org.springframework/spring-beans -->
    14        <dependency>
    15            <groupId>org.springframework</groupId>
    16            <artifactId>spring-beans</artifactId>
    17            <version>5.2.5.RELEASE</version>
    18        </dependency>
    
  • 添加配置文件 applicationContext.xml

    bean 标签

    id 属性:指定 Bean 的名称,在 Bean 被别的类依赖时使用

    name 属性:用于指定 Bean 的别名,如果没有 id,也可以用 name

    class 属性:用于指定 Bean 的来源,要创建的 Bean 的 class 类,需要全限定名

 1<?xml version="1.0" encoding="UTF-8"?>
 2<beans xmlns="http://www.springframework.org/schema/beans"
 3       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 4       xsi:schemaLocation="http://www.springframework.org/schema/beans
 5        https://www.springframework.org/schema/beans/spring-beans.xsd">
 6
 7    <bean id="video" class="net.xdclass.sp.domain.Video">
 8
 9        <property name="id" value="9"/>
10        <property name="title" value="Spring 5.X" />
11
12    </bean>
13</beans>

Video

 1package net.xdclass.sp.domain;
 2
 3public class Video {
 4
 5    private int id;
 6
 7    private String title;
 8
 9    public int getId() {
10        return id;
11    }
12
13    public void setId(int id) {
14        this.id = id;
15    }
16
17    public String getTitle() {
18        return title;
19    }
20
21    public void setTitle(String title) {
22        this.title = title;
23    }
24}

app

 1package net.xdclass.sp;
 2
 3import net.xdclass.sp.domain.Video;
 4import org.springframework.context.ApplicationContext;
 5import org.springframework.context.support.ClassPathXmlApplicationContext;
 6
 7public class App {
 8
 9    public static void main(String [] args){
10
11        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
12
13        Video  video = (Video)applicationContext.getBean("video");
14
15        System.out.println(video.getTitle());
16    }
17}

IoC

  • 什么是 IoC Inverse of Control(控制反转)是一种设计思想 将原本在程序中手动创建对象的流程,交由 Spring 框架来管理 核心:把创建对象的控制权反转给 Spring 框架,对象的生命周期由 Spring 统一管理。把 Spring IoC 当成一个容器,里面存储管理的对象称为 Bean,类实例
  • 案例实操 配置文件里面定义一个 bean,通过代码去获取
1<bean  name="video" class="net.xdclass.sp.domain.Video">
2        <property name="id" value="9"/>
3        <property name="title" value="Spring 5.X课程" />
4</bean>
5
6
7ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
8Video  video = (Video)applicationContext.getBean("video");
9System.out.println(video.getTitle())

IOC 容器

DI

  • 什么是 DI Dependency Injection ,依赖注入
    IoC 容器在运行期间,动态地将对象某种依赖关系注入到对象之中,比如视频订单对象,依赖用视频对象
  • 案例实操

Video

 1package net.xdclass.sp.domain;
 2
 3public class Video {
 4
 5    private int id;
 6
 7    private String title;
 8
 9    public int getId() {
10        return id;
11    }
12
13    public void setId(int id) {
14        this.id = id;
15    }
16
17    public String getTitle() {
18        return title;
19    }
20
21    public void setTitle(String title) {
22        this.title = title;
23    }
24
25    @Override
26    public String toString() {
27        return "Video{" +
28                "id=" + id +
29                ", title='" + title + '\'' +
30                '}';
31    }
32}

VideoOrder

 1package net.xdclass.sp.domain;
 2
 3public class VideoOrder {
 4    private int id;
 5    private String outTradeNo;
 6    private Video video;
 7
 8    public int getId() {
 9        return id;
10    }
11
12    public void setId(int id) {
13        this.id = id;
14    }
15
16    public String getOutTradeNo() {
17        return outTradeNo;
18    }
19
20    public void setOutTradeNo(String outTradeNo) {
21        this.outTradeNo = outTradeNo;
22    }
23
24    public Video getVideo() {
25        return video;
26    }
27
28    public void setVideo(Video video) {
29        this.video = video;
30    }
31
32    @Override
33    public String toString() {
34        return "VideoOrder{" +
35                "id=" + id +
36                ", outTradeNo='" + outTradeNo + '\'' +
37                ", video=" + video +
38                '}';
39    }
40}

applicationContext.xml

 1<?xml version="1.0" encoding="UTF-8"?>
 2<beans xmlns="http://www.springframework.org/schema/beans"
 3       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 4       xsi:schemaLocation="http://www.springframework.org/schema/beans
 5        https://www.springframework.org/schema/beans/spring-beans.xsd">
 6
 7    <bean id="video" class="net.xdclass.sp.domain.Video">
 8
 9        <property name="id" value="9"/>
10        <property name="title" value="Spring 5.X课程" />
11
12    </bean>
13
14    <bean id="videoOrder" class="net.xdclass.sp.domain.VideoOrder">
15        <property name="id" value="12"/>
16        <property name="outTradeNo" value="244242121"/>
17        <property name="video" ref="video"/>
18    </bean>
19</beans>

App

 1package net.xdclass.sp;
 2
 3import net.xdclass.sp.domain.Video;
 4import net.xdclass.sp.domain.VideoOrder;
 5import org.springframework.context.ApplicationContext;
 6import org.springframework.context.support.ClassPathXmlApplicationContext;
 7
 8public class App {
 9
10    public static void main(String [] args){
11
12        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
13        //Spring 5.X课程
14        Video  video = (Video)applicationContext.getBean("video");
15        System.out.println(video.getTitle());
16  
17        VideoOrder videoOrder = (VideoOrder)applicationContext.getBean("videoOrder");
18        //VideoOrder{id=12, outTradeNo='244242121', video=Video{id=9, title='Spring 5.X课程'}}
19        System.out.println(videoOrder);
20    }
21}

作用域

scope 属性

  • singleton:单例, 默认值,调用 getBean 方法返回是同一个对象,实例会被缓存起来,效率比较高,当一个 bean 被标识为 singleton 时候,Spring 的 IoC 容器中只会存在一个该 bean

  • prototype: 多例,调用 getBean 方法创建不同的对象,会频繁的创建和销毁对象造成很大的开销

  • 其他少用 (作用域 只在 WebApplicationContext)

    • request :每个 Http 请求都会创建一个新的 bean
    • session: 每个 Http Session 请求都会创建一个新的 bean
    • global session(基本不用)
 1<!--<bean id="video" class="net.xdclass.sp.domain.Video" scope="singleton"> -->
 2<bean id="video" class="net.xdclass.sp.domain.Video" scope="prototype">
 3
 4        <property name="id" value="9"/>
 5        <property name="title" value="Spring 5.X课程" />
 6
 7</bean>
 8
 9
10
11 private static  void testScope(ApplicationContext context){
12        Video  video1 = (Video)context.getBean("video");
13        Video  video2 = (Video)context.getBean("video");
14
15        //靠匹配内存地址,== 是匹配内存地址
16        System.out.println( video1 == video2 );
17
18  }

常见注入方式

  • 使用 set 方法注入
1<bean id="video" class="net.xdclass.sp.domain.Video" scope="singleton">
2
3        <property name="id" value="9"/>
4        <property name="title" value="Spring 5.X课程" />
5
6</bean>
  • 使用带参的构造函数注入
1    <bean id="video" class="net.xdclass.sp.domain.Video" >
2        <constructor-arg name="title" value="面试专题课程第一季"></constructor-arg>
3    </bean>
4
5
6
7    public Video(String title) {
8        this.title = title;
9    }
  • POJO 类型注入(property 没有使用 value 属性,而是使用了 ref 属性,也是通过 setting 方式注入的)
 1  <bean id="video" class="net.xdclass.sp.domain.Video" >
 2
 3        <constructor-arg name="title" value="面试专题课程第一季"></constructor-arg>
 4
 5    </bean>
 6
 7
 8    <bean id="videoOrder" class="net.xdclass.sp.domain.VideoOrder" >
 9        <property name="id" value="8" />
10        <property name="outTradeNo" value="23432fnfwedwefqwef2"/>
11        <property name="video" ref="video"/>
12    </bean>
  • 注意: 类的构造函数重写的时候,一定要保留空构造函数!!!否则 setting 注入方式会报错!!!

List、Map 类型的注入

  • 复杂类型注入,添加两个属性
 1<bean id="video" class="net.xdclass.sp.domain.Video" >
 2
 3        <!--list类型注入-->
 4        <property name="chapterList">
 5            <list>
 6                <value>第一章SpringBoot</value>
 7                <value>第二章Mybatis</value>
 8                <value>第三章Spring</value>
 9            </list>
10        </property>
11
12        <property name="videoMap">
13            <map>
14                <entry key="1" value="SpringCloud课程"></entry>
15                <entry key="2" value="面试课程"></entry>
16                <entry key="3" value="javaweb课程"></entry>
17            </map>
18        </property>
19</bean>
20
21
22
23public class Video {
24
25    private int id;
26
27    private String title;
28
29    private List<String> chapterList;
30
31    private Map<Integer,String> videoMap;
32
33    //省略set get方法
34}

IoC 容器中 Bean 之间的依赖和继承

  • bean 继承:两个类之间大多数的属性都相同,避免重复配置,通过 bean 标签的 parent 属性重用已有的 Bean 元素的配置信息,继承指的是配置信息的复用,和 Java 类的继承没有关系
1<bean id="video" class="net.xdclass.sp.domain.Video" scope="singleton">
2        <property name="id" value="9"/>
3        <property name="title" value="Spring 5.X课程" />
4</bean>
5
6
7<bean id="video2" class="net.xdclass.sp.domain.Video2" scope="singleton" parent="video">
8        <property name="summary" value="这个是summary"></property>
9</bean>
  • 属性依赖: 如果类 A 是作为类 B 的属性, 想要类 A 比类 B 先实例化,设置两个 Bean 的依赖关系
 1<bean id="video" class="net.xdclass.sp.domain.Video" scope="singleton">
 2        <property name="id" value="9"/>
 3        <property name="title" value="Spring 5.X课程" />
 4</bean>
 5
 6
 7<!--设置两个bean的关系,video要先于videoOrder实例化-->
 8<bean id="videoOrder" class="net.xdclass.sp.domain.VideoOrder" depends-on="video">
 9        <property name="id" value="8" />
10        <property name="outTradeNo" value="23432fnfwedwefqwef2"/>
11        <property name="video" ref="video"/>
12</bean>

IoC 容器中 Bean 的生命周期 init()、destroy()方法

 1package net.xdclass.sp.domain;
 2
 3public class Video {
 4
 5    private int id;
 6
 7    private String title;
 8
 9    public Video(String title) {
10        this.title = title;
11    }
12
13    public void init(){
14        System.out.println("video 类 init 方法被调用");
15    }
16
17    public void destroy(){
18        System.out.println("video 类 destroy 方法被调用");
19    }
20
21    public Video(){
22        System.out.println("video 类 空构造函数被调用");
23    }
24    public int getId() {
25        return id;
26    }
27
28    public void setId(int id) {
29        this.id = id;
30    }
31
32    public String getTitle() {
33        return title;
34    }
35
36    public void setTitle(String title) {
37        this.title = title;
38    }
39
40    @Override
41    public String toString() {
42        return "Video{" +
43                "id=" + id +
44                ", title='" + title + '\'' +
45                '}';
46    }
47}
48
 1    <bean id="video" class="net.xdclass.sp.domain.Video" init-method="init" destroy-method="destroy">
 2        <property name="id" value="9"/>
 3        <property name="title" value="Spring 5.X课程" />
 4    </bean>
 5
 6
 7
 8        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
 9        //Spring 5.X课程
10        Video  video = (Video)applicationContext.getBean("video");
11        System.out.println(video.getTitle());
12        //注册钩子函数才会触发调用destroy()
13        ((ClassPathXmlApplicationContext)applicationContext).registerShutdownHook();

后置处理器 BeanPostProcessor

  • 什么是 BeanPostProcessor
    • 是 Spring IoC 容器给我们提供的一个扩展接口
    • 在调用初始化方法前后对 Bean 进行额外加工,ApplicationContext 会自动扫描实现了 BeanPostProcessor 的 bean,并注册这些 bean 为后置处理器
    • 是 Bean 的统一前置后置处理而不是基于某一个 bean
  • 执行顺序
1video 空构造函数被调用(Spring IOC容器实例化Bean)
2调用BeanPostProcessor的postProcessBeforeInitialization方法
3调用bean实例的初始化方法 init()
4调用BeanPostProcessor的postProcessAfterInitialization方法

注意:接口重写的两个方法不能返回 null,如果返回 null 那么在后续初始化方法将报空指针异常或者通过 getBean()方法获取不到 bean 实例对象

 1public class CustomBeanPostProcessor implements BeanPostProcessor,Ordered {
 2
 3    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
 4
 5        System.out.println("CustomBeanPostProcessor1 postProcessBeforeInitialization beanName="+beanName);
 6
 7        return bean;
 8    }
 9
10    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
11        System.out.println("CustomBeanPostProcessor1 postProcessAfterInitialization beanName="+beanName);
12        return bean;
13    }
14
15
16    public int getOrder() {
17        return 1;
18    }
19}

注册自定义的 CustomBeanPostProcessor

1<bean class="net.xdclass.sp.processor.CustomBeanPostProcessor"/>

可以注册多个 BeanPostProcessor 顺序

  • 在 Spring 机制中可以指定后置处理器调用顺序,通过 BeanPostProcessor 接口实现类实现 Ordered 接口 getOrder 方法,该方法返回整数,默认值为 0 优先级最高,值越大优先级越低

BeanPostProcessor 和 Bean 的 init()、destroy()区别?

  • init、destroy 是针对单个 Bean。
  • BeanPostProcessor 对容器中所有 Bean 都生效。

bean 自动装配 Autowire 属性

  • 属性注入

    • 前面学过属性注入,set 方法、构造函数等,属于手工注入
    • 有没办法实现自动装配注入?
  • Spring 自动注入

    • 使用元素的 autowire 属性为一个 bean 定义指定自动装配模式
    • autowire 设置值
      • no:没开启
      • byName: 根据 bean 的 id 名称,注入到对应的属性里面 , video
      • byType:根据 bean 需要注入的类型,注入到对应的属性里面
        • 如果按照类型注入,存在 2 个以上 bean 的话会抛异常
        • expected single matching bean but found 2
      • constructor: 通过构造函数注入,需要这个类型的构造函数 VideoOrder(xx,xx,Video video)
 1    <bean id="video" class="net.xdclass.sp.domain.Video" init-method="init" destroy-method="destroy">
 2        <property name="id" value="9"/>
 3        <property name="title" value="Spring 5.X课程" />
 4    </bean>
 5
 6<!--<bean id="videoOrder" class="net.xdclass.sp.domain.VideoOrder" autowire="byName">-->
 7<!--<bean id="videoOrder" class="net.xdclass.sp.domain.VideoOrder" autowire="byType">-->
 8    <bean id="videoOrder" class="net.xdclass.sp.domain.VideoOrder" autowire="constructor">
 9
10        <property name="id" value="8" />
11        <property name="outTradeNo" value="23432fnfwedwefqwef2"/>
12	<!-- <property name="video" ref="video"/> -->
13    </bean>

AOP

  • 什么是 AOP

    • Aspect Oriented Program 面向切面编程
    • 在不改变原有逻辑上增加额外的功能,比如解决系统层面的问题,或者增加新的功能
  • 场景

    • 权限控制
    • 缓存
    • 日志处理
    • 事务控制
  • AOP 思想把功能分两个部分,分离系统中的各种关注点

    • 核心关注点

      • 业务的主要功能
    • 横切关注点

      • 非核心、额外增加的功能
  • 好处

    • 减少代码侵入,解耦
    • 可以统一处理横切逻辑
    • 方便添加和删除横切逻辑

AOP 核心概念

  • 横切关注点

    • 对哪些方法进行拦截,拦截后怎么处理,这些就叫横切关注点
    • 比如 权限认证、日志、事务
  • 通知 Advice

    • 在特定的切入点上执行的增强处理,有 5 种通知,后面讲解
    • 做啥? 比如你需要记录日志,控制事务 ,提前编写好通用的模块,需要的地方直接调用
  • 连接点 JointPoint

    • 要用通知的地方,业务流程在运行过程中需要插入切面的具体位置,
    • 一般是方法的调用前后,全部方法都可以是连接点
    • 只是概念,没啥特殊
  • 切入点 Pointcut

    • 不能全部方法都是连接点,通过特定的规则来筛选连接点, 就是 Pointcut,选中那几个你想要的方法
    • 在程序中主要体现为书写切入点表达式(通过通配、正则表达式)过滤出特定的一组 JointPoint 连接点
    • 过滤出相应的 Advice 将要发生的 joinpoint 地方
  • 切面 Aspect

    • 通常是一个类,里面定义 **切入点 + 通知 **, 定义在什么地方; 什么时间点、做什么事情
    • 通知 advice 指明了时间和做的事情(前置、后置等)
    • 切入点 pointcut 指定在什么地方干这个事情
    • Web 接口设计中,Web 层-> 网关层-> 服务层-> 数据层,每一层之间也是一个切面,对象和对象,方法和方法之间都是一个个切面
  • 目标 target

    • 目标类,真正的业务逻辑,可以在目标类不知情的条件下,增加新的功能到目标类的链路上
  • 织入 Weaving

    • 把切面(某个类)应用到目标函数的过程称为织入
  • AOP 代理

    • AOP 框架创建的对象,代理就是目标对象的加强
    • Spring 中的 AOP 代理可以使 JDK 动态代理,也可以是 CGLIB 代理

Advice 的类型

  • @Before 前置通知

    • 在执行目标方法之前运行
  • @After 后置通知

    • 在目标方法运行结束之后
  • @AfterReturning 返回通知

    • 在目标方法正常返回值后运行
  • @AfterThrowing 异常通知

    • 在目标方法出现异常后运行
  • @Around 环绕通知

    • 在目标方法完成前、后做增强处理 ,环绕通知是最重要的通知类型 ,像事务,日志等都是环绕通知,注意编程中核心是一个 ProceedingJoinPoint,需要手动执行 joinPoint.procced()

AOP 面向切面编程流程

 1//目标类 VideoOrderService; 里面每个方法都是连接点,;切入点是CUD类型的方法,R读取的不作为切入点
 2//CRDU全称:增加(Create)、读取查询(Retrieve)、更新(Update)和删除(Delete)
 3
 4VideoOrderService{
 5    //新增订单
 6    addOrder(){ }
 7    //查询订单
 8   findOrderById(){}
 9   //删除订单
10   delOrder(){}
11   //更新订单
12   updateOrder(){}   
13}
14
15
16//权限切面类 = 切入点+通知 
17PermissionAspects{
18  
19  //切入点  定义了什么地方
20    @Pointcut("execution(public int net.xdclass.sp.service.VideoOrderService.*(..))")
21  public void pointCut(){}
22  
23  
24  //before 通知 表示在目标方法执行前切入, 并指定在哪个方法前切入
25  //什么时候,做什么事情
26  @Before("pointCut()")
27  public void permissionCheck(){
28  
29    System.out.println("在 xxx 之前执行权限校验");
30  }
31  ....
32}
33
34//日志切面类 = 切入点+通知 
35LogAspect{
36  
37  //切入点  定义了什么地方
38    @Pointcut("execution(public int net.xdclass.sp.service.VideoOrderService.*(..))")
39  public void pointCut(){}
40  
41  
42  //after 通知 表示在目标方法执行后切入, 并指定在哪个方法前切入
43  //什么时候,做什么事情
44  @After("pointCut()")
45  public void logStart(){
46  
47    System.out.println("在 xxx 之后记录日志");
48  }
49  ....
50}

切入点表达式

切入点表示式

  • 除了返回类型、方法名和参数外,其它项都是可选的 (修饰符基本都是省略不写)
1        访问修饰符          返回值类型(必填)     包和类                    方法(必填)
2execution(modifiers-pattern? ret-type-pattern declaring-type-pattern?name-pattern(param-pattern)  throws-pattern?) 
  • @Pointcut("execution(public int net.xdclass.sp.service.VideoOrderService.*(..))")

常见匹配语法

  • *:匹配任何数量字符 单个;
  • ..:匹配任何数量字符,可以多个,在类型模式中匹配任何数量子包;在方法参数模式中匹配任何数量参数
1() 匹配一个不接受任何参数的方法
2(..) 匹配一个接受任意数量参数的方法
3(*) 匹配了一个接受一个任何类型的参数的方法
4(*,Integer) 匹配了一个接受两个参数的方法,其中第一个参数是任意类型,第二个参数必须是Integer类型

常见例子

  • 任意公共方法
1execution(public * *(..))
  • 任何一个名字以“save”开始的方法
1execution(* save*(..))
  • VideoService 接口定义的任意方法(识别)
1execution(* net.xdclass.service.VideoService.*(..))
  • 在 service 包中定义的任意方法(识别)
1execution(* net.xdclass.service.*.*(..))
  • 匹配 service 包,子孙包下所有类的所有方法(识别)
1execution(* net.xdclass.service..*.*(..))

代理

  • 什么是代理

    • 为某一个对象创建一个代理对象,程序不直接用原本的对象,而是由创建的代理对象来控制对原对象,通过代理类这中间一层,能有效控制对委托类对象的直接访问,也可以很好地隐藏和保护委托类对象,同时也为实施不同控制策略预留了空间
    • A ->B-> C
  • 什么是静态代理

    • 由程序创建或特定工具自动生成源代码,在程序运行前,代理类的.class 文件就已经存在
  • 什么是动态代理

    • 在程序运行时,运用反射机制动态创建而成,无需手动编写代码
      • JDK 动态代理
      • CGLIB 动态代理

静态代理

  • 什么是静态代理

    • 由程序创建或特定工具自动生成源代码,在程序运行前,代理类的.class 文件就已经存在
    • 通过将目标类与代理类实现同一个接口,让代理类持有真实类对象,然后在代理类方法中调用真实类方法,在调用真实类方法的前后添加我们所需要的功能扩展代码来达到增强的目的
    • A -> B -> C
  • 优点

    • 代理使客户端不需要知道实现类是什么,怎么做的,而客户端只需知道代理即可
    • 方便增加功能,拓展业务逻辑
  • 缺点

    • 代理类中出现大量冗余的代码,非常不利于扩展和维护
    • 如果接口增加一个方法,除了所有实现类需要实现这个方法外,所有代理类也需要实现此方法。增加了代码维护的复杂度

PayService 接口

 1package net.xdclass.sp.proxy;
 2
 3public interface PayService {
 4    /**
 5     * 支付回调
 6     * @param outTradeNo
 7     * @return
 8     */
 9    String callback(String outTradeNo);
10
11    /**
12     * 下单
13     * @param userId
14     * @param productId
15     * @return
16     */
17    int save(int userId, int productId);
18}
19

PayServiceImpl 目标类

 1package net.xdclass.sp.proxy;
 2
 3public class PayServiceImpl implements PayService{
 4
 5    public String callback(String outTradeNo) {
 6        System.out.println("目标类 PayServiceImpl 回调 方法 callback");
 7        return outTradeNo;
 8    }
 9
10    public int save(int userId, int productId) {
11        System.out.println("目标类 save 回调 方法 save");
12        return productId;
13    }
14}
15

StaticProxyPayServiceImpl 静态代理类

 1package net.xdclass.sp.proxy;
 2
 3public class PayServiceImpl implements PayService{
 4
 5    public String callback(String outTradeNo) {
 6        System.out.println("目标类 PayServiceImpl 回调 方法 callback");
 7        return outTradeNo;
 8    }
 9
10    public int save(int userId, int productId) {
11        System.out.println("目标类 save 回调 方法 save");
12        return productId;
13    }
14}
15

ProxyTest 测试类

 1package net.xdclass.sp.proxy;
 2
 3public class ProxyTest {
 4    public static void main(String[] args) {
 5//        PayService payService = new PayServiceImpl();
 6//        payService.callback("asdasd");
 7
 8        PayService payService = new PayServiceImpl();
 9        StaticProxyPayServiceImpl staticProxyPayService = new StaticProxyPayServiceImpl(payService);
10        staticProxyPayService.callback("asdasd");
11        staticProxyPayService.save(123, 52522331);
12
13    }
14}

控制台输出

1StaticProxyPayServiceImpl callback begin
2目标类 PayServiceImpl 回调 方法 callback
3StaticProxyPayServiceImpl callback end
4
5StaticProxyPayServiceImpl save begin
6目标类 save 回调 方法 save
7StaticProxyPayServiceImpl save end

AOP 的实现策略之 JDK 动态代理

什么是动态代理

  • 在程序运行时,运用反射机制动态创建而成,无需手动编写代码
  • JDK 动态代理与静态代理一样,目标类需要实现一个代理接口,再通过代理对象调用目标方法

实操:

 1定义一个java.lang.reflect.InvocationHandler接口的实现类,重写invoke方法
 2
 3//Object proxy:被代理的对象  
 4//Method method:要调用的方法  
 5//Object[] args:方法调用时所需要参数  
 6public interface InvocationHandler {  
 7    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable;  
 8}  
 9
10
11
12public class JdkProxy implements InvocationHandler {
13
14    //目标类
15    private Object targetObject;
16
17    //获取代理对象
18    public Object newProxyInstance(Object targetObject){
19        this. targetObject = targetObject;
20
21        //绑定关系,也就是和具体的哪个实现类关联
22        return  Proxy.newProxyInstance(targetObject.getClass().getClassLoader(),
23                targetObject.getClass().getInterfaces(),this);
24    }
25
26
27    public Object invoke(Object proxy, Method method, Object[] args) {
28
29
30        Object result = null;
31
32        try{
33            System.out.println("通过JDK动态代理调用 "+method.getName() +", 打印日志 begin");
34
35            result = method.invoke(targetObject,args);
36
37            System.out.println("通过JDK动态代理调用 "+method.getName() +", 打印日志 end");
38
39        }catch (Exception e){
40
41            e.printStackTrace();
42        }
43
44        return result;
45
46
47    }
48}

PayService 接口

 1package net.xdclass.sp.proxy;
 2
 3public interface PayService {
 4    /**
 5     * 支付回调
 6     * @param outTradeNo
 7     * @return
 8     */
 9    String callback(String outTradeNo);
10
11    /**
12     * 下单
13     * @param userId
14     * @param productId
15     * @return
16     */
17    int save(int userId, int productId);
18}
19

PayServiceImpl 实现类

 1package net.xdclass.sp.proxy;
 2
 3public class PayServiceImpl implements PayService{
 4
 5    public String callback(String outTradeNo) {
 6        System.out.println("目标类 PayServiceImpl 回调 方法 callback");
 7        return outTradeNo;
 8    }
 9
10    public int save(int userId, int productId) {
11        System.out.println("目标类 save 回调 方法 save");
12        return productId;
13    }
14}
15

JdkProxy 动态代理

 1package net.xdclass.sp.proxy;
 2
 3import java.lang.reflect.InvocationHandler;
 4import java.lang.reflect.Method;
 5import java.lang.reflect.Proxy;
 6
 7public class JdkProxy implements InvocationHandler {
 8
 9    private Object targetObject;
10
11    /**
12     * 代理类和目标类绑定关系
13     */
14
15    public Object newProxyInstance(Object targetObject){
16        this.targetObject = targetObject;
17	//生成代理对象
18        return Proxy.newProxyInstance(targetObject.getClass().getClassLoader(), targetObject.getClass().getInterfaces(), this);
19    }
20
21    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
22        Object result = null;
23        try{
24            System.out.println("通过JDK动态代理调用" + method.getName() + ",打印日志 begin");
25            result = method.invoke(targetObject, args);
26            System.out.println("通过JDK动态代理调用" + method.getName() + ",打印日志 end");
27        }catch (Exception e){
28            e.printStackTrace();
29        }
30        return result;
31    }
32}
33
34

ProxyTest

 1package net.xdclass.sp.proxy;
 2
 3public class ProxyTest {
 4    public static void main(String[] args) {
 5        //JDK动态代理
 6        JdkProxy jdkProxy = new JdkProxy();
 7        //获取代理类对象
 8        PayService payService = (PayService)jdkProxy.newProxyInstance(new PayServiceImpl());
 9        //调用真正的方法
10        payService.callback("asdads");
11	payService.save(123, 22321);
12    }
13}

控制台输出

1通过JDK动态代理调用callback,打印日志 begin
2目标类 PayServiceImpl 回调 方法 callback
3通过JDK动态代理调用callback,打印日志 end
4
5通过JDK动态代理调用save,打印日志 begin
6目标类 save 回调 方法 save
7通过JDK动态代理调用save,打印日志 end

AOP 的实现策略之 CGLib 动态代理

什么是动态代理

  • 在程序运行时,运用反射机制动态创建而成,无需手动编写代码
  • CgLib 动态代理的原理是对指定的业务类生成一个子类,并覆盖其中的业务方法来实现代理

CglibProxy

 1package net.xdclass.sp.proxy;
 2
 3import org.springframework.cglib.proxy.Enhancer;
 4import org.springframework.cglib.proxy.MethodInterceptor;
 5import org.springframework.cglib.proxy.MethodProxy;
 6
 7import java.lang.reflect.Method;
 8
 9public class CglibProxy implements MethodInterceptor {
10
11    //目标类
12    private Object targetObject;
13
14    //绑定关系
15    public Object newProxyInstance(Object targetObject){
16        this.targetObject = targetObject;
17        //用于绑定目标类和代理类的关系
18        Enhancer enhancer = new Enhancer();
19        //设置代理类的父类(目标类)
20        enhancer.setSuperclass(this.targetObject.getClass());
21        //设置回调函数
22        enhancer.setCallback(this);
23        //创建子类(代理对象)
24        return enhancer.create();
25    }
26
27    //类似invoke() 进行拦截和增强
28    public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
29        Object result = null;
30        try{
31            System.out.println("通过CGLIB动态代理调用 "+method.getName() +", 打印日志 begin");
32            result = methodProxy.invokeSuper(o,args);
33            System.out.println("通过CGLIB动态代理调用 "+method.getName() +", 打印日志 begin");
34
35        }catch (Exception e){
36            e.printStackTrace();
37        }
38        return result;
39    }
40}

ProxyTest 测试类

 1package net.xdclass.sp.proxy;
 2
 3public class ProxyTest {
 4    public static void main(String[] args) {
 5        //CGlib动态代理
 6        CglibProxy cglibProxy = new CglibProxy();
 7        PayService payService = (PayService)cglibProxy.newProxyInstance(new PayServiceImpl());
 8        payService.callback("123123");
 9        payService.save(123,1232313);
10    }
11}

控制台输出

1通过CGLIB动态代理调用 callback, 打印日志 begin
2目标类 PayServiceImpl 回调 方法 callback
3通过CGLIB动态代理调用 callback, 打印日志 begin
4
5通过CGLIB动态代理调用 save, 打印日志 begin
6目标类 save 回调 方法 save
7通过CGLIB动态代理调用 save, 打印日志 begin

CGLib 动态代理和 JDK 动态代理总结

  • 动态代理与静态代理相比较,最大的好处是接口中声明的所有方法都被转移到调用处理器一个集中的方法中处理,解耦和易维护

  • 两种动态代理的区别:

    • JDK 动态代理:要求目标对象实现一个接口,但是有时候目标对象只是一个单独的对象,并没有实现任何的接口,这个时候就可以用 CGLib 动态代理
    • CGLib 动态代理,它是在内存中构建一个子类对象从而实现对目标对象功能的扩展
    • JDK 动态代理是自带的,CGlib 需要引入第三方包
    • CGLib 动态代理基于继承来实现代理,所以无法对 final 类、private 方法和 static 方法实现代理
  • Spring AOP 中的代理使用的默认策略:

    • 如果目标对象实现了接口,则默认采用 JDK 动态代理
    • 如果目标对象没有实现接口,则采用 CgLib 进行动态代理
    • 如果目标对象实现了接扣,程序里面依旧可以指定使用 CGlib 动态代理

面向切面编程 Spring AOP 示例

需求分析:针对 VideoService 接口实现日志打印。

三个核心包:

  • spring-aop:AOP 核心功能,例如代理工厂
  • aspectjweaver:简单理解,支持切入点表达式
  • aspectjrt:简单理解,支持 aop 相关注解

引入相关包

 1        <!-- https://mvnrepository.com/artifact/org.springframework/spring-context -->
 2        <dependency>
 3            <groupId>org.springframework</groupId>
 4            <artifactId>spring-context</artifactId>
 5            <version>5.2.5.RELEASE</version>
 6        </dependency>
 7        <!-- https://mvnrepository.com/artifact/org.springframework/spring-core -->
 8        <dependency>
 9            <groupId>org.springframework</groupId>
10            <artifactId>spring-core</artifactId>
11            <version>5.2.5.RELEASE</version>
12        </dependency>
13
14        <!-- https://mvnrepository.com/artifact/org.springframework/spring-beans -->
15        <dependency>
16            <groupId>org.springframework</groupId>
17            <artifactId>spring-beans</artifactId>
18            <version>5.2.5.RELEASE</version>
19        </dependency>
20
21        <dependency>
22            <groupId>org.aspectj</groupId>
23            <artifactId>aspectjweaver</artifactId>
24            <version>1.6.11</version>
25        </dependency>

定义 service 接口和实现类

VideoService

1package net.xdclass.sp.service;
2
3import net.xdclass.sp.domain.Video;
4
5public interface VideoService {
6    int save(Video video);
7    Video findById(int id);
8}

VideoServiceImpl

 1package net.xdclass.sp.service;
 2
 3import net.xdclass.sp.domain.Video;
 4
 5public class VideoServiceImpl implements VideoService{
 6
 7    public int save(Video video) {
 8        System.out.println("保存video");
 9        return 1;
10    }
11
12    public Video findById(int id) {
13        System.out.println("根据id找视频");
14        return new Video();
15    }
16
17}
18

定义横切关注点 TimeHandler

 1package net.xdclass.sp.aop;
 2
 3import java.time.LocalDateTime;
 4
 5//横切关注点
 6public class TimeHandler {
 7    public void printBefore(){
 8        System.out.println("printBefore 日志 time = " + LocalDateTime.now().toString());
 9    }
10
11    public void printAfter(){
12        System.out.println("printAfter 日志 time = " + LocalDateTime.now().toString());
13    }
14}
15

引入 schema,配置 AOP

 1<?xml version="1.0" encoding="UTF-8"?>
 2<beans xmlns="http://www.springframework.org/schema/beans"
 3       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 4       xsi:schemaLocation="http://www.springframework.org/schema/beans
 5        https://www.springframework.org/schema/beans/spring-beans.xsd
 6        http://www.springframework.org/schema/aop
 7        http://www.springframework.org/schema/aop/spring-aop-4.2.xsd"
 8       xmlns:aop="http://www.springframework.org/schema/aop">
 9
10    <bean id="timeHandler" class="net.xdclass.sp.aop.TimeHandler"/>
11    <bean id="videoService" class="net.xdclass.sp.service.VideoServiceImpl"/>
12
13    <!--aop配置-->
14    <aop:config>
15
16        <!--横切关注点-->
17        <aop:aspect id="timeAspect" ref="timeHandler">
18            <!--定义切入点表达式-->
19            <!--<aop:pointcut id="allMethodLogPointCut" expression="execution(* net.xdclass.sp.service.VideoService.sav*(..))"/>-->
20            <aop:pointcut id="allMethodLogPointCut" expression="execution(* net.xdclass.sp.service.VideoService.*(..))"/>
21
22            <!--配置前置通知和后置通知-->
23            <aop:before method="printBefore" pointcut-ref="allMethodLogPointCut"/>
24            <aop:after method="printAfter" pointcut-ref="allMethodLogPointCut"/>
25
26        </aop:aspect>
27    </aop:config>
28</beans>

测试类

 1package net.xdclass.sp;
 2
 3import net.xdclass.sp.domain.Video;
 4import net.xdclass.sp.domain.VideoOrder;
 5import net.xdclass.sp.service.VideoService;
 6import org.springframework.context.ApplicationContext;
 7import org.springframework.context.support.ClassPathXmlApplicationContext;
 8
 9public class App {
10
11    public static void main(String [] args){
12
13        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
14        VideoService videoService = (VideoService)applicationContext.getBean("videoService");
15        videoService.save(new Video());
16	videoService.findById(22);
17    }
18}

控制台输出

1printBefore 日志 time = 2020-11-12T20:48:26.529
2保存video
3printAfter 日志 time = 2020-11-12T20:48:26.529
4
5printBefore 日志 time = 2020-11-12T20:48:26.530
6根据id找视频
7printAfter 日志 time = 2020-11-12T20:48:26.530

XML 配置转换到注解配置

  • Spring 的使用方式有两种 XML 配置和注解

    • 有些公司只用其中一种,也有公司 XML 配置与注解配置一起使用
  • 注解的优势:配置简单,维护方便

  • XML 的优势:单修改 XML 时不用改源码,不用重新编译和部署

  • 结论: 看团队开发规范进行选择,没有强调一定用哪个 更多的是 xml+ 注解配合使用,比如 Spring 整合 MyBatis

注解配置项目

  • 开启注解配置和包扫描
 1    public static void main(String [] args){
 2        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
 3
 4        //扫描指定的包,包括子包
 5        context.scan("net.xdclass");
 6
 7        //里面完成初始化操作,核心方法
 8        context.refresh();
 9
10        VideoService videoService = (VideoService) context.getBean("videoService");
11        videoService.findById(2);
12
13        Video video = (Video) context.getBean("video");
14    }

VideoServiceImpl

 1package net.xdclass.sp.service;
 2
 3import net.xdclass.sp.domain.Video;
 4import org.springframework.stereotype.Component;
 5
 6@Component("videoService")
 7public class VideoServiceImpl implements VideoService{
 8
 9    public int save(Video video) {
10        System.out.println("保存video");
11        return 1;
12    }
13
14    public Video findById(int id) {
15        System.out.println("根据id找视频");
16        return new Video();
17    }
18}

XML 和注解对比

常用注解

  • bean 定义

    • XML 方式:
    • 注解方式:@Component 通用组件 细分:
      • @Controller (用于 Web 层)
      • @Service (用于 service 层)
      • @Repository (用于 dao 仓库层)
  • bean 取名

    • XML 方式:通过 id 或者 name
    • 注解方式:@Component("XXXX")
  • bean 注入

    • XML 方式:通过 bean 标签下的 property
    • 注解方式:类型注入 @Autowired 名称注入 @Qualifier
  • bean 生命周期

    • XML 方式:init-method、destroy-method
    • 注解方式:@PostConstruct 初始化、@PreDestroy 销毁 :直接加在方法上即可
  • bean 作用范围

    • XML 方式:scope 属性
    • 注解方式:@scope("prototype")

Spring 的 @Configuration 和 @Bean 注解定义第三方 bean

  • @Configuration 标注在类上,相当于把该类作为 Spring 的 XML 配置文件中的 beans,作用为:配置 Spring 容器(应用上下文)
  • @bean 注解:用于告诉方法产生一个 Bean 对象,然后这个 Bean 对象交给 Spring 管理,Spring 将会将这个 Bean 对象放在自己的 IoC 容器中
  • 注意点:SpringIOC 容器管理一个或者多个 bean,这些 bean 都需要在 @Configuration 注解下进行创建
 1@Configuration
 2public class AppConfig {
 3
 4    //使用@bean注解,表明这个bean交个spring 进行管理
 5    // 如果没有指定名称,默认采用 方法名 + 第一个字母小写 作为bean的名称
 6    @Bean(name = "videoOrderName",initMethod = "init",destroyMethod = "destroy")
 7    @Scope
 8    public VideoOrder videoOrder(){
 9        return new VideoOrder();
10    }
11
12}

PropertySource 配置文件映射 Bean 属性

  • @PropertySource,指定加载配置文件
    • 配置文件映射到实体类
  • 使用 @Value 映射到具体的 Java 属性
 1@Configuration
 2@PropertySource(value = {"classpath:config.properties"})
 3public class CustomConfig {
 4
 5    @Value("${server.host}")
 6    private String host;
 7
 8    @Value("${server.port}")
 9    private int port;
10
11    public String getHost() {
12        return host;
13    }
14
15    public void setHost(String host) {
16        this.host = host;
17    }
18
19    public int getPort() {
20        return port;
21    }
22
23    public void setPort(int port) {
24        this.port = port;
25    }
26}
1@Autowired
2private CustomConfig customConfig;
3
4psvm(){
5	System.out.println(customConfig.getHost());
6}

基于注解的 AOP 示例

  • 声明切面类 @Aspect(切面): 通常是一个类,里面可以定义切入点和通知
  • 配置切入点和通知
 1package net.xdclass.sp.aop;
 2
 3import org.aspectj.lang.JoinPoint;
 4import org.aspectj.lang.annotation.After;
 5import org.aspectj.lang.annotation.Aspect;
 6import org.aspectj.lang.annotation.Before;
 7import org.aspectj.lang.annotation.Pointcut;
 8import org.springframework.stereotype.Component;
 9
10@Component
11//告诉spring,这个一个切面类,里面可以定义切入点和通知
12@Aspect
13public class LogAdvice {
14
15    //切入点表达式
16    @Pointcut("execution(* net.xdclass.sp.service.VideoServiceImpl.*(..))")
17    public void aspect(){
18    }
19
20    //前置通知
21    @Before("aspect()")
22    public void beforeLog(JoinPoint joinPoint){
23        System.out.println("LogAdvice  beforeLog");
24    }
25  
26    //后置通知
27    @After("aspect()")
28    public void afterLog(JoinPoint joinPoint){
29        System.out.println("LogAdvice  afterLog");
30    }
31
32}
  • 开启 SpringAOP 注解配置
 1package net.xdclass.sp.aop;
 2
 3import org.springframework.context.annotation.ComponentScan;
 4import org.springframework.context.annotation.Configuration;
 5import org.springframework.context.annotation.EnableAspectJAutoProxy;
 6
 7@Configuration
 8@ComponentScan("net.xdclass")
 9@EnableAspectJAutoProxy  //开启了spring对aspect的支持
10public class AnnotationAppConfig {
11}
12

启动类加载 AnnotationAppConfig 配置类

 1public class App {
 2
 3    public static void main(String [] args){
 4        //加载配置类AnnotationAppConfig
 5        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AnnotationAppConfig.class);
 6
 7        VideoService videoService = (VideoService) context.getBean("videoService");
 8        videoService.findById(2);
 9    }
10}
11

控制台输出

1LogAdvice  beforeLog
2根据id找视频
3LogAdvice  afterLog

环绕通知统计接口耗时

LogAdvice

 1package net.xdclass.sp.aop;
 2
 3import org.aspectj.lang.JoinPoint;
 4import org.aspectj.lang.ProceedingJoinPoint;
 5import org.aspectj.lang.annotation.*;
 6import org.springframework.stereotype.Component;
 7
 8@Component
 9//告诉spring,这个一个切面类,里面可以定义切入点和通知
10@Aspect
11public class LogAdvice {
12
13    //切入点表达式
14    @Pointcut("execution(* net.xdclass.sp.service.VideoServiceImpl.*(..))")
15    public void aspect(){
16    }
17
18    //前置通知
19    @Before("aspect()")
20    public void beforeLog(JoinPoint joinPoint){
21        System.out.println("LogAdvice  beforeLog");
22    }
23
24    //后置通知
25    @After("aspect()")
26    public void afterLog(JoinPoint joinPoint){
27        System.out.println("LogAdvice  afterLog");
28    }
29
30    /**
31     * 环绕通知
32     * @param joinPoint
33     */
34    @Around("aspect()")
35    public void around(JoinPoint joinPoint){
36
37        Object target = joinPoint.getTarget().getClass().getName();
38        System.out.println("调用者="+target);
39
40        //目标方法签名
41        System.out.println("调用方法="+joinPoint.getSignature());
42
43        //通过joinPoint获取参数
44        Object [] args = joinPoint.getArgs();
45        System.out.println("参数="+args[0]);
46
47
48        long start = System.currentTimeMillis();
49        System.out.println("环绕通知 环绕前=========");
50
51        //执行连接点的方法
52        try {
53            ((ProceedingJoinPoint)joinPoint).proceed();
54        } catch (Throwable throwable) {
55            throwable.printStackTrace();
56        }
57
58        long end = System.currentTimeMillis();
59        System.out.println("环绕通知 环绕后=========");
60
61        System.out.println("调用方法总耗时 time = " + (end - start) +" ms");
62    }
63}
64

App

 1package net.xdclass.sp;
 2
 3import net.xdclass.sp.aop.AnnotationAppConfig;
 4import net.xdclass.sp.domain.Video;
 5import net.xdclass.sp.domain.VideoOrder;
 6import net.xdclass.sp.service.VideoService;
 7import org.springframework.context.ApplicationContext;
 8import org.springframework.context.annotation.AnnotationConfigApplicationContext;
 9import org.springframework.context.support.ClassPathXmlApplicationContext;
10
11public class App {
12
13    public static void main(String [] args){
14        //加载配置类AnnotationAppConfig
15        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AnnotationAppConfig.class);
16
17        VideoService videoService = (VideoService) context.getBean("videoService");
18        videoService.findById(2);
19    }
20}
21package net.xdclass.sp;
22
23import net.xdclass.sp.aop.AnnotationAppConfig;
24import net.xdclass.sp.domain.Video;
25import net.xdclass.sp.domain.VideoOrder;
26import net.xdclass.sp.service.VideoService;
27import org.springframework.context.ApplicationContext;
28import org.springframework.context.annotation.AnnotationConfigApplicationContext;
29import org.springframework.context.support.ClassPathXmlApplicationContext;
30
31public class App {
32
33    public static void main(String [] args){
34        //加载配置类AnnotationAppConfig
35        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AnnotationAppConfig.class);
36
37        VideoService videoService = (VideoService) context.getBean("videoService");
38        videoService.findById(2);
39    }
40}

控制台输出

1调用者=net.xdclass.sp.service.VideoServiceImpl
2调用方法=Video net.xdclass.sp.service.VideoService.findById(int)
3参数=2
4环绕通知 环绕前=========
5LogAdvice  beforeLog
6根据id找视频
7环绕通知 环绕后=========
8调用方法总耗时 time = 2000 ms
9LogAdvice  afterLog

Spring 常见事务管理

  • 事务:多个操作,要么同时成功,要么失败后一起回滚

    • 具备 ACID 四种特性
      • Atomic(原子性)
      • Consistency(一致性)
      • Isolation(隔离性)
      • Durability(持久性)
  • 你知道常见的 Spring 事务管理方式吗

  • 编程式事务管理:

1* 代码中调用beginTransaction()、commit()、rollback()等事务管理相关的方法,通过TransactionTempalte手动管理事务(用的少)
  • 声明式事务管理:
1* 通过AOP实现,可配置文件方式或者注解方式实现事务的管理控制(用的多)
2
3声明式事务管理本质:本质是对方法前后进行拦截,底层是建立在 AOP 的基础之上在目标方法开始之前创建或者加入一个事务,在执行完目标方法之后根据执行情况提交或者回滚事务

Spring 事务的传播属性和隔离级别

  • 事物传播行为介绍:

    • 如果在开始当前事务之前,一个事务上下文已经存在,此时有若干选项可以指定一个事务性方法的执行行为
      • @Transactional(propagation=Propagation.REQUIRED) 如果有事务, 那么加入事务, 没有的话新建一个(默认情况下)
      • @Transactional(propagation=Propagation.NOT_SUPPORTED) 不为这个方法开启事务
      • @Transactional(propagation=Propagation.REQUIRES_NEW) 不管是否存在事务,都创建一个新的事务,原来的挂起,新的执行完毕,继续执行老的事务
      • @Transactional(propagation=Propagation.MANDATORY) 必须在一个已有的事务中执行,否则抛出异常
      • @Transactional(propagation=Propagation.NEVER) 必须在一个没有的事务中执行,否则抛出异常(与 Propagation.MANDATORY 相反)
      • @Transactional(propagation=Propagation.SUPPORTS) 如果其他 bean 调用这个方法,在其他 bean 中声明事务,那就用事务.如果其他 bean 没有声明事务,那就不用事务.
      • @Transactional(propagation=Propagation.NESTED) 如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行; 如果当前没有事务,则该取值等价于 Propagation.REQUIRED。
  • 事务隔离级别: 是指若干个并发的事务之间的隔离程度

    • @Transactional(isolation = Isolation.READ_UNCOMMITTED) 读取未提交数据(会出现脏读, 不可重复读) 基本不使用
    • @Transactional(isolation = Isolation.READ_COMMITTED) 读取已提交数据(会出现不可重复读和幻读)
    • @Transactional(isolation = Isolation.REPEATABLE_READ) 可重复读(会出现幻读)
    • @Transactional(isolation = Isolation.SERIALIZABLE) 串行化
  • MYSQL: 默认为 REPEATABLE_READ 级别

NEW SSM @Transactional 事务控制

示例代码

多表操作,通过 @Transactional 控制事务

  • 启动类加注解 @EnableTransactionManagement
  • 业务类 加 @Transactional

作者:Soulboy