java程序中AOP实现日志功能优化
今天我们通过切面来实现日志记录。
第一点:概念
要理解切面编程,就需要先理解什么是切面。
AOP = Aspect Oriental Programing,即面向切面编程。什么概念,我们看如下的图片:
三个方法中,重复使用了代码A和代码B,典型的场景比如“开启事务,数据处理,提交事务”。这些重复的代码大多是所谓的权限管理、日志登陆、事务管理等必需却又污染了业务逻辑代码的内容,我们自然希望将它们提取出来,还业务逻辑一个清新的世界。
你知道Servlet过滤器,可我们目前对象的粒度已经不是整个方法了,而是更加细化到了方法中的代码片段。你当然可以曲线救国地使用匿名内部类来抽取重复代码,但是它并不怎么优雅,而AOP,就可以通过横向切割的方式来抽取代码,达到我们的目的。
spring中的几个概念
#Spring#
Aspect(切面): Aspect 声明类似于 Java 中的类声明,在 Aspect 中会包含着一些 Pointcut 以及相应的 Advice。
Joint point(连接点):表示在程序中明确定义的点,典型的包括方法调用,对类成员的访问以及异常处理程序块的执行等等,它自身还可以嵌套其它 joint point。
Pointcut(切点):表示一组 joint point,这些 joint point 或是通过逻辑关系组合起来,或是通过通配、正则表达式等方式集中起来,它定义了相应的 Advice 将要发生的地方。
Advice(增强):Advice 定义了在 Pointcut 里面定义的程序点具体要做的操作,它通过 before、after 和 around 来区别是在每个 joint point 之前、之后还是代替执行的代码。
Target(目标对象):织入 Advice 的目标对象.。
Weaving(织入):将 Aspect 和其他对象连接起来, 并创建 Adviced object 的过程。
首先我们知道, 在 Spring AOP 中 Joint point 指代的是所有方法的执行点, 而 point cut 是一个描述信息, 它修饰的是 Joint point, 通过 point cut, 我们就可以确定哪些 Joint point 可以被织入 Advice. 对应到我们在上面举的例子, 我们可以做一个简单的类比, Joint point 就相当于 爪哇的小县城里的百姓,pointcut 就相当于 老王所做的指控, 即凶手是个男性, 身高约七尺五寸, 而 Advice 则是施加在符合老王所描述的嫌疑人的动作: 抓过来审问。
源码如下:
package com.itheima.xiaotuxian.aspect;
import java.lang.annotation.Annotation;
import javax.servlet.http.HttpServletRequest;
import com.alibaba.fastjson.JSON;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
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.bind.annotation.PathVariable;
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 lombok.extern.slf4j.Slf4j;
/**
* 日志切面类
* 参考https://blog.csdn.net/qq_41611676/article/details/105943836
*/
@Aspect
@Component
@Slf4j
public class LogAspect {
/**
* ..表示包及子包 该方法代表controller层的所有方法
* 路径需要根据自己项目定义
*/
@Pointcut("execution(public * com.itheima.xiaotuxian.controller..*.*(..))" +
"|| execution(public * com.itheima.xiaotuxian.exception..*.*(..))")
public void controllerMethod() {
}
/**
* 方法环绕执行
*
* @param joinPoint
* @throws Exception
*/
@Around("controllerMethod()")
public Object handle(ProceedingJoinPoint joinPoint) {
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes())
.getRequest();
// IP地址
String ipAddr = getRemoteHost(request);
String url = request.getRequestURL().toString();
String reqParam = preHandle(joinPoint, request);
log.info("请求源IP:【{}】,请求URL:【{}】,请求参数:【{}】", ipAddr, url, reqParam);
Object result = null;
try {
result = joinPoint.proceed();
} catch (Throwable e) {
e.printStackTrace();
log.error("请求源IP:【{}】,请求URL:【{}】,报错信息:【{}】", ipAddr, url, e.getMessage());
}
String respParam = postHandle(result);
log.info("请求源IP:【{}】,请求URL:【{}】,返回参数:【{}】", ipAddr, url, respParam);
return result;
}
/**
* 返回数据
*
* @param retVal
* @return
*/
private String postHandle(Object retVal) {
if (null == retVal) {
return "";
}
return JSON.toJSONString(retVal);
}
/**
* 获取目标主机的ip
*
* @param request
* @return
*/
private String getRemoteHost(HttpServletRequest request) {
String ip = request.getHeader("x-forwarded-for");
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("Proxy-Client-IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("WL-Proxy-Client-IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getRemoteAddr();
}
return "0:0:0:0:0:0:0:1".equals(ip) ? "127.0.0.1" : ip;
}
private String preHandle(ProceedingJoinPoint joinPoint, HttpServletRequest request) {
StringBuilder reqParam = new StringBuilder();
Signature signature = joinPoint.getSignature();
MethodSignature methodSignature = (MethodSignature) signature;
java.lang.reflect.Method targetMethod = methodSignature.getMethod();
Object[] args = joinPoint.getArgs();
Annotation[][] parameterAnnotations = targetMethod.getParameterAnnotations();
int i = 0;
for (Annotation[] annotations : parameterAnnotations) {
for1: for (Annotation annotation : annotations) {
if (annotation instanceof RequestParam) {
reqParam.append(((RequestParam) annotation).name()).append(":").append(JSON.toJSONString(args[i]))
.append(",");
i++;
break for1;
} else if (annotation instanceof PathVariable) {
reqParam.append(JSON.toJSONString(args[i])).append(",");
i++;
break for1;
} else if (annotation instanceof RequestBody) {
reqParam.append(JSON.toJSONString(args[i])).append(",");
i++;
break for1;
}
}
}
return reqParam.toString();
}
}