SpringBoot中使用AOP打印接口日志的方法
前言
AOP 是 Aspect Oriented Program (面向切面)的編程的縮寫。他是和面向對象編程相對的一個概念。在面向對象的編程中,我們傾向于采用封裝、繼承、多態(tài)等概念,將一個個的功能在對象中來實現(xiàn)。但是,我們在實際情況中也發(fā)現(xiàn),會有另外一種需求就是一類功能在很多對象的很多方法中都有需要。例如有一些對數(shù)據(jù)庫訪問的方法有事務管理的需求,有很多方法中要求打印日志。按照面向對象的方式,那么這些相同的功能要在很多地方來實現(xiàn)或者在很多地方來調用。這就非常繁瑣并且和這些和業(yè)務不相關的需求耦合太緊密了。所以后來就出現(xiàn)了面向切面的編程來解決這一類問題,并對面向對象的編程做了很好的補充
概念
要很好的理解面向切面的編程,先要理解 AOP 的一些概念。在 Java 中 AspectJ 比較完整的實現(xiàn)了 AOP 的功能,但是使用起來也比較復,所以這里主要是討論 Spring 的 AOP 。Spring AOP 采用簡單夠用的原則,實現(xiàn)了 AOP 的核心功能。下面先說說 AOP 中的具體概念
- Aspect:方面。一個可以切入多個類的關注點。這個關注點實現(xiàn)了我們前面說的具體的業(yè)務功能。例如打印日志,進行數(shù)據(jù)庫的事務管理等。
- Joint point:被切入點。是指具體要實現(xiàn)前面所說的例如打印日志,數(shù)據(jù)庫事務管理的被切入的點。也就是通過 AOP 將切面功能動態(tài)加入進去的程序位置。在 Spring AOP 里面這個指的都是某個方法
- Pointcut:切點。用來指明如何通過規(guī)則匹配 Joint point。這個規(guī)則是一個表達式。在 Spring 中,默認使用的是 AspectJ 的 pointcut 表達式語言
- Advice:指明在一個切入點的不同位置上采取的動作。例如對于一個數(shù)據(jù)庫訪問事務管理來說,在進入方法后要開啟事務,在方法結束前要提交事務,在發(fā)生錯誤的時候要回滾事務。這屬于三個不同的 Advice,要分別進行實現(xiàn)。Advice 通常和具體的 Pointcut 關聯(lián)在一起。
- AOP proxy:AOP 代理。用來實現(xiàn)將 Advice 功能動態(tài)加入到 Pointcut 的方法。在 Spring 的 AOP 中采用動態(tài)代理和 CGLIB 代理的方式來實現(xiàn)。而 AspectJ 則采用了特定編譯器侵入字節(jié)碼的方式來實現(xiàn)。
SprinBoot AOP 實現(xiàn)
前面我們已經(jīng)用好幾章講述了 SpringBoot 的基本使用。那么這里我們就用 SpringBoot 和 AOP 結合來實現(xiàn)一個輸出所有 Rest 接口輸入?yún)?shù)和返回參數(shù)的日志的功能。
實現(xiàn) rest 服務功能。
根據(jù)前面的文章,我們先建立一個 SpingBoot 的工程如下圖所示
demo 工程
SpringBoot 項目配置
我們對 SpringBoot 項目配置如下
server: port: 3030 servlet: context-path: /aop-demo spring: jackson: date-format: yyyy-MM-dd HH:mm:ss serialization: indent-output: true logging: level: com.yanggch: debug
其中 jackson 相關配置是為了將對象輸出成 json 字符串后能夠格式化輸出
實現(xiàn)一個 rest 接口的 Controller 類
在這里,我們實現(xiàn)兩個 rest 接口。一個是返回 hello 信息。一個是根據(jù)輸入返回登錄信息。
package com.yanggch.demo.aop.web; import com.yanggch.demo.aop.domain.LoginEntity; import com.yanggch.demo.aop.domain.SecurityEntity; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RestController; import java.util.Date; /** * 安全相關 rest 服務 * * @author : 楊高超 * @since : 2018-05-27 */ @RestController @RequestMapping("/api/v1/security") public class SecurityApi { @RequestMapping(value = "/login/{shopId}", method = RequestMethod.POST) public SecurityEntity login(@RequestBody LoginEntity loginEntity, @PathVariable Long shopId) { SecurityEntity securityEntity = new SecurityEntity(); securityEntity.setShopId(shopId); securityEntity.setAccount(loginEntity.getAccount()); securityEntity.setPwd(loginEntity.getPwd()); securityEntity.setLoginTime(new Date()); return securityEntity; } @RequestMapping(value = "/echo/{name}", method = RequestMethod.GET) public String login(@PathVariable String name) { return "hello," + name; } }
先在我們要通過 AOP 功能將所有 Rest 接口的輸入?yún)?shù)和返回結果輸出到日志中。
實現(xiàn) Web Aop 功能。
package com.yanggch.demo.aop.comment; import com.fasterxml.jackson.databind.ObjectMapper; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.annotation.AfterReturning; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.aspectj.lang.annotation.Pointcut; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import org.springframework.web.multipart.MultipartFile; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; /** * web 接口日志 * * @author : 楊高超 * @since : 2018-05-27 */ @Aspect @Component public class WebLogAspect { private static Logger log = LoggerFactory.getLogger(WebLogAspect.class); private final ObjectMapper mapper; @Autowired public WebLogAspect(ObjectMapper mapper) { this.mapper = mapper; } @Pointcut("@annotation(org.springframework.web.bind.annotation.RequestMapping)") public void webLog() { } @Before("webLog()") public void doBefore(JoinPoint joinPoint) { for (Object object : joinPoint.getArgs()) { if ( object instanceof MultipartFile || object instanceof HttpServletRequest || object instanceof HttpServletResponse ) { continue; } try { if (log.isDebugEnabled()) { log.debug( joinPoint.getTarget().getClass().getName() + "." + joinPoint.getSignature().getName() + " : request parameter : " + mapper.writeValueAsString(object) ); } } catch (Exception e) { e.printStackTrace(); } } } @AfterReturning(returning = "response", pointcut = "webLog()") public void doAfterReturning(Object response) throws Throwable { if (response != null) { log.debug("response parameter : " + mapper.writeValueAsString(response)); } } }
這里有幾個需要注意的地方,
- 需要在類上聲明 org.aspectj.lang.annotation.Aspect 注解。
- 需要通過方法上的 org.aspectj.lang.annotation.Pointcut 注解聲明一個 Pointcut ,用來指明要在哪些方法切入。我們的 rest 接口都有 org.springframework.web.bind.annotation.RequestMapping 注解,所以我們這里就用了 "@annotation(org.springframework.web.bind.annotation.RequestMapping)" 表達式來指明。
- 通過 Advice 相關注解來說明在切入方法的什么位置做什么事。這里用 org.aspectj.lang.annotation.Before
- 這個實現(xiàn)是指明在所有具備 org.springframework.web.bind.annotation.RequestMapping 注解的方法上,方法進入后打印入口參數(shù)。方法返回后,打印返回參數(shù)。
測試
在前臺通過 postman 發(fā)起請求,后臺日志輸入結果如下
2018-05-27 19:58:42.941 DEBUG 86072 --- [nio-3030-exec-4] c.yanggch.demo.aop.comment.WebLogAspect : com.yanggch.demo.aop.web.SecurityApi.login : request parameter : {
"account" : "yanggch",
"pwd" : "123456"
}
2018-05-27 19:58:42.941 DEBUG 86072 --- [nio-3030-exec-4] c.yanggch.demo.aop.comment.WebLogAspect : com.yanggch.demo.aop.web.SecurityApi.login : request parameter : 2001
2018-05-27 19:58:42.942 DEBUG 86072 --- [nio-3030-exec-4] c.yanggch.demo.aop.comment.WebLogAspect : response parameter : {
"shopId" : 2001,
"account" : "yanggch",
"pwd" : "123456",
"loginTime" : "2018-05-27 11:58:42"
}
2018-05-27 19:58:45.796 DEBUG 86072 --- [nio-3030-exec-5] c.yanggch.demo.aop.comment.WebLogAspect : com.yanggch.demo.aop.web.SecurityApi.echo : request parameter : "yanggch"
2018-05-27 19:58:45.796 DEBUG 86072 --- [nio-3030-exec-5] c.yanggch.demo.aop.comment.WebLogAspect : response parameter : "hello,yanggch"
由此可見,我們雖然沒有在 rest 接口方法中寫輸出日志的代碼,但是通過 AOP 的方式可以自動的給各個 rest 入口方法中添加上輸出入口參數(shù)和返回參數(shù)的代碼并正確執(zhí)行。
其他說明
前面提到了 Advice 的類型和 Pointcut 的 AOP 表達式語言。具體參考如下。
Advice 類型
- before advice 在方法執(zhí)行前執(zhí)行。
- after returning advice 在方法執(zhí)行后返回一個結果后執(zhí)行。
- after throwing advice 在方法執(zhí)行過程中拋出異常的時候執(zhí)行。
- Around advice 在方法執(zhí)行前后和拋出異常時執(zhí)行,相當于綜合了以上三種通知。
AOP 表達式語言
1、方法參數(shù)匹配
@args()
2、方法描述匹配
execution(modifiers-pattern? ret-type-pattern declaring-type-pattern? name-pattern(param-pattern)throws-pattern?)
其中 returning type pattern,name pattern, and parameters pattern是必須的.
. ret-type-pattern:可以為表示任何返回值,全路徑的類名等.
*. name-pattern:指定方法名, *代表所有
.set代表以set開頭的所有方法.
. parameters pattern:指定方法參數(shù)(聲明的類型),(..)代表所有參數(shù),()代表一個參數(shù)
. (,String)代表第一個參數(shù)為任何值,第二個為String類型.
3、當前AOP代理對象類型匹配
4、目標類匹配
@target()
@within()
5、標有此注解的方法匹配
@annotation()
以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持腳本之家。
- springboot使用自定義注解實現(xiàn)aop切面日志
- SpringBoot使用AOP記錄接口操作日志詳解
- SpringBoot使用AOP實現(xiàn)統(tǒng)計全局接口訪問次數(shù)詳解
- 在springboot中使用AOP進行全局日志記錄
- SpringBoot使用AOP,內部方法失效的解決方案
- Springboot使用@Valid 和AOP做參數(shù)校驗及日志輸出問題
- 詳解基于SpringBoot使用AOP技術實現(xiàn)操作日志管理
- SpringBoot使用AOP+注解實現(xiàn)簡單的權限驗證的方法
- SpringBoot項目中使用AOP的方法
- Springboot 中使用 Aop代碼實戰(zhàn)教程
相關文章
Javaweb會話跟蹤技術Cookie和Session的具體使用
本文主要介紹了Javaweb會話跟蹤技術Cookie&Session的具體使用,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2022-07-07SpringBoot整合Docker實現(xiàn)一次構建到處運行的操作方法
本文講解的是 SpringBoot 引入容器化技術 Docker 實現(xiàn)一次構建到處運行,包括鏡像構建、Docker倉庫搭建使用、Docker倉庫可視化UI等內容,需要的朋友可以參考下2022-10-10idea2023設置啟動參數(shù)、單元測試啟動參數(shù)
在使用IDEA進行開發(fā)時,我們可以通過設置一些啟動參數(shù)來優(yōu)化開發(fā)環(huán)境的性能和體驗,具有一定的參考價值,感興趣的可以了解一下2023-11-11java system類使用方法示例 獲取系統(tǒng)信息
這篇文章主要介紹了java system類使用方法,該類中的方法都是靜態(tài)的。不能被實例化,沒有對外提供構造函數(shù),該類可以獲取系統(tǒng)信息2014-01-01Springboot啟動同時創(chuàng)建數(shù)據(jù)庫和表實現(xiàn)方法
這篇文章主要介紹了Springboot啟動同時創(chuàng)建數(shù)據(jù)庫和表,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習吧2023-01-01微信開發(fā)準備第二步 springmvc mybatis項目結構搭建
這篇文章主要為大家詳細介紹了微信開發(fā)準備第二步,springmvc和mybatis項目結構的搭建,具有一定的參考價值,感興趣的小伙伴們可以參考一下2017-04-04SpringBoot2 Jpa 批量刪除功能的實現(xiàn)
這篇文章主要介紹了SpringBoot2 Jpa 批量刪除功能的實現(xiàn),具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2021-01-01Spring?Boot?Admin?監(jiān)控指標接入Grafana可視化的實例詳解
Spring Boot Admin2 自帶有部分監(jiān)控圖表,如圖,有線程、內存Heap和內存Non Heap,這篇文章主要介紹了Spring?Boot?Admin?監(jiān)控指標接入Grafana可視化,需要的朋友可以參考下2022-11-11AsyncHttpClient的ConnectionSemaphore方法源碼流程解讀
這篇文章主要為大家介紹了AsyncHttpClient的ConnectionSemaphore方法源碼流程解讀,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2023-12-12