基于 Spring Boot Aop条件下自定义、实现注解
转自: 青灯抽丝-基于SpringBoot AOP条件下自定义、实现注解
作者: 青灯抽丝
链接:https://mp.weixin.qq.com/s/qGpd2_6MwP5sYbSVPbxaPQ
来源: 青灯抽丝
本文介绍SpringBoot条件下,借助于AOP实现自定义注解
所谓元注解,就是Java提供的、负责修饰其他注解的注解。常见地有:
其定义了注解可以作用的位置,其value属性地常用取值有:
- ElementType.PACKAGE:包
- ElementType.TYPE:类、接口、枚举
- ElementType.FIELD:字段
- ElementType.METHOD:方法
- ElementType.PARAMETER:方法形参
- ElementType.CONSTRUCTOR:构造器方法
@Target 注解的 value 是数组类型,当只有一个元素时,可以省略数组写法。示例如下所示:
// 该注解可用于方法上
@Target(ElementType.METHOD)
// 该注解可用于字段、方法、构造器方法上
@Target({
ElementType.FIELD, ElementType.METHOD, ElementType.CONSTRUCTOR
})
其定义了注解的生命周期。其value属性地常用取值有:
- RetentionPolicy.SOURCE:在Java源文件中有效。编译器会丢弃掉
- RetentionPolicy.CLASS:在Class文件中有效。运行时JVM会丢弃掉
- RetentionPolicy.RUNTIME:运行时有效。此时即可通过反射获取到该注解
日常开发中,对于@Retention注解而言。我们用的更多的就是RetentionPolicy.RUNTIME了。示例如下
// 该注解保留到运行时
@Retention(RetentionPolicy.RUNTIME)
默认情况下,注解不可以在同一处重复使用。为此 Java 8中引入了 @Repeatable 注解解决该问题。通过添加 @Repeatable 注解表示 @Family 注解可在同一处重复使用。同时,我们需要在 @Repeatable 注解的值中指定另一个注解 @Families。表示可以通过 @Families 注解的值来包含这个可重复的注解 @Family。显然此时,@Families 中 value 属性的类型则必须是 @Family 注解的数组
@Target(ElementType.FIELD) // 该注解用于字段
@Retention(RetentionPolicy.RUNTIME) // 该注解保留到运行时
@Repeatable(Families.class) // 该注解可重复
public @interface Family {
String value() default "";
}
...
@Target(ElementType.FIELD) // 该注解用于字段
@Retention(RetentionPolicy.RUNTIME) // 该注解保留到运行时
public @interface Families {
Family[] value();
}
现在,我们就可以在同一处重复使用@Family注解了。下述两种写法均可
public class User {
@Family("Aaron")
@Family("Bob")
private int name1;
@Families({
@Family("Aaron"), @Family("Bob")
})
private String name2;
}
自定义注解的基本语法格式如下例所示。其中自定义注解可通过下述形式定义注解属性。可通过default指定属性的默认值,如果不指定默认值,则在使用注解时必须显式设置属性值,而无法使用默认值。需注意属性类型仅限下述几种:
- 基本数据类型(boolean, byte, char, short, int, long, float, double)
- String类型、Class类型、注解类型、枚举类
- 上述类型的数组
/**
* 自定义注解
*/
@Target(ElementType.METHOD) // 该注解用于方法
@Retention(RetentionPolicy.RUNTIME) // 该注解保留到运行时
public @interface MyLog {
// 定义类型为long、名为value的属性
long value();
// 定义类型为String、名为level的属性, 默认值为 "INFO"
String level() default "INFO";
}
完成自定义注解后,我们期望在方法上添加注解,能够在调用方法的前后实现日志输出(包含方法入参、方法结果等信息)。这里我们结合SpringBoot的AOP来实现对自定义注解输出日志的功能
/**
* 实现@Mylog注解功能的切面类
*/
@Component
@Aspect
@Slf4j
public class MyLogAop {
@Around( "@annotation(com.aaron.SpringBoot1.annotation.MyLog)" )
public void log(ProceedingJoinPoint joinPoint) throws Throwable {
// 获取方法信息
MethodSignature methodSignature = (MethodSignature)joinPoint.getSignature();
String methodName = methodSignature.getName(); // 获取方法名
String[] paramNames = methodSignature.getParameterNames(); // 获取方法参数名
Object[] args = joinPoint.getArgs(); // 获取方法参数值
// 获取注解信息
MyLog myLog = methodSignature.getMethod().getAnnotation( MyLog.class);
long timestamp = myLog.value();
String level = myLog.level();
StringBuilder sb = new StringBuilder();
for (int i=0; i<paramNames.length; i++) {
sb.append("<").append(paramNames[i]).append(":").append(args[i]).append(">");
}
// 执行开始前打印日志
String startMsg = "[START] TS: "+ timestamp + " <"+level+">" + " MethodName: " + methodName + " Param: " + sb.toString();
log.info(startMsg);
// 调用目标方法
Object res="Exec Failed";
try {
res = joinPoint.proceed();
} catch (Exception e) {
log.error("Happen Excep: {}", e.getMessage());
}
// 执行完成后打印日志
String endMsg = "[END] TS: "+ timestamp + " <"+level+">" + " MethodName: " + methodName + " Result: " + res;
log.info(endMsg);
}
}
至此,就可以使用该注解了
@RestController
@RequestMapping("test")
public class TestController {
@MyLog(1995832)
@GetMapping("/demo1")
public String test1(@RequestParam(required = false) String firstName, @RequestParam(required = false) String lastName) {
String res = "Hello";
if( firstName!=null ) {
res += " " + firstName;
}
if( lastName!=null ) {
res += " " + lastName;
}
return res;
}
}
发送下述请求
curl "127.0.0.1:8080/test/demo1?firstName=Tony&lastName=Wang"
结果如下所示
如果一个注解中有一个名为value的属性。在使用该注解时,如果只设置value属性的话(要么该注解中只有一个value属性、要么其他属性均使用默认值),则可以省略掉属性名value。如下所示
public class Task {
@MyLog(22)
public void runTask1() {
...
}
@MyLog(value = 22)
public void runTask2() {
...
}