AOP
,即 Aspect-Oriented Program
面向切面编程,相比较继承、装饰者模式等纵向增强对象的方式, AOP
是横向的、无入侵性的、可插拔的、高复用的。因此作为 Spring
的核心模块之一,它广泛应用于日志记录、事务管理、权限控制、异常处理等场景。
由于 AOP
是基于动态代理的,所以本节先简单介绍一下代理模式。代理模式分为静态代理和动态代理,核心思想是在调用方和被调用方(被代理对象,也称目标对象)起到一个中介(代理对象)的作用,这样能够达到解耦、复用、保护的效果:
代理对象对调用请求是不做实际的业务处理的
通过实现和目标对象同样的接口,以实现和目标对象有相同的外观。通过保存目标对象的引用来对请求做实际的业务处理。
静态代理示例代码如下:
public interface UserService { void add(); } public class UserReadServiceImpl implements UserService { public void add() { System.out.println("illegal invoke"); } public void query() { System.out.println("query user"); } } public class UserWriteServiceImpl implements UserService { public void add() { System.out.println("insert user"); } public void query() { System.out.println("illegal invoke"); } } public class UserServiceProxy implements UserService { private UserService userService; public UserServiceProxy(UserService userService) { this.userService = userService; } public void add() { userService.add(); } public void query() { userService.query(); } } 复制代码
测试如下:
public class UserServiceProxyTest { @Test public void add() { UserService userService = new UserWriteServiceImpl(); UserServiceProxy userServiceProxy = new UserServiceProxy(userService); userServiceProxy.add(); } @Test public void query() { UserService userService = new UserReadServiceImpl(); UserServiceProxy userServiceProxy = new UserServiceProxy(userService); userServiceProxy.query(); } } insert user query user 复制代码
静态代理的弊端:
UserService
就是一个域),都需要手动编写一个代理类 针对静态代理的这些缺点,采用动态代理就能很好的规避。
目前动态代理有两种方案,即 jdk
代理和 cglib
代理。
jdk
代理是指 jdk
内置的,通过 jdk api
就可以实现, 只能对目标对象实现的接口方法做增强 cglib
代理是指使用第三方类库 cglib
实现动态代理, cglib
还依赖了 asm
库, asm
是一种字节码修改技术,而 cglib
就是通过修改字节码中的符号引用来生成代理对象的。字节码文件结构及符号引用可参阅《深入理解Java虚拟机(第二版)》(周志明)。由于 cglib
动态代理是基于继承的,所以 cglib
动态代理可以 增强目标对象所有的可继承方法 jdk
代理的示例代码如下:
package cn.tuhu.springaop.proxy; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; public class LogProxy { private Object target; private LogInvocationHandler logInvocationHandler = new LogInvocationHandler(); public LogProxy(Object target) { this.target = target; } public Object getProxyObject() { ClassLoader classLoader = target.getClass().getClassLoader(); Class<?>[] interfaces = target.getClass().getInterfaces(); return Proxy.newProxyInstance(classLoader, interfaces, logInvocationHandler); } private class LogInvocationHandler implements InvocationHandler { public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("log for request"); method.invoke(target, args); return proxy; } } } 复制代码
测试如下:
package cn.tuhu.springaop.proxy; import cn.tuhu.springaop.service.UserService; import cn.tuhu.springaop.service.impl.UserReadServiceImpl; import org.junit.Test; import static org.junit.Assert.*; public class LogProxyTest { @Test public void getProxyObject() { UserService userService = new UserReadServiceImpl(); LogProxy logProxy = new LogProxy(userService); UserService userServiceProxy = (UserService) logProxy.getProxyObject(); userServiceProxy.query(); userServiceProxy.add(); } } log for request query user log for request illegal invoke 复制代码
上述以讲述了动态代理的基本原理, Spring
的 AOP
底层用的就是动态代理,它由以下几个要素组成:
pointcut/joinpoint
advice
aspect
引入 SpringAOP
相关依赖:
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-core</artifactId> <version>3.0.6.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>3.0.6.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aop</artifactId> <version>3.0.6.RELEASE</version> </dependency> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjrt</artifactId> <version>1.6.1</version> </dependency> <dependency> <groupId>aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.5.3</version> </dependency> <dependency> <groupId>cglib</groupId> <artifactId>cglib</artifactId> <version>2.1_2</version> </dependency> 复制代码
添加 spring
配置文件:
<?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:p="http://www.springframework.org/schema/p" 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="cn.tuhu.springaop"></context:component-scan> <!-- 开启AOP编程注解,开启后标识为@Aspect的bean的AOP才会生效 --> <aop:aspectj-autoproxy></aop:aspectj-autoproxy> </beans> 复制代码
业务接口:
package cn.tuhu.springaop.service; public interface UserService { void add(); void query(); } 复制代码
业务实现:
package cn.tuhu.springaop.service.impl; import cn.tuhu.springaop.service.UserService; import org.springframework.stereotype.Service; @Service public class UserServiceImpl implements UserService { public void add() { System.out.println("insert user"); } public void query() { System.out.println("query user"); } } 复制代码
切面类:
package cn.tuhu.springaop; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.*; import org.springframework.stereotype.Component; @Aspect @Component public class TransactionAspect { //前置通知 @Before(value = "execution(* cn.tuhu.springaop.service.impl.*.*(..))") //切入点:service.impl包下的所有方法 public void before() { System.out.println("Before:最先执行"); } //后置通知 @After(value = "execution(* cn.tuhu.springaop.service.impl.*.*(..))") public void after() { System.out.println("After:方法执行之后执行"); } //环绕通知 @Around(value = "execution(* cn.tuhu.springaop.service.impl.*.*(..))") public void around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable { System.out.println("Around Begin:Before执行之后方法执行之前执行"); proceedingJoinPoint.proceed(); System.out.println("Around End:After执行之后执行"); } //正常结束通知 @AfterReturning(value = "execution(* cn.tuhu.springaop.service.impl.*.*(..))") public void afterReturning() { System.out.println("AfterReturning:最后执行"); } //异常终止通知 @AfterThrowing(value = "execution(* cn.tuhu.springaop.service.impl.*.*(..))") public void afterThrowing() { System.out.println("AfterThrowing:抛出异常后执行"); } } 复制代码
测试类:
package cn.tuhu.springaop.service.impl; import cn.tuhu.springaop.service.UserService; import org.junit.Test; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class UserServiceImplTest { ApplicationContext context = new ClassPathXmlApplicationContext("spring.xml"); @Test public void add() { UserService userService = context.getBean(UserService.class); userService.add(); } } Before:最先执行 Around Begin:Before执行之后方法执行之前执行 insert user After:方法执行之后执行 Around End:After执行之后执行 AfterReturning:最后执行 ====================== Before:最先执行 Around Begin:Before执行之后方法执行之前执行 query user After:方法执行之后执行 Around End:After执行之后执行 AfterReturning:最后执行 复制代码
可见, AOP
的切面机制很灵活,可以有不同方式的增强和灵活的关注点配置( execution
表达式)。
编程式事务,即在业务方法中手动编码开启事务会话、提交事务、回滚。
环境准备,需要连接数据库以及使用 Spring
提供的 JdbcTemplate
<dependency> <groupId>com.mchange</groupId> <artifactId>c3p0</artifactId> <version>0.9.5.2</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.37</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-orm</artifactId> <version>3.0.6.RELEASE</version> </dependency> 复制代码
在配置文件中配置数据源以及事务管理器:
<!-- 1. 数据源对象: C3P0连接池 --> <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"> <property name="driverClass" value="com.mysql.jdbc.Driver"></property> <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/test"></property> <property name="user" value="root"></property> <property name="password" value="123456"></property> </bean> <!-- 2. JdbcTemplate工具类实例 --> <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"> <property name="dataSource" ref="dataSource"></property> </bean> <!-- 3.配置事务 --> <bean id="dataSourceTransactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"></property> </bean> 复制代码
Dao
package cn.tuhu.springaop.dao; import cn.tuhu.springaop.entity.User; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.stereotype.Repository; @Repository public class UserDao { @Autowired JdbcTemplate jdbcTemplate; public void add(User user) { String sql = "insert into user (id,name) values(" + user.getId() + ",'" + user.getName() + "');"; jdbcTemplate.execute(sql); } } 复制代码
Service
package cn.tuhu.springaop.service.impl; import cn.tuhu.springaop.dao.UserDao; import cn.tuhu.springaop.entity.User; import cn.tuhu.springaop.service.UserService; import cn.tuhu.springaop.util.TransactionUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.TransactionStatus; @Service public class UserServiceImpl implements UserService { @Autowired UserDao userDao; @Autowired TransactionUtils transactionUtils; public void add() { TransactionStatus transactionStatus = null; try { //begin transactionStatus = transactionUtils.begin(); User user = new User(1L, "张三"); userDao.add(user); user = new User(2L, "李四"); int i = 1 / 0; userDao.add(user); if (transactionStatus != null) { transactionUtils.commit(transactionStatus); } } catch (Exception e) { if (transactionStatus != null) { transactionUtils.rollback(transactionStatus); } } } } 复制代码
测试事务
public class UserServiceImplTest { ApplicationContext context = new ClassPathXmlApplicationContext("spring.xml"); @Test public void add() { UserService userService = context.getBean(UserService.class); userService.add(); } } 复制代码
刷新数据库 user
表发现没有数据插入,而注释掉 i=1/0
则插入两条数据,说明事务生效。
上述事务的开启、提交、回滚是模板式的代码,我们应该抽取出来以供复用,这时AOP就派上用场了。
package cn.tuhu.springaop.proxy; import cn.tuhu.springaop.util.TransactionUtils; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import org.springframework.transaction.TransactionStatus; @Component @Aspect public class TransactionAop { @Autowired TransactionUtils transactionUtils; @Around(value = "execution(* cn.tuhu.springaop.service.impl.*.add*(..))," + "execution(* cn.tuhu.springaop.service.impl.*.update*(..))," + "execution(* cn.tuhu.springaop.service.impl.*.delete*(..))") public void transactionHandler(ProceedingJoinPoint proceedingJoinPoint) { TransactionStatus transactionStatus = transactionUtils.begin(); try { proceedingJoinPoint.proceed(); transactionUtils.commit(transactionStatus); } catch (Throwable throwable) { throwable.printStackTrace(); transactionUtils.rollback(transactionStatus); } } } 复制代码
业务类则只需关注业务代码
@Service public class UserServiceImpl implements UserService { @Autowired UserDao userDao; public void add() { User user = new User(1L, "张三"); userDao.add(user); user = new User(2L, "李四"); int i = 1 / 0; userDao.add(user); } } 复制代码
Spring
的声明式事务是通过 @Transactional
注解来实现的,首先我们先屏蔽上节写的 TransactionAop
:
//@Component //@Aspect public class TransactionAop { 复制代码
在 spring.xml
中开启声明式事务注解(注意引入 tx
名称空间):
<?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:context="http://www.springframework.org/schema/context" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx" 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 http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd"> <context:component-scan base-package="cn.tuhu.springaop"></context:component-scan> <aop:aspectj-autoproxy></aop:aspectj-autoproxy> <!-- 开启事物注解 --> <!-- 1. 数据源对象: C3P0连接池 --> <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"> <property name="driverClass" value="com.mysql.jdbc.Driver"></property> <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/test"></property> <property name="user" value="root"></property> <property name="password" value="123456"></property> </bean> <!-- 2. JdbcTemplate工具类实例 --> <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"> <property name="dataSource" ref="dataSource"></property> </bean> <!-- 3.配置事务 --> <bean id="dataSourceTransactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"></property> </bean> <tx:annotation-driven transaction-manager="dataSourceTransactionManager"/> </beans> 复制代码
只需在方法和类上添加 @Transactional
注解即可添加事务控制(若在类上添加则相当于在每个方法上都加了 @Transactional
):
package cn.tuhu.springaop.service.impl; import cn.tuhu.springaop.dao.UserDao; import cn.tuhu.springaop.entity.User; import cn.tuhu.springaop.service.UserService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @Service public class UserServiceImpl implements UserService { @Autowired UserDao userDao; @Transactional(rollbackFor = Exception.class) public void add() { User user = new User(1L, "张三"); userDao.add(user); user = new User(2L, "李四"); int i = 1 / 0; //不注释测一次,注释起来再测一次 userDao.add(user); } } 复制代码
Spring
通过扫包发现被注解为 @Transactional
的方法,再通过AOP的方法进行编程式事务控制。
定义事务注解
package cn.tuhu.springaop.annotation; public @interface MyTransactional { } 复制代码
编写切面类,增强带有事务注解的方法
package cn.tuhu.springaop.proxy; import cn.tuhu.springaop.annotation.MyTransactional; import cn.tuhu.springaop.util.TransactionUtils; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.AfterThrowing; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.reflect.MethodSignature; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import org.springframework.transaction.TransactionStatus; import org.springframework.transaction.interceptor.TransactionAspectSupport; import java.lang.reflect.Method; @Component @Aspect public class TransactionAop { @Autowired TransactionUtils transactionUtils; private ProceedingJoinPoint proceedingJoinPoint; @Around(value = "execution(* cn.tuhu.springaop.service.impl.*.*(..))") public void transactionHandler(ProceedingJoinPoint proceedingJoinPoint) throws Throwable { TransactionStatus transactionStatus = null; if (hasTransaction(proceedingJoinPoint)) { transactionStatus = transactionUtils.begin(); } proceedingJoinPoint.proceed(); // 若hasTransaction(proceedingJoinPoint)判断通过,则transactionStatus不为null if (transactionStatus != null) { transactionUtils.commit(transactionStatus); } } /** * 判断切入点是否标注了@MyTransactional注解 * * @param proceedingJoinPoint * @return */ private boolean hasTransaction(ProceedingJoinPoint proceedingJoinPoint) throws NoSuchMethodException { this.proceedingJoinPoint = proceedingJoinPoint; //获取方法名 String methodName = proceedingJoinPoint.getSignature().getName(); //获取方法所在类的class对象 Class clazz = proceedingJoinPoint.getSignature().getDeclaringType(); //获取参数列表类型 Class[] parameterTypes = ((MethodSignature) proceedingJoinPoint.getSignature()).getParameterTypes(); //根据方法名和方法参列各参数类型可定位类中唯一方法 Method method = clazz.getMethod(methodName, parameterTypes); //根据方法对象获取方法上的注解信息 MyTransactional myTransactional = method.getAnnotation(MyTransactional.class); return myTransactional == null ? false : true; } @AfterThrowing(value = "execution(* cn.tuhu.springaop.service.impl.*.*(..))") public void handleTransactionRollback() throws NoSuchMethodException { if (hasTransaction(proceedingJoinPoint)) { //获取当前事务并回滚 TransactionAspectSupport.currentTransactionStatus().setRollbackOnly(); } } } 复制代码
测试通过!
Spring
事务注解 @Transactional
中有一个属性 propagation
表示当前事务的传播行为,可选值如下:
事务的传播行为发生在多个事务之间,默认值为 REQUIRED
Propagation propagation() default Propagation.REQUIRED; 复制代码
最常用的也只有 REQUIRED
和 REQUIRED_NEW
两个选项,下面仅介绍这两者的含义,其他的可自己尝试。
假设有这样一个场景,对于订单表 order
有一张订单日志表 order_log
专门用来记录生成订单的请求。要求每次请求生成订单时无论订单是否生成成功,该请求都应该被记录下来,即请求信息始终会插入 order_log
而不应该受 OrderService
事务控制的影响。
如下,我们可以为插入日志的操作指定 REQUIRED_NEW
,这样如果在调用 addOrder
中调用 addLog
时会因为 addOrder
已开启了事务于是将该事务挂起,并为 addLog
新建一个事务,这样 addLog
独立于 addOrder
的事务之外自然不会受其回滚的影响了。
@Service public class OrderLogService{ @Transactional(rollbackFor = Exception.class,propagation = Propagation.REQUIRES_NEW) public void addLog(){ // recode request info } } @Service public class OrderService{ @Autowire OrderLogService orderLogService; @Transactional(rollbackFor = Exception.class) public void addOrder(){ orderLogService.addLog(); // generate order // ... int i = 1 / 0 ; } } 复制代码
否则,如果不为 addLog
添加事务或者将其事务传播行为采用默认的 REQUIRED
的话, addLog
中的逻辑就会与 addOrder
中的逻辑处于同一事务中,一旦生成订单过程中出现异常,那么插入日志也会一起被回滚。