• Spring(三)




    Spring


    一、AOP简介


    什么是AOP


    AOP:Aspect Oriented Programming,面向切面编程。是通过预编译方式(aspectj)或者运行期动态代理(Spring)实现程序功能的统一维护的技术。

    AOP是OOP(面向对象编程)的技术延续,是软件开发中的一个热点,也是Spring中的一个重要内容。利用AOP可以实现对业务逻辑各个部分之间的隔离,从而使得业务逻辑各部分之间的耦合性降低,提高程序的可重用性,同时提高了开发效率。


    AOP的作用

    作用:不修改源码的情况下,进行功能增强,通过动态代理实现的

    优势:减少重复代码,提高开发效率,方便维护

    比如:给功能增加日志输出, 事务管理的功能

    10个方法: 想给10个方法都增加一种打印日志的功能,但是又不想(不能)改源码,此时可以给它使用AOP增强。


    AOP的底层实现

    实际上,Spring的AOP,底层是通过动态代理实现的。在运行期间,通过代理技术动态生成代理对象,代理对象方法执行时进行功能的增强介入,再去调用目标方法,从而完成功能增强。

    常用的动态代理技术有:

    • JDK的动态代理:基于接口实现的

    • cglib的动态代理:基于子类实现的

    Spring的AOP采用了哪种代理方式?

    • 如果目标对象有接口,就采用JDK的动态代理技术

    • 如果目标对象没有接口,就采用cglib技术


    小结

    • AOP是:在不修改源码的情况下,进行功能增强

    • AOP的本质是:动态代理


    二、Spring的AOP


    AOP相关的概念

    • 目标对象(Target):要代理的/要增强的目标对象。

    • 代理对象(Proxy):目标对象被AOP织入增强后,就得到一个代理对象

    • 连接点(JoinPoint):能够被拦截到的点,在Spring里指的是方法

      目标类里,所有能够进行增强的方法,都是连接点

    • 切入点(PointCut):要对哪些连接点进行拦截的定义

      已经增强的连接点,叫切入点

    • 通知/增强(Advice):拦截到连接点之后要做的事情

      对目标对象的方法,进行功能增强的代码

    • 切面(Aspect):是切入点和通知的结合

    • 织入(Weaving):把增强/通知 应用到 目标对象来创建代理对象的过程。Spring采用动态代理技术织入,而AspectJ采用编译期织入和装载期织入


    在这里插入图片描述


    AOP开发前要明确的事项

    我们要做的事情:

    • 编写核心业务代码(Target目标类的目标方法)

    • 编写通知类,通知类中有通知方法(Advice增强功能方法)

    • 在配置文件中,配置织入关系,即将哪些通知与哪些切入点 结合,形成切面


    Spring的AOP做的事情:

    • 生成动态代理的过程(把通知织入到切入点的过程),是由Spring来实现的

    • Spring会监控切入点方法的执行,一旦发现切入点方法执行,使用代理机制动态创建目标对象的代理对象,根据通知类别,在代理对象的对应位置,将通知对应的功能织入,完成完整的代码逻辑运行。


    小结

    AOP相关的概念/术语

    • 目标类Target:要对哪个类进行增强

    • 代理对象Proxy:对目标类增强后的那个代理对象

    • 连接点JoinPoint:目标类里可增强的方法

    • 切入点PointCut:要增强的方法

    • 通知Advice:要增强的功能方法

    • 切面Aspect:切入点 + 通知

    • 织入Weaving:把切入点 和 通知 进行结合,生成代理对象的过程

    • 使用AOP,我们要做的事情:

      • 编写目标类,自己的业务代码

      • 编写通知类

      • 配置切面

    • 使用AOP,Spring做的事情

      • 根据我们配置的切面,进行织入生成代理对象

    基于XML的AOP


    创建maven项目,导入坐标

    <dependencies>
            
            <dependency>
                <groupId>org.springframeworkgroupId>
                <artifactId>spring-contextartifactId>
                <version>5.1.2.RELEASEversion>
            dependency>
            
            <dependency>
                <groupId>org.aspectjgroupId>
                <artifactId>aspectjweaverartifactId>
                <version>1.9.4version>
            dependency>
            
            <dependency>
                <groupId>org.springframeworkgroupId>
                <artifactId>spring-testartifactId>
                <version>5.1.2.RELEASEversion>
            dependency>
            
            <dependency>
                <groupId>junitgroupId>
                <artifactId>junitartifactId>
                <version>4.12version>
            dependency>
        dependencies>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    创建目标类和通知类


    目标类:com.execise.aop.UserServiceImpl

    
    package com.execise.service;
    
    public interface UserService {
        void add();
    
        void update();
    }
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    package com.execise.service.impl;
    
    import com.execise.service.UserService;
    
    public class UserServiceImpl implements UserService {
        public void add() {
            System.out.println("调用了UserServiceImpl的add方法~!");
            //int a = 1 / 0 ;
        }
    
        public void update() {
            System.out.println("调用了UserServiceImpl的update方法~!");
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    通知类|增强:com.execise.aop.MyAdvice

    package com.execise.advice;
    
    import org.aspectj.lang.ProceedingJoinPoint;
    
    public class MyAdvice {
    
        public void print(){
            System.out.println("打印日志~!");
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    修改配置文件

    1. 把目标类和通知类都配置到Spring配置文件中

    2. 配置切入和通知方法(增强方法)的织入关系

    
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop"
           xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
    
    
        
        <bean id="us" class="com.execise.service.impl.UserServiceImpl"/>
        <bean id="myAdvice" class="com.execise.advice.MyAdvice"/>
    
        
        <aop:config>
            <aop:aspect ref="myAdvice">
                <aop:before method="print" pointcut="execution(* com.execise.service.impl.UserServiceImpl.add())"/>
            aop:aspect>
        aop:config>
    
    
    beans>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    注意:在xml中增加了aop的名称空间如下:


    在这里插入图片描述


    测试代码

    package com.execise.test;
    
    import com.execise.service.UserService;
    import org.junit.Test;
    import org.junit.runner.RunWith;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.test.context.ContextConfiguration;
    import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
    
    @RunWith(SpringJUnit4ClassRunner.class)
    @ContextConfiguration("classpath:applicationContext.xml")
    public class TestUserServiceImpl {
    
        @Autowired
        private UserService us;
    
        @Test
        public void testAdd(){
            us.add();
        }
    
        @Test
        public void testUpdate(){
            us.update();
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    步骤小结

    1. 导入jar包:spring-context, aspectjweaver

    2. 编写目标类、编写通知类

    3. 配置切面

    <aop:config>
    	<aop:aspect ref="通知对象">
            <aop:before method="通知对象里的通知方法" pointcut="切入点表达式"/>
        aop:aspect>
    aop:config>
    
    • 1
    • 2
    • 3
    • 4
    • 5

    AOP详解


    1) 切点表达式的写法

    
    execution([权限修饰符] 返回值类型 包名.类名.方法名(参数列表))
    
    • 1
    • 2

    修饰符:可以省略

    • 返回值类型:

      • 可以指定类型。比如String (如果类型有歧义,就写全限定类名,比如:java.util.Date

      • *,表示任意字符。比如Str*,或者*

    包名:

    • 可以写.:表示当前包下的类或者子包。比如com.execise.service

    • 可以写..:表示当前包里所有后代类、后代包。比如com..service

    • *:表示任意字符。比如:com.it*, com.*

    类名:

    • 可以指定类名。比如:UserServiceImpl

    • * 表示任意字符。比如:*ServiceImpl*

    方法名:

    • 可以指定方法名

    • * 表示任意字符。比如:save**

    参数列表:

    • 可以指定类型。比如:String,Integer表示第一个参数是String,第二个参数是Integer类型

    • *表示任意字符。比如:

      • String, * 表示第一个参数是String,第二个参数是任意类型

      • Str*, Integer表示第一个参数类型Str开头,第二个参数是Integer类型

    • 可以使用..表示任意个数、任意类型的参数


    示例

    execution(public void com.execise.dao.impl.UserDao.save())
    execution(void com.execise.dao.impl.UserDao.*(..))
    execution(* com.execise.dao.impl.*.*(..))
    execution(* com.execise.dao..*.*(..))
    execution(* *..*.*(..)) --不建议使用
    
    • 1
    • 2
    • 3
    • 4
    • 5
           
        <aop:config>
            <aop:aspect ref="myAdvice">
                
    
                
                
                
                
                
                
                
                
                
    
                
                
    
                
                <aop:before method="print" pointcut="execution(* com.execise..*.*(..))"/>
            aop:aspect>
        aop:config>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30

    2) 通知的种类


    通知的语法

    <aop:通知类型 method="通知中的方法" pointcut="切点表达式">aop:通知类型>
    
    • 1

    通知的类型


    名称标签说明
    前置通知通知方法在切入点方法之前执行
    后置通知在切入点方法正常执行之后,执行通知方法
    异常通知在切入点方法抛出异常时,执行通知方法
    最终通知无论切入点方法是否有异常,最终都执行通知方法
    环绕通知通知方法在切入点方法之前、之后都执行

    通知示例

    注意:通知方法的名称随意,我们这里是为了方便理解,才起名称为:before, after等等


    前置通知


    通知方法定义MyAdvicebefore方法:

    public void before(){
        System.out.println("前置通知");
    }
    
    • 1
    • 2
    • 3

    xml配置

    <aop:before method="before" 
                pointcut="execution(* com.execise.service..*.*())"/>
    
    • 1
    • 2

    后置通知


    通知方法定义

    public void afterReturning(){
        System.out.println("后置通知");
    }
    
    • 1
    • 2
    • 3

    xml配置

    <aop:after-returning method="afterReturning" 
                         pointcut="execution(* com.execise.service..*.*())"/>
    
    • 1
    • 2

    环绕通知

    通知方法定义

    /*
      环绕增强比较特殊一些,它需要我们在增强的方法里面手动调用目标方法。
    */
    public void around(ProceedingJoinPoint joinPoint) throws Throwable {
      //System.out.println("环绕:打印日志~!");
    
      before();
    
      //调用目标方法
      //joinPoint.proceed(); //目标方法没有参数的调用
      joinPoint.proceed(joinPoint.getArgs()); //目标方法有参的方式调用
    
      afterReturning();
    
    
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    xml配置

    
    <aop:around method="around" 
                pointcut="execution(* com.execise.service..*.*())"/>
    
    • 1
    • 2
    • 3

    异常抛出通知

    通知方法定义

    public void afterThrowing(){
        System.out.println("抛出异常通知");
    }
    
    • 1
    • 2
    • 3

    xml配置

    <aop:after-throwing method="afterThrowing" 
                        pointcut="execution(* com.execise.service..*.*())"/>
    
    • 1
    • 2

    最终通知

    通知方法定义

    public void after(){
        System.out.println("最终通知");
    }
    
    • 1
    • 2
    • 3

    xml配置

    • 1
    • 2

    3) 切点表达式的抽取


    当多个切面的切入点表达式相同时,可以将切入点表达式进行抽取;在增强中使用pointcut-ref代替pointcut,来引入切入点表达式。


    示例:

        
        <aop:config>
            <aop:aspect ref="myAdvice">
    
                
                <aop:pointcut id="pointCut01" expression="execution(* com.execise..*.*(..))"/>
    
    
    
    
                
                
                <aop:before method="before" pointcut-ref="pointCut01"/>
    
                
                 
                 <aop:after-returning method="afterReturning" pointcut-ref="pointCut01"/>
    
                
               
    
                
                
    
                
               
            aop:aspect>
        aop:config>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36

    1. 小结

    需要我们编写的内容:

    编写目标类,编写通知(增强)类

    配置切面

    <aop:config>
    	<aop:pointcut id="xxx" expression="切入点表达式"/>
        <aop:aspect ref="通知对象">
            <aop:before method="通知对象里的通知方法" pointcut-ref="xxx"/>
            <aop:after-returning method="通知对象里的通知方法" pointcut-ref="xxx"/>
            <aop:after-throwing method="通知对象里的通知方法" pointcut-ref="xxx"/>
            <aop:after method="通知对象里的通知方法" pointcut-ref="xxx"/>
            
            <aop:around method="通知对象里的通知方法" pointcut-ref="xxx"/>
        aop:aspect>
    aop:config>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    注意环绕通知的方法

    public Object aroundMethod(ProceedingJoinPoint pjp){
    	Object reuslt = null;
        
        try{
            //写前置通知代码
            
            //调用目标对象的方法
        	result = pjp.proceed(pjp.getArgs());
            
            //写后置通知代码
        }catch(Throwable t){
            //写异常通知代码
        }finally{
            //写最终通知代码
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    基于注解的AOP


    创建maven项目,导入坐标

    注意:需要增加AOP的实现包:aspectjweaver

    <dependencies>
            
            <dependency>
                <groupId>org.springframeworkgroupId>
                <artifactId>spring-contextartifactId>
                <version>5.1.2.RELEASEversion>
            dependency>
            
            <dependency>
                <groupId>org.aspectjgroupId>
                <artifactId>aspectjweaverartifactId>
                <version>1.9.4version>
            dependency>
            
            <dependency>
                <groupId>org.springframeworkgroupId>
                <artifactId>spring-testartifactId>
                <version>5.1.2.RELEASEversion>
            dependency>
            
            <dependency>
                <groupId>junitgroupId>
                <artifactId>junitartifactId>
                <version>4.12version>
            dependency>
        dependencies>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    创建目标类,创建通知类

    1. 使用注解标注两个类,配置成为bean对象

      • 实际开发中,使用@Repository, @Service, @Controller注解,按照分层进行配置
    2. 在通知类中,使用注解配置织入关系

      • 目标类com.execise.aop.Target

    package com.execise.service;
    
    public interface UserService {
    
        void add();
        void update();
    }
       
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    package com.execise.service.impl;
    
    import com.execise.service.UserService;
    import org.springframework.stereotype.Service;
    
    @Service
    public class UserServiceImpl implements UserService {
        public void add() {
            System.out.println("调用了UserServiceImpl的add方法~!~");
            //int a =  1 / 0 ;
        }
    
        public void update() {
            System.out.println("调用了UserServiceImpl的update方法~!~");
    
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    通知类com.execise.aop.MyAdvice

    package com.execise.advice;
    
    import org.aspectj.lang.ProceedingJoinPoint;
    import org.aspectj.lang.annotation.AfterReturning;
    import org.aspectj.lang.annotation.Aspect;
    import org.aspectj.lang.annotation.Before;
    import org.aspectj.lang.annotation.Pointcut;
    import org.springframework.stereotype.Component;
    /*
        MyAdvice是增强类,它需要做:
            1. 把自己交给spring管理 , 打上注解@Component
            2. 表示这个类是切面增强类,专门用来做增强的,打注解 @Aspect
            3. 把什么方法用来做什么增强(增强谁),就在这个方法上面打注解
                前置增强 === @Before
                后置增强 === @AfterReturning
     */
    
    @Component
    @Aspect
    public class MyAdvice {
    
        @Before("execution(* com.execise..*.*(..))")
        public void print(){
            System.out.println("打印日志~");
        }
    
    
        ...
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30

    开启组件扫描和AOP自动代理

    applicationContext.xml

    
    
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xmlns:context="http://www.springframework.org/schema/context"
           xmlns:aop="http://www.springframework.org/schema/aop"
           xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
    
        
        <context:component-scan base-package="com.execise"/>
    
        
        <aop:aspectj-autoproxy/>
    beans>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    测试

    package com.execise.test;
    
    import com.execise.service.UserService;
    import org.junit.Test;
    import org.junit.runner.RunWith;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.test.context.ContextConfiguration;
    import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
    
    @RunWith(SpringJUnit4ClassRunner.class)
    @ContextConfiguration("classpath:applicationContext.xml")
    public class TestUserServiceImpl {
    
        @Autowired
        private UserService us;
    
        @Test
        public void testAdd(){
            us.add();
    
        }
    }
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    4) 步骤小结

    1. 创建功能类UserServiceImpl

    2. 创建增强类MyAdvice

    3. 给他们都打上注解

      • UserServiceImpl : @Service

        • MyAdvice : @Component @Aspect

          • 方法上面打上前置或者后置的注解
    4. 在applicationContext.xml中打开开关

          
          <context:component-scan base-package="com.execise"/>
      
          
          <aop:aspectj-autoproxy/>
      
      • 1
      • 2
      • 3
      • 4
      • 5

    AOP详解


    1) 通知的种类

    通知的语法

    @通知注解("切入点表达式")
    
    • 1

    通知的类型


    名称注解说明
    前置通知@Before通知方法在切入点方法之前执行
    后置通知@AfterRuturning通知方法在切入点方法之后执行
    异常通知@AfterThrowing通知方法在抛出异常时执行
    最终通知@After通知方法无论是否有异常,最终都执行
    环绕通知@Around通知方法在切入点方法之前、之后都执行

    注意:

    • 注解方式配置的通知,执行顺序是:前置->最终->后置/异常

    • 如果想要指定执行的顺序,就使用环绕通知 , 因为环绕增强是由我们手动控制的。


    2) 切点表达式的抽取

    同xml的AOP一样,当多个切面的切入点表达式相同时,可以将切入点表达式进行抽取;

    抽取方法是:

    在增强类(切面类,即被`@Aspect`标的类)上增加一个额外的方法,在方法上使用`@Pointcut`注解定义切入点表达式,
    
    • 1

    在增强注解中引用切入点表达式所在的方法


    示例:

    package com.execise.advice;
    
    import org.aspectj.lang.ProceedingJoinPoint;
    import org.aspectj.lang.annotation.*;
    import org.springframework.stereotype.Component;
    
    /*
        MyAdvice是增强类,它需要做:
            1. 把自己交给spring管理 , 打上注解@Component
            2. 表示这个类是切面增强类,专门用来做增强的,打注解 @Aspect
            3. 把什么方法用来做什么增强(增强谁),就在这个方法上面打注解
                前置增强 === @Before
                后置增强 === @AfterReturning
     */
    
    @Aspect
    @Component
    public class MyAdvice {
    
        //@Before("execution(* com.execise..*.*(..))")
        public void print(){
            System.out.println("打印日志~!");
        }
    
        //===============================================================
    
        //这个abc方法的作用就是为了抽取切点表达式! ,并且这个abc方法不会被调用!
        @Pointcut("execution(* com.execise..*.*(..))")
        public void abc(){
            System.out.println("调用abc方法了~!");
        }
    
    
        //@Before("execution(* com.execise..*.*(..))")
        @Before("abc()")
        public void before(){
            System.out.println("前置:打印日志~!");
        }
    
        //@AfterReturning("execution(* com.execise..*.*(..))")
        @AfterReturning("abc()")
        public void afterReturning(){
            System.out.println("后置:打印日志~!");
        }
    
        //@AfterThrowing("execution(* com.execise..*.*(..))")
        public void afterThrowing(){
            System.out.println("异常:打印日志~!");
        }
    
        //@After("execution(* com.execise..*.*(..))")
        public void after(){
            System.out.println("最终:打印日志~!");
        }
    
        /*
            环绕增强比较特殊一些,它需要我们在增强的方法里面手动调用目标方法。
         */
        //@Around("execution(* com.execise..*.*(..))")
        public void around(ProceedingJoinPoint joinPoint) throws Throwable {
            //System.out.println("环绕:打印日志~!");
    
            before();
    
            //调用目标方法
            //joinPoint.proceed(); //目标方法没有参数的调用
            joinPoint.proceed(joinPoint.getArgs()); //目标方法有参的方式调用
    
            afterReturning();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71

    3) 小结

    1. 在通知类上加注解@Aspect,声明成一个切面

    2. 在通知类里方法上加注解@Before/@AfterReturning/@AfterThrowing/@After/@Around,配置切入点表达式

    3. 在xml里开启aop的自动代理:


    纯注解的AOP


    主要是把XML的配置,放到核心配置类上


    使用 @EnableAspectJAutoProxy 来允许AOP的自动配置


    核心配置类

    package com.execise.config;
    
    import org.springframework.context.annotation.ComponentScan;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.context.annotation.EnableAspectJAutoProxy;
    
    @Configuration
    @ComponentScan("com.execise")
    @EnableAspectJAutoProxy
    public class AppConfig {
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    增强类 : MyAdvice
    package com.execise.aop;
    
    import org.aspectj.lang.annotation.Aspect;
    import org.aspectj.lang.annotation.Before;
    import org.springframework.stereotype.Component;
    
    @Component
    @Aspect
    public class MyAdvice {
    
        @Before("execution(* com.execise..*.*(..))")
        public void print(){
            System.out.println("打印日志~");
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    UserService接口

    package com.execise.service;
    
    public interface UserService {
    
        void add();
    
        void update();
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    UserServiceImpl实现类

    package com.execise.service.impl;
    
    import com.execise.service.UserService;
    import org.springframework.stereotype.Service;
    
    @Service
    public class UserServiceImpl implements UserService {
        public void add() {
            System.out.println("调用了UserServiceImpl的add方法~!~");
        }
    
        public void update() {
            System.out.println("调用了UserServiceImpl的update方法~!~");
        }
    }
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    单元测试

    package com.execise.test;
    
    import com.execise.config.AppConfig;
    import com.execise.service.UserService;
    import org.junit.Test;
    import org.junit.runner.RunWith;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.test.context.ContextConfiguration;
    import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
    
    @RunWith(SpringJUnit4ClassRunner.class)
    @ContextConfiguration(classes = AppConfig.class)
    public class TestUserServiceImpl02 {
    
        @Autowired
        private UserService us;
    
        @Test
        public void testAdd(){
            us.add();
    
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    案例-测量业务层接口万次执行效率


    【第一步】编写通知类

    @Component
    @Aspect
    public class ProjectAdvice {
        //匹配业务层的所有方法
        @Pointcut("execution(* com.execise.service.*Service.*(..))")
        private void servicePt(){}
    
        //设置环绕通知,在原始操作的运行前后记录执行时间
        @Around("ProjectAdvice.servicePt()") //本类类名可以省略不写
        public void runSpeed(ProceedingJoinPoint pjp) throws Throwable {
            //获取执行的签名对象
            Signature signature = pjp.getSignature();
            //获取接口/类全限定名
            String className = signature.getDeclaringTypeName();
            //获取方法名
            String methodName = signature.getName();
            //记录开始时间
            long start = System.currentTimeMillis();
            //执行万次操作
            for (int i = 0; i < 10000; i++) {
               pjp.proceed();
            }
            //记录结束时间
            long end = System.currentTimeMillis();
            //打印执行结果
            System.out.println("万次执行:"+ className+"."+methodName+"---->" +(end-start) + "ms");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28

    【第二步】在SpringConfig配置类上开启AOP注解功能

    @Configuration
    @ComponentScan("com.execise")
    @PropertySource("classpath:jdbc.properties")
    @Import({JdbcConfig.class,MybatisConfig.class})
    @EnableAspectJAutoProxy //开启AOP注解功能
    public class SpringConfig {
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    【第三步】运行测试类,查看结果

    @RunWith(SpringJUnit4ClassRunner.class)
    @ContextConfiguration(classes = SpringConfig.class)
    public class AccountServiceTestCase {
        @Autowired
        private AccountService accountService;
        @Test
        public void testFindById(){
            Account account = accountService.findById(2);
        }
        @Test
        public void testFindAll(){
            List<Account> list = accountService.findAll();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    在这里插入图片描述


    三、Spring的事务管理


    1. 编程式事务管理

    所谓事务管理,即:按照给定的事务规则,来执行提交或回滚操作。其中:

    • “给定的事务规则”:用TransactionDefinition表示

    • “按照…来执行提交或回滚操作”:用PlatformTransactionManager来完成

    • TransactionStatus用于表示一个运行着的事务的状态


    关于编程式事务的说明

    编程式事务管理:通过编写代码的方式实现事务管理

    编程式事务管理,因事务管理与业务功能耦合性太强,不方便维护,目前已经基本不用


    spring 2.0 就已经提供了 xml配置的声明式事务管理的支持


    如果想要了解Spring的编程式事务,可参考《资料/spring02_transaction_program》

    以下API仅做介绍了解,用于了解Spring事务相关的API,并回顾事务相关的概念


    PlatformTransactionManager

    • 是Spring提供的事务管理器接口,它提供了我们常用的操作事务的方法:开启事务、提交事务等

    • 注意:PlatformTransactionManager是接口类型,不同的dao层技术有不同的实现,例如:

      • dao层是jdbcTemplate或Mybatis时,实现类是:DataSourceTransactionManager

      • dao层是Hibernate时,实现类是:HibernateTransactionManager


    方法返回值说明
    getTransaction(TransactionDefinition td)TransactionStatus开启事务,并得到事务状态
    commit(TransactionStatus status)提交事务
    rollback(TransactionStatus status)回滚事务

    TransactionDefinition

    事务的定义信息对象,提供了以下常用方法:


    方法参数返回值说明
    getIsolationLevel()int获取事务的隔离级别
    getPropogationBehavior()int获取事务的传播行为
    getTimeout()int获取超时时间
    isReadOnly()boolean是否只读的事务

    事务的隔离级别:

    • ISOLATION_DEFAULT:默认事务隔离级别

      • MySql默认隔离级别:repeatable read

      • Oracle默认隔离级别:read committed

    • ISOLATION_READ_UNCOMMITTED:读未提交–存在脏读、不可重复读、幻读

    • ISOLATION_READ_COMMITTED:读已提交–存在不可重复读、幻读

    • ISOLATION_REPEATABLE_READ:重复读–存在幻读

    • ISOLATION_SERIALIZABLE:串行化–没有并发问题


    事务的传播行为:

    用于解决业务方法调用业务方法时,事务的统一性问题的

    比如: A方法开启事务了之后,就调用了B方法,那么B方法是否也会被纳入事务管理的范畴呢?


    以下三个,是要当前事务的


    • PROPAGATION_REQUIRED需要有事务。默认

      • 如果有事务,就使用这个事务

      • 如果没有事务,就创建事务。

    • PROPAGATION_SUPPORTS:支持事务

      • 如果有事务,就使用当前事务,

      • 如果没有事务,就以非事务方式执行(没有事务)

    • PROPAGATION_MANDATORY:强制的

      • 如果有事务,就使用当前事务

      • 如果没有事务,就抛异常


    以下三个,是不要当前事务的


    • PROPAGATION_REQUIRES_NEW:新建的

      • 如果有事务,就把事务挂起,再新建事务

      • 如果没有事务,新建事务

    • PROPAGATION_NOT_SUPPORTED:不支持的

      • 如果有事务,就把事务挂起,以非事务方式执行

      • 如果没有事务,就以非事务方式执行

    • PROPAGATION_NEVER:非事务的

      • 如果有事务,就抛异常

      • 如果没有事务,就以非事务方式执行


    最后一个,是特殊的


    PROPAGATION_NESTED:嵌套的

    • 如果有事务,就在事务里再嵌套一个事务执行

    • 如果没有事务,就是类似REQUIRED的操作


    事务运行的超时时间:

    超时后事务自动回滚

    • 默认值-1,表示没有超时限制

    • 如果有,可以以秒为单位进行设置


    是否只读:

    • 如果设置为只读,那么方法只能查询,不能增删改

    • 通常是查询方法设置为只读


    TransactionStatus

    • 提供了查询事务具体运行状态的方法,常用方法如下:

    方法返回值说明
    hasSavePoint()boolean事务是否有回滚点
    isCompleted()boolean事务是否已经完成
    isNewTransaction()boolean是否是新事务
    isRollbackOnly()boolean事务是否是 要回滚的状态

    小结

    • PlatformTransactionManager接口:

      • 如果dao层用的是Mybatis、JdbcTemplate:用DataSourceTransactionManager

      • 如果dao层用的是Hibernate:用HibernateTransactionManager

    • 事务定义信息:

      • 事务的隔离级别:通常使用默认ISOLATION_DEFAULT

      • 事务的传播行为:通常使用默认PROPAGATION_REQUIRED

      • 事务的超时时间:如果事务执行超时,会回滚。单位是秒。值为-1表示永不超时

      • 事务是否是只读:如果只读,事务里只能执行查询操作,不能增删改


    2. 声明式事务管理


    转账功能的环境准备

    zs给ls转账,不带事务的功能实现,为后边的事务控制做准备


    1) 创建Maven项目,导入依赖坐标


    dao层技术要使用MyBatis

    
    <dependencies>
        
        <dependency>
            <groupId>junitgroupId>
            <artifactId>junitartifactId>
            <version>4.12version>
            <scope>testscope>
        dependency>
        
        <dependency>
            <groupId>org.springframeworkgroupId>
            <artifactId>spring-contextartifactId>
            <version>5.1.2.RELEASEversion>
        dependency>
    
        
        
        <dependency>
            <groupId>mysqlgroupId>
            <artifactId>mysql-connector-javaartifactId>
            <version>5.1.47version>
        dependency>
        
        <dependency>
            <groupId>com.alibabagroupId>
            <artifactId>druidartifactId>
            <version>1.1.23version>
        dependency>
        
        <dependency>
            <groupId>org.mybatisgroupId>
            <artifactId>mybatisartifactId>
            <version>3.5.6version>
        dependency>
        
        
        <dependency>
            <groupId>org.mybatisgroupId>
            <artifactId>mybatis-springartifactId>
            <version>2.0.6version>
        dependency>
        
        <dependency>
            <groupId>org.springframeworkgroupId>
            <artifactId>spring-jdbcartifactId>
            <version>5.1.2.RELEASEversion>
        dependency>
    
        
        
        <dependency>
            <groupId>org.slf4jgroupId>
            <artifactId>slf4j-apiartifactId>
            <version>1.7.20version>
        dependency>
        
        <dependency>
            <groupId>ch.qos.logbackgroupId>
            <artifactId>logback-classicartifactId>
            <version>1.2.3version>
        dependency>
        
        <dependency>
            <groupId>ch.qos.logbackgroupId>
            <artifactId>logback-coreartifactId>
            <version>1.2.3version>
        dependency>
    
        <dependency>
            <groupId>org.springframeworkgroupId>
            <artifactId>spring-testartifactId>
            <version>5.1.2.RELEASEversion>
        dependency>
        
        
        <dependency>
            <groupId>org.aspectjgroupId>
            <artifactId>aspectjweaverartifactId>
            <version>1.9.4version>
        dependency>
    dependencies>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82

    2) 创建dao,Service:


    AccountDao接口:

    
    package com.execise.dao;
    
    import org.apache.ibatis.annotations.Param;
    
    public interface AccountDao {
    
        void kouqian(@Param("from") String from ,@Param("money") int money);
    
        void jiaqian(@Param("to") String to ,@Param("money") int money);
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    AccountDao.xml映射文件:

    
    DOCTYPE mapper
            PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
            "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
    <mapper namespace="com.execise.dao.AccountDao">
    
        <update id="kouqian">
            update account set money = money - #{money} where name = #{from}
        update>
    
        <update id="jiaqian">
            update account set money = money + #{money} where name = #{to}
        update>
    
    mapper>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    AccountServiceAccountServiceImpl

    package com.execise.service;
    
    public interface AccountService {
        void transfer(String from ,String to , int money);
    }
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    package com.execise.service.impl;
    
    import com.execise.dao.AccountDao;
    import com.execise.service.AccountService;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Service;
    
    /*
        1. 把这个类交给spring管理
        2. 注入进来dao的对象!
     */
    
    @Service
    public class AccountServiceImpl implements AccountService {
    
        @Autowired
        private AccountDao dao;
    
        /**
         * 转账
         * @param from
         * @param to
         * @param money
         */
        public void transfer(String from, String to, int money) {
    
            //扣钱
            dao.kouqian(from ,money);
    
            //加钱
            dao.jiaqian(to , money);
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34

    3) 配置bean和依赖注入


    applicationContext.xml

    
    
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xmlns:context="http://www.springframework.org/schema/context"
           xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
    
        
        <context:component-scan base-package="com.execise"/>
    
        
        <context:property-placeholder location="classpath:db.properties"/>
    
        
        <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
            <property name="driverClassName" value="${db.driverClass}"/>
            <property name="url" value="${db.url}"/>
            <property name="username" value="${db.username}"/>
            <property name="password" value="${db.password}"/>
        bean>
    
        
        <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
            
            <property name="dataSource" ref="dataSource"/>
            
            <property name="typeAliasesPackage" value="com.execise.bean"/>
        bean>
    
        
        <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
            <property name="basePackage" value="com.execise.dao"/>
        bean>
    beans>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34

    4) 功能测试

    package com.execise.test;
    
    import com.execise.service.AccountService;
    import org.junit.Test;
    import org.junit.runner.RunWith;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.test.context.ContextConfiguration;
    import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
    
    @RunWith(SpringJUnit4ClassRunner.class)
    @ContextConfiguration("classpath:applicationContext.xml")
    public class TestAccountServiceImpl {
    
        @Autowired
        private AccountService as;
    
        @Test
        public void testTransfer(){
            as.transfer("zs", "ls" , 100);
        }
    }
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    什么是声明式事务控制


    介绍:

    • 声明式事务控制,是采用声明的方式进行事务管理。所谓的声明,指的就是在配置文件中进行配置。

    • 通过声明式(配置)的方式来处理事务,代替编码式事务控制

    • 作用:

      • 事务管理不入侵开发的组件,松耦合

        • 业务逻辑代码中,没有事务的代码,甚至不会意识到正在事务当中。

        • 事实上也应该如此,业务逻辑代码只处理业务功能,事务控制是属于系统层面的服务;如果想要更改事务,只需要在配置文件中重新配置即可

      • 能以模板的方式使用

        • Spring的声明式事务以AOP为基础,但是几乎是固定的配置模板,即使不懂AOP,也可以配置实现事务管理
      • 易维护。

        • 在不需要事务管理的时候,只需要在配置文件中进行修改,即可把事务管理移除掉,而不需要修改源码,方便维护

    注意:Spring的声明式事务,底层就是AOP


    基于XML的声明式事务控制


    1) 需要明确的事项

    • 谁是目标类?(哪个类想用事务) AccountserviceImpl

    • 谁是切入点?(哪个方法想用事务 ) transfer

    • 谁是通知(增强)?(给上面的方法增强什么功能) 事务管理

    • dao层技术是JdbcTemplate,事务的管理员使用DataSourceTransactionManager


    2) 快速入门

    通过Spring的xml配置,对银行转账功能,进行事务控制


    实现步骤

    只需要修改applicationContext.xml即可:

    1. 在配置文件中增加aop和tx的名称空间

    2. 配置事务的通知(增强)

    3. 配置切面,把事务通知织入到转账方法中


    功能实现

    
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xmlns:context="http://www.springframework.org/schema/context"
           xmlns:tx="http://www.springframework.org/schema/tx"
           xmlns:aop="http://www.springframework.org/schema/aop"
           xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
           http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
           http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd
           http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
    
        
        <context:component-scan base-package="com.execise"/>
    
        
        <context:property-placeholder location="classpath:db.properties"/>
    
        
        <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
            <property name="driverClassName" value="${db.driverClass}"/>
            <property name="url" value="${db.url}"/>
            <property name="username" value="${db.username}"/>
            <property name="password" value="${db.password}"/>
        bean>
    
        
        <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
            
            <property name="dataSource" ref="dataSource"/>
            
            <property name="typeAliasesPackage" value="com.execise.bean"/>
            
            
            
            
        bean>
    
        
        <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
            <property name="basePackage" value="com.execise.dao"/>
        bean>
    
        
        
    
        
        <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
            <property name="dataSource" ref="dataSource"/>
        bean>
    
        
        <tx:advice id="txAdvice" transaction-manager="transactionManager">
            <tx:attributes>
                
                
                <tx:method name="*" isolation="DEFAULT" propagation="REQUIRED" read-only="false" timeout="-1"/>
    
    
                
                <tx:method name="save*" isolation="DEFAULT" propagation="REQUIRED" read-only="false"/>
    
                
                <tx:method name="edit*" isolation="DEFAULT" propagation="REQUIRED" read-only="false"/>
    
                
                <tx:method name="delete*" isolation="DEFAULT" propagation="REQUIRED" read-only="false"/>
    
                
                <tx:method name="query*" isolation="DEFAULT" propagation="REQUIRED" read-only="true"/>
    
                
                <tx:method name="find*" isolation="DEFAULT" propagation="REQUIRED" read-only="true"/>
            tx:attributes>
        tx:advice>
    
        
        <aop:config>
            <aop:advisor advice-ref="txAdvice" pointcut="execution(* com.execise..*.*(..))"/>
        aop:config>
    beans>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100

    3) 配置详解


    aop:config:切面配置


    这个标签的配置,就是为了找到方法,然后给这些方法应用上事务。

    
    <aop:config>
        <aop:advisor advice-ref="txAdvice"  
                     pointcut="execution(* com.execise.service.impl..*.*(..))"/>
    aop:config>
    
    • 1
    • 2
    • 3
    • 4
    • 5

    aop:config:aop提供的用于配置切面的标签

    aop:advisor:Spring提供的专门用于配置事务的,作用类似于aop:aspect

    • advice-ref:要引入的通知配置,必须要引用所配置的事务通知

    • pointcut:切入点表达式


    tx:advice:事务通知配置


    id属性:唯一标识

    transaction-manager属性:配置一个事务管理器,即PlatformTransactionManager的实现类对象
    类似于我们的自己编写的事务管理器,里边提供了事务管理的方法,例如:提交、回滚事务的方法等等

    tx:attributes:在标签内部设置事务的属性信息(事务定义信息,TransactionDefinition)

    tx:method:要进行事务控制的方法配置,表示 要对哪些方法,进行什么样的事务控制

    name属性:要进行事务控制方法名称,可以使用通配符*

    isolation属性:事务的隔离级别设置

    propagation属性:事务传播特性

    read-only属性:是否只读

    timeout属性:超时时间。默认-1表示不限制,如果设置的话,单位是秒


    <tx:advice id="txAdvice" transaction-manager="txManager">
        <tx:attributes>
            
            <tx:method name="transfer" isolation="REPEATABLE_READ" propagation="REQUIRED" read-only="false"/>    
            
            
            <tx:method name="save*" isolation="DEFAULT" propagation="REQUIRED" read-only="false"/>
            
            
            <tx:method name="edit*" isolation="DEFAULT" propagation="REQUIRED" read-only="false"/>
            
            
            <tx:method name="delete*" isolation="DEFAULT" propagation="REQUIRED" read-only="false"/>
            
            
            <tx:method name="query*" isolation="DEFAULT" propagation="REQUIRED" read-only="true"/>
            
            
            <tx:method name="find*" isolation="DEFAULT" propagation="REQUIRED" read-only="true"/>
        tx:attributes>
    tx:advice>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    4) 小结


    service里的方法,不需要有任何事务管理相关的代码

    只需要在xml里配置即可


    
    <bean id="txManager" class="DataSourceTransactionManager全限定类名">
    	<property name="dataSource" ref="连接池"/>
    bean>
    
    
    <tx:advice id="txAdvice" transaction-manager="txManager">
    	<tx:attributes>
        	<tx:method name="*"/>
        tx:attributes>
    tx:advice>
    
    
    <aop:config>
    	<aop:advisor advice-ref="txAdvice" pointcut="切入点表达式"/>
    aop:config>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    基于注解的声明式事务控制


    1) 快速入门


    通过Spring的注解配置,对银行转账功能,进行事务控制


    实现步骤


    在需要事务控制的方法/类上增加注解@Transactional

    
    @Transactional //类里面的所有方法都有事务
    @Service
    public class AccountServiceImpl implements AccountService {
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    在配置文件applicationContext.xml中修改配置

    • 配置事务管理器

    • 开启事务的注解驱动

     
        <tx:annotation-driven transaction-manager="tm"/>
    
    • 1
    • 2

    功能实现


    修改银行转账的Service接口:AccountService接口

    
    package com.execise.service;
    
    import com.execise.bean.Account;
    import org.springframework.transaction.annotation.Isolation;
    import org.springframework.transaction.annotation.Propagation;
    import org.springframework.transaction.annotation.Transactional;
    
    import java.util.List;
    
    /*
        注解事务的配置:
            0.在applicationContext.xml中配置事务管理员bean
            1. 在类上或者方法上打注解 @Transactional
                1.1 在类身上打,即表示该类中的所有方法都会应用上事务
                1.2 在方法身上打,即表示只有这个方法会应用上事务。
    
            2. 在xml里面打开注解的开关
                
            注意:事务注解可以打在业务层接口或实现类上,一般建议打在接口上,这样程序的耦合性更低一些
     */
    
    //1. 类上打注解
    //@Transactional
    public interface AccountService {
    
        //2.方法上打注解
        //@Transactional
        @Transactional(isolation = Isolation.DEFAULT , propagation = Propagation.REQUIRED , readOnly = false , timeout = -1)
        void transfer(String from ,String to , int money);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31

    修改配置文件applicationContext.xml

    
    
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xmlns:context="http://www.springframework.org/schema/context"
           xmlns:tx="http://www.springframework.org/schema/tx"
           xmlns:aop="http://www.springframework.org/schema/aop"
           xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
           http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
           http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd
           http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
    
        
        <context:component-scan base-package="com.execise"/>
    
        
        <context:property-placeholder location="classpath:db.properties"/>
    
        
        <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
            <property name="driverClassName" value="${db.driverClass}"/>
            <property name="url" value="${db.url}"/>
            <property name="username" value="${db.username}"/>
            <property name="password" value="${db.password}"/>
        bean>
    
        
        <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
            
            <property name="dataSource" ref="dataSource"/>
            
            <property name="typeAliasesPackage" value="com.execise.bean"/>
            
            
            
            
        bean>
    
        
        <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
            <property name="basePackage" value="com.execise.dao"/>
        bean>
    
        
    
    
        
        <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
            <property name="dataSource" ref="dataSource"/>
        bean>
    
        
        
        
        <tx:annotation-driven />
    beans>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63

    2) 配置详解


    注解@Transactional

    • 加在 需要进行事务控制的方法/类上,用于代替xml配置中的tx:advice和事务切面的aop:config

    • isolation属性:设置事务的隔离级别,从枚举Isolation中取值

    • propagation属性:设置事务的传播特性,从枚举Propagation中取值

    • readOnly属性:设置是否是只读的

    • timeout属性:设置超时时间,单位秒。-1表示不限制


    开启事务的注解驱动


    XML方式

    • 使用注解进行事务管理,必须要在applicationContext.xml中开启 事务的注解驱动,否则无效
    
    <tx:annotation-driven transaction-manager="txManager"/>
    
    • 1
    • 2
    
    <tx:annotation-driver/>
    
    • 1
    • 2

    纯注解方式

    • 如果是纯注解,开启事务的注解驱动,需要在核心配置类上增加注解:@EnableTransactionManagement

    配置示例

    package com.execise.config;
    
    import com.alibaba.druid.pool.DruidDataSource;
    import org.mybatis.spring.SqlSessionFactoryBean;
    import org.mybatis.spring.annotation.MapperScan;
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.ComponentScan;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.context.annotation.PropertySource;
    import org.springframework.jdbc.datasource.DataSourceTransactionManager;
    import org.springframework.transaction.PlatformTransactionManager;
    import org.springframework.transaction.annotation.EnableTransactionManagement;
    
    import javax.sql.DataSource;
    
    @Configuration
    @ComponentScan("com.execise")
    @PropertySource("classpath:db.properties")
    @MapperScan("com.execise.dao")
    //4.核心配置类上使用@EnableTransactionManagement注解开启事务管理
    @EnableTransactionManagement
    public class AppConfig {
    
        @Value("${db.driverClass}")
        private String driver;
        @Value("${db.url}")
        private String url;
        @Value("${db.username}")
        private String username;
        @Value("${db.password}")
        private String password;
    
    
        //1.创建数据源bean 交由Spring管理
        @Bean
        public DataSource dataSource(){
            DruidDataSource dataSource = new DruidDataSource();
            dataSource.setDriverClassName(driver);
            dataSource.setUrl(url);
            dataSource.setUsername(username);
            dataSource.setPassword(password);
            return  dataSource;
        }
    
        //2.创建SqlSessionFactory bean 交由Spring管理
        @Bean
        public SqlSessionFactoryBean sqlSessionFactory(DataSource dataSource){
            SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
            sqlSessionFactoryBean.setDataSource(dataSource);
            sqlSessionFactoryBean.setTypeAliasesPackage("com.execise.bean");
            return sqlSessionFactoryBean;
        }
    
        //3.声明事务管理员bean 交由Spring管理
        @Bean
        public PlatformTransactionManager transactionManager(DataSource dataSource){
            DataSourceTransactionManager tm = new DataSourceTransactionManager();
            tm.setDataSource(dataSource);
            return tm;
        }
        
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63

    单元测试

    package com.execise.test;
    
    import com.execise.config.AppConfig;
    import com.execise.service.AccountService;
    import org.junit.Test;
    import org.junit.runner.RunWith;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.test.context.ContextConfiguration;
    import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
    
    @RunWith(SpringJUnit4ClassRunner.class)
    @ContextConfiguration(classes = AppConfig.class)
    public class TestAccountServiceImpl02 {
    
        @Autowired
        private AccountService as;
    
        @Test
        public void testTransfer(){
            as.transfer("zs", "ls" , 100);
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    3) 小结


    在xml文件里

    
    <bean id="txManager" class="DataSourceTransactionManager全限定类名">
    	<property name="dataSource" ref="连接池"/>
    bean>
    
    
    <tx:annotation-driven transaction-manager="txManager"/>
    <context:component-scan base-package="com.execise"/>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    哪个方法需要事务管理,就在哪个方法上加注解:@Transactional


    3 Spring事务相关配置


    什么样的异常,Spring事务默认是不进行回滚的?


    事务配置


    在这里插入图片描述


    说明:对于RuntimeException类型异常或者Error错误,Spring事务能够进行回滚操作。但是对于编译器异常,Spring事务是不进行回滚的,所以需要使用rollbackFor来设置要回滚的异常。


    案例:转账业务追加日志

    需求和分析

    • 需求:实现任意两个账户间转账操作,并对每次转账操作在数据库进行留痕

    • 需求微缩:A账户减钱,B账户加钱,数据库记录日志

    • 分析:

      ①:基于转账操作案例添加日志模块,实现数据库中记录日志

      ②:业务层转账操作(transfer),调用减钱、加钱与记录日志功能

    • 实现效果预期:

      无论转账操作是否成功,均进行转账操作的日志留痕
      
      • 1
    • 存在的问题:

      日志的记录与转账操作隶属同一个事务,同成功同失败
      
      • 1
    • 实现效果预期改进:

      无论转账操作是否成功,日志必须保留
      
      • 1
    • 事务传播行为:事务协调员对事务管理员所携带事务的处理态度


    在这里插入图片描述


    【准备工作】环境整备

    USE day32;
    CREATE TABLE tbl_log(
    	id INT PRIMARY KEY AUTO_INCREMENT,
    	info VARCHAR(255),
    	createDate TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
    );
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    public interface LogService {
        //propagation设置事务属性:传播行为设置为当前操作需要新事务
        @Transactional
        void log(String from, String to, Integer money);
    }
    
    @Service
    public class LogServiceImpl implements LogService {
    
        @Autowired
        private LogDao logDao;
        
        @Override
        public void log(String from,String to,Integer money ) {
            logDao.log("转账操作由"+from+"到"+to+",金额:"+money);
        }
    }
    
    public interface LogDao {
        @Insert("insert into tbl_log (info) values(#{info})")
        void log(String info);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    【第一步】在AccountServiceImpl中调用logService中添加日志的方法

    package com.execise.service.impl;
    
    import com.execise.bean.Account;
    import com.execise.dao.AccountDao;
    import com.execise.service.AccountService;
    import com.execise.service.LogService;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Service;
    
    import java.util.List;
    
    @Service
    public class AccountServiceImpl implements AccountService {
    
        @Autowired
        private AccountDao accountDao;
    
        @Autowired
        private LogService logService;
    
        @Override
        public void transfer(String from, String to, int money) {
    
            try {
                //扣钱
                accountDao.kouqian(from ,money);
                //测试异常
                int i=1/0;
                //加钱
                accountDao.jiaqian(to , money);
            } finally {
                logService.log(from,to,money);
            }
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36

    【第二步】在LogService的log()方法上设置事务的传播行为

    public interface LogService {
        //propagation设置事务属性:传播行为设置为当前操作需要新事务
        @Transactional(propagation = Propagation.REQUIRES_NEW)
        void log(String from, String to, Integer money);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    【第三步】运行测试类,查看结果

    @RunWith(SpringJUnit4ClassRunner.class)
    @ContextConfiguration(classes = AppConfig.class)
    public class AccountServiceTest {
        @Autowired
        private AccountService as;
    
        @Test
        public void testTransfer() throws IOException {
            as.transfer("zs","ls",200);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    事务传播行为


    在这里插入图片描述

  • 相关阅读:
    Vue项目实战——【基于 Vue3.x + Vant UI】实现一个多功能记账本(项目演示、涉及知识点、源码分享)
    解决WPF+Avalonia在openKylin系统下默认字体问题
    电力系统IEEE14节点系统同步模型(Simulink)
    SpringBoot内置Tomcat报错RFC7230 and RFC3986终极方案(修改Http11Processor源码)
    分享Markdown编写文档的技巧
    Redis 主从复制,哨兵,集群——(3)集群篇
    【Jfrog Artifactory】配置邮件服务器
    摸鱼三天,我写了一个通用的组建树TreeUtil工具
    getline的使用详解
    ue4学习日记4(植被,光照,光束遮挡,天空球)
  • 原文地址:https://blog.csdn.net/m0_67559541/article/details/126671475