朱晔和你聊Spring系列S1E6:容易犯错的Spring AOP
2021-05-16 09:29
标签:过程 模板 项目 拦截器 一个 推荐 std target 切换 标题有点标题党了,这里说的容易犯错不是Spring AOP的错,是指使用的时候容易犯错。本文会以一些例子来展开讨论AOP的使用以及使用过程中容易出错的点。 织入说通俗点就是怎么把增强代码注入到连接点,和被增强的代码融入到一起。 新建一个模块: 在这里我们引入了jackson,以后我们会用来做JSON序列化。引入了mybatis启动器,以后我们会用mybstis做数据访问。引入了h2嵌入式数据库,方便本地测试使用。引入了web启动器,之后我们还会来测试一下对web项目的Controller进行注入。 然后,我们在resources文件夹下创建schema.sql文件来初始化h2数据库: 还可以在resources文件夹下创建data.sql来初始化数据: 这样程序启动后就会有一个PERSON表,表里有一条ID为1的记录。 这里我们定义了4个方法: 现在我们来创建服务接口: 定义了插入数据和查询数据的两个方法,下面是实现: getData方法我们就不细说了,只是实现了休眠然后根据传入的myBean作为模板组装了count条测试数据返回。我们来重点看一下insertData方法,这就是使用Spring AOP的一个坑了。看上去配置啥的都没问题,但是_insertData是不能生效自动事务管理的。 我们知道Spring AOP使用代理目标对象方式实现AOP,在从外部调用insertData方法的时候其实走的是代理,这个时候事务环绕可以生效,在方法内部我们通过this引用调用_insertData方法,虽然方法外部我们设置了Transactional注解,但是由于走的不是代理调用,Spring AOP自然无法通过AOP增强为我们做事务管理。 我们来创建主程序测试一下: 在Runner中,我们使用true和false调用了两次insertData方法。后面一次调用肯定会失败,因为_insert方法中会进行重复ID的数据插入。运行程序后得到如下输出: 从日志的几处我们都可以得到结论,事务管理没有生效: 那么,如何解决这个问题呢,有三种方式: 这里还容易犯错的地方是,这里不能对异常进行捕获,否则Spring事务代理无法捕获到异常也就无法实现回滚。 那么原来这段代码如何不改造实现事务呢?可以通过AspjectJ编译时静态织入实现。整个配置过程如下: 这里的一个坑是ajc编译器无法支持lambok,我们需要先使用lombok的插件在生成源码阶段对lombok代码进行预处理,然后我们再通过aspjectj插件来编译代码。Pom文件中还需要加入如下依赖: 然后需要配置Spring来使用ASPECTJ的增强方式来做事务管理: 运行程序可以看到如下日志: 而且最后输出的结果是2,说明第二次插入数据整体回滚了。 我们先使用刚才说的方法3改造一下代码,使得Spring AOP可以处理事务(Aspject AOP功能虽然强大但是和Spring结合的不好,所以我们接下去的测试还是使用Spring AOP),删除aspjectj相关依赖,在IDEA配置回javac编译器重新编译项目。本节中我们尝试建立第一个我们的切面: 在这里,我们的切点是所有标记了@Transactional注解的类以及标记了@Transactional注解的方法,我们的增强比较简单,在事务同步管理器注册一个回调方法,用于事务完成后进行额外的处理。这里的一个坑是Spring如何实例化切面。通过查文档或做实验可以得知,默认情况下TranscationalAspect是单例的,在多线程情况下,可能会有并发,保险起见我们使用ThreadLocal来存放。运行代码后可以看到如下输出: 可以看到Spring AOP做了事务管理,我们两次事务提交第一次成功第二次失败,失败后记录数还是2。这个功能还可以通过Spring的@TransactionalEventListener注解实现,这里不详述。 我们现在注入的是接口,我们知道对于这种情况Spring AOP应该使用的是JDK代理。但是SpringBoot默认开启了下面的属性来全局启用CGLIB代理: 我们尝试把这个属性设置成false,然后在刚才的TransationalAspect中的增强方法设置断点,可以看到这是一个ReflectiveMethodInvocation: 我们修改我们的MyServiceImpl,去掉实现接口的代码和@Override注解,使之成为一个普通的类,重新运行程序可以看到我们的代理方式自动降级为了CGLIB方式(虽然spring.aop.proxy-target-class参数我们现在设置的是false)。 现在我们来实现一个复杂点的切面的例子。我们知道,出错记录异常信息,对于方法调用记录打点信息(如果不知道什么是打点可以参看《朱晔的互联网架构实践心得S1E4:简单好用的监控六兄弟》),甚至有的时候为了排查问题需要记录方法的入参和返回,这三个事情是我们经常需要做的和业务逻辑无关的事情,我们可以尝试使用AOP的方式一键切入这三个事情的实现,在业务代码无感知的情况下做好监控和打点。 下面我们就来实现这个切面: 看上去代码量很多,其实实现比较简单: 正确实现了参数日志、异常日志、成功失败打点(含耗时统计)等功能。 运行程序打开浏览器访问http://localhost:8080/data后能看到如下输出: 最后,我们再来踩一个坑。我们来测一下ignoreException吞掉异常的功能(默认为false): 这个功能会吞掉异常,在和Transactional事务管理结合时候会不会出问题呢? 再次运行程序可以看到事务正确回滚。 本文我们通过一些例子覆盖了如下内容: 在整个过程中,也踩了下面的坑,印证的本文的标题: 老样子,本系列文章代码见我的github:https://github.com/JosephZhu1983/Spring101。 朱晔和你聊Spring系列S1E6:容易犯错的Spring AOP 标签:过程 模板 项目 拦截器 一个 推荐 std target 切换 原文地址:https://www.cnblogs.com/lovecindywang/p/9749658.html几句话说清楚AOP
有关必要术语:
有关使用方式:
有关织入方式:
使用Spring AOP实现事务的坑
先来定义一下我们的测试数据类:package me.josephzhu.spring101aop;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.math.BigDecimal;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class MyBean {
private Long id;
private String name;
private Integer age;
private BigDecimal balance;
}
CREATE TABLE PERSON(
ID BIGINT PRIMARY KEY AUTO_INCREMENT,
NAME VARCHAR(255),
AGE SMALLINT,
BALANCE DECIMAL
);
INSERT INTO PERSON (NAME, AGE, BALANCE) VALUES ('zhuye', 35, 1000);
通过启动器使用Mybatis非常简单,无需进行任何配置,建一个Mapper接口:package me.josephzhu.spring101aop;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;
import java.util.List;
@Mapper
public interface DbMapper {
@Select("SELECT COUNT(0) FROM PERSON")
int personCount();
@Insert("INSERT INTO PERSON (NAME, AGE, BALANCE) VALUES ('zhuye', 35, 1000)")
void personInsertWithoutId();
@Insert("INSERT INTO PERSON (ID, NAME, AGE, BALANCE) VALUES (1,'zhuye', 35, 1000)")
void personInsertWithId();
@Select("SELECT * FROM PERSON")
List
为了我们可以观察到数据库连接是否被Spring纳入事务管理,我们在application.properties配置文件中设置mybatis的Spring事务日志级别为DEBUG:logging.level.org.mybatis.spring.transaction=DEBUG
package me.josephzhu.spring101aop;
import java.time.Duration;
import java.util.List;
public interface MyService {
void insertData(boolean success);
List
package me.josephzhu.spring101aop;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.time.Duration;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
@Service
public class MyServiceImpl implements MyService {
@Autowired
private DbMapper dbMapper;
@Transactional(rollbackFor = Exception.class)
public void _insertData(boolean success){
dbMapper.personInsertWithoutId();
if(!success)
dbMapper.personInsertWithId();
}
@Override
public void insertData(boolean success) {
try {
_insertData(success);
} catch (Exception ex) {
ex.printStackTrace();
}
System.out.println("记录数:" + dbMapper.personCount());
}
@Override
public List
package me.josephzhu.spring101aop;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.AdviceMode;
import org.springframework.context.annotation.Configuration;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import java.math.BigDecimal;
import java.time.Duration;
@SpringBootApplication
public class Spring101AopApplication implements CommandLineRunner {
public static void main(String[] args) {
SpringApplication.run(Spring101AopApplication.class, args);
}
@Autowired
private MyService myService;
@Override
public void run(String... args) throws Exception {
myService.insertData(true);
myService.insertData(false);
System.out.println(myService.getData(new MyBean(0L, "zhuye",35, new BigDecimal("1000")),
5,
Duration.ofSeconds(1)));
}
}
2018-10-07 09:11:44.605 INFO 19380 --- [ main] m.j.s.Spring101AopApplication : Started Spring101AopApplication in 3.072 seconds (JVM running for 3.74)
2018-10-07 09:11:44.621 DEBUG 19380 --- [ main] o.m.s.t.SpringManagedTransaction : JDBC Connection [HikariProxyConnection@2126664214 wrapping conn0: url=jdbc:h2:mem:testdb user=SA] will not be managed by Spring
2018-10-07 09:11:44.626 DEBUG 19380 --- [ main] o.m.s.t.SpringManagedTransaction : JDBC Connection [HikariProxyConnection@775174220 wrapping conn0: url=jdbc:h2:mem:testdb user=SA] will not be managed by Spring
记录数:2
2018-10-07 09:11:44.638 DEBUG 19380 --- [ main] o.m.s.t.SpringManagedTransaction : JDBC Connection [HikariProxyConnection@2084486251 wrapping conn0: url=jdbc:h2:mem:testdb user=SA] will not be managed by Spring
2018-10-07 09:11:44.638 DEBUG 19380 --- [ main] o.m.s.t.SpringManagedTransaction : JDBC Connection [HikariProxyConnection@26418585 wrapping conn0: url=jdbc:h2:mem:testdb user=SA] will not be managed by Spring
2018-10-07 09:11:44.642 INFO 19380 --- [ main] o.s.b.f.xml.XmlBeanDefinitionReader : Loading XML bean definitions from class path resource [org/springframework/jdbc/support/sql-error-codes.xml]
org.springframework.dao.DuplicateKeyException:
### Error updating database. Cause: org.h2.jdbc.JdbcSQLException: Unique index or primary key violation: "PRIMARY KEY ON PUBLIC.PERSON(ID)"; SQL statement:
INSERT INTO PERSON (ID, NAME, AGE, BALANCE) VALUES (1,'zhuye', 35, 1000) [23505-197]
2018-10-07 09:11:44.689 DEBUG 19380 --- [ main] o.m.s.t.SpringManagedTransaction : JDBC Connection [HikariProxyConnection@529949842 wrapping conn0: url=jdbc:h2:mem:testdb user=SA] will not be managed by Spring
记录数:3
[MyBean(id=1, name=zhuye1, age=35, balance=1000), MyBean(id=2, name=zhuye2, age=35, balance=1000), MyBean(id=3, name=zhuye3, age=35, balance=1000), MyBean(id=4, name=zhuye4, age=35, balance=1000), MyBean(id=5, name=zhuye5, age=35, balance=1000)]
@Override
@Transactional(rollbackFor = Exception.class)
public void insertData(boolean success) {
dbMapper.personInsertWithoutId();
if(!success)
dbMapper.personInsertWithId();
}
使用AspectJ静态织入进行改造
首先在pom中加入下面的配置:
@EnableTransactionManagement(mode = AdviceMode.ASPECTJ)
public class Spring101AopApplication implements CommandLineRunner {
重新使用maven编译代码后可以看到,相关代码已经变了样:@Transactional(
rollbackFor = {Exception.class}
)
public void _insertData(boolean success) {
AnnotationTransactionAspect var10000 = AnnotationTransactionAspect.aspectOf();
Object[] var3 = new Object[]{this, Conversions.booleanObject(success)};
var10000.ajc$around$org_springframework_transaction_aspectj_AbstractTransactionAspect$1$2a73e96c(this, new MyServiceImpl$AjcClosure1(var3), ajc$tjp_0);
}
public void insertData(boolean success) {
try {
this._insertData(success);
} catch (Exception var3) {
var3.printStackTrace();
}
System.out.println("记录数:" + this.dbMapper.personCount());
}
2018-10-07 09:35:12.360 DEBUG 19459 --- [ main] o.m.s.t.SpringManagedTransaction : JDBC Connection [HikariProxyConnection@1169317628 wrapping conn0: url=jdbc:h2:mem:testdb user=SA] will be managed by Spring
如果使用IDEA的话还可以配置先由javac编译再由ajc后处理,具体参见IDEA官网这里不详述。使用AOP进行事务后处理
package me.josephzhu.spring101aop;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.transaction.support.TransactionSynchronizationAdapter;
import org.springframework.transaction.support.TransactionSynchronizationManager;
@Aspect
@Component
@Slf4j
class TransactionalAspect extends TransactionSynchronizationAdapter {
@Autowired
private DbMapper dbMapper;
private ThreadLocal
2018-10-07 10:01:32.384 INFO 19599 --- [ main] m.j.spring101aop.TransactionalAspect : 【class me.josephzhu.spring101aop.MyServiceImpl】【public void me.josephzhu.spring101aop.MyServiceImpl.insertData(boolean)】事务提交 成功,目前记录数:2
2018-10-07 10:01:32.385 DEBUG 19599 --- [ main] o.m.s.t.SpringManagedTransaction : JDBC Connection [HikariProxyConnection@1430104337 wrapping conn0: url=jdbc:h2:mem:testdb user=SA] will be managed by Spring
2018-10-07 10:01:32.449 DEBUG 19599 --- [ main] o.m.s.t.SpringManagedTransaction : JDBC Connection [HikariProxyConnection@1430104337 wrapping conn0: url=jdbc:h2:mem:testdb user=SA] will be managed by Spring
2018-10-07 10:01:32.449 INFO 19599 --- [ main] m.j.spring101aop.TransactionalAspect : 【class me.josephzhu.spring101aop.MyServiceImpl】【public void me.josephzhu.spring101aop.MyServiceImpl.insertData(boolean)】事务提交 失败,目前记录数:2
切换JDK代理和CGLIB代理
spring.aop.proxy-target-class=true
把配置改为true重新观察可以看到变为了CglibMethodInvocation:
我们把开关改为false,然后切换到注入实现,运行程序会得到如下错误提示,意思就是我我们走JDK代理的话不能注入实现,需要注入接口:The bean 'myServiceImpl' could not be injected as a 'me.josephzhu.spring101aop.MyServiceImpl' because it is a JDK dynamic proxy that implements:
me.josephzhu.spring101aop.MyService
使用AOP无缝实现日志+异常+打点
首先实现我们的注解,通过这个注解我们可以细化控制一些功能:package me.josephzhu.spring101aop;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Metrics {
/**
* 是否在成功执行方法后打点
* @return
*/
boolean recordSuccessMetrics() default true;
/**
* 是否在执行方法出错时打点
* @return
*/
boolean recordFailMetrics() default true;
/**
* 是否记录请求参数
* @return
*/
boolean logParameters() default true;
/**
* 是否记录返回值
* @return
*/
boolean logReturn() default true;
/**
* 是否记录异常
* @return
*/
boolean logException() default true;
/**
* 是否屏蔽异常返回默认值
* @return
*/
boolean ignoreException() default false;
}
package me.josephzhu.spring101aop;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.time.Duration;
import java.time.Instant;
@Aspect
@Component
@Slf4j
public class MetricsAspect {
private static ObjectMapper objectMapper = new ObjectMapper();
@Around("@annotation(me.josephzhu.spring101aop.Metrics) || @within(org.springframework.stereotype.Controller)")
public Object metrics(ProceedingJoinPoint pjp) throws Throwable {
//1
MethodSignature signature = (MethodSignature) pjp.getSignature();
Metrics metrics;
String name;
if (signature.getDeclaringType().isInterface()) {
Class implClass = pjp.getTarget().getClass();
Method method = implClass.getMethod(signature.getName(), signature.getParameterTypes());
metrics = method.getDeclaredAnnotation(Metrics.class);
name = String.format("【%s】【%s】", implClass.toString(), method.toString());
} else {
metrics = signature.getMethod().getAnnotation(Metrics.class);
name = String.format("【%s】【%s】", signature.getDeclaringType().toString(), signature.toLongString());
}
//2
if (metrics == null)
metrics = new Metrics() {
@Override
public boolean logException() {
return true;
}
@Override
public boolean logParameters() {
return true;
}
@Override
public boolean logReturn() {
return true;
}
@Override
public boolean recordFailMetrics() {
return true;
}
@Override
public boolean recordSuccessMetrics() {
return true;
}
@Override
public boolean ignoreException() {
return false;
}
@Override
public Class extends Annotation> annotationType() {
return Metrics.class;
}
};
RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
if (requestAttributes != null) {
HttpServletRequest request = ((ServletRequestAttributes) requestAttributes).getRequest();
if (request != null)
name += String.format("【%s】", request.getRequestURL().toString());
}
//3
if (metrics.logParameters())
log.info(String.format("【入参日志】调用 %s 的参数是:【%s】", name, objectMapper.writeValueAsString(pjp.getArgs())));
//4
Object returnValue;
Instant start = Instant.now();
try {
returnValue = pjp.proceed();
if (metrics.recordSuccessMetrics())
log.info(String.format("【成功打点】调用 %s 成功,耗时:%s", name, Duration.between(Instant.now(), start).toString()));
} catch (Exception ex) {
if (metrics.recordFailMetrics())
log.info(String.format("【失败打点】调用 %s 失败,耗时:%s", name, Duration.between(Instant.now(), start).toString()));
if (metrics.logException())
log.error(String.format("【异常日志】调用 %s 出现异常!", name), ex);
if (metrics.ignoreException())
returnValue = getDefaultValue(signature.getReturnType().toString());
else
throw ex;
}
//5
if (metrics.logReturn())
log.info(String.format("【出参日志】调用 %s 的返回是:【%s】", name, returnValue));
return returnValue;
}
private static Object getDefaultValue(String clazz) {
if (clazz.equals("boolean")) {
return false;
} else if (clazz.equals("char")) {
return '\u0000';
} else if (clazz.equals("byte")) {
return 0;
} else if (clazz.equals("short")) {
return 0;
} else if (clazz.equals("int")) {
return 0;
} else if (clazz.equals("long")) {
return 0L;
} else if (clazz.equals("flat")) {
return 0.0F;
} else if (clazz.equals("double")) {
return 0.0D;
} else {
return null;
}
}
}
最后,我们修改一下MyServiceImpl的实现,在insertData和getData两个方法上加入我们的@Metrics注解。运行程序可以看到如下输出:2018-10-07 10:47:00.813 INFO 19737 --- [ main] me.josephzhu.spring101aop.MetricsAspect : 【入参日志】调用 【class me.josephzhu.spring101aop.MyServiceImpl】【public void me.josephzhu.spring101aop.MyServiceImpl.insertData(boolean)】 的参数是:【[true]】
2018-10-07 10:47:00.864 INFO 19737 --- [ main] me.josephzhu.spring101aop.MetricsAspect : 【成功打点】调用 【class me.josephzhu.spring101aop.MyServiceImpl】【public void me.josephzhu.spring101aop.MyServiceImpl.insertData(boolean)】 成功,耗时:PT-0.048S
2018-10-07 10:47:00.864 INFO 19737 --- [ main] me.josephzhu.spring101aop.MetricsAspect : 【出参日志】调用 【class me.josephzhu.spring101aop.MyServiceImpl】【public void me.josephzhu.spring101aop.MyServiceImpl.insertData(boolean)】 的返回是:【null】
2018-10-07 10:47:00.927 INFO 19737 --- [ main] me.josephzhu.spring101aop.MetricsAspect : 【入参日志】调用 【class me.josephzhu.spring101aop.MyServiceImpl】【public void me.josephzhu.spring101aop.MyServiceImpl.insertData(boolean)】 的参数是:【[false]】
2018-10-07 10:47:01.084 INFO 19737 --- [ main] me.josephzhu.spring101aop.MetricsAspect : 【失败打点】调用 【class me.josephzhu.spring101aop.MyServiceImpl】【public void me.josephzhu.spring101aop.MyServiceImpl.insertData(boolean)】 失败,耗时:PT-0.156S
2018-10-07 10:47:01.102 ERROR 19737 --- [ main] me.josephzhu.spring101aop.MetricsAspect : 【异常日志】调用 【class me.josephzhu.spring101aop.MyServiceImpl】【public void me.josephzhu.spring101aop.MyServiceImpl.insertData(boolean)】 出现异常!
2018-10-07 10:47:01.231 INFO 19737 --- [ main] me.josephzhu.spring101aop.MetricsAspect : 【入参日志】调用 【class me.josephzhu.spring101aop.MyServiceImpl】【public java.util.List me.josephzhu.spring101aop.MyServiceImpl.getData(me.josephzhu.spring101aop.MyBean,int,java.time.Duration)】 的参数是:【[{"id":0,"name":"zhuye","age":35,"balance":1000},5,{"seconds":1,"zero":false,"nano":0,"units":["SECONDS","NANOS"],"negative":false}]】
2018-10-07 10:47:02.237 INFO 19737 --- [ main] me.josephzhu.spring101aop.MetricsAspect : 【成功打点】调用 【class me.josephzhu.spring101aop.MyServiceImpl】【public java.util.List me.josephzhu.spring101aop.MyServiceImpl.getData(me.josephzhu.spring101aop.MyBean,int,java.time.Duration)】 成功,耗时:PT-1.006S
2018-10-07 10:47:02.237 INFO 19737 --- [ main] me.josephzhu.spring101aop.MetricsAspect : 【出参日志】调用 【class me.josephzhu.spring101aop.MyServiceImpl】【public java.util.List me.josephzhu.spring101aop.MyServiceImpl.getData(me.josephzhu.spring101aop.MyBean,int,java.time.Duration)】 的返回是:【[MyBean(id=1, name=zhuye1, age=35, balance=1000), MyBean(id=2, name=zhuye2, age=35, balance=1000), MyBean(id=3, name=zhuye3, age=35, balance=1000), MyBean(id=4, name=zhuye4, age=35, balance=1000), MyBean(id=5, name=zhuye5, age=35, balance=1000)]】
[MyBean(id=1, name=zhuye1, age=35, balance=1000), MyBean(id=2, name=zhuye2, age=35, balance=1000), MyBean(id=3, name=zhuye3, age=35, balance=1000), MyBean(id=4, name=zhuye4, age=35, balance=1000), MyBean(id=5, name=zhuye5, age=35, balance=1000)]
下面我们创建一个Controller来测试一下是否可以自动切入Controller:package me.josephzhu.spring101aop;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import java.util.List;
@Controller
public class MyController {
@Autowired
private DbMapper dbMapper;
@ResponseBody
@GetMapping("/data")
public List
2018-10-07 10:49:53.811 INFO 19737 --- [nio-8080-exec-1] me.josephzhu.spring101aop.MetricsAspect : 【入参日志】调用 【class me.josephzhu.spring101aop.MyController】【public java.util.List me.josephzhu.spring101aop.MyController.getPersonList()】【http://localhost:8080/data】 的参数是:【[]】
2018-10-07 10:49:53.819 INFO 19737 --- [nio-8080-exec-1] me.josephzhu.spring101aop.MetricsAspect : 【成功打点】调用 【class me.josephzhu.spring101aop.MyController】【public java.util.List me.josephzhu.spring101aop.MyController.getPersonList()】【http://localhost:8080/data】 成功,耗时:PT-0.008S
2018-10-07 10:49:53.819 INFO 19737 --- [nio-8080-exec-1] me.josephzhu.spring101aop.MetricsAspect : 【出参日志】调用 【class me.josephzhu.spring101aop.MyController】【public java.util.List me.josephzhu.spring101aop.MyController.getPersonList()】【http://localhost:8080/data】 的返回是:【[MyBean(id=1, name=zhuye, age=35, balance=1000), MyBean(id=2, name=zhuye, age=35, balance=1000)]】
@Transactional(rollbackFor = Exception.class)
@Override
@Metrics(ignoreException = true)
public void insertData(boolean success){
dbMapper.personInsertWithoutId();
if(!success)
dbMapper.personInsertWithId();
}
开启这个配置后刷新页面可以看到数据库内有三条记录了,说明第二次的insertData方法执行没有成功回滚事务。这也是合情合理的,毕竟我们的MetricsAspect吃掉了异常。
怎么绕开这个问题呢?答案是我们需要手动控制一下我们的切面的执行优先级,我们希望这个切面优先级比Spring事务控制切面优先级低:@Aspect
@Component
@Slf4j
@Order(1)
public class MetricsAspect {
总结
文章标题:朱晔和你聊Spring系列S1E6:容易犯错的Spring AOP
文章链接:http://soscw.com/index.php/essay/86193.html