AOP日志实现
注解方式实现AOP日志
为什么要使用AOP?
通常在项目中,我们需要配置日志打印来获取服务运行时我们需要的一些基本信息,这些信息是提供给我们后期用于排查错误的基本凭据
但是如果直接在业务逻辑中添加日志打印的代码,会使得日志打印与业务逻辑产生耦合,也降低了业务代码的可读性,大量的日志打印可能都是一些重复的代码,降低了编程效率,更不便于统一管理
因此我们采用注解+AOP来配置需要被记录日志的方法
常见的AOP场景有:日志打印,权限认证,事务处理
自定义日志注解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface SystemLog {
//业务信息,用于存放被增强方法的业务名称信息
String businessName();
}
配置切面类
@Aspect 声明当前类是一个切面类
@Component 交由Spring容器管理
@Pointcut 配置切入点表达式
- 这里我们使用注解的方式来配置切入点
@annotation(自定义注解全类名)
被自定义注解注释的方法都将成为连接点
@Component
@Aspect
public class LogAspect {
@Pointcut("@annotation(com.os467.annotation.SystemLog)")
public void pt(){
}
@Around("pt()")
public Object printLog(ProceedingJoinPoint joinPoint){
}
}
环绕通知
这里我们选择环绕通知,可以在此通知中为方法设置前置和后置通知,功能较为强大
需要提供一个参数ProceedingJoinPoint joinPoint
- Proceedingjoinpoint 继承了 JoinPoint,是在JoinPoint的基础上暴露出
proceed()
这个方法,proceed很重要,这个是aop代理链执行的方法
joinPoint基本方法
//返回目标对象,即被代理的对象
Object getTarget();
//返回切入点的参数
Object[] getArgs();
//返回切入点的签名
Signature getSignature();
//返回切入的类型
String getKind();
配置切面
详细流程
配置切入点表达式
首先需要声明一个切入点表达式
使用**@Pointcut注解**
这里通过读取注解来获取到连接点
@Pointcut("@annotation(com.os467.annotation.SystemLog)")
public void pt(){
}
配置环绕通知
@Around 指定配置切入点表达式的方法
ProceedingJoinPoint 连接点,即在底层动态代理能访问到的方法,调用proceed()
来执行需要被增强的方法
@Around("pt()")
public Object printLog(ProceedingJoinPoint joinPoint){
}
执行目标方法
@Around("pt()")
public Object printLog(ProceedingJoinPoint joinPoint){
//前置通知...
//执行目标方法
ret = joinPoint.proceed();
//后置通知...
//返回方法执行后的结果
return ret;
}
通过joinPoint获取到切入点的方法签名
有时候我们还需要获取到自定义注解内的信息用于日志打印的凭据,因此我们在配置切面的时候还需要获取到方法的注解字节码对象
getSignature()
此方法会返回一个Signature实现类的对象,我们需要将其向下转型成MethodSignature类通过
getMethod()
获取到方法字节码,此时就可以调用getAnnotation()
来获取到方法注解上的信息
//获取当前切入点的方法签名
MethodSignature methodSignature = (MethodSignature)joinPoint.getSignature();
//获取到修饰方法的注解字节码对象
SystemLog systemLog = methodSignature.getMethod().getAnnotation(SystemLog.class);
案例
@Component
@Aspect
@Slf4j
public class LogAspect {
@Pointcut("@annotation(com.os467.annotation.SystemLog)")
public void pt(){
}
/**
* 配置切面
* @param joinPoint
* @return
* @throws Throwable
*/
@Around("pt()")
public Object printLog(ProceedingJoinPoint joinPoint) throws Throwable {
//获取目标方法的返回值
Object ret;
try {
//前置通知
printBefore(joinPoint);
//执行目标方法
ret = joinPoint.proceed();
//后置通知
printAfter(ret);
} finally {
// 结束后换行,获取到当前系统的换行符
log.info("=======End=======" + System.lineSeparator());
}
//返回切入点方法执行结果
return ret;
}
/**
* 执行方法后需要打印的通知
* @param ret
*/
private void printAfter(Object ret) {
// 打印出参
log.info("Response : {}",JSON.toJSONString(ret));
}
/**
* 执行方法之前需要打印的通知
*/
private void printBefore(ProceedingJoinPoint joinPoint) {
//获取到当前线程的请求对象
RequestAttributes attributes = RequestContextHolder.getRequestAttributes();
HttpServletRequest request = null;
//向下转型
ServletRequestAttributes requestAttributes = (ServletRequestAttributes)attributes;
//获取到当前线程请求对象
request = requestAttributes.getRequest();
//通过反射获取被增强方法上的注解对象
SystemLog systemLog = getSystemLog(joinPoint);
log.info("=======Start=======");
// 打印请求 URL
log.info("URL : {}",request.getRequestURL());
// 打印描述信息
log.info("BusinessName : {}",systemLog.businessName());
// 打印 Http method
log.info("HTTP Method : {}", request.getMethod());
// 打印调用 controller 的全路径以及执行方法
log.info("Class Method : {}.{}", joinPoint.getSignature().getDeclaringType(),((MethodSignature)joinPoint.getSignature()).getName());
// 打印请求的 IP
log.info("IP : {}",request.getRemoteHost());
// 打印请求入参
log.info("Request Args : {}", JSON.toJSONString(joinPoint.getArgs()));
}
/**
* 获取到被增强方法上的注解信息
* @param joinPoint
* @return
*/
private SystemLog getSystemLog(ProceedingJoinPoint joinPoint) {
MethodSignature methodSignature = (MethodSignature)joinPoint.getSignature();
SystemLog systemLog = methodSignature.getMethod().getAnnotation(SystemLog.class);
return systemLog;
}
}
使用注解实现日志通知
@SystemLog(businessName = "用户信息修改")
@PutMapping("/userInfo")
public ResponseResult updateUser(@RequestBody User user){
return userService.updateUser(user);
}
转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以邮件至 1300452403@qq.com