SpringBoot中使用AOP打印接口日志的方法
前言
AOP 是 Aspect Oriented Program (面向切面)的編程的縮寫。他是和面向?qū)ο缶幊滔鄬?duì)的一個(gè)概念。在面向?qū)ο蟮木幊讨?,我們傾向于采用封裝、繼承、多態(tài)等概念,將一個(gè)個(gè)的功能在對(duì)象中來實(shí)現(xiàn)。但是,我們?cè)趯?shí)際情況中也發(fā)現(xiàn),會(huì)有另外一種需求就是一類功能在很多對(duì)象的很多方法中都有需要。例如有一些對(duì)數(shù)據(jù)庫(kù)訪問的方法有事務(wù)管理的需求,有很多方法中要求打印日志。按照面向?qū)ο蟮姆绞?,那么這些相同的功能要在很多地方來實(shí)現(xiàn)或者在很多地方來調(diào)用。這就非常繁瑣并且和這些和業(yè)務(wù)不相關(guān)的需求耦合太緊密了。所以后來就出現(xiàn)了面向切面的編程來解決這一類問題,并對(duì)面向?qū)ο蟮木幊套隽撕芎玫难a(bǔ)充
概念
要很好的理解面向切面的編程,先要理解 AOP 的一些概念。在 Java 中 AspectJ 比較完整的實(shí)現(xiàn)了 AOP 的功能,但是使用起來也比較復(fù),所以這里主要是討論 Spring 的 AOP 。Spring AOP 采用簡(jiǎn)單夠用的原則,實(shí)現(xiàn)了 AOP 的核心功能。下面先說說 AOP 中的具體概念
- Aspect:方面。一個(gè)可以切入多個(gè)類的關(guān)注點(diǎn)。這個(gè)關(guān)注點(diǎn)實(shí)現(xiàn)了我們前面說的具體的業(yè)務(wù)功能。例如打印日志,進(jìn)行數(shù)據(jù)庫(kù)的事務(wù)管理等。
- Joint point:被切入點(diǎn)。是指具體要實(shí)現(xiàn)前面所說的例如打印日志,數(shù)據(jù)庫(kù)事務(wù)管理的被切入的點(diǎn)。也就是通過 AOP 將切面功能動(dòng)態(tài)加入進(jìn)去的程序位置。在 Spring AOP 里面這個(gè)指的都是某個(gè)方法
- Pointcut:切點(diǎn)。用來指明如何通過規(guī)則匹配 Joint point。這個(gè)規(guī)則是一個(gè)表達(dá)式。在 Spring 中,默認(rèn)使用的是 AspectJ 的 pointcut 表達(dá)式語言
- Advice:指明在一個(gè)切入點(diǎn)的不同位置上采取的動(dòng)作。例如對(duì)于一個(gè)數(shù)據(jù)庫(kù)訪問事務(wù)管理來說,在進(jìn)入方法后要開啟事務(wù),在方法結(jié)束前要提交事務(wù),在發(fā)生錯(cuò)誤的時(shí)候要回滾事務(wù)。這屬于三個(gè)不同的 Advice,要分別進(jìn)行實(shí)現(xiàn)。Advice 通常和具體的 Pointcut 關(guān)聯(lián)在一起。
- AOP proxy:AOP 代理。用來實(shí)現(xiàn)將 Advice 功能動(dòng)態(tài)加入到 Pointcut 的方法。在 Spring 的 AOP 中采用動(dòng)態(tài)代理和 CGLIB 代理的方式來實(shí)現(xiàn)。而 AspectJ 則采用了特定編譯器侵入字節(jié)碼的方式來實(shí)現(xiàn)。
SprinBoot AOP 實(shí)現(xiàn)
前面我們已經(jīng)用好幾章講述了 SpringBoot 的基本使用。那么這里我們就用 SpringBoot 和 AOP 結(jié)合來實(shí)現(xiàn)一個(gè)輸出所有 Rest 接口輸入?yún)?shù)和返回參數(shù)的日志的功能。
實(shí)現(xiàn) rest 服務(wù)功能。
根據(jù)前面的文章,我們先建立一個(gè) SpingBoot 的工程如下圖所示

demo 工程
SpringBoot 項(xiàng)目配置
我們對(duì) SpringBoot 項(xiàng)目配置如下
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 相關(guān)配置是為了將對(duì)象輸出成 json 字符串后能夠格式化輸出
實(shí)現(xiàn)一個(gè) rest 接口的 Controller 類
在這里,我們實(shí)現(xiàn)兩個(gè) rest 接口。一個(gè)是返回 hello 信息。一個(gè)是根據(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;
/**
* 安全相關(guān) rest 服務(wù)
*
* @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ù)和返回結(jié)果輸出到日志中。
實(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));
}
}
}
這里有幾個(gè)需要注意的地方,
- 需要在類上聲明 org.aspectj.lang.annotation.Aspect 注解。
- 需要通過方法上的 org.aspectj.lang.annotation.Pointcut 注解聲明一個(gè) Pointcut ,用來指明要在哪些方法切入。我們的 rest 接口都有 org.springframework.web.bind.annotation.RequestMapping 注解,所以我們這里就用了 "@annotation(org.springframework.web.bind.annotation.RequestMapping)" 表達(dá)式來指明。
- 通過 Advice 相關(guān)注解來說明在切入方法的什么位置做什么事。這里用 org.aspectj.lang.annotation.Before
- 這個(gè)實(shí)現(xiàn)是指明在所有具備 org.springframework.web.bind.annotation.RequestMapping 注解的方法上,方法進(jìn)入后打印入口參數(shù)。方法返回后,打印返回參數(shù)。
測(cè)試
在前臺(tái)通過 postman 發(fā)起請(qǐng)求,后臺(tái)日志輸入結(jié)果如下
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 的方式可以自動(dòng)的給各個(gè) rest 入口方法中添加上輸出入口參數(shù)和返回參數(shù)的代碼并正確執(zhí)行。
其他說明
前面提到了 Advice 的類型和 Pointcut 的 AOP 表達(dá)式語言。具體參考如下。
Advice 類型
- before advice 在方法執(zhí)行前執(zhí)行。
- after returning advice 在方法執(zhí)行后返回一個(gè)結(jié)果后執(zhí)行。
- after throwing advice 在方法執(zhí)行過程中拋出異常的時(shí)候執(zhí)行。
- Around advice 在方法執(zhí)行前后和拋出異常時(shí)執(zhí)行,相當(dāng)于綜合了以上三種通知。
AOP 表達(dá)式語言
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ù),()代表一個(gè)參數(shù)
. (,String)代表第一個(gè)參數(shù)為任何值,第二個(gè)為String類型.
3、當(dāng)前AOP代理對(duì)象類型匹配
4、目標(biāo)類匹配
@target()
@within()
5、標(biāo)有此注解的方法匹配
@annotation()
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
- springboot使用自定義注解實(shí)現(xiàn)aop切面日志
- SpringBoot使用AOP記錄接口操作日志詳解
- SpringBoot使用AOP實(shí)現(xiàn)統(tǒng)計(jì)全局接口訪問次數(shù)詳解
- 在springboot中使用AOP進(jìn)行全局日志記錄
- SpringBoot使用AOP,內(nèi)部方法失效的解決方案
- Springboot使用@Valid 和AOP做參數(shù)校驗(yàn)及日志輸出問題
- 詳解基于SpringBoot使用AOP技術(shù)實(shí)現(xiàn)操作日志管理
- SpringBoot使用AOP+注解實(shí)現(xiàn)簡(jiǎn)單的權(quán)限驗(yàn)證的方法
- SpringBoot項(xiàng)目中使用AOP的方法
- Springboot 中使用 Aop代碼實(shí)戰(zhàn)教程
相關(guān)文章
如何在SpringBoot項(xiàng)目中使用Oracle11g數(shù)據(jù)庫(kù)
這篇文章主要介紹了在SpringBoot項(xiàng)目中使用Oracle11g數(shù)據(jù)庫(kù)的操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-06-06
如何解決getReader() has already been called&
這篇文章主要介紹了如何解決getReader() has already been called for this request問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-05-05
關(guān)于springboot 配置date字段返回時(shí)間戳的問題
這篇文章主要介紹了springboot 配置date字段返回時(shí)間戳的問題,在springboot2.0后,spring會(huì)將Date字段自動(dòng)給轉(zhuǎn)成UTC字符串了(在沒有配置的情況下),所以date需要轉(zhuǎn)換成時(shí)間戳還是yyyy-MM-dd HH:mm:ss,具體解決方法跟隨小編一起看看吧2021-07-07
Java用BigDecimal解決double類型相減時(shí)可能存在的誤差
這篇文章主要介紹了Java用BigDecimal解決double類型相減時(shí)可能存在的誤差,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-05-05
Java+Selenium調(diào)用JavaScript的方法詳解
這篇文章主要為大家講解了java在利用Selenium操作瀏覽器網(wǎng)站時(shí)候,有時(shí)會(huì)需要用的JavaScript的地方,代碼該如何實(shí)現(xiàn)呢?快跟隨小編一起學(xué)習(xí)一下吧2023-01-01
SpringBoot中web模版數(shù)據(jù)渲染展示的案例詳解
憑借 Spring Framework 的模塊、與你最喜歡的工具的大量集成以及插入你自己的功能的能力,Thymeleaf 是現(xiàn)代 HTML5 JVM Web 開發(fā)的理想選擇——盡管它還有更多功能,本文重點(diǎn)給大家介紹SpringBoot中web模版數(shù)據(jù)渲染展示,需要的朋友可以參考下2022-01-01

