Spring AOP中級——應用場景


  在《Spring AOP初級——入門及簡單應用》中對AOP作了簡要的介紹,以及一些專業術語的解釋,同時寫了一個簡單的Spring AOPdemo。本文將繼續探討Spring AOP在實際場景中的應用。

  對用戶操作日志的記錄是很常見的一個應用場景,本文選取“用戶管理”作為本文Spring AOP的示例。當然,該示例只是對真實場景的模擬,實際的環境一定比該示例更復雜。

  該示例的完整代碼路徑在https://github.com/yu-linfeng/BlogRepositories/tree/master/repositories/Spring%20AOP%E4%B8%AD%E7%BA%A7%E2%80%94%E2%80%94%E5%BA%94%E7%94%A8%E5%9C%BA%E6%99%AF。本文僅對Spring AOP相關的代碼進行講解。

  在這個示例中首次采用RESTful架構風格,對於以下RESTful API的設計可能並不完美,如果有熟悉、精通RESTful架構風格的朋友希望能夠指出我的錯誤。

  

  使用RESTful的前后端分離架構風格后,我感受到了前所未有的暢快,所以此次示例並沒有前端頁面的展示,完全使用JUnit進行單元測試包括對HTTP請求的Mock模擬,這部分代碼不會進行詳細講解,之后會繼續深入JUnit單元測試的一些學習研究。

  數據庫只有一張表:

  回到正題,我們回顧下切面由哪兩個部分組成: 通知 切點 首先明確我們需要在何時記錄日志:

  通知

  切點

  首先明確我們需要在何時記錄日志:

  1. 查詢所有用戶時,並沒有參數(此示例沒有作分頁),只有在返回時才會有數據的返回,所以對查詢所有用戶的方法采用返回通知(AfterReturning)。

  2. 新增用戶時,會帶有新增的參數,此時可采用前置通知(Before)。

  3. 修改用戶時,也會帶有新增的參數,此時同樣采用前置通知(Before)。

  4. 刪除用戶時,通常會帶有唯一標識符ID,此時采用前置通知(Before)記錄待刪除的用戶ID。

  在明確了通知類型后,此時我們需要明確切點,也就是在哪個地方記錄日志。當然上面實際已經明確了日志記錄的位置,但主要是切面表達式的書寫。 在有了《Spring AOP初級——入門及簡單應用》的基礎,相信對日志切面類已經比較熟悉了:

 1 package com.manager.aspect;
 2 
 3 import org.apache.log4j.Logger;
 4 import org.aspectj.lang.JoinPoint;
 5 import org.aspectj.lang.annotation.*;
 6 import org.springframework.stereotype.Component;
 7 
 8 import java.util.Arrays;
 9 
10 /**
11  * 日志切面
12  * Created by Kevin on 2017/10/29.
13  */
14 @Aspect
15 @Component
16 public class LogAspect {
17     /**
18      * 操作日志文件名
19      */
20     private static final String OPERATION_LOG_NAME = "operationLog";
21     private static final String LOG_FORMATTER = "%s.%s - %s";
22     Logger log = Logger.getLogger(OPERATION_LOG_NAME);
23     /**
24      * 對查詢方法記錄日志的切點
25      */
26     @Pointcut("execution(* com.manager..*.*Controller.query*(..))")
27     public void query(){}
28 
29     /**
30      * 對新增方法記錄日志的切點
31      */
32     @Pointcut("execution(* com.manager..*.*Controller.add*(..))")
33     public void add(){}
34 
35     /**
36      * 對修改方法記錄日志的切點
37      */
38     @Pointcut("execution(* com.manager..*.*Controller.update*(..))")
39     public void update(){}
40 
41     /**
42      * 對刪除方法記錄日志的切點
43      */
44     @Pointcut("execution(* com.manager..*.*Controller.delete*(..))")
45     public void delete(){}
46 
47     @AfterReturning(value = "query()", returning = "rvt")
48     public void queryLog(JoinPoint joinPoint, Object rvt) {
49         String className = joinPoint.getTarget().getClass().getName();
50         String methodName = joinPoint.getSignature().getName();
51         String returnResult = rvt.toString();
52         log.info(String.format(LOG_FORMATTER, className, methodName, returnResult));
53     }
54 
55     @Before("add()")
56     public void addLog(JoinPoint joinPoint) {
57         String className = joinPoint.getTarget().getClass().getName();
58         String methodName = joinPoint.getSignature().getName();
59         Object[] params = joinPoint.getArgs();
60         log.info(String.format(LOG_FORMATTER, className, methodName, Arrays.toString(params)));
61     }
62 
63     @Before("update()")
64     public void updateLog(JoinPoint joinPoint) {
65         String className = joinPoint.getTarget().getClass().getName();
66         String methodName = joinPoint.getSignature().getName();
67         Object[] params = joinPoint.getArgs();
68         log.info(String.format(LOG_FORMATTER, className, methodName, Arrays.toString(params)));
69     }
70 
71     @Before("delete()")
72     public void deleteLog(JoinPoint joinPoint) {
73         String className = joinPoint.getTarget().getClass().getName();
74         String methodName = joinPoint.getSignature().getName();
75         Object[] params = joinPoint.getArgs();
76         log.info(String.format(LOG_FORMATTER, className, methodName, Arrays.toString(params)));
77     }
78 }

  上面的日志切面類中出現了JointPoint類作為參數的情況,這個參數能夠傳遞被通知方法的相信,例如被通知方法所處的類以及方法名等。在第47行中的Object rvt參數就是獲取被通知方法的返回值。 上面的切面並沒有關注被通知方法的參數,如果要使得切面和被通知方法參數參數關聯可以使用以下的方式:

@Pointcut("execution(* com.xxx.demo.Demo.method(int)) && args(arg)")
public void aspectMethod(int arg){}

@Before(“aspectMedhot(arg)”)
    public void method(int arg) {
    //此時arg參數就是被通知方法的參數
}

  本例中最主要的切面部分就完成了。注意在結合Spring時需要在applicationContext.xml中加入以下語句:

<!--啟用AspectJ自動代理,其中proxy-target-class為true表示使用CGLib的代理方式,false表示JDK的代理方式,默認false-->
<aop:aspectj-autoproxy />

  示例中關於log4j、pom.xml依賴、JUnit如何結合Spring進行單元測試等等均可可以參考完整代碼。特別是JUnit是很值得學習研究的一部分,這部分在將來慢慢我也會不斷學習推出新的博客,在這里就只貼出JUnit的代碼,感興趣的可以瀏覽一下:

 1 package com.manager.user.controller;
 2 
 3 import com.fasterxml.jackson.databind.ObjectMapper;
 4 import com.manager.user.pojo.User;
 5 import org.junit.Before;
 6 import org.junit.Test;
 7 import org.junit.runner.RunWith;
 8 import org.springframework.beans.factory.annotation.Autowired;
 9 import org.springframework.http.MediaType;
10 import org.springframework.test.context.ContextConfiguration;
11 import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
12 import org.springframework.test.context.web.WebAppConfiguration;
13 import org.springframework.test.web.servlet.MockMvc;
14 import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
15 import org.springframework.test.web.servlet.result.MockMvcResultMatchers;
16 import org.springframework.test.web.servlet.setup.MockMvcBuilders;
17 import org.springframework.web.context.WebApplicationContext;
18 
19 import static org.junit.Assert.assertNotNull;
20 
21 /**
22  * UserController單元測試
23  * Created by Kevin on 2017/10/26.
24  */
25 @RunWith(SpringJUnit4ClassRunner.class)
26 @ContextConfiguration({"classpath*:applicationContext.xml", "classpath*:spring-servlet.xml"})
27 @WebAppConfiguration
28 public class UserControllerTest {
29     @Autowired
30     private WebApplicationContext wac;
31     private MockMvc mvc;
32 
33     @Before
34     public void initMockHttp() {
35         this.mvc = MockMvcBuilders.webAppContextSetup(wac).build();
36     }
37 
38     @Test
39     public void testQueryUsers() throws Exception {
40         mvc.perform(MockMvcRequestBuilders.get("/users"))
41                 .andExpect(MockMvcResultMatchers.status().isOk());
42     }
43 
44     @Test
45     public void testAddUser() throws Exception {
46         User user = new User();
47         user.setName("kevin");
48         user.setAge(23);
49         mvc.perform(MockMvcRequestBuilders.post("/users")
50                 .contentType(MediaType.APPLICATION_JSON_UTF8)
51                 .content(new ObjectMapper().writeValueAsString(user)))
52                 .andExpect(MockMvcResultMatchers.status().isOk())
53                 .andExpect(MockMvcResultMatchers.jsonPath("$.name").value("kevin"))
54                 .andExpect(MockMvcResultMatchers.jsonPath("$.age").value(23));
55     }
56 
57     @Test
58     public void testQueryUserById() throws Exception {
59         User user = new User();
60         user.setId(8);
61         mvc.perform(MockMvcRequestBuilders.get("/users/" + user.getId()))
62                 .andExpect(MockMvcResultMatchers.status().isOk())
63                 .andExpect(MockMvcResultMatchers.jsonPath("$.name").value("kevin"))
64                 .andExpect(MockMvcResultMatchers.jsonPath("$.age").value(23));
65 
66     }
67 
68     @Test
69     public void testUpdateUserById() throws Exception {
70         User user = new User();
71         user.setId(9);
72         user.setName("tony");
73         user.setAge(99);
74         mvc.perform(MockMvcRequestBuilders.put("/users/" + user.getId())
75                 .contentType(MediaType.APPLICATION_JSON_UTF8)
76                 .content(new ObjectMapper().writeValueAsString(user)))
77                 .andExpect(MockMvcResultMatchers.status().isOk());
78     }
79 
80     @Test
81     public void testDeleteUserById() throws Exception {
82         long id = 10;
83         mvc.perform(MockMvcRequestBuilders.delete("/users/" + id))
84                 .andExpect(MockMvcResultMatchers.status().isOk());
85     }
86 }

  有了初級和中級,接下來必然就是Spring AOP高級——源碼實現。

 

 

這是一個能給程序員加buff的公眾號 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM