我的博客:zhenganwen.topphp
AOP
,即Aspect-Oriented Program
面向切面编程,相比较继承、装饰者模式等纵向加强对象的方式,AOP
是横向的、无入侵性的、可插拔的、高复用的。所以做为Spring
的核心模块之一,它普遍应用于日志记录、事务管理、权限控制、异常处理等场景。java
因为AOP
是基于动态代理的,因此本节先简单介绍一下代理模式。代理模式分为静态代理和动态代理,核心思想是在调用方和被调用方(被代理对象,也称目标对象)起到一个中介(代理对象)的做用,这样可以达到解耦、复用、保护的效果:mysql
代理对象对调用请求是不作实际的业务处理的spring
经过实现和目标对象一样的接口,以实现和目标对象有相同的外观。经过保存目标对象的引用来对请求作实际的业务处理。sql
静态代理示例代码以下:数据库
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
复制代码
静态代理的弊端:api
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
中的逻辑处于同一事务中,一旦生成订单过程当中出现异常,那么插入日志也会一块儿被回滚。