使用注解可以大大减少开发的代码量,所以在实际项目的开发中会使用到很多的注解。特别是做一些公共基础的功能,比如日志记录,事务管理,权限控制这些功能,使用注解会非常高效且优雅。
而对于自定义注解,主要有三个步骤,定义注解,标记注解,解析注解,其实并不是很难。所以关于自定义注解的更多内容就不在这里展开说了,大家也可以看我之前的文章Java注解Annotation小结。
正如文章标题所言,通过自定义注解+AOP切面编程实现日志记录功能,开发实现流程主要分为下面四步:
- 1 定义注解
- 2 标记注解:在对应需要记录日志的方法上标记注解
- 3 解析注解:编写切面类,进行相应处理逻辑
- 4 开启切面能力
下面实战一下,自定义一个注解@DoLog,用于方法上,当方法被调用时即打印日志,在控制台显示调用方传入的参数和调用返回的结果。
1 定义注解
首先定义注解@DoLog
,在方法上使用,为了能在反射中读取注解信息,当然是设置为RUNTIME
。
@Target(value = ElementType.METHOD)
@Documented
@Retention(value = RetentionPolicy.RUNTIME)
public @interface DoLog {
String dataStatus() default "";
String[] labelNames() default {};
}
说明:这里注解里面定义的属性,对于记录日志没有实际意义,这里只是顺便演示一下如何在切面类中获取注解中的属性值。
2 标记注解:在对应需要记录日志的方法上标记注解
注意:使用切面的注解不能加在私有方法上,否则切入不了。这个要注意一下,你也可以自己试一下。
@RestController
@Slf4j
public class DemoController {
@GetMapping("/demo2/{orderno}")
@DoLog(dataStatus = "1",labelNames = {"name","age"})
public BaseResult<String> test1(@PathVariable(value = "orderno") String orderno) {
return BaseResult.success("aaa");
}
}
3 解析注解:编写切面类,进行相应处理逻辑
最关键的一步来了,解析注解,一般在项目中会使用Spring的AOP技术解析注解,当然如果只需要解析一次的话,也可以使用Spring处理器BeanPostProcessor来读取注解,相关内容可以参考我的文章使用Spring的BeanPostProcessor优雅的实现工厂模式。
咱这里的场景是打印每次方法被调用的日志,所以使用AOP比较合适。
创建一个切面类DoLogAspect进行解析。
@Aspect
@Slf4j
@Component
public class DoLogAspect {
//切面点为标记了@DoLog注解的方法
@Pointcut("@annotation(cn.test.util.DoLog)")
public void doLog() {
}
//环绕通知
@Around("doLog()")
@SuppressWarnings("unchecked")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
long starTime = System.currentTimeMillis();
//通过反射获取被调用方法的Class
Class type = joinPoint.getSignature().getDeclaringType();
//获取类名
String typeName = type.getSimpleName();
//方法名
String methodName = joinPoint.getSignature().getName();
//获取参数列表
Object[] args = joinPoint.getArgs();
//参数Class的数组
Class[] clazz = new Class[args.length];
for (int i = 0; i < args.length; i++) {
clazz[i] = args[i].getClass();
}
//通过反射获取调用的方法method
Method method = type.getMethod(methodName, clazz);
DoLog doLog = method.getAnnotation(DoLog.class);
log.info("注解参数:{},{}",logApi.dataStatus(),JSONObject.toJSONString(logApi.labelNames()));
//获取方法的参数
Parameter[] parameters = method.getParameters();
//拼接字符串,格式为{参数1:值1,参数2::值2}
StringBuilder sb = new StringBuilder();
for (int i = 0; i < parameters.length; i++) {
Parameter parameter = parameters[i];
String name = parameter.getName();
sb.append(name).append(":").append(args[i]).append(",");
}
if (sb.length() > 0) {
sb.deleteCharAt(sb.lastIndexOf(","));
}
//执行结果
Object res;
try {
//执行目标方法,获取执行结果
res = joinPoint.proceed();
log.info("调用{}.{}方法成功,参数为[{}],返回结果[{}]", typeName, methodName, sb.toString(), JSONObject.toJSONString(res));
} catch (Exception e) {
log.error("调用{}.{}方法发生异常", typeName, methodName);
//如果发生异常,则抛出异常
throw e;
} finally {
log.info("调用{}.{}方法,耗时{}ms", typeName, methodName, (System.currentTimeMillis() - starTime));
}
//返回执行结果
return res;
}
}
注意:自己在主逻辑之外使用切面加入的处理逻辑,一定要try处以免影响到正常业务逻辑。
扩展(Pointcut定义):
public class DoLogAspect {
//切面点为标记了@DoLog注解的方法
@Pointcut("@annotation(cn.test.util.DoLog)")
public void doLog() {
}
//环绕通知
@Around("doLog()")
@SuppressWarnings("unchecked")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
//处理代码
}
以上代码同样可以替换为下面的代码:
public class DoLogAspect {
//环绕通知
@Around("@annotation(cn.test.util.DoLog)")
@SuppressWarnings("unchecked")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
//处理代码
}
4 开启切面能力
定义完切面类后,就需要在启动类添加启动AOP的注解。
@SpringBootApplication
//添加此注解,开启AOP
@EnableAspectJAutoProxy
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
5 测试
启动项目,请求接口,我们可以看到控制台出现被调用方法的日志信息,如下:
2021-11-13 06:16:26.617|||INFO|||-|||-|||RMI TCP Connection(2)-192.168.0.100|||DispatcherServlet--->Completed initialization in 22 ms
2021-11-13 06:30:08.807|||INFO|||-|||-|||http-nio-8084-exec-1|||DoLogAspect--->注解参数:1,["name","age"]
2021-11-13 06:30:08.823|||INFO|||-|||-|||http-nio-8084-exec-1|||DoLogAspect--->调用DemoController.test1方法成功,参数为[arg0:1001],返回结果[{"data":"aaa","msg":"success","ret":"0"}]
2021-11-13 06:30:08.823|||INFO|||-|||-|||http-nio-8084-exec-1|||DoLogAspect--->调用DemoController.test1方法,耗时20ms
总结
这种使用注解和切面编程,记录接口请求参数和返回值的功能,因为使用起来特别方便,而且代码侵入性又特别小,所以在实际项目中基本上都会使用,而最常见的场景就是日志、监控打点,异常收口。
关于自定义注解+AOP实现记录日志功能就分享到这里,期待能够给有缘的同学带来一些启发和帮助。
扩展:
Java AOP切面@PointCut的用法: https://www.cnblogs.com/liaojie970/p/7883687.html