AOP
面向切面编程。
将日志记录,性能统计,安全控制,事务处理,异常处理等代码从业务逻辑代码中划分出来,通过对这些行为的分离,可以将它们独立到非指导业务逻辑的方法中,进而改变这些行为的时候不影响业务逻辑的代码。
像平时开发,基本都要求在接口出入口增加Logback日志记录,方便调试排查问题。
最简单的方法当然就是手动添加Log.info();
但为什么不尝试Aop呢?一个注解就可以解决所有的出入口日志,不仅如此还可以同时记录接口执行时间
Spring AOP
在Spring中:
1、如果目标对象实现了接口,默认情况下会采用JDK的动态代理实现AOP;
2、如果目标对象实现了接口,可以强制使用CGLIB实现AOP;
3、如果目标对象没有实现接口,必须采用CGLIB库,spring会自动在JDK动态代理和CGLIB之间转换;
具体什么是JDK动态代理,大致就是根据类加载器和接口创建代理类;而CGLIB动态代理并不要求委托类必须实现接口,底层采用asm字节码生成框架生成代理类的字节码。
日志切面示例
注解类
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
@Documented
public @interface PrintLog {
/**
* 自定义日志描述信息文案
*/
String description() default "";
}
切面类
@Aspect //切面注解
@Component
@Slf4j
//@Profile({"prod"}) //指定打印日志的环境
public class LogAspect {
// 以 @PrintLog 注解为切面入口
@Pointcut("@annotation(com.test.PrintLog)")
public void PrintLog() {
}
// 切面方法入参日志打印
@Before("PrintLog()") // 前置通知: 目标方法之前执行
public void doBefore(JoinPoint joinPoint) throws Throwable {
// 获取注解描述description
String methodDetailDescription = this.getBeforeDescription(joinPoint);
// log打印接口入参日志
log.info("{} 入参: {}", methodDetailDescription, JSON.toJSONString(joinPoint.getArgs()));
}
// 可加入打印执行时间
@Around("PrintLog()") // 环绕通知: 环绕目标方法执行
public Object doAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
long startTime = System.currentTimeMillis();
// 执行被代理的方法,并获取执行结果
Object result = proceedingJoinPoint.proceed();
log.info("结果: {} ms", JSON.toJSONString(result));
log.info("耗时: {} ms", System.currentTimeMillis() - startTime);
return result;
}
@After("PrintLog()") // 后置通知:目标方法之后执行(始终执行)
public void doAfter(JoinPoint joinPoint) throws Throwable {
log.info("执行结束" + System.lineSeparator());
}
// 传递切面的class、签名、参数
public String getBeforeDescription(JoinPoint joinPoint) throws Exception {
String targetName = joinPoint.getTarget().getClass().getName();
String methodName = joinPoint.getSignature().getName();
Object[] arguments = joinPoint.getArgs();
return getAspectMethodDesc(targetName, methodName, arguments);
}
// 反射获取Printlog注解的Description
public String getAspectMethodDesc(String targetName, String methodName, Object[] arguments) throws Exception {
Class targetClass = Class.forName(targetName);
Method[] methods = targetClass.getMethods();
StringBuilder description = new StringBuilder();
for (Method method : methods) {
if (method.getName().equals(methodName)) {
Class[] clazzs = method.getParameterTypes();
if (clazzs.length == arguments.length) {
description.append(method.getAnnotation(PrintlnLog.class).description());
break;
}
}
}
return description.toString();
}
}
总结
之后只需要在controller中指定的映射上加上 @PrintLog 注解并填充description,如@PrintLog(description = “用户登录”),就可以不用手动写Log.info()了。
但需要注意的是,这种Aop打印日志的方式一般只在测试、开发环境使用,因为会损耗一定的性能而延缓接口返回时间。所以如果需要在返回时间要求很高的生产环境使用的话,最好少在切点执行损耗性能的方法。