Spring学习笔记(四)—— Spring中的AOP

一、AOP概述

  AOP(Aspect Oriented Programming),即面向切面编程,可以说是OOP(Object Oriented Programming,面向对象编程)的补充和完善。OOP引入封装、继承、多态等概念来建立一种对象层次结构,用于模拟公共行为的一个集合。不过OOP允许开发者定义纵向的关系,但并不适合定义横向的关系,例如日志功能。日志代码往往横向地散布在所有对象层次中,而与它对应的对象的核心功能毫无关系对于其他类型的代码,如安全性、异常处理和透明的持续性也都是如此,这种散布在各处的无关的代码被称为横切(cross cutting),在OOP设计中,它导致了大量代码的重复,而不利于各个模块的重用。

  AOP技术恰恰相反,它利用一种称为"横切"的技术,剖解开封装的对象内部,并将那些影响了多个类的公共行为封装到一个可重用模块,并将其命名为"Aspect",即切面。所谓"切面",简单说就是那些与业务无关,却为业务模块所共同调用的逻辑或责任封装起来,便于减少系统的重复代码,降低模块之间的耦合度,并有利于未来的可操作性和可维护性。

  使用"横切"技术,AOP把软件系统分为两个部分:核心关注点横切关注点。业务处理的主要流程是核心关注点,与之关系不大的部分是横切关注点。横切关注点的一个特点是,他们经常发生在核心关注点的多处,而各处基本相似,比如权限认证、日志、事务。AOP的作用在于分离系统中的各种关注点,将核心关注点和横切关注点分离开来。

  下面举个生活上的例子来帮助理解AOP是什么样的思想,先看一下传统程序的流程,比如银行系统会有一个取款流程:

   

  我们可以把方框里的流程合为一个,另外系统还会有一个查询余额的流程,我们先把这两个流程放在一起:

  

  这有个问题就是,有多少接口,就要copy多少次代码,这显然不符合我们的要求。好,我们提取一个公共的方法,让每个接口都来调用这个方法,这里有点切面的味道了:

  

  同样有个问题,虽然不用每次都copy代码了,但是每个接口总要调用这个方法吧。有没有想过把这个验证用户的代码提取出去,不放到主流程里去呢?这就是AOP的作用了,有了AOP,你写代码时不要把这个验证用户步骤写进去,即完全不考虑验证用户,你写完之后,在另外一个地方,写好验证用户的代码,然后告诉Spring你要把这段代码加到哪几个地方,Spring就会帮你加过去,而不要你自己Copy过去,这里还是两个地方,如果你有多个控制流呢,这个写代码的方法可以大大减少你的时间,不过AOP的目的不是这样,这只是一个“副作用”,真正目的是,你写代码的时候,事先只需考虑主流程,而不用考虑那些不重要的流程。举一个通用的例子,经常在debug的时候要打log吧,你也可以写好主要代码之后,把打log的代码写到另一个单独的地方,然后命令AOP把你的代码加过去,注意AOP不会把代码加到源文件里,但是它会正确的影响最终的机器代码。

  现在大概明白了AOP了吗,我们来理一下头绪,上面那个方框像不像个平面,你可以把它当块板子,这块板子插入一些控制流程,这块板子就可以当成是AOP中的一个切面。所以AOP的本质是在一系列纵向的控制流程中,把那些相同的子流程提取成一个横向的面,这句话应该好理解吧,我们把纵向流程画成一条直线,然把相同的部分以绿色突出,如下图左,而AOP相当于把相同的地方连一条横线,如下图右,这个图没画好,大家明白意思就行。

      

二、AOP的核心概念

  使用AOP之前,我们需要理解几个概念。

  • 通知/增强(Advice):,AOP(切面编程)是用来给某一类特殊的连接点,添加一些特殊的功能,那么我们添加的功能也就是通知(增强)了,比如:添加日志、管理事务等。你给先定义好了,然后在想用的地方用一下。不过通知(增强)不仅仅包含需要增加的功能代码而已,它还包含了方位信息。通知(增强)分为前置、后置、异常、最终、环绕通知五类。为什么要方位信息呢?切点不是确定了需要增强的位置了吗?切点定位的是“在什么类的什么方法上”,也就是说,切点只是定位到了方法本身(也叫执行点,特殊的连接点),但是我们增强的内容是放在该方法的前面呢、后面呢?还是前后都要呢?这些切点却没有告诉我们,那么我们该如何确定具体位置呢?所以,我们才需要用到方位信息,进一步的定位到具体的增强代码放置的位置。即增强包含了【功能】又包含了【方位】。

          

  • 连接点(Join Point):所有可能的需要注入切面的地方。如方法前后、类初始化、属性初始化前后等等。简单来说,就是spring允许你使用通知的地方,那可真就多了,基本每个方法的前,后(两者都有也行),或抛出异常时都可以是连接点,spring只支持方法连接点,其他如aspectJ还可以让你在构造器或属性注入时都行,不过那不是咱关注的,只要记住,和方法有关的前前后后(抛出异常),都是连接点

  • 切点(Poincut):需要做某些处理(如打印日志、处理缓存等等)的连接点。如果把连接点当做数据库中的记录,那么切点就是查找该记录的查询条件。所以,一般我们要实现一个切点时,那么我们需要判断哪些连接点是符合我们的条件的,如:方法名是否匹配、类是否是某个类、以及子类等。

  • 切面(Aspect):通知+切点的集合,定义在什么地方什么时间做什么事情。现在发现了吧,没连接点什么事情,连接点就是为了让你好理解切点,搞出来的,明白这个概念就行了。通知说明了干什么和什么时候干(什么时候通过方法名中的before,after,around等就能知道),而切入点说明了在哪干(指定到底是哪个方法),这就是一个完整的切面定义。

  • 目标对象(Target):被通知的对象,也就是真正的业务逻辑,他可以在毫不知情的情况下,被咱们织入切面。而自己专注于业务本身的逻辑。

  • 织入(Weaving):把切面应用到目标对象来创建新的代理对象的过程。

三、Spring对AOP的支持

  

  Spring中AOP代理由Spring的IOC容器负责生成、管理,其依赖关系也由IOC容器负责管理。因此,AOP代理可以直接使用容器中的其它bean实例作为目标,这种关系可由IOC容器的依赖注入提供。Spring创建代理的规则为:

    1、默认使用Java动态代理来创建AOP代理,这样就可以为任何接口实例创建代理了

    2、当需要代理的类不是代理接口的时候,Spring会切换为使用CGLIB代理,也可强制使用CGLIB

  AOP编程其实是很简单的事情,纵观AOP编程,程序员只需要参与三个部分:

    1、定义普通业务组件

    2、定义切入点,一个切入点可能横切多个业务组件

    3、定义增强处理,增强处理就是在AOP框架为普通业务组件织入的处理动作

  所以进行AOP编程的关键就是定义切入点和定义增强处理,一旦定义了合适的切入点和增强处理,AOP框架将自动生成AOP代理,即:代理对象的方法=增强处理+被代理对象的方法。

四、Spring底层AOP的实现原理(了解)

4.1 JDK动态代理

public class MyJDKProxy implements InvocationHandler {

    private UserDao userDao;

    public MyJDKProxy(UserDao userDao) {
        this.userDao = userDao;
    }
    
    // 编写工具方法:生成代理
    public UserDao createProxy(){
        UserDao userDaoProxy = (UserDao) Proxy.newProxyInstance(userDao.getClass().getClassLoader(), userDao.getClass().getInterfaces(), this);
        return userDaoProxy;
    }
    
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        if("save".equals(method.getName())){
            System.out.println("权限校验================");
        }
        return method.invoke(userDao, args);
    }

}

  注意:JDK 给我们提供的动态代理只能代理接口,而不能代理没有接口的类

4.2 CGLib 动态代理

public class MyCglibProxy implements MethodInterceptor {

    private CustomerDao customerDao;
    
    public MyCglibProxy(CustomerDao customerDao) {
        this.customerDao = customerDao;
    }
    
    // 生成代理的方法
    public CustomerDao createProxy(){
        // 创建Cglib的核心类
        Enhancer enhancer = new Enhancer();
        // 设置父类
        enhancer.setSuperclass(CustomerDao.class);
        // 设置回调
        enhancer.setCallback(this);
        // 生成代理
        CustomerDao customerDaoProxy = (CustomerDao) enhancer.create();
        return customerDaoProxy;
    }
    
    @Override
    public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
        if("delete".equals(method.getName())){
            Object obj = methodProxy.invokeSuper(proxy, args);
            System.out.println("日志记录==============");
            return obj;
        }
        return methodProxy.invokeSuper(proxy, args);
    }
}

  Cglib代理可以对任何类生成代理,代理的原理是对目标对象进行继承代理.,如果目标对象被final修饰.那么该类无法被cglib代理。

五、AOP原理的通俗理解

  spring用代理类包裹切面,把他们织入到Spring管理的bean中。也就是说代理类伪装成目标类,它会截取对目标类中方法的调用,让调用者对目标类的调用都先变成调用伪装类,伪装类中就先执行了切面,再把调用转发给真正的目标bean。

  现在可以自己想一想,怎么搞出来这个伪装类,才不会被调用者发现(过JVM的检查,JAVA是强类型检查,哪里都要检查类型)。

  1.实现和目标类相同的接口,我也实现和你一样的接口,反正上层都是接口级别的调用,这样我就伪装成了和目标类一样的类(实现了同一接口,咱是兄弟了),也就逃过了类型检查,到java运行期的时候,利用多态的后期绑定(所以spring采用运行时),伪装类(代理类)就变成了接口的真正实现,而他里面包裹了真实的那个目标类,最后实现具体功能的还是目标类,只不过伪装类在之前干了点事情(写日志,安全检查,事物等)。

  这就好比,一个人让你办件事,每次这个时候,你弟弟就会先出来,当然他分不出来了,以为是你,你这个弟弟虽然办不了这事,但是他知道你能办,所以就答应下来了,并且收了点礼物(写日志),收完礼物了,给把事给人家办了啊,所以你弟弟又找你这个哥哥来了,最后把这是办了的还是你自己。但是你自己并不知道你弟弟已经收礼物了,你只是专心把这件事情做好。

  顺着这个思路想,要是本身这个类就没实现一个接口呢,你怎么伪装我,我就压根没有机会让你搞出这个双胞胎的弟弟,那么就用第2种代理方式,创建一个目标类的子类,生个儿子,让儿子伪装我

  2.生成子类调用,这次用子类来做为伪装类,当然这样也能逃过JVM的强类型检查,我继承的吗,当然查不出来了,子类重写了目标类的所有方法,当然在这些重写的方法中,不仅实现了目标类的功能,还在这些功能之前,实现了一些其他的(写日志,安全检查,事物等)。

  这次的对比就是,儿子先从爸爸那把本事都学会了,所有人都找儿子办事情,但是儿子每次办和爸爸同样的事之前,都要收点小礼物(写日志),然后才去办真正的事。当然爸爸是不知道儿子这么干的了。这里就有件事情要说,某些本事是爸爸独有的(final的),儿子学不了,学不了就办不了这件事,办不了这个事情,自然就不能收人家礼了。

  前一种兄弟模式,spring会使用JDK的java.lang.reflect.Proxy类,它允许Spring动态生成一个新类来实现必要的接口,织入通知,并且把对这些接口的任何调用都转发到目标类。

  后一种父子模式,spring使用CGLIB库生成目标类的一个子类,在创建这个子类的时候,spring织入通知,并且把对这个子类的调用委托到目标类。

  相比之下,还是兄弟模式好些,他能更好的实现松耦合,尤其在今天都高喊着面向接口编程的情况下,父子模式只是在没有实现接口的时候,也能织入通知,应当做一种例外。

六、Spring使用AspectJ进行AOP的开发

6.1 XML配置

  第一步:引入相应的jar包

  

  第二步:准备目标对象

public class UserServiceImpl implements UserService {

    public void save() {
        System.out.println("保存用户");
    }

    public void delete() {
        System.out.println("删除用户");
    }

    public void update() {
        System.out.println("更新用户");
    }

    public void find() {
        System.out.println("查找用户");
    }

}

  第三步:准备通知

public class MyAdvice {
    // 前置通知:目标方法运行之前调用
    public void before(){
        System.out.println("这是前置通知");
    }
    
    // 后置通知:在目标方法运行之后调用(如果出现异常不会调用)
    public void afterReturning(){
        System.out.println("这是后置通知(如果出现异常不会调用)");
    }
    
    // 环绕通知:在目标方法之前和之后都调用
    public Object around(ProceedingJoinPoint pjp) throws Throwable{
        System.out.println("这是环绕通知之前的部分");
        Object proceed = pjp.proceed();// 调用目标方法
        System.out.println("这是环绕通知之后的部分");
        return proceed;
    }
    
    // 异常拦截通知(如果出现异常,就会调用)
    public void afterException(){
        System.out.println("出现异常了");
    }
    
    // 后置通知:在目标方法运行之后调用(无论是否出现异常,都会调用)
    public void after(){
        System.out.println("这是后置通知(出现异常也会调用)");
    }
}

   第四步:准备applicationContext.xml,并导入aop约束

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
    xmlns="http://www.springframework.org/schema/beans" 
    xmlns:aop="http://www.springframework.org/schema/aop" 
    xsi:schemaLocation="http://www.springframework.org/schema/beans 
                        http://www.springframework.org/schema/beans/spring-beans-4.2.xsd 
                        http://www.springframework.org/schema/aop 
                        http://www.springframework.org/schema/aop/spring-aop-4.0.xsd ">

</beans>

  第五步:将通知织入目标对象中

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
    xmlns="http://www.springframework.org/schema/beans" 
    xmlns:aop="http://www.springframework.org/schema/aop" 
    xsi:schemaLocation="http://www.springframework.org/schema/beans 
                        http://www.springframework.org/schema/beans/spring-beans-4.2.xsd 
                        http://www.springframework.org/schema/aop 
                        http://www.springframework.org/schema/aop/spring-aop-4.0.xsd ">
    <!-- 1.准备目标对象 -->
    <bean id="userService" class="cn.itcast.service.impl.UserServiceImpl"></bean>
    <!-- 2.准备通知对象 -->
    <bean id="myAdvice" class="cn.itcast.service.MyAdvice"></bean>
    <!-- 3.将通知织入目标对象 -->
    <aop:config>
        <!-- 配置切入点 
            public void cn.itcast.service.UserServiceImpl.save()
            void cn.itcast.service.UserServiceImpl.save()
            * cn.itcast.service.UserServiceImpl.save()
            * cn.itcast.service.UserServiceImpl.*()
            
            * cn.itcast.service.*ServiceImpl.*(..)
            * cn.itcast.service..*ServiceImpl.*(..)
        -->
        <aop:pointcut expression="execution(* cn.itcast.service..*ServiceImpl.*(..))" id="pointcut"/>
        <aop:aspect ref="myAdvice">
            <!-- 指定名为before的方法作为前置通知 -->
            <aop:before method="before" pointcut-ref="pointcut"/>
            <!-- 后置 -->
            <aop:after-returning method="afterReturning" pointcut-ref="pointcut"/>
            <!-- 环绕通知 -->
            <aop:around method="around" pointcut-ref="pointcut"/>
            <!-- 异常拦截通知 -->
            <aop:after-throwing method="afterException" pointcut-ref="pointcut"/>
            <!-- 后置 -->
            <aop:after method="after" pointcut-ref="pointcut"/>
        </aop:aspect>
    </aop:config>
</beans>

  第六步:编写测试方法

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class Demo {
    @Resource(name="userService")
    private UserService userService;
    
    @Test
    public void fun1() throws Exception {
        userService.save();
    }
}

   结果输出:

  

6.2 注解配置

  第一步:导包

  

  第二步:准备目标对象

public class UserServiceImpl implements UserService {

    public void save() {
        System.out.println("保存用户");
    }

    public void delete() {
        System.out.println("删除用户");
    }

    public void update() {
        System.out.println("更新用户");
    }

    public void find() {
        System.out.println("查找用户");
    }

}

  第三步:准备通知

// 表示该类是一个通知类
@Aspect
public class MyAdvice {
    @Pointcut("execution(* cn.itcast.service..*ServiceImpl.*(..))")
    public void pointcut(){
        
    }
    
    // 前置通知:目标方法运行之前调用
    // 指定该方法是前置通知,并指定切入点
    @Before("MyAdvice.pointcut()")
    public void before(){
        System.out.println("这是前置通知");
    }
    
    // 后置通知:在目标方法运行之后调用(如果出现异常不会调用)
    // 这里的execution(* cn.itcast.service..*ServiceImpl.*(..))等价于MyAdvice.pointcut()
    @AfterReturning("execution(* cn.itcast.service..*ServiceImpl.*(..))")
    public void afterReturning(){
        System.out.println("这是后置通知(如果出现异常不会调用)");
    }
    
    // 环绕通知:在目标方法之前和之后都调用
    @Around("execution(* cn.itcast.service..*ServiceImpl.*(..))")
    public Object around(ProceedingJoinPoint pjp) throws Throwable{
        System.out.println("这是环绕通知之前的部分");
        Object proceed = pjp.proceed();// 调用目标方法
        System.out.println("这是环绕通知之后的部分");
        return proceed;
    }
    
    // 异常拦截通知(如果出现异常,就会调用)
    @AfterThrowing("execution(* cn.itcast.service..*ServiceImpl.*(..))")
    public void afterException(){
        System.out.println("出现异常了");
    }
    
    // 后置通知:在目标方法运行之后调用(无论是否出现异常,都会调用)
    @After("execution(* cn.itcast.service..*ServiceImpl.*(..))")
    public void after(){
        System.out.println("这是后置通知(出现异常也会调用)");
    }
}

   第四步:开启使用注解完成织入

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
    xmlns="http://www.springframework.org/schema/beans" 
    xmlns:aop="http://www.springframework.org/schema/aop" 
    xsi:schemaLocation="http://www.springframework.org/schema/beans 
                        http://www.springframework.org/schema/beans/spring-beans-4.2.xsd 
                        http://www.springframework.org/schema/aop 
                        http://www.springframework.org/schema/aop/spring-aop-4.0.xsd ">
    <!-- 1.准备目标对象 -->
    <bean id="userService" class="cn.itcast.service.impl.UserServiceImpl"></bean>
    <!-- 2.准备通知对象 -->
    <bean id="myAdvice" class="cn.itcast.service.MyAdvice"></bean>
    <!-- 3.开启使用注解完成织入 -->
    <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
</beans>

  第五步:测试

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class Demo {
    @Resource(name="userService")
    private UserService userService;
    
    @Test
    public void fun1() throws Exception {
        userService.save();
    }
}

七、Spring中的AOP事务

7.1 Spring事务管理API

  Spring 的事务管理,主要用到两个事务相关的接口。

  (1)事务管理器接口——PlatformTransactionManager 

  不同平台,操作事务的代码各不相同,为此spring提供了一个PlatformTransactionManager 接口,其主要用于完成事务的提交、回滚,及获取事务的状态信息。  PlatformTransactionManager 接口有两个常用的实现类:

    • DataSourceTransactionManager:使用 JDBC 或 iBatis  进行持久化数据时使用。
    • HibernateTransactionManager:使用 Hibernate 进行持久化数据时使用。

   (2)事务定义接口——TransactionDefinition 

  TransactionDefinition 中定义了事务的相关信息:

  • 隔离级别

    READ_UNCOMMITTED:读未提交。未解决任何并发问题。

    READ_COMMITTED:读已提交。解决脏读,存在不可重复读与幻读。(Oracle默认)

    REPEATABLE_READ:可重复读。解决脏读、不可重复读,存在幻读(Mysql默认)

    SERIALIZABLE:串行化。不存在并发问题。

  • 传播行为

    所谓事务传播行为是指,处于不同事务中的方法在相互调用时,执行期间事务的维护情况。如,A 事务中的方法 doSome()调用 B 事务中的方法 doOther(),在调用执行期间事务的维护情况,就称为事务传播行为。事务传播行为是加在方法上的。  
    事务传播行为常量都是以 PROPAGATION_  开头,形如 PROPAGATION_XXX。


    PROPAGATION_REQUIRED:指定的方法必须在事务内执行。若当前存在事务,就加入到当前事务中;若当前没有事务,则创建一个新事务。(默认)

    PROPAGATION_SUPPORTS:指定的方法支持当前事务,但若当前没有事务,就不使用事务

    PROPAGATION_MANDATORY:指定的方法必须在当前事务内执行,若当前没有事务,则直接抛出异常。

    PROPAGATION_REQUIRES_NEW:总是新建一个事务,若当前存在事务,就将当前事务挂起,直到新事务执行完毕。

    PROPAGATION_NOT_SUPPORTED:指定的方法不能在事务环境中执行,若当前存在事务,就将当前事务挂起。

    PROPAGATION_NEVER:指定的方法不能在事务环境下执行,若当前存在事务,就直接抛出异常。

    PROPAGATION_NESTED:指定的方法必须在事务内执行。若当前存在事务,则在嵌套事务内执行;若当前没有事务,则创建一个新事务。

  • 超时信息
  • 是否只读

7.2 Spring管理事务的方式

  准备工作:

  • AccountService

    public interface AccountService {
        // 转账方法
        void transfer(Integer from,Integer to,Double money);
    }
  • AccountServiceImpl

    public class AccountServiceImpl implements AccountService {
    
        // 业务层注入Dao
        private AccountDao accountDao;
    
        public void setAccountDao(AccountDao accountDao) {
            this.accountDao = accountDao;
        }
    
        /**
         * from:转出的账号 
         * to:转入的账号 
         * money:转账金额
         */
        @Override
        public void transfer(Integer from, Integer to, Double money) {
            // 减钱
            accountDao.decreaseMoney(from, money);
            int i = 1 / 0;
            // 加钱
            accountDao.increaseMoney(to, money);
        }
    
    }
  • AccountDao

    public interface AccountDao {
    
        // 减钱
        void decreaseMoney(Integer from, Double money);
        // 加钱
        void increaseMoney(Integer to, Double money);
    
    }
  •  AccountDaoImpl

    public class AccountDaoImpl extends JdbcDaoSupport implements AccountDao {
    
        @Override
        public void decreaseMoney(Integer from, Double money) {
            this.getJdbcTemplate().update("update t_account set money=money-? where id=?", money, from);
        }
    
        @Override
        public void increaseMoney(Integer to, Double money) {
            this.getJdbcTemplate().update("update t_accout set money=money+? where id=?", money, to);
        }
    
    }

Spring的编程式事务(了解)

  手动编写代码完成事务的管理:

  • 第一步:配置核心事务管理器
    <!-- 事务核心管理器,封装了所有事务的操作,依赖于连接池 -->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"></property>
    </bean>
  • 第二步:配置TransactionTemplate模板
    <!-- 事务模板对象 -->
    <bean id="transactionTemplate" class="org.springframework.transaction.support.TransactionTemplate">
        <property name="transactionManager" ref="transactionManager"></property>
    </bean>
  • 第三步:将事务模板注入Service
    <!-- Service -->
    <bean id="accountService" class="cn.itcast.service.impl.AccountServiceImpl">
        <property name="accountDao" ref="accountDao"></property>
        <property name="template" ref="transactionTemplate"></property>
    </bean>
  • 第四步:在Service中手动编写代码完成事务管理
    public class AccountServiceImpl implements AccountService {
        // 业务层注入Dao
        private AccountDao accountDao;
        // 编码式才有用
        private TransactionTemplate template;
    
        public void setTemplate(TransactionTemplate template) {
            this.template = template;
        }
    
        public void setAccountDao(AccountDao accountDao) {
            this.accountDao = accountDao;
        }
    
        /**
         * from:转出的账号 
         * to:转入的账号 
         * money:转账金额
         */
        @Override
        public void transfer(final Integer from, final Integer to, final Double money) {
            template.execute(new TransactionCallbackWithoutResult() {
                
                @Override
                protected void doInTransactionWithoutResult(TransactionStatus arg0) {
                    // 减钱
                    accountDao.decreaseMoney(from, money);
                    int i = 1 / 0;
                    // 加钱
                    accountDao.increaseMoney(to, money);
                }
            });
        }
    }
  • 第五步:编写测试类
    @RunWith(SpringJUnit4ClassRunner.class)
    @ContextConfiguration("classpath:applicationContext.xml")
    public class Demo {
        @Resource(name="accountService")
        private AccountService accountService;
        
        @Test
        public void demo1() throws Exception {
            accountService.transfer(1, 2, 100d);
        }
    } 

Spring的声明式事务管理XML方式(****):思想就是AOP

   不需要进行手动编写代码,通过一段配置完成事务管理

  • 第一步:导包

  

  • 第二步:导入新的约束(tx)

  

  • 第三步:配置事务管理器
    <!-- 事务核心管理器,封装了所有事务的操作,依赖于连接池 -->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"></property>
    </bean>
  •  第四步:配置事务通知  

    <!-- 配置事务通知 -->
    <tx:advice id="txAdvice" transaction-manager="transactionManager">
        <tx:attributes>
            <!-- 以方法为单位,指定方法应用什么事务属性
                isolation:隔离级别
                propagation:传播行为
                read-only:是否只读
             -->
            <tx:method name="save*" isolation="REPEATABLE_READ" propagation="REQUIRED" read-only="false"/>
            <tx:method name="persist*" isolation="REPEATABLE_READ" propagation="REQUIRED" read-only="false"/>
            <tx:method name="update*" isolation="REPEATABLE_READ" propagation="REQUIRED" read-only="false"/>
            <tx:method name="modify*" isolation="REPEATABLE_READ" propagation="REQUIRED" read-only="false"/>
            <tx:method name="delete*" isolation="REPEATABLE_READ" propagation="REQUIRED" read-only="false"/>
            <tx:method name="remove*" isolation="REPEATABLE_READ" propagation="REQUIRED" read-only="false"/>
            <tx:method name="get*" isolation="REPEATABLE_READ" propagation="REQUIRED" read-only="true"/>
            <tx:method name="find*" isolation="REPEATABLE_READ" propagation="REQUIRED" read-only="true"/>
            <tx:method name="transfer*" isolation="REPEATABLE_READ" propagation="REQUIRED" read-only="false"/>
        </tx:attributes>
    </tx:advice>
  • 第五步:将通知织入目标对象
    <!-- 配置织入 -->
    <aop:config>
        <!-- 配置切点表达式 -->
        <aop:pointcut expression="execution(* cn.itcast.service..*ServiceImpl.*(..))" id="txPc"/>
        <!-- 配置切面:通知+切点
            advice-ref:通知的名称
            pointcut-ref:切点的名称
         -->
         <aop:advisor advice-ref="txAdvice" pointcut-ref="txPc"/>
    </aop:config>
  • 第六步:测试

Spring的声明式事务的注解方式(****)

  • 第一步:导包

  

  • 第二步:导入新的约束(tx)
  • 第三步:配置事务管理器
    <!-- 事务核心管理器,封装了所有事务的操作,依赖于连接池 -->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"></property>
    </bean>
  • 第四步:开启事务管理的注解
    <!-- 开启使用注解管理aop事务 -->
    <tx:annotation-driven/>
  • 第五步:在使用事务的类上添加一个注解:@Transactional
    // 全局注解,作用于该类中的所有方法
    @Transactional(isolation=Isolation.REPEATABLE_READ,propagation=Propagation.REQUIRED,readOnly=true)
    public class AccountServiceImpl implements AccountService {
        // 业务层注入Dao
        private AccountDao accountDao;
    
        public void setAccountDao(AccountDao accountDao) {
            this.accountDao = accountDao;
        }
    
        // 局部注解,只作用于该方法,优先级大于全局注解
        @Transactional(isolation=Isolation.REPEATABLE_READ,propagation=Propagation.REQUIRED,readOnly=false)
        public void transfer(Integer from, Integer to, Double money) {
            // 减钱
            accountDao.decreaseMoney(from, money);
            int i = 1 / 0;
            // 加钱
            accountDao.increaseMoney(to, money);
        }
    }
  • 第六步:测试

参考资料:https://www.cnblogs.com/Wolfmanlq/p/6036019.html

     https://www.jianshu.com/p/570c5283b1fc

原文地址:https://www.cnblogs.com/yft-javaNotes/p/10295292.html