本篇分享的内容是在相同类中方法间调用时Aop失效处理方案,该问题我看有很多文章描述了,不过大多是从事务角度分享的,本篇打算从日志aop方面分享(当然都是aop,失效和处理方案都是一样),以下都是基于springboot演示;
日志我还是喜欢log4j,大部分朋友也同样吧,这里lombok与log4j结合来完成我们的日志,如下maven包(最新mvn还是建议去官网找):
1 <dependency> 2 <groupId>org.projectlombok</groupId> 3 <artifactId>lombok</artifactId> 4 </dependency> 6 <dependency> 7 <groupId>org.slf4j</groupId> 8 <artifactId>slf4j-api</artifactId> 9 <version>2.0.0-alpha0</version> 10 </dependency> 11 <dependency> 12 <groupId>org.slf4j</groupId> 13 <artifactId>slf4j-log4j12</artifactId> 14 <version>2.0.0-alpha0</version> 15 </dependency>
先继承log4j的AppenderSkeleton重写下append方法,简单记录下就行,如下:
1 public class MyLogAppend extends AppenderSkeleton { 2 private String author; 3 4 public void setAuthor(String author) { 5 this.author = author; 6 } 7 8 @Override 9 protected void append(LoggingEvent loggingEvent) { 10 System.out.println( 11 JsonUtil.formatMsg("date -- {},level -- {},message -- {}", 12 LocalDate.now(), 13 loggingEvent.getLevel(), 14 loggingEvent.getMessage())); 15 } 16 17 @Override 18 public void activateOptions() { 19 super.activateOptions(); 20 System.out.println("author:" + this.author); 21 } 22 23 @Override 24 public void close() { 25 this.closed = true; 26 } 27 28 @Override 29 public boolean requiresLayout() { 30 return false; 31 } 32 }
然后项目根目录增加log4j.properties配置文件,配置内容定义info级别,就此完成了log4j自定义记录日志了:
1 log4j.rootLogger=info,MyLogAppend 2 log4j.appender.MyLogAppend=com.sm.component.log.MyLogAppend 3 log4j.appender.MyLogAppend.author=shenniu003
通常同类中不同方法调用是常事,可以直接用this.xx();有时有这样需求,需要各个调用方法时候的参数记录下来,因此我们需要个拦截器,再增加个自定义注解方便使用:
1 @Aspect 2 @Component 3 @Slf4j 4 public class MyLogInterceptor { 5 6 private final String pointcut = "@annotation(com.sm.component.ServiceLog)"; 7 8 @Pointcut(pointcut) 9 public void log() { 10 } 11 12 @Before(value = "log()") 13 void before(JoinPoint joinPoint) { 14 Signature signature = joinPoint.getSignature(); 15 log.info( 16 JsonUtil.formatMsg("method:{},params:{}", 17 signature.toLongString(), 18 joinPoint.getArgs())); 19 } 20 }
1 @Documented 2 @Target({ElementType.METHOD}) 3 @Retention(RetentionPolicy.RUNTIME) 4 public @interface ServiceLog { 5 }
拦截器拦截带有@ServiceLog注解的方法,然后记录请求参数和方法名;
利用上面完成的日志注解,这里在OrderService类中用getOrderDetail方法去调用getOrderLog方法,他两都标记日志注解便于记录参数日志;同时getOrderDetail方法也调用另外一个UserService类中的getNickName方法,便于比较:
1 @Service 2 public class OrderService { 3 4 @Autowired 5 UserService userService; 6 7 @ServiceLog 8 public String getOrderDetail(String orderNum) { 9 String des = "订单号【" + orderNum + "】月饼一盒"; 11 userService.getNickName(orderNum); 13 this.getOrderLog(orderNum + "11111"); 15 return des; 16 } 17 18 @ServiceLog 19 public List<String> getOrderLog(String orderNum) { 20 List<String> logs = new ArrayList<>(); 21 IntStream.range(0, 5).forEach(b -> { 22 logs.add("用户" + b + "购买成功"); 23 }); 24 return logs; 25 } 26 }
1 @Service 2 public class UserService { 3 @ServiceLog 4 public String getNickName(String userId) { 5 return "神牛" + userId; 6 } 7 }
方法调用重点截图:
然后运行程序,接口触发调用getOrderDetail方法,以下拦截器中记录的日志信息:
能够看出拦截器只记录到了getOrderDetail和getNickName方法的日志,因此可以肯定getOrderLog根本没有走拦截器,尽管在方法上加了日志@ServiceLog注解也没用。
就上面相同类中方法间调用拦截器(aop)没起作用,我们有如下常用两种方式处理方案;
第一种:主要使用注解方法引入自身代理依赖,不要使用构造的方式会有循环依赖问题,以下使用方式:
第二种:通过暴露代理类方式,实际原理是把代理类添加到当前请求的ThreadLocal里面,然后在使用时从ThreadLocal中获取代理类,再调用对应的方法,开启方式需要:
1 @EnableAspectJAutoProxy(exposeProxy = true)
然后方法中如下使用即可:
最后来看下使用这两种方式正常走拦截器效果:
不管是日志拦截器或事务,他们都是aop的方式,底层原理走的代理方式,只有使用代理类才会正常执行拦截器,而this.xxx()使用的是自身实例对象,因此会出现上面失效的情况。