根據(jù)之前講使用springboot整合AOP記錄訪問(wèn)mymes日志,沒(méi)看過(guò)的可以回顧前面的文章
SpringBoot使用AOP記錄接口訪問(wèn)mymes日志
本章主要概述mymes項(xiàng)目中使用AOP記錄接口日志,通過(guò)在Controller層建立一個(gè)切面來(lái)實(shí)現(xiàn)接口的統(tǒng)一訪問(wèn)日志記錄
AOP(面向切面編程)
在軟件業(yè),AOP為Aspect Oriented Programming的縮寫(xiě),意為:面向切面編程,通過(guò)預(yù)編譯方式和運(yùn)行期間動(dòng)態(tài)代理實(shí)現(xiàn)程序功能的統(tǒng)一維護(hù)的一種技術(shù)。AOP是OOP的延續(xù),是軟件開(kāi)發(fā)中的一個(gè)熱點(diǎn),也是Spring框架中的一個(gè)重要內(nèi)容,是函數(shù)式編程的一種衍生范型。利用AOP可以對(duì)業(yè)務(wù)邏輯的各個(gè)部分進(jìn)行隔離,從而使得業(yè)務(wù)邏輯各部分之間的耦合度降低,提高程序的可重用性,同時(shí)提高了開(kāi)發(fā)的效率。
AOP相關(guān)術(shù)語(yǔ)
1.Advice(通知)
通知描述了切面要完成的工作以及何時(shí)執(zhí)行。比如我們的日志切面需要記錄每個(gè)接口調(diào)用時(shí)長(zhǎng),就需要在接口調(diào)用前后分別記錄當(dāng)前時(shí)間,再取差值。
- 前置通知(Before):在目標(biāo)方法調(diào)用前調(diào)用通知功能;
- 后置通知(After):在目標(biāo)方法調(diào)用之后調(diào)用通知功能,不關(guān)心方法的返回結(jié)果;
- 返回通知(AfterReturning):在目標(biāo)方法成功執(zhí)行之后調(diào)用通知功能;
- 異常通知(AfterThrowing):在目標(biāo)方法拋出異常后調(diào)用通知功能;
- 環(huán)繞通知(Around):通知包裹了目標(biāo)方法,在目標(biāo)方法調(diào)用之前和之后執(zhí)行自定義的行為。
2.JoinPoint(連接點(diǎn))
所謂的連接點(diǎn),就是指被攔截到的點(diǎn),在springboot中,點(diǎn)指的就是方法,因?yàn)閟pringboot只支持方法類(lèi)型的連接點(diǎn),例如:裂口方法被調(diào)用的時(shí)候就是日志切面連接點(diǎn)。
3.Pointcut(切點(diǎn))
所謂切入點(diǎn)是指需要進(jìn)行增強(qiáng)的連接點(diǎn)(Joinpoint),定義了通知功能被應(yīng)用的范圍。例如:日志切面的應(yīng)用范圍就是所有接口,即所有controller層的接口方法。
4.Aspect(切面)
切面就是Advice(通知)+Pointcut(切點(diǎn)),定義了什么時(shí)候去通知功能
5.Introduction(引入)
允許我們向現(xiàn)有的類(lèi)添加新方法屬性。這不就是把切面(也就是新方法屬性:通知定義的)用到目標(biāo)類(lèi)中嗎
6.Weaving(織入)
把切面應(yīng)用到目標(biāo)對(duì)象來(lái)創(chuàng)建新的代理對(duì)象的過(guò)程。
使用注解方式創(chuàng)建注解切面
切面注解
- @Aspect:定義切面
- @Before: 在目標(biāo)方法調(diào)用前,該通知方法會(huì)執(zhí)行
- @After: 在目標(biāo)方法調(diào)用后,該通知方法會(huì)執(zhí)行
- @AfterReturning: 在目標(biāo)方法返回后,該通知方法會(huì)執(zhí)行
- @BeforetThrowing: 在目標(biāo)方法調(diào)用并拋出異常后,該通知方法會(huì)執(zhí)行
- @Around: 該方法會(huì)將目標(biāo)注解封裝起來(lái)
- @Pointcut:定義切點(diǎn)
切點(diǎn):
指定通知被使用的范圍,注解格式:
execution(方法修飾符 返回類(lèi)型 方法所屬的包.類(lèi)名.方法名稱(chēng)(方法參數(shù))
//com.cn.mymes.controller包中所有類(lèi)的public方法都應(yīng)用切面里的通知
execution(public * com.cn.mymes.controller.*.*(..))
//com.cn.mymes.service包及其子包下所有類(lèi)中的所有方法都應(yīng)用切面里的通知
execution(* com.cn.mymes.service..*.*(..))
//com.cn.mymes.service.MyMesBrandService類(lèi)中的所有方法都應(yīng)用切面里的通知
execution(* com.cn.mymes.service.MyMesBrandService.*(..))
在mymes中添加AOP切面實(shí)現(xiàn)mymes接口切面日志記錄
添加日志信息封裝類(lèi)MyMesLog
用于封裝序列記錄的日志信息,包括操作描述,時(shí)間,url,參數(shù),返回結(jié)果等信息
package com.cn.mymes.dto;
/**
* Controller層的日志封裝類(lèi)
* Created by zbb on 2021/1/3
*/
public class MyMesLog {
? ? /**
? ? * 操作描述
? ? */
? ? private String description;
? ? /**
? ? * 操作用戶(hù)
? ? */
? ? private String username;
? ? /**
? ? * 操作時(shí)間
? ? */
? ? private? Long? startTime;
? ? /**
? ? * 消耗時(shí)間
? ? */
? ? private Integer spendTime;
? ? /**
? ? * 根路徑
? ? */
? ? private String? basePath;
? ? /**
? ? * URI
? ? */
? ? private String uri;
? ? /**
? ? * URL
? ? */
? ? private String url;
? ? public String getUrl() {
? ? ? ? return url;
? ? }
? ? public void setUrl(String url) {
? ? ? ? this.url = url;
? ? }
? ? /**
? ? * 請(qǐng)求類(lèi)型
? ? */
? ? private String method;
? ? /**
? ? * IP地址
? ? */
? ? private String ip;
? ? /**
? ? * 請(qǐng)求參數(shù)
? ? */
? ? private Object parameter;
? ? /**
? ? * 請(qǐng)求返回的結(jié)果
? ? */
? ? private Object result;
? ? public String getDescription() {
? ? ? ? return description;
? ? }
? ? public void setDescription(String description) {
? ? ? ? this.description = description;
? ? }
? ? public String getUsername() {
? ? ? ? return username;
? ? }
? ? public void setUsername(String username) {
? ? ? ? this.username = username;
? ? }
? ? public Long getStartTime() {
? ? ? ? return startTime;
? ? }
? ? public void setStartTime(Long startTime) {
? ? ? ? this.startTime = startTime;
? ? }
? ? public Integer getSpendTime() {
? ? ? ? return spendTime;
? ? }
? ? public void setSpendTime(Integer spendTime) {
? ? ? ? this.spendTime = spendTime;
? ? }
? ? public String getBasePath() {
? ? ? ? return basePath;
? ? }
? ? public void setBasePath(String basePath) {
? ? ? ? this.basePath = basePath;
? ? }
? ? public String getUri() {
? ? ? ? return uri;
? ? }
? ? public void setUri(String uri) {
? ? ? ? this.uri = uri;
? ? }
? ? public String getMethod() {
? ? ? ? return method;
? ? }
? ? public void setMethod(String method) {
? ? ? ? this.method = method;
? ? }
? ? public String getIp() {
? ? ? ? return ip;
? ? }
? ? public void setIp(String ip) {
? ? ? ? this.ip = ip;
? ? }
? ? public Object getParameter() {
? ? ? ? return parameter;
? ? }
? ? public void setParameter(Object parameter) {
? ? ? ? this.parameter = parameter;
? ? }
? ? public Object getResult() {
? ? ? ? return result;
? ? }
? ? public void setResult(Object result) {
? ? ? ? this.result = result;
? ? }
}
添加切面MyMesLogAspect
定義日志切面,在環(huán)繞通知中獲取日志需要的信息,并應(yīng)用到controller層中所有的public方法中去。
package com.cn.mymes.component;
/*
* ------AOP---------------
* AOP為Aspect Oriented Programming的縮寫(xiě),意為:面向切面編程,通過(guò)預(yù)編譯方式和運(yùn)行期動(dòng)態(tài)代理實(shí)現(xiàn)程
* 序功能的統(tǒng)一維護(hù)的一
* 種技術(shù)。利用AOP可以對(duì)業(yè)務(wù)邏輯的各個(gè)部分進(jìn)行隔離,從而使得業(yè)務(wù)邏輯各部分之間的耦合度降低,
* 提高程序的可重用性,同時(shí)提高了開(kāi)發(fā)的效率。
*1.前置通知(Before):在目標(biāo)方法調(diào)用前調(diào)用通知功能;
*2.后置通知(After):在目標(biāo)方法調(diào)用之后調(diào)用通知功能,不關(guān)心方法的返回結(jié)果;
*3.返回通知(AfterReturning):在目標(biāo)方法成功執(zhí)行之后調(diào)用通知功能;、
*4.異常通知(AfterThrowing):在目標(biāo)方法拋出異常后調(diào)用通知功能;
*5.環(huán)繞通知(Around):通知包裹了目標(biāo)方法,在目標(biāo)方法調(diào)用之前和之后執(zhí)行自定義的行為。
*6.連接點(diǎn)(JoinPoint) 通知功能被應(yīng)用的時(shí)機(jī)。比如接口方法被調(diào)用的時(shí)候就是日志切面的連接點(diǎn)。
*7.切點(diǎn)(Pointcut) 切點(diǎn)定義了通知功能被應(yīng)用的范圍。比如日志切面的應(yīng)用范圍就是所有接口,即所有controller層的接口方法
*8.切面(Aspect)切面是通知和切點(diǎn)的結(jié)合,定義了何時(shí)、何地應(yīng)用通知功能。
*9.引入(Introduction) 在無(wú)需修改現(xiàn)有類(lèi)的情況下,向現(xiàn)有的類(lèi)添加新方法或?qū)傩浴?br> *10.織入(Weaving)? 把切面應(yīng)用到目標(biāo)對(duì)象并創(chuàng)建新的代理對(duì)象的過(guò)程。
*-----------切點(diǎn)表達(dá)式
* execution(方法修飾符 返回類(lèi)型 方法所屬的包.類(lèi)名.方法名稱(chēng)(方法參數(shù))
*/
import cn.hutool.core.util.StrUtil;
import cn.hutool.core.util.URLUtil;
import cn.hutool.json.JSONUtil;
import com.cn.mymes.dto.MyMesLog;
import io.swagger.annotations.ApiOperation;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* 統(tǒng)一日志處理切面
* Created by zbb on 2021/1/3
*/
@Aspect
@Component
@Order(1)
public class MyMesLogAspect {
? ? private static final Logger LOGGER = LoggerFactory.getLogger(MyMesLogAspect.class);
? ? @Pointcut("execution(public * com.cn.mymes.controller.*.*(..))")
? ? public void MyMesLog() {
? ? }
? ? @Before("MyMesLog()")
? ? public void doBefore(JoinPoint joinPoint) throws Throwable {
? ? }
? ? @AfterReturning(value = "MyMesLog()", returning = "ret")
? ? public void doAfterReturning(Object ret) throws Throwable {
? ? }
? ? @Around("MyMesLog()")
? ? public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
? ? ? ? long startTime = System.currentTimeMillis();
? ? ? ? //獲取當(dāng)前請(qǐng)求對(duì)象
? ? ? ? ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
? ? ? ? HttpServletRequest request = attributes.getRequest();
? ? ? ? //記錄請(qǐng)求信息
? ? ? ? MyMesLog webLog = new MyMesLog();
? ? ? ? Object result = joinPoint.proceed();
? ? ? ? Signature signature = joinPoint.getSignature();
? ? ? ? MethodSignature methodSignature = (MethodSignature) signature;
? ? ? ? Method method = methodSignature.getMethod();
? ? ? ? if (method.isAnnotationPresent(ApiOperation.class)) {
? ? ? ? ? ? ApiOperation apiOperation = method.getAnnotation(ApiOperation.class);
? ? ? ? ? ? webLog.setDescription(apiOperation.value());
? ? ? ? }
? ? ? ? long endTime = System.currentTimeMillis();
? ? ? ? String urlStr = request.getRequestURL().toString();
? ? ? ? webLog.setBasePath(StrUtil.removeSuffix(urlStr, URLUtil.url(urlStr).getPath()));
? ? ? ? webLog.setIp(request.getRemoteUser());
? ? ? ? webLog.setMethod(request.getMethod());
? ? ? ? webLog.setParameter(getParameter(method, joinPoint.getArgs()));
? ? ? ? webLog.setResult(result);
? ? ? ? webLog.setSpendTime((int) (endTime - startTime));
? ? ? ? webLog.setStartTime(startTime);
? ? ? ? webLog.setUri(request.getRequestURI());
? ? ? ? webLog.setUrl(request.getRequestURL().toString());
? ? ? ? LOGGER.info("{}", JSONUtil.parse(webLog));
? ? ? ? return result;
? ? }
? ? /**
? ? * 根據(jù)方法和傳入的參數(shù)獲取請(qǐng)求參數(shù)
? ? */
? ? private Object getParameter(Method method, Object[] args) {
? ? ? ? List<Object> argList = new ArrayList<>();
? ? ? ? Parameter[] parameters = method.getParameters();
? ? ? ? for (int i = 0; i < parameters.length; i++) {
? ? ? ? ? ? //將RequestBody注解修飾的參數(shù)作為請(qǐng)求參數(shù)
? ? ? ? ? ? RequestBody requestBody = parameters[i].getAnnotation(RequestBody.class);
? ? ? ? ? ? if (requestBody != null) {
? ? ? ? ? ? ? ? argList.add(args[i]);
? ? ? ? ? ? }
? ? ? ? ? ? //將RequestParam注解修飾的參數(shù)作為請(qǐng)求參數(shù)
? ? ? ? ? ? RequestParam requestParam = parameters[i].getAnnotation(RequestParam.class);
? ? ? ? ? ? if (requestParam != null) {
? ? ? ? ? ? ? ? Map<String, Object> map = new HashMap<>();
? ? ? ? ? ? ? ? String key = parameters[i].getName();
? ? ? ? ? ? ? ? if (!StringUtils.isEmpty(requestParam.value())) {
? ? ? ? ? ? ? ? ? ? key = requestParam.value();
? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? map.put(key, args[i]);
? ? ? ? ? ? ? ? argList.add(map);
? ? ? ? ? ? }
? ? ? ? }
? ? ? ? if (argList.size() == 0) {
? ? ? ? ? ? return null;
? ? ? ? } else if (argList.size() == 1) {
? ? ? ? ? ? return argList.get(0);
? ? ? ? } else {
? ? ? ? ? ? return argList;
? ? ? ? }
? ? }
}
運(yùn)行項(xiàng)目
訪問(wèn)Swagger 接口地址http://localhost:9999/swagger-ui.html ,測(cè)試接口。
控制臺(tái)上顯示的調(diào)用信息
{
? ? "basePath": "http://localhost:9999",
? ? "description": "獲取驗(yàn)證碼",
? ? "method": "GET",
? ? "parameter": {
? ? ? ? "telephone": "XXXXXXXXXX"
? ? },
? ? "result": {
? ? ? ? "code": 200,
? ? ? ? "data": "010146",
? ? ? ? "message": "獲取驗(yàn)證碼成功"
? ? },
? ? "spendTime": 849,
? ? "startTime": 1609686183897,
? ? "uri": "/sso/getAuthCode",
? ? "url": "http://localhost:9999/sso/getAuthCode"
}
