在pom.xml
文件中添加依赖,这里不用依赖也行,需要的时候加上:
package com.example.aspect.log.annotation;
import java.lang.annotation.*;
@Retention(RetentionPolicy.RUNTIME) // 在运行时使用该注解
@Target({ElementType.METHOD}) // 注解作用于方法上
@Documented // 注解将包含在JavaDoc上
public @interface LogAnnotation { // 注解名为LogAnnotation
// 模块名称
String module();
// 是否从当前的上下文中获取获取 url、ip
boolean request() default true;
// 记录和打印请求参数
boolean recordParam() default true;
// 记录和打印响应参数
boolean recordResponse() default true;
// 记录到数据库
boolean recordToDB() default false;
}
在配置AOP切面之前,我们需要了解下aspectj
相关注解的作用
切点定义好后,就是围绕这个切点做文章:
LogAnnotaion.java
的切面类,声明一个切点:// 以自定义@LogAnnotaion注解为切点,指向刚才注解的位置
@Pointcut("@annotation(com.example.aspect.log.annotation)")
public void webLog(){}
定义@Around环绕,用户何时执行切点:
@Around("webLog()")
public Object doAround(ProceedingJoinPoint joinPoint, LogAnnotation logAnnotation) throws Throwable {
long startTime = System.currentTimeMillis();
Object result = joinPoint.proceed(); // 执行切点
// 打印出参
logger.info("Response Args : {}", new Gson().toJson(result));
// 执行耗时
logger.info("Time-Consuming : {} ms", System.currentTimeMillis() - startTime);
// 返回接口返参结果,这里必须要有返回
return result;
}
log.aop.LogImportSelector.java
package com.example.aspect.log.aop;
import org.springframework.context.annotation.ImportSelector;
import org.springframework.core.type.AnnotationMetadata;
public class LogImportSelector implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
return new String[]{WebLogAspect.class.getName()};
}
}
ImportSelector接口是至spring中导入外部配置的核心接口,在SpringBoot的自动化配置和@EnableXXX(功能性注解)都有它的存在,关于SpringBoot的分析可以参考:深入理解SpringBoot的自动装配。
log.annotation.EnableAOPLog.java
package com.example.aspect.log.annotation;
import com.example.aspect.log.aop.LogImportSelector;
import org.springframework.context.annotation.Import;
import java.lang.annotation.*;
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(LogImportSelector.class)
public @interface EnableAOPLog {
}
package com.example.aspect;
import com.example.aspect.log.annotation.EnableAOPLog;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@EnableAOPLog // 打开日志切面开关
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
package com.example.aspect.controller;
import com.example.aspect.log.annotation.LogAnnotation;
import com.example.aspect.log.params.LoginParam;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;
@Slf4j
@RestController
@RequestMapping(value = "/v1")
public class demoController {
@PostMapping(value = "/login")
@LogAnnotation(module = "serviceA") // 添加日志注解
public String login(@RequestBody LoginParam loginParam){
log.info("login方法开始执行");
return loginParam.getUsername();
}
}
package com.example.aspect.log.annotation;
import com.example.aspect.log.aop.LogImportSelector;
import org.springframework.context.annotation.Import;
import java.lang.annotation.*;
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(LogImportSelector.class)
public @interface EnableAOPLog {
}
package com.example.aspect.log.annotation;
import java.lang.annotation.*;
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
@Documented
public @interface LogAnnotation {
// 模块
String module();
// 是否从当前的上下文中获取获取 url、ip
boolean request() default true;
// 记录和打印请求参数
boolean recordParam() default true;
// 记录和打印响应参数
boolean recordResponse() default true;
// 记录到数据库
boolean recordToDB() default false;
}
package com.example.aspect.log.aop;
import org.springframework.context.annotation.ImportSelector;
import org.springframework.core.type.AnnotationMetadata;
public class LogImportSelector implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
return new String[]{WebLogAspect.class.getName()};
}
}
package com.example.aspect.log.aop;
import com.example.aspect.log.annotation.LogAnnotation;
import com.example.aspect.log.params.SysLogParam;
import com.example.aspect.utils.ParemetersDesensitive;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
/**
* https://www.exception.site/springboot/spring-boot-aop-web-request
*/
@Slf4j
@Aspect
public class WebLogAspect {
@Around("@annotation(logAnnotation)")
public Object doAround(ProceedingJoinPoint joinPoint, LogAnnotation logAnnotation) throws Throwable {
SysLogParam sysLogParam = new SysLogParam();
HttpServletRequest httpServletRequest = ((ServletRequestAttributes) RequestContextHolder
.currentRequestAttributes()).getRequest();
// 记录进入时间
long startTime = System.currentTimeMillis();
DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
sysLogParam.setLogTime(dateFormat.format(new Date(startTime)));
// 记录归属模块/服务
sysLogParam.setModule(logAnnotation.module());
// todo:记录登录用户
// 记录客户端相关信息
if (logAnnotation.request() && httpServletRequest != null) {
sysLogParam.setMethod(httpServletRequest.getMethod());
sysLogParam.setIp(null);
sysLogParam.setUrl(httpServletRequest.getRequestURI());
}
// 记录请求参数
if (logAnnotation.recordParam()) {
ParemetersDesensitive.recordRequestParam(joinPoint, sysLogParam);
} else {
sysLogParam.setParams("[-]");
}
log.info("开始请求:{} {}, req:{} ", sysLogParam.getMethod(), sysLogParam.getUrl(), sysLogParam.getParams());
Object result = null;
try {
// 执行方法
result = joinPoint.proceed();
sysLogParam.setFlag(Boolean.TRUE);
} catch (Exception e) {
sysLogParam.setFlag(Boolean.FALSE);
log.error("请求报错{}", e.getMessage());
throw e;
} finally {
try {
if (logAnnotation.recordResponse()) {
log.info("结束请求:{} {}, time:{}, req:{}, resp:{}", sysLogParam.getMethod(), sysLogParam.getUrl(), (System.currentTimeMillis() - startTime), sysLogParam.getParams(), ParemetersDesensitive.resultStr(result));
} else {
log.info("结束请求:{} {}, time:{}, req:{}", sysLogParam.getMethod(), sysLogParam.getUrl(), (System.currentTimeMillis() - startTime), sysLogParam.getParams());
}
if (logAnnotation.recordToDB()) {
recordToDb(sysLogParam);
}
log.trace("退出日志切面");
} catch (Exception e) {
log.warn("记录日志时,出现异常");
}
}
return result;
}
// 记录执行参数到数据库
private void recordToDb(SysLogParam sysLog) {
// if (logService == null) {
// return;
// }
// CompletableFuture.runAsync(() -> {
// try {
// log.trace("日志落库开始:{}", sysLog);
// logService.save(sysLog);
// log.trace("开始落库结束:{}", sysLog);
// } catch (Exception e) {
// log.error("落库失败:{}", e.getMessage());
// }
// }, taskExecutor);
}
}
package com.example.aspect.log.params;
import lombok.Data;
import java.io.Serializable;
@Data
public class SysLogParam implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 对应mongodb的id
*/
private String id;
/**
* 客户端IP地址
*/
private String ip;
/**
* 登录用户信息:账号
*/
private String loginName;
/**
* 登录用户信息:用户Id
*/
private String userId;
/**
* 登录用户信息:用户名
*/
private String userName;
/**
* 归属服务ID
*/
private String module;
/**
* 访问的方法:GET/PUT/POST/DELETE
*/
private String method;
/**
* 访问的url地址
*/
private String url;
/**
* 传递的参数信息
*/
private String params;
/**
* 是否执行成功
*/
private Boolean flag;
/**
* 访问时间
*/
private String logTime;
/**
* 接口耗时
*/
private String durationTime;
}