文章目录
- 创建自定义注解类
- Spring AOP的基础知识
- AOP实现日志记录功能
- 日志记录演示
创建自定义注解类
- 首先创建自定义注解类
EnableLog
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface EnableLog {
String value() default "";
}
- 注解类上的三个注解称为元注解(
元注解 是负责对其它注解进行说明的注解
),其分别代表的含义如下:
- @Documented:注解信息会被添加到Java文档中
- @Retention:注解的生命周期,表示注解会被保留到什么阶段,可以选择编译阶段、类加载阶段,或运行阶段(
RetentionPolicy.RUNTIME
). - @Target:注解作用的位置,
ElementType.METHOD
表示该注解仅能作用于方法上
- 目前注解可以加到方法上,但是并没有任何作用。因为仅仅是声明了注解,下面通过Spring AOP去完善注解的作用。
Spring AOP的基础知识
AOP(Aspect Oriented Programming),即面向切面编程。所谓"切面",简单说就是那些与业务无关,却为业务模块所共同调用的逻辑或责任封装起来,便于减少系统的重复代码,降低模块之间的耦合度,并有利于未来的可操作性和可维护性。
AOP 术语
- 切面(aspect): 类是对物体特征的抽象,切面就是对横切关注点的抽象
- 连接点(joinpoint): 被拦截到的点,因为Spring只支持方法类型的连接点,所以在Spring中连接点指的就是被拦截到的方法,实际上连接点还可以是字段或者构造器
- 切入点(pointcut): 对连接点进行拦截的定义
- 通知(advice): 所谓通知指的就是指拦截到连接点之后要执行的代码,通知分为前置、后置、异常、最终、环绕通知五类
Spring切面可以应用5种类型的通知
- 前置通知(Before):在目标方法被调用之前调用通知
- 后置通知(After):在目标方法完成之后调用通知(无论是正常还是异常退出都调用)
- 返回通知(After-returning):在目标方法成功执行之后调用通知
- 异常通知(After-throwing):在目标方法抛出异常后调用通知
- 环绕通知(Around):通知包裹了被通知的方法,在被通知的方法调用之前和调用之后执行自定义的行为
AOP实现日志记录功能
/**
* @Component 该注解表示把类加入Spring容器
* @Aspect 该注解表示切面
*/
@Aspect
@Component
@Slf4j
public class OperationLogAspect {
ThreadLocal<Long> startTime = new ThreadLocal<>();
/**
* 设置操作日志切入点,这里介绍两种方式:
* 1、基于注解切入(也就是打了自定义注解的方法才会切入)
* @Pointcut("@annotation( 包名.EnableLog)")
* 2、基于包扫描切入
* @Pointcut("execution(public * 包名.controller..*.*(..))")
*/
// @Pointcut 声明了一个公共切点(这里的切点是我们上面自定义的注解类)
@Pointcut("@annotation( 包名.EnableLog)")
public void operLog() {
}
public HttpServletRequest getRequest() {
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = attributes.getRequest();
return request;
}
// @Around 环绕通知:通知包裹了被通知的方法,在被通知的方法调用之前和调用之后执行自定义的行为
@Around("operLog()")
public Result doAround(ProceedingJoinPoint pjp) throws Throwable {
startTime.set(System.currentTimeMillis());
// AOP的JoinPoint类: 用来获取代理类和被代理类的信息。
String className = pjp.getSignature().getDeclaringTypeName();
String methodName = pjp.getSignature().getName();
MethodSignature methodSignature = (MethodSignature) pjp.getSignature();
Method method = methodSignature.getMethod();
EnableLog enableLog = method.getAnnotation(EnableLog.class);
Object[] args = pjp.getArgs();
String[] argNames = methodSignature.getParameterNames();
Map<String, Object> paramMap = new HashMap<>();
for (int i = 0; i < args.length; i++) {
paramMap.put(argNames[i], args[i]);
}
HttpServletRequest request = getRequest();
log.info("*****************************************************");
log.info("用户:{}", UserUtils.getUserAccount());
log.info("URL:{}", request.getRequestURL());
log.info("类名:{}", className);
log.info("方法:{}", methodName);
log.info("IP地址:{}", request.getRemoteHost());
log.info("操作:{}", enableLog.value());
log.info("参数:{}", JSONObject.toJSONString(paramMap));
try {
Object process = pjp.proceed();
Result result = (Result) process;
log.info("结果:{}", result.getMsg());
return result;
} catch (Exception e) {
log.info("异常:{}", e.getMessage());
throw e;
} finally {
log.info("耗时:{}", System.currentTimeMillis() - startTime.get());
log.info("*****************************************************");
startTime.remove();
}
}
}
日志记录演示
- 在需要获取日志的方法上加上
@EnableLog
注解 - 调用该方法,输出一下日志:
你知道的越多,你不知道的越多。