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朋友们,分享一些他们的技术见解和生活故事。