尚未看完AndroidJava 之旅

中级15 - Java的注解

2020-06-02  本文已影响0人  晓风残月1994

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

1. 注解是什么

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

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


image.pngimage.png
public @interface Target /* extends Annotation */ { // 隐含地继承自 java.lang.annotation.Annotation
    // ...
}

2. 元注解

用来修饰注解的注解就是元注解,最常用的就是 @Target@Retention

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 的常用自带注解

5. 注解是如何工作的

定义了注解,并且使用了注解,并不一定生效,最关键的是接下来如何处理注解信息。
原理就是在运行期通过反射获取注解信息,进行动态字节码增强。

可以借助第三方库 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. 注解-廖雪峰
上一篇下一篇

猜你喜欢

热点阅读