中级15 - Java的注解

作者: 晓风残月1994 | 来源:发表于2020-06-02 17:39 被阅读0次

注解使程序更加简洁,花更少的力气完成更多的工作——这也是计算机的意义所在。

  • 什么是注解
  • 运行时获取注解信息

1. 注解是什么

Class 对象是 Java 类的说明书,你(通过反射)或者 JVM 阅读该说明书,创建类的实例。
而注解就是说明书中的一小段信息、文本或者标记。

  • 可以携带参数
  • 可以在运行时(@Retention)被阅读

注解本质上也是一个广义的 Java class,也会被编译为 class 文件,创建时选择 Annotation 即可:


image.pngimage.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));
    }
}

7. 参考

  1. 自定义注解详细介绍
  2. 注解-廖雪峰

相关文章

  • 中级15 - Java的注解

    注解使程序更加简洁,花更少的力气完成更多的工作——这也是计算机的意义所在。 什么是注解 运行时获取注解信息 1. ...

  • 菜鸟学服务端技术----Spirng基础

    注解 Java基础加强总结(一)——注解(Annotation) java中的注解是如何工作的? java 注解 ...

  • 自定义注解

    java annotation基础 java注解分为标准注解和元注解。 标准注解是java为我们提供的预定义的注解...

  • Java原生注解和Spring注解的说明

    注解 java 原生注解 Spring 中的注解 一 Java原生注解 Java注解是在JDK1.5以后引入的新特...

  • Java注解的使用

    Java注解的使用 参考 廖雪峰java教程 使用注解 什么是注解(Annotation)?注解是放在Java源码...

  • Java 注解

    JAVA注解 Java 自带注解(系统注解) @Override 表示重写注解 @Deprecated 表示过时的...

  • 1.8 Java 注解annotation

    1.1 注解声明 Java注解Annotation,有声明注解和元注解 元注解:Java提供的元注解,所谓元注解就...

  • Java注解学习总结(脑图)

    注解的提取测试:定义注解: 测试注解提取: 参考:《Java编程思想》java注解

  • 注解

    在java中合理使用注解是代码优雅的重要方式,使用注解在代码的可读性上会有所降低(对于初中级开发会导致一些阅读障碍...

  • Java注解(三)—元注解

    元注解就是注解的注解,是Java内置的注解。Java提供四个元注解:@Retention @Target @Doc...

网友评论

    本文标题:中级15 - Java的注解

    本文链接:https://www.haomeiwen.com/subject/kvcpzhtx.html