中级15 - Java的注解
2020-06-02 本文已影响0人
晓风残月1994
注解使程序更加简洁,花更少的力气完成更多的工作——这也是计算机的意义所在。
- 什么是注解
- 运行时获取注解信息
1. 注解是什么
Class 对象是 Java 类的说明书,你(通过反射)或者 JVM 阅读该说明书,创建类的实例。
而注解就是说明书中的一小段信息、文本或者标记。
- 可以携带参数
- 可以在运行时(@Retention)被阅读
注解本质上也是一个广义的 Java class,也会被编译为 class 文件,创建时选择 Annotation 即可:
image.png
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);
}
@Log
public 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 {
// 标记缓存的时长(秒),默认60s
int 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));
}
}