1、問題描述
微信公號h5開發,前后端分離,因為是在微信公號里面操作頁面,還有涉及到不同手機操作也不一樣,導致聯調比較麻煩,定位問題也麻煩,以前寫過通過aop記錄所有前端http請求,就又拿出來梳理了下,記錄日志,記錄下,希望可以幫到有需要的朋友。
2、解決方案
項目是springboot項目,通過springboot-aop,配置環繞通知,記錄調用地址、入參、返回參數、ip,同時記錄執行時間等,以便定位問題。具體的入庫就是弄個表,保存下獲取到的值,這里就不多做介紹了。
2.1 AOP簡要說明
(1)什么是AOP?
AOP為Aspect Oriented Programming的縮寫,是面向切面編程,通過預編譯方式和運行期動態代理實現程序功能的統一維護的一種技術。
(2)AOP的五種通知方式
前置通知(Before):在目標方法或者說連接點被調用前執行的通知;
后置通知(After):指在某個連接點完成后執行的通知;
返回通知(After-returning):指在某個連接點成功執行之后執行的通知;
異常通知(After-throwing):指在方法拋出異常后執行的通知;
環繞通知(Around):指包圍一個連接點通知,在被通知的方法調用之前和之后執行自定義的方法。
(3)說明
軟件老王用的比較多的是前置通知和環繞通知;
前置通知用於權限控制的比較多一些,簡單說就是再http請求調用方法前,進行鑒權校驗等,鑒權通過再放行;
后置通知也簡單用過,記錄返回值的,用的不是很多;
然后就是用的最多的環繞通知,環繞通知=前置通知+后置通知,記錄日志非常方便;
2.2 pom文件配置
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
springboot項目下,gav配置后,代碼中直接使用標簽就可以配置aop通知了,非常方便。
2.3 代碼分解介紹
import com.alibaba.fastjson.JSON;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
@Aspect
@Component
public class WebLogAspect {
@Pointcut("execution(public * com.spring.wx.oauth.conntroller.*.*(..))")
public void webLog(){
}
@Around("webLog()")
public Object saveSysLog(ProceedingJoinPoint proceedingJoinPoint) {
System.out.println("環繞通知開始。。。。。");
MethodSignature signature = (MethodSignature) proceedingJoinPoint.getSignature();
Method method = signature.getMethod();
String className = proceedingJoinPoint.getTarget().getClass().getName();
String methodName = method.getName();
System.out.println(className);
System.out.println(methodName);
System.out.println(className + "." + methodName);
//請求的參數
Object[] args = proceedingJoinPoint.getArgs();
String params = JSON.toJSONString(args);
System.out.println(params);
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = attributes.getRequest();
// 記錄下請求內容
System.out.println("URL : " + request.getRequestURL().toString());
System.out.println("HTTP_METHOD : " + request.getMethod());
System.out.println("IP : " + request.getRemoteAddr());
//記錄時間
long start = System.currentTimeMillis();
Object result =null;
try {
result = proceedingJoinPoint.proceed();
System.out.println(result.toString());
} catch (Throwable throwable) {
throwable.printStackTrace();
System.out.println(throwable.getMessage());
}
Long time = System.currentTimeMillis() - start;
System.out.println(time);
System.out.println("環繞通知結束。。。。。");
return result;
}
}
說明:
(1)兩個標簽,@Component和@Aspect,@Component標簽,將類加入到spring容器,@Aspec,aop通知標簽,只有pom中配置了gav才會有;
(2) @Pointcut,定義切入點,軟件老王這里配置的conntroller包下的所有類;
@Pointcut("execution(public * com.spring.wx.oauth.conntroller.*.*(..))")
public void webLog(){
}
(3) @Around("webLog()"),環繞通知,value為上面配置的切入點;
@Around("webLog()")
public void saveSysLog(ProceedingJoinPoint proceedingJoinPoint)
(4)接着就是使用取值入庫或打印
//打印請求的類和方法
System.out.println(className + "." + methodName);
//打印入參
System.out.println(params);
// 記錄下請求內容
System.out.println("URL : " + request.getRequestURL().toString());
System.out.println("HTTP_METHOD : " + request.getMethod());
System.out.println("IP : " + request.getRemoteAddr());
//記錄執行時間
System.out.println(time);
2.4 執行驗證
2.4.1 controller類
簡單的兩個測試方法,一個是返回有異常;一個是正常執行,分別是laowang和shuaige
@ResponseBody
@GetMapping("/laowang")
public String laowang(String test) {
userService.laowang();
return "SUCCESS";
}
@ResponseBody
@GetMapping("/shuaige")
public UserEntity shuaige(String test) {
UserEntity userEntity = userService.shuaige();
return userEntity;
}
2.4.2 service類
public void laowang() {
UserEntity userEntity = new UserEntity();
userEntity.setType(0);
userEntity.setOpenid("1111");
insert(userEntity);
int i = 1/0;
// System.out.println(i);
}
public UserEntity shuaige() {
UserEntity userEntity = new UserEntity();
userEntity.setType(0);
userEntity.setOpenid("1111");
return userEntity;
}
2.4.3 運行過程及說明
(1)瀏覽器地址:http://localhost/laowang?test=333,首先不會進入執行方法,而是進入aop通知;
(2) 執行過程
(3)進入執行方法體,laowang方法是拋異常的方法體(1/0),報錯前;
(4)try-catch捕獲異常,並打印出來,記錄執行時間
(5)shauige方法,無異常方法
瀏覽器地址:http://localhost/shuaige?test=333,執行過程;
(6)shuaige方法執行完畢,打印返回參數和時間。
3、總結
我們的業務主要需求是:記錄入參、出參、執行時間,方便定位問題,AOP的環繞通知已經能滿足了;
入參:params;
出參:result.toString(),同時假如方法執行有異常,會將異常記錄下來;
執行時間:time;
---20210813--
修改兩行代碼,增加了通知的返回,通知不返回的話,會把后端方法的返回給“吃掉”,導致前端無法獲取后端返回數據(異常)。
@Around("webLog()")
public Object saveSysLog(ProceedingJoinPoint proceedingJoinPoint) {
return result;
}
更多信息請關注公眾號:「軟件老王」,關注不迷路,軟件老王和他的IT朋友們,分享一些他們的技術見解和生活故事。