目录
1. 常用注解和理解
2. 自定义注解
2.1 案例背景
2.2 设计思路
3 总结
1. 常用注解和理解
注解在我的理解下,就是代码中的特殊标记,这些标记可以在编译、类加载、运行时被读取,并执行相对应的处理。
可能有些抽象,简单来说注解其实在开发中是非常常见的,比如我们在使用各种框架时(Spring)就会用到非常多的注解,@Controller/@Param / @Select 等。
除了框架实现的注解,Java原生也有@Overrided、@Deprecated、@FunctionalInterface等基本注解,不过Java原生注解大多数用于标记和检查。除了这些基本注解之外,还有一种叫做元Annotation,用来修饰注解的,常用的元注解有@Retention和@Target。
- @Retention注解可以简单理解为设置注解的生命周期。
- @Target可以理解为注解的修饰对象(方法、成员变量、包等)。
2. 自定义注解
2.1 案例背景
假设现在有个监控告警系统,我们需要将一些程序的审计日志,比如成功/失败日志,通过自定义注解的方式设置监控指标,如果这些逻辑在业务代码中混合就会显得不那么优雅。
2.2 设计思路
类似这种监控信息显然都可以通过AOP切面的方式去处理,而如果使用注解配置相关的信息,配合AOP解析就会比较优雅。
自定义注解首先考虑我们是在何时解析这个注解,需要用到@Retention注解,这个注解会修饰我们自定义注解的生命周期,@Retention注解传入的是RetentionPolicy枚举,包括SOURCE、CLASS和RUNTIME。
理解这块就得了解从java文件到class文件再到class被jvm加载的过程了。
从上图可以发现有个注解抽象语法树,这里其实就回去解析注解,然后做逻辑处理。
重点是,如果想在编译期间处理注解相关的逻辑,需要集成AbstractProcessor并实现process方法,比如lombok就是通过AnnotationProcessor集成了AbstractProcessor。
一般只要自定义注解中@Retention注解设置为SOURCE和CLASS这两种级别就需要继承并实现,因为这两个级别加载到jvm的时候,注解就被抹除了。
比如lombok的@Data注解能有getter/setter等方法,就是在这个时候加上去的。
所以一般来说自定义注解都是什么级别?
一般来说我们自定义注解都是RUNTIME级别的,因为大多数情况我们是根据运行时环境去做处理,因为反射是Java获取运行时信息的重要手段,自定义注解需要配合反射来使用。
@Around("@annotation(com.sanwai.service.openapi.monitor.Monitor)")
public Object antispan(ProceedingJoinPoint pjp) throws Throwable {
String functionName = pjp.getSignature().getName();
Map<String, String> tags = new HashMap<>();
logger.info(functionName);
tags.put("functionName", functionName);
tags.put("flag", "done");
monitor.sum(functionName, "start", 1);
//方法执行开始时间
long startTime = System.currentTimeMillis();
Object o = null;
try {
o = pjp.proceed();
} catch (Exception e) {
//方法执行结束时间
long endTime = System.currentTimeMillis();
tags.put("flag", "fail");
monitor.avg("rt", tags, endTime - startTime);
monitor.sum(functionName, "fail", 1);
throw e;
}
//方法执行结束时间
long endTime = System.currentTimeMillis();
monitor.avg("rt", tags, endTime - startTime);
if (null != o) {
monitor.sum(functionName, "done", 1);
}
return o;
}
3 总结
注解是代码的特殊标记,可以在编译、类加载、运行时被读取,对应了RetentionPolicy的三种级别。
SOURCE和CLASS级别需要继承AbstractProcessor,实现process方法处理自定义注解的逻辑,而RUNTIME是我们日常开发用的最多的,配合反射机制可以在很多场景优化代码。