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