一、Spring AOP 简介
如果说 IoC 是 Spring 的核心,那么面向切面编程就是 Spring 最为重要的功能之一了,在数据库事务中切面编程被广泛使用。
AOP 即 Aspect Oriented Program 面向切面编程
首先,在面向切面编程的思想里面,把功能分为核心业务功能,和周边功能。
● 所谓的核心业务,比如登陆,增加数据,删除数据都叫核心业务
● 所谓的周边功能,比如性能统计,日志,事务管理等等
周边功能在 Spring 的面向切面编程AOP思想里,即被定义为切面。
在面向切面编程AOP的思想里面,核心业务功能和切面功能分别独立进行开发,然后把切面功能和核心业务功能 "编织" 在一起,这就叫AOP。
AOP 的目的
AOP能够将那些与业务无关,却为业务模块所共同调用的逻辑或责任(例如事务处理、日志管理、权限控制等)封装起来,便于减少系统的重复代码,降低模块间的耦合度,并有利于未来的可拓展性和可维护性。
二、aop概念介绍(面向切面的编程)
AOP行话:
● 连接点(JointPoint):目标对象中,所有可以增强的方法
● 切入点(Pointcut):目标对象中,已经增强的方法
● 通知(Advice):增强的代码
● 目标对象(Target Object):被代理的对象
● 织入(Weaving):将通知应用到切入点的过程
● 代理(AOP Proxy):将通知织入到目标对象之后,形成的对象
● 切面(Aspect):切入点+通知
三、AOP配置使用
AOP所需的包:
aop核心包:spring-aspects 和 spring-aop
aop联盟包:springsource.org.aopalliance
aop织入包:springsource.org.aspectj.weaver
1. XML配置AOP:
① 准备目标对象(被代理对象,被通知的对象,被增强的类对象)
package com.sikiedu.service; public interface UserService { // 增 void save(); // 删 void delete(); // 改 void update(); // 查 void find(); }
package com.sikiedu.service; public class UserServiceImpl implements UserService { @Override public void save() { System.out.println("save...run"); } @Override public void delete() { System.out.println("delete...run"); } @Override public void update() { System.out.println("update...run"); } @Override public void find() { System.out.println("find...run"); } }
② 准备通知(被增强方法的代码,想要实现功能的方法代码)
package com.sikiedu.aop; import org.aspectj.lang.ProceedingJoinPoint; /** * 自定义通知类 * * @author ASUS * */ public class MyAdvice { // before 前置通知 - 在目标方法前调用 public void befor() { System.out.println("前置通知"); } // afterReturning 成功通知(后置通知)- 在目标方法执行后,并且执行成功,如果方法出现异常则不调用 public void afterReturning() { System.out.println("成功通知"); } // around 环绕通知 - 需要我们手动调用目标方法,并且可以设置通知 public Object around(ProceedingJoinPoint pjp) throws Throwable { System.out.println("环绕通知 - 前"); Object proceed = pjp.proceed(); System.out.println("环绕通知 - 后"); return proceed; } // afterThrowing 异常通知(后置通知)- 在目标方法执行出现异常的时候才会调用 public void afterThrowing() { System.out.println("异常通知"); } // after 最终通知(后置通知)- 在目标方法后调用,无论是否出现异常都会执行,finally public void after() { System.out.println("最终通知"); } }
③ 配置 applicationContext.xml
1 - 导入aop(约束)命名空间
2 - 配置目标对象
3 - 配置通知对象
4 - 配置将通知织入目标对象
<?xml version="1.0" encoding="UTF-8"?> <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-4.3.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.3.xsd"> <!-- 配置目标对象 --> <bean name="userService" class="com.sikiedu.service.UserServiceImpl"> </bean> <!-- 配置通知对象 --> <bean name="myAdvice" class="com.sikiedu.aop.MyAdvice"></bean> <!-- 织入 - 将通知织入到目标对象中 --> <aop:config> <!-- 切入点 1、expression:切入点表达式,可以配置要增强的方法 ① public void com.sikiedu.service.UserServiceImpl.save() - 增强单一方法save,必须为无参、public类型、返回值为void ② * com.sikiedu.service.*.*(..) - 增强service包下所有类的所有方法 2、id:唯一标识 --> <aop:pointcut expression="execution(* com.sikiedu.service.*.*(..))" id="servicePc" /> <!-- 切面:通知+切入点--> <aop:aspect ref="myAdvice"> <!-- 通知类型 --> <!-- 前置通知 --> <aop:before method="befor" pointcut-ref="servicePc"/> <!-- 成功通知 --> <aop:after-returning method="afterReturning" pointcut-ref="servicePc"/> <!-- 环绕通知 --> <aop:around method="around" pointcut-ref="servicePc"/> <!-- 异常通知 --> <aop:after-throwing method="afterThrowing" pointcut-ref="servicePc"/> <!-- 最终通知 --> <aop:after method="after" pointcut-ref="servicePc"/> </aop:aspect> </aop:config> </beans>
④ 测试
package com.sikiedu.test; import javax.annotation.Resource; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import com.sikiedu.service.UserService; @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration("classpath:applicationContext.xml") public class AopTest { @Resource(name = "userService") UserService us; @Test public void test1() { us.delete(); } }
运行结果:
- 非异常:,异常:
2. 注解配置AOP
① 配置applicationContext.xml - 配置目标对象、通知对象、开启使用注解完成织入
<?xml version="1.0" encoding="UTF-8"?> <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-4.3.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.3.xsd"> <!-- 配置目标对象 --> <bean name="userService" class="com.sikiedu.service.UserServiceImpl"> </bean> <!-- 配置通知对象 --> <bean name="myAdvice" class="com.sikiedu.aop.MyAdvice"></bean> <!-- 开启使用注解完成织入 --> <aop:aspectj-autoproxy></aop:aspectj-autoproxy> </beans>
② 书写通知类
@Aspect:指定切面类
@JoinPoint:作为函数的参数传入切面方法,可以得到目标方法的相关信息
声明一个切入点:@Pointcut("execution(返回值 全类名.方法名(参数))")
package com.sikiedu.aop; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.After; import org.aspectj.lang.annotation.AfterReturning; import org.aspectj.lang.annotation.AfterThrowing; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.aspectj.lang.annotation.Pointcut; /** * 自定义通知类 * * @author ASUS * */ @Aspect // 通知类 - 使用Aspect表示是一个通知类 public class MyAdvice { // 声明一个切入点 - servicePc为切入点名称 @Pointcut("execution(* com.sikiedu.service.*.*(..))") public void servicePc() { }; @Before("MyAdvice.servicePc()") // before 前置通知 - 在目标方法前调用 public void befor() { System.out.println("前置通知"); } @AfterReturning("MyAdvice.servicePc()") // afterReturning 成功通知(后置通知)- 在目标方法执行后,并且执行成功,如果方法出现异常则不调用 public void afterReturning() { System.out.println("成功通知"); } @Around("MyAdvice.servicePc()") // around 环绕通知 - 需要我们手动调用目标方法,并且可以设置通知。 public void around(ProceedingJoinPoint pjp) throws Throwable { System.out.println("环绕通知 - 前"); pjp.proceed(); System.out.println("环绕通知 - 后"); } @AfterThrowing("MyAdvice.servicePc()") // afterThrowing 异常通知(后置通知)- 在目标方法执行出现异常的时候才会调用。 public void afterThrowing() { System.out.println("异常通知"); } @After("MyAdvice.servicePc()") // after 最终通知(后置通知)- 在目标方法后调用,无论是否出现异常都会执行。 public void after() { System.out.println("最终通知"); } }
● 注意环绕通知:需要我们手动调用目标方法
proceed()表示对拦截的方法进行放行,若注释proceed()则不会执行被AOP匹配的方法。
public void around(ProceedingJoinPoint pjp) throws Throwable { System.out.println("环绕通知 - 前") pjp.proceed(); System.out.println("环绕通知 - 后") }
③ 测试:
package com.sikiedu.test; import javax.annotation.Resource; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import com.sikiedu.service.UserService; @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration("classpath:applicationContext2.xml") public class AopTest { @Resource(name = "userService") UserService us; @Test public void test1() { us.delete(); } }
运行结果:
- 非异常:,异常:
① 前置通知(前置通知):目标方法运行之前调用
@Before:在目标方法被调用之前做增强处理,@Before只需要指定切入点表达式即可
② 成功通知(后置通知):目标方法运行之后调用(如果出现异常不调用) @AfterReturning:在目标方法正常完成后做增强,@AfterReturning除了指定切入点表达式后,还可以指定一个返回值形参名returning,代表目标方法的返回值
④ 异常拦截通知(后置通知):如果出现异常,就会调用 @AfterThrowing:主要用来处理程序中未处理的异常,@AfterThrowing除了指定切入点表达式后,还可以指定一个throwing的返回值形参名,可以通过该形参名来访问目标方法中所抛出的异常对象
④ 最终通知(后置通知):目标方法运行之后调用(无论是否出现异常都会调用) @After:在目标方法完成之后做增强,无论目标方法是否成功完成。@After可以指定一个切入点表达式
③ 环绕通知:目标方法之前和之后都调用 - 需要我们手动调用目标方法,并且可以设置通知 @Around:环绕通知,在目标方法完成前后做增强处理,环绕通知是最重要的通知类型,像事务,日志等都是环绕通知,注意编程中核心是一个ProceedingJoinPoint
四、Spring - AOP事务
Spring封装了事务管理的代码(打开,提交,回滚事务)
事务操作对象,因为在不同平台,操作事务的代码各不相同。
Spring提供了一个接口:PlatformTransactionManager接口,在不同平台,实现不同的接口即可。
为不同平台提供对应的事务管理器的实现:
- JDBC & Mybatis:DataSourceTransactionManager
- Hibernate:HibernateTransactionManager
在Spring事务管理,最为核心的对象就是TransactionManager对象。
事物的四大特性 - ACID
● 原子性(Atomicity):事务包含的所有操作,要么全部成功,要么全部失败回滚,成功全部应用到数据库,失败不能对数据库有任何影响; ● 一致性(Consistency):事务在执行前和执行后必须一致;例如A和B一共有100块钱,无论A、B之间如何转账,他们的钱始终相加都是100; ● 隔离性(Isolation):多用户并发访问同一张表时,数据库为每一个用户开启新的事务,该事务不能被其他事务所影响,相互有隔离; ● 持久性(Durability):一个事务一旦提交,则对数据库中数据的改变是永久的,即便系统故障也不会丢失;
⁽⁽ଘ( - 事务回顾 - )ଓ⁾⁾*
1. Spring事务分类
Spring中事务可以分为编程式事务控制和声明式事务控制。
● 编程式事务控制
自己手动控制事务,可以对指定的方法,指定的方法的某几行添加事务控制。 比较灵活,但开发较为繁琐,每次都要开启、提交、回滚;
- Jdbc:Conn.setAutoCommit(false);// 设置手动控制事务 - Hibernate:Session.beginTransaction();// 开启一个事务
注意:编程式事务控制开启事物后一定要手动释放(提交或回滚),否则长期占用内存,有可能报事务异常。
● 声明式事务控制 - 核心实现基于Aop
Spring声明式事务管理,只能给整个方法应用事务,不可以对方法中的某几行应用事务(因为aop拦截的是方法)。 Spring提供了对事务控制的实现。用户如果想要用Spring的声明式事务管理,只需要在配置文件中配置即可;不想要使用时直接移除配置。这个实现了对事务控制的最大程度的解耦。 Spring声明式事务管理器:
- Jdbc事务:DataSourceTransactionManager
- Hibernate事务:HibernateTransactionManager
2. Spring管理事务的属性介绍
● 事务的隔离级别
隔离级别 | 含义 |
数据库默认 ISOLATION_DEFAULT |
使用底层数据库的默认隔离级别,大部分数据库,默认隔离级别都是READ_COMMITED |
读以提交 ISOLATION_READ_UNCOMMITTED |
最低的隔离级别,允许读取尚未提交的数据变更,可能会导致脏读、幻读或不可重复读 |
读未提交 ISOLATION_READ_COMMITTED |
允许读取并发事务已经提交的数据,可以阻止脏读,但是幻读或不可重复读仍有可能发生 |
可重复读 ISOLATION_REPEATABLE_READ |
对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改,可以阻止脏读和不可重复读,但幻读仍有可能发生 |
串行化 ISOLATION_SERIALIZABLE |
最高的隔离级别,完全服从ACID的隔离级别,确保阻止脏读、不可重复读以及幻读,也是最慢的事务隔离级别,因为它通常是通过完全锁定事务相关的数据库表来实现的 |
● 是否只读
true:不可改变数据库中的数据,查询操作推荐, false:可以改变数据库数据;
● 事务的传播行为 - 多个事务方法调用时,如何定义方法间事务的传播
① PROPAGATION_REQUIRED:如果当前没有事务,就创建一个新事务,如果当前存在事务,就加入该事务,该设置是最常用的设置; ② PROPAGATION_REQUIRES_NEW:创建新事务,无论当前存不存在事务,都创建新事务; ③ PROPAGATION_SUPPORTS:支持当前事务,如果当前存在事务,就加入该事务,如果当前不存在事务,就以非事务执行; ④ PROPAGATION_NOT_SUPPORTED:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起(暂停); ⑤ PROPAGATION_MANDATORY:支持当前事务,如果当前存在事务,就加入该事务,如果当前不存在事务,就抛出异常; ⑥ PROPAGATION_NEVER:以非事务方式执行,如果当前存在事务,则抛出异常; ⑦ PROPAGATION_NESTED:如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与REQUIRED类似的操作。
3. Spring事务使用
① 使用事务需要额外带入 tx 包和 tx 约束
② 配置Jdbc事务的核心管理器,它封装了所有事务,依赖于连接池
<!-- JDBC - 事务核心管理器 --> <bean name="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <!-- 依赖注入数据源 --> <property name="dataSource" ref="dataSource" /> </bean>
● XML配置 - 配置通知、将通知织入目标对象
配置事务通知 - tx:advice
<!-- 配置通知 --> <tx:advice id="advice" transaction-manager="transactionManager"> <tx:attributes> <!-- 以方法为单位 指定什么方法使用什么属性 isolation:事务隔离级别 read-only:是否只读 propagation:传播行为 --> <tx:method name="save*" isolation="REPEATABLE_READ" read-only="false" propagation="REQUIRED" /> <tx:method name="delete*" isolation="REPEATABLE_READ" read-only="false" propagation="REQUIRED" /> <tx:method name="update*" isolation="REPEATABLE_READ" read-only="false" propagation="REQUIRED" /> <tx:method name="select*" isolation="REPEATABLE_READ" read-only="true" propagation="REQUIRED" /> </tx:attributes> </tx:advice>
配置aop将通知织入目标
<!-- 配置织入:将上面的通知织入到目标对象 --> <aop:config> <!-- 配置切点表达式 --> <aop:pointcut expression="execution(* com.sikiedu.service.*ServiceImpl.*(..))" id="pointcut" /> <!-- 配置切面(通知+切点) --> <aop:advisor advice-ref="advice" pointcut-ref="pointcut" /> </aop:config>
● 注解配置
在spring配置文件中开启注解事务
<!-- 开启注解事务 --> <tx:annotation-driven />
在需要管理的方法或者类中使用@Transactiona()声明配置事务管理
//所有方法开启事务 @Transactional(isolation = Isolation.REPEATABLE_READ, readOnly = false, propagation = Propagation.REQUIRED) public class UserServiceImpl implements UserService { // Code... }
// 当前方法使用注解管理spring中的aop事务 @Transactional(isolation = Isolation.REPEATABLE_READ, readOnly = false, propagation = Propagation.REQUIRED) public void updateBalance() { double money = 1000; // 转出钱的方法 userDao.subBalance(2, money); // int i = 1 / 0; // 转入钱的方法 userDao.addBalance(1, money); }