注解使程序更加简洁,花更少的力气完成更多的工作——这也是计算机的意义所在。
- 什么是注解
- 运行时获取注解信息
1. 注解是什么
Class 对象是 Java 类的说明书,你(通过反射)或者 JVM 阅读该说明书,创建类的实例。
而注解就是说明书中的一小段信息、文本或者标记。
- 可以携带参数
- 可以在运行时(@Retention)被阅读
注解本质上也是一个广义的 Java class,也会被编译为 class 文件,创建时选择 Annotation 即可:
public @interface Target /* extends Annotation */ { // 隐含地继承自 java.lang.annotation.Annotation// ...}
2. 元注解
用来修饰注解的注解就是元注解,最常用的就是 @Target 和 @Retention。
- @Target(ElementType)
- TYPE 【类、接口(包括注解类型)或枚举声明】
- FIELD 【字段声明(包括枚举常量)】
- METHOD 【方法声明】
- PARAMETER 【参数声明】
- CONSTRUCTOR 【构造方法声明】
- LOCAL_VARIABLE 【局部变量声明】
- ANNOTATION_TYPE 【注解类型声明,即声明为元注解,用于注解其他注解】
- PACKAGE 【包声明】
- @Retention(RetentionPolicy)注解被保留的策略/等级
- SOURCE 【只存在于源码中,编译器处理之后被擦除】
- CLASS(默认) 【被记录到编译后的 .class 文件中,但 VM 运行期中不存在】
- RUNTIME 【存在于 .class 文件中,而且还能被 JVM 在运行时读入】
- @Inherited 【定义子类是否可以继承父类定义的注解】
- @Repeatable 【定义注解是否可以重复使用】
3. 如何定义一个注解
注解的属性可以有:基本数据类型 + String + 类以及他们的数组。
第一步:用@interface 定义注解
第二步:添加参数、默认值(把最常用的参数定义为 value(),推荐所有参数都尽量设置默认值)
第三步:用元注解配置注解(一般是设置 @Target 和 @Retention)
// 定义注解@Retention(RetentionPolicy.RUNTIME)@Target(ElementType.TYPE)public @interface Report {int type() default 0;String level() default "info";String value() default "";}// 使用注解@Report(value="即将爆炸", type=2, level="warning")public class Hello {}
4. JDK 的常用自带注解
- @Deprecated 【标记相关元素被废弃】
- @Override 【标记覆盖/重写】
- @SuppressWarnings【阻止警告】
- @FunctionalInterface【标记函数式接口】
5. 注解是如何工作的
定义了注解,并且使用了注解,并不一定生效,最关键的是接下来如何处理注解信息。
原理就是在运行期通过反射获取注解信息,进行动态字节码增强。
- Method.getAnnotation
- Class.getAnnotation
- Field.getAnnotation
- …
可以借助第三方库 Byte Buddy,否则就是自己慢慢通过反射进行操作。
<dependency><groupId>net.bytebuddy</groupId><artifactId>byte-buddy</artifactId><version>LATEST</version></dependency>
6. 实战
6.1 编写一个@Log注解来自动生成日志
在运⾏时,拦截方法的进⼊和退出,打印相应的⽇志。
import java.lang.annotation.Retention;import java.lang.annotation.RetentionPolicy;@Retention(RetentionPolicy.RUNTIME)public @interface Log {String value() default "WARNING!";int level() default 0;}
public class MyService {@Log(value = "ERROR")public void queryDatabase(int param) {System.out.println("query db:" + param);}@Logpublic void provideHttpResponse(String param) {System.out.println("provide http service:" + param);}public void noLog() {System.out.println("no have log!");}}
import net.bytebuddy.ByteBuddy;import net.bytebuddy.implementation.MethodDelegation;import net.bytebuddy.implementation.bind.annotation.SuperCall;import java.lang.reflect.InvocationTargetException;import java.lang.reflect.Method;import java.util.List;import java.util.concurrent.Callable;import java.util.stream.Collectors;import java.util.stream.Stream;public class Main {// 把 MyService 中所有带有 @Log 注解的方法都过滤出来static List<String> methodsWithLog = Stream.of(MyService.class.getMethods()).filter(Main::isAnnotationWithLog).map(Method::getName).collect(Collectors.toList());private static MyService enhanceByAnnotation() throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {return new ByteBuddy()// 创建用于生成子类的 builder.subclass(MyService.class)// 控制目标子类上具有哪些方法.method(method -> methodsWithLog.contains(method.getName()))// 对匹配到的方法进行拦截,传入定制化的方法实现,继续返回 builder.intercept(MethodDelegation.to(LoggerInterceptor.class))// 根据 builder 中的信息生成尚未加载的动态类型(目标子类).make()// 尝试加载该动态类型.load(Main.class.getClassLoader())// 获取加载之后的 class 对象.getLoaded().getConstructor().newInstance();}private static boolean isAnnotationWithLog(Method method) {// 尝试通过反射获取方法上的注解return method.getAnnotation(Log.class) != null;}// 方法拦截器public static class LoggerInterceptor {public static void log(@SuperCall Callable<Void> zuper) throws Exception {System.out.println("Start!");try {zuper.call();} finally {System.out.println("End!");}}}// 测试public static void main(String[] args) throws InvocationTargetException, NoSuchMethodException, InstantiationException, IllegalAccessException {MyService service = enhanceByAnnotation();service.queryDatabase(1);service.provideHttpResponse("abc");service.noLog();}}
6.2 编写一个 @Cache注解实现缓存AOP(基于注解的缓存装饰器)
装饰器模式(decorator pattern)是一种非常常用的高级设计模式。
请实现一个基于@Cache注解的装饰器,能够将传入的服务类的Class进行装饰,使之具有缓存功能。
如果缓存中不存在方法调⽤的结果,调⽤真实的⽅法,将结果放入缓存。
如果缓存中已经存在结果,检查是否过期。
如果过期,调用真实的⽅法,将结果放入缓存。否则,缓存中存在结果且不过期,直接返回缓存中的结果。
import java.lang.annotation.Retention;import java.lang.annotation.RetentionPolicy;@Retention(RetentionPolicy.RUNTIME)public @interface Cache {// 标记缓存的时长(秒),默认60sint cacheSeconds() default 60;}
import java.util.List;import java.util.Random;import java.util.stream.Collectors;import java.util.stream.IntStream;public class DataService {/*** 根据数据ID查询一列数据,有缓存。** @param id 数据ID* @return 查询到的数据列表*/@Cache(cacheSeconds = 2)public List<Object> queryData(int id) {// 模拟一个查询操作Random random = new Random();int size = random.nextInt(10) + 10;return IntStream.range(0, size).mapToObj(i -> random.nextInt(10)).collect(Collectors.toList());}/*** 根据数据ID查询一列数据,无缓存。** @param id 数据ID* @return 查询到的数据列表*/public List<Object> queryDataWithoutCache(int id) {// 模拟一个查询操作Random random = new Random();int size = random.nextInt(10) + 1;return IntStream.range(0, size).mapToObj(i -> random.nextBoolean()).collect(Collectors.toList());}}
import net.bytebuddy.ByteBuddy;
import net.bytebuddy.implementation.MethodDelegation;
import net.bytebuddy.implementation.bind.annotation.AllArguments;
import net.bytebuddy.implementation.bind.annotation.Origin;
import net.bytebuddy.implementation.bind.annotation.RuntimeType;
import net.bytebuddy.implementation.bind.annotation.SuperCall;
import net.bytebuddy.implementation.bind.annotation.This;
import net.bytebuddy.matcher.ElementMatchers;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Objects;
import java.util.concurrent.Callable;
import java.util.concurrent.ConcurrentHashMap;
public class CacheClassDecorator {
// 将传入的服务类Class进行增强
// 使得返回一个具有如下功能的Class:
// 如果某个方法标注了@Cache注解,则返回值能够被自动缓存注解所指定的时长
// 这意味着,在短时间内调用同一个服务的同一个@Cache方法两次
// 它实际上只被调用一次,第二次的结果直接从缓存中获取
// 注意,缓存的实现需要是线程安全的
@SuppressWarnings("unchecked")
public static <T> Class<T> decorate(Class<T> klass) {
return (Class<T>) new ByteBuddy()
.subclass(klass)
.method(ElementMatchers.isAnnotatedWith(Cache.class))
.intercept(MethodDelegation.to(CacheAdvisor.class))
.make()
.load(klass.getClassLoader())
.getLoaded();
}
public static class CacheAdvisor {
private static ConcurrentHashMap<CacheKey, CacheValue> cache = new ConcurrentHashMap<>();
@RuntimeType
public static Object cache(
@SuperCall Callable<Object> zuper,
@Origin Method method, // 原始方法
@This Object thisObject, // 当前ByteBuddy动态对象
@AllArguments Object[] arguments
) throws Exception {
CacheKey cacheKey = new CacheKey(thisObject, method.getName(), arguments);
final CacheValue resultExistingInCache = cache.get(cacheKey);
if (resultExistingInCache != null) {
if (isCacheExpires(resultExistingInCache, method)) {
return invokeRealMethodAndPutIntoCache(zuper, cacheKey);
} else {
return resultExistingInCache.value;
}
} else {
return invokeRealMethodAndPutIntoCache(zuper, cacheKey);
}
}
private static boolean isCacheExpires(CacheValue cacheValue, Method method) {
long time = cacheValue.time;
int cacheSeconds = method.getAnnotation(Cache.class).cacheSeconds();
return System.currentTimeMillis() - time > cacheSeconds * 1000;
}
private static Object invokeRealMethodAndPutIntoCache(@SuperCall Callable<Object> zuper, CacheKey cacheKey) throws Exception {
Object realMethodInvocationResult = zuper.call();
cache.put(cacheKey, new CacheValue(realMethodInvocationResult, System.currentTimeMillis()));
return realMethodInvocationResult;
}
}
private static class CacheKey {
private Object thisObject;
private String methodName;
private Object[] arguments;
CacheKey(Object thisObject, String methodName, Object[] arguments) {
this.thisObject = thisObject;
this.methodName = methodName;
this.arguments = arguments;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
CacheKey cacheKey = (CacheKey) o;
return Objects.equals(thisObject, cacheKey.thisObject) &&
Objects.equals(methodName, cacheKey.methodName) &&
Arrays.equals(arguments, cacheKey.arguments);
}
@Override
public int hashCode() {
int result = Objects.hash(thisObject, methodName);
result = 31 * result + Arrays.hashCode(arguments);
return result;
}
}
private static class CacheValue {
private final Object value;
private final long time;
CacheValue(Object value, long time) {
this.value = value;
this.time = time;
}
}
public static void main(String[] args) throws Exception {
DataService dataService = decorate(DataService.class).getConstructor().newInstance();
// 有缓存的查询:只有第一次执行了真正的查询操作,第二次从缓存中获取
System.out.println(dataService.queryData(1));
Thread.sleep(1 * 1000);
System.out.println(dataService.queryData(1));
Thread.sleep(2 * 1000);
System.out.println(dataService.queryData(1));
// 无缓存的查询:两次都执行了真正的查询操作
System.out.println(dataService.queryDataWithoutCache(1));
Thread.sleep(1 * 1000);
System.out.println(dataService.queryDataWithoutCache(1));
}
}
