spring AOP+自定义注解,实现记录接口调用日志
前言
提示:随意记录
提示:以下是本篇文章正文内容,下面案例可供参考
一、自定义注解
注解在Java中,与类、接口、枚举类似,因此其声明语法基本一致,只是所使用的关键字有所不同@interface。在底层实现上,所有定义的注解都会自动继承java.lang.annotation.Annotation接口。
代码如下(示例):
@Target(ElementType.METHOD)//@Target注解,是专门用来限定某个自定义注解能够被应用在哪些Java元素上面的。它使用一个枚举类型定义
@Retention(RetentionPolicy.RUNTIME)//@Retention注解,翻译为持久力、保持力。即用来修饰自定义注解的生命力。
//注解的生命周期有三个阶段:1、Java源文件阶段;2、编译到class文件阶段;3、运行期阶段。同样使用了
public @interface InterfaceLog {
String value() default "";
}
二、使用aop记录日志
1.切入点方法类,记录日志存入数据库
代码如下(示例):
import com.alibaba.fastjson.JSONObject;
import com.opensymphony.xwork2.ActionContext;
import org.apache.struts2.ServletActionContext;
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.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
@Aspect
@Component
public class InterfaceLogAspect {
//private final Logger logger = LoggerFactory.getLogger(this.getClass());
@Autowired
InterfaceCallLogServiceImpl interfaceCallLogService;
@Pointcut("@annotation(com.XXXX.XXXXX.common.InterfaceLog)")
private void pointCut() {
}
/**
* 环绕通知
*
* @param joinPoint 连接点
* @return 切入点返回值
* @throws Throwable 异常信息
*/
@Around(value = "pointCut() && @annotation(around)") //around 与 下面参数名around对应
public Object apiLog(ProceedingJoinPoint joinPoint, InterfaceLog around) throws Throwable {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
ActionContext context = ActionContext.getContext();
HttpServletRequest request = (HttpServletRequest) context.get(ServletActionContext.HTTP_REQUEST);
String userAgent = request.getHeader("user-agent");
String ip = IpUtil.getIp(request);
String methodName = this.getMethodName(joinPoint);
String params = LogAspectUtil.getMethodParams(joinPoint);
MethodSignature msig = (MethodSignature) joinPoint.getSignature();
Method pointMethod = joinPoint.getTarget().getClass().getMethod(msig.getName(), msig.getParameterTypes());
String methodPath = String.format("%s.%s", joinPoint.getSignature().getDeclaringTypeName(), pointMethod.getName());
System.out.println("开始请求方法:[{"+methodName+"}] 服务:[{"+methodPath+"}] 参数:[{"+params+"}] IP:[{"+ip+"}] userAgent [{"+userAgent+"}]");
long start = System.currentTimeMillis();
Object result = joinPoint.proceed();
long end = System.currentTimeMillis();
String outResult = LogAspectUtil.deleteSensitiveContent(result);
System.out.println("结束请求方法:[{"+methodName+"}] 参数:[{"+params+"}] 返回结果[{"+outResult+"}] 耗时:[{"+(end - start)+"}]毫秒 ");
/**
InterfaceCallLog interfaceCallLog = new InterfaceCallLog();
interfaceCallLog.setIlInterfaceName(methodName);
JSONObject jsonObject = JSONObject.parseObject(params);
interfaceCallLog.setIlCaller(jsonObject.get("caller")!=null?jsonObject.get("caller").toString():"");
interfaceCallLog.setIlCallerIp(ip);
interfaceCallLog.setIlInParameter(params);
interfaceCallLog.setIlOutParameter(outResult);
interfaceCallLogService.save(interfaceCallLog); */
return result;
}
private String getMethodName(ProceedingJoinPoint joinPoint) {
String methodName = joinPoint.getSignature().toShortString();
String shortMethodNameSuffix = "(..)";
if (methodName.endsWith(shortMethodNameSuffix)) {
methodName = methodName.substring(0, methodName.length() - shortMethodNameSuffix.length());
}
return methodName;
}
}
2.获取访问ip地址工具类
代码如下(示例):
import com.opensymphony.xwork2.ActionContext;
import org.apache.struts2.ServletActionContext;
import javax.servlet.http.HttpServletRequest;
/**
* @description:
* @projectName:sydj0112
* @see:com.XXX.XXX.util
* @author:gongsi
* @createTime:2021/4/26 9:06
* @version:1.0
*/
public class IpUtil {
public static String getIp(HttpServletRequest request){
String ip = null;
//X-Forwarded-For:Squid 服务代理
String ipAddresses = request.getHeader("X-Forwarded-For");
if (ipAddresses == null || ipAddresses.length() == 0 || "unknown".equalsIgnoreCase(ipAddresses)) {
//Proxy-Client-IP:apache 服务代理
ipAddresses = request.getHeader("Proxy-Client-IP");
}
if (ipAddresses == null || ipAddresses.length() == 0 || "unknown".equalsIgnoreCase(ipAddresses)) {
//WL-Proxy-Client-IP:weblogic 服务代理
ipAddresses = request.getHeader("WL-Proxy-Client-IP");
}
if (ipAddresses == null || ipAddresses.length() == 0 || "unknown".equalsIgnoreCase(ipAddresses)) {
//HTTP_CLIENT_IP:有些代理服务器
ipAddresses = request.getHeader("HTTP_CLIENT_IP");
}
if (ipAddresses == null || ipAddresses.length() == 0 || "unknown".equalsIgnoreCase(ipAddresses)) {
//X-Real-IP:nginx服务代理
ipAddresses = request.getHeader("X-Real-IP");
}
//有些网络通过多层代理,那么获取到的ip就会有多个,一般都是通过逗号(,)分割开来,并且第一个ip为客户端的真实IP
if (ipAddresses != null && ipAddresses.length() != 0) {
ip = ipAddresses.split(",")[0];
}
//还是不能获取到,最后再通过request.getRemoteAddr();获取
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ipAddresses)) {
ip = request.getRemoteAddr();
}
// ip配置
if (ip.equals("127.0.0.1") || ip.endsWith("0:0:0:0:0:0:1")) {
// 根据网卡取本机配置的IP
ip = "127.0.0.1";
}
return ip;
}
}
2.参数脱敏
代码如下(示例):
import net.sf.json.JSONObject;
import org.aspectj.lang.ProceedingJoinPoint;
import org.springframework.web.multipart.MultipartFile;
import org.testng.v6.Lists;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Field;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Vector;
/**
* Created with IntelliJ IDEA.
* Description:
* AOP记录日志的一些共用方法
* @author LErry.li
* Date: 2018-06-17
* Time: 15:19
*/
public class LogAspectUtil {
private LogAspectUtil(){
}
/**
* 获取需要记录日志方法的参数,敏感参数用*代替
* @param joinPoint 切点
* @return 去除敏感参数后的Json字符串
*/
public static String getMethodParams(ProceedingJoinPoint joinPoint){
Object[] arguments = joinPoint.getArgs();
StringBuilder sb = new StringBuilder();
if(arguments ==null || arguments.length <= 0){
return sb.toString();
}
for (Object arg : arguments) {
//移除敏感内容
String paramStr;
if (arg instanceof HttpServletResponse) {
paramStr = HttpServletResponse.class.getSimpleName();
} else if (arg instanceof HttpServletRequest) {
paramStr = HttpServletRequest.class.getSimpleName();
} else if (arg instanceof MultipartFile) {
long size = ((MultipartFile) arg).getSize();
paramStr = MultipartFile.class.getSimpleName() + " size:" + size;
} else {
paramStr = deleteSensitiveContent(arg);
}
sb.append(paramStr).append(",");
}
return sb.deleteCharAt(sb.length() - 1).toString();
}
/**
* 删除参数中的敏感内容
* @param obj 参数对象
* @return 去除敏感内容后的参数对象
*/
public static String deleteSensitiveContent(Object obj) {
JSONObject jsonObject = new JSONObject();
if (obj == null || obj instanceof Exception) {
return jsonObject.toString();
}
List<String> sensitiveFieldList = getSensitiveFieldList();
if(obj instanceof Map<?,?>||obj instanceof Vector<?>||obj instanceof List<?>||obj instanceof Set<?>){
try {
jsonObject = JSONObject.fromObject(obj);
}catch (Exception e) {
return String.valueOf(obj);
}
}else{
Class cls = obj.getClass();
Field[] fields = cls.getDeclaredFields();
for(int i=0; i<fields.length; i++){
Field f = fields[i];
f.setAccessible(true);
try {
if(f.get(obj)!=null){
jsonObject.put(f.getName(),f.get(obj));
}
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
for (String sensitiveField : sensitiveFieldList) {
if (jsonObject.containsKey(sensitiveField)) {
String value=jsonObject.get(sensitiveField).toString();
value = desensitization(value);
jsonObject.put(sensitiveField,value);
}
}
return jsonObject.toString();
}
/**
* 敏感字段列表(当然这里你可以更改为可配置的)
*/
private static List<String> getSensitiveFieldList() {
List<String> sensitiveFieldList = Lists.newArrayList();
sensitiveFieldList.add("pwd");
sensitiveFieldList.add("password");
return sensitiveFieldList;
}
/**
* 敏感字段列表(当然这里你可以更改为可配置的)
*/
private static String desensitization(String value) {
if (value.length() < 1) {
value = "****";
} else if (value.length() == 1) {
value = "*" + value;
} else if (value.length() == 2) {
value = "*" + value.substring(1);
} else if (value.length() == 3) {
value = "**" + value.substring(2);
} else if(value.length() > 3 && value.length() < 7) {
value = value.substring(0,2) + "****" + value.substring(3);
}else{
value = value.substring(0,2) + "****" + value.substring(value.length()-4);
}
return value;
}
}
注意
注意:1.使用spring boot时aop注解@Component会自动生效。
2.使用springMvc 时,aop无效;
查询发现Spring与SpringMVC是2个不同的父子容器, @Aspect如果被spring容器加载的话,而@Controller注解的这些类的实例化以及注入却是由SpringMVC来完成。 @Aspect如果被spring容器加载的时候,可能Spring MVC容器还未初始化, Controller类还未初始化,所以无法正常织入。
解决:把 aop:aspectj-autoproxy 从applicationContext.xml移入springmvc配置文件中,并定义bean,如下:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.3.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.3.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-3.0.xsd
">
<!-- 这个配置一定要配置在component-scan以后 -->
<aop:aspectj-autoproxy proxy-target-class="true" />
<bean id="paramValidAspect" class="com.gs.log.LogAspect"/>
总结
注意:做一次记录
create table INTERFACE_CALL_LOG
(
il_cdate DATE,
il_udate DATE,
il_status VARCHAR2(8),
il_ord NUMBER,
il_interface_name VARCHAR2(800),
il_caller_ip VARCHAR2(48),
il_caller VARCHAR2(200),
il_in_parameter CLOB,
il_out_parameter CLOB
)