1. 程式人生 > >相同類中方法間呼叫時日誌Aop失效處理

相同類中方法間呼叫時日誌Aop失效處理

本篇分享的內容是在相同類中方法間呼叫時Aop失效處理方案,該問題我看有很多文章描述了,不過大多是從事務角度分享的,本篇打算從日誌aop方面分享(當然都是aop,失效和處理方案都是一樣),以下都是基於springboot演示;

  • 快速定義個日誌Appender
  • 快速定義個攔截器和日誌註解(aop)
  • 模擬相同類中方法間呼叫時aop失效
  • Aop失效處理方案(就兩種足夠了)

快速定義個日誌Appender

日誌我還是喜歡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

快速定義個攔截器和日誌註解(aop)

通常同類中不同方法呼叫是常事,可以直接用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註解的方法,然後記錄請求引數和方法名;

模擬相同類中方法間呼叫時aop失效

利用上面完成的日誌註解,這裡在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失效處理方案(就兩種足夠了)

就上面相同類中方法間呼叫攔截器(aop)沒起作用,我們有如下常用兩種方式處理方案;

  1. 用@Autowired或Resource引入自身依賴
  2. 開啟暴露代理類,AopContext.currentProxy()方式獲取代理類

第一種:主要使用註解方法引入自身代理依賴,不要使用構造的方式會有迴圈依賴問題,以下使用方式:

第二種:通過暴露代理類方式,實際原理是把代理類新增到當前請求的ThreadLocal裡面,然後在使用時從ThreadLocal中獲取代理類,再呼叫對應的方法,開啟方式需要:

1 @EnableAspectJAutoProxy(exposeProxy = true)

然後方法中如下使用即可:

最後來看下使用這兩種方式正常走攔截器效果:

不管是日誌攔截器或事務,他們都是aop的方式,底層原理走的代理方式,只有使用代理類才會正常執行攔截器,而this.xxx()使用的是自身例項物件,因此會出現上面失效的情