• JAVA 注解小结



    一个注解到底是怎么实现的呢?

    注解是什么

    「java.lang.annotation.Annotation」接口中有这么一句话,用来描述『注解』:所有的注解类型都继承自这个普通的接口,因此,注解的本质就是一个继承了 Annotation 接口的接口

    而解析一个类或者方法的注解往往有两种形式,一种是编译期直接的扫描,一种是运行期反射。反射的事情我们待会说,而编译器的扫描指的是编译器在对 java 代码编译字节码的过程中会检测到某个类或者方法被一些注解修饰,这时它就会对于这些注解进行某些处理。

    典型的就是注解 @Override,一旦编译器检测到某个方法被修饰了 @Override 注解,编译器就会检查当前方法的方法签名是否真正重写了父类的某个方法,也就是比较父类中是否具有一个同样的方法签名

    注解就像一个标签,是贴在程序代码上供另一个程序读取的

    内置的注解

    Java 定义了一套注解,共有 7 个,3 个在 java.lang 中,剩下 4 个在 java.lang.annotation 中。

    作用在代码的注解是

    • @Override - 检查该方法是否是重写方法。如果发现其父类,或者是引用的接口中并没有该方法时,会报编译错误。
    • @Deprecated - 标记过时方法。如果使用该方法,会报编译警告。
    • @SuppressWarnings - 指示编译器去忽略注解中声明的警告。

    作用在其他注解的注解(或者说 元注解)是:

    • @Retention - 标识这个注解怎么保存,是只在代码中,还是编入class文件中,或者是在运行时可以通过反射访问。
    • @Documented - 标记这些注解是否包含在用户文档中。
    • @Target - 标记这个注解应该是哪种 Java 成员。
    • @Inherited - 标记这个注解是继承于哪个注解类(默认 注解并没有继承于任何子类)

    @Retention的可能取值:

    public enum RetentionPolicy {
        SOURCE,            /* Annotation信息仅存在于编译器处理期间,编译器处理完之后就没有该Annotation信息了,比如@Override  */
    
        CLASS,             /* 编译器将Annotation存储于类对应的.class文件中。默认行为  */
    
        RUNTIME            /* 编译器将Annotation存储于class文件中,并且可由JVM读入 */
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    @Target的可能取值:

    public enum ElementType {
        TYPE,               /* 类、接口(包括注释类型)或枚举声明  */
    
        FIELD,              /* 字段声明(包括枚举常量)  */
    
        METHOD,             /* 方法声明  */
    
        PARAMETER,          /* 参数声明  */
    
        CONSTRUCTOR,        /* 构造方法声明  */
    
        LOCAL_VARIABLE,     /* 局部变量声明  */
    
        ANNOTATION_TYPE,    /* 注释类型声明  */
    
        PACKAGE             /* 包声明  */
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    自定义注解

    我们可以自己写一个注解

    @Target(METHOD)
    @Retention(RUNTIME)
    public @interface MyAnnotation {
        String value() default "hello";
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    发现什么奇怪的问题没有,不说是接口了,就算是类也不能给方法赋值啊。而且这么操作有什么用吗

    我们定义了注解的作用域、作用目标之后,在注解内写的方法可以看作注解蕴含的信息,因此只要用到注解,必然有三角关系:定义注解、使用注解、读取注解

    如果注解本质上是继承了 Annotation 接口的接口,那是不是可以通过反射获得这个注解所包含的信息了,获得信息就可以对其标记的东西进行操作了

    来个实例吧,这是个被我自定义注解标记的类

    public class JustTest {
    
        @MyAnnotation
        public void sayHello(){
            System.out.println("say hello");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    测试的类,这里面的操作就是读取注解的程序,这部分一般被框架隐藏起来

        @Test
        public void test() throws IllegalAccessException, InstantiationException, InvocationTargetException {
            // 1.先找到测试类的字节码
            Class clazz = JustTest.class;
            Object obj = clazz.newInstance();
    
            // 2.获取类中的公共方法
            Method[] methods = clazz.getMethods();
    
            // 3.测试一下是否包含自定义注解,拿到并输出自定义注解中的值,执行该方法
            System.out.println(methods[0].isAnnotationPresent(MyAnnotation.class));
            System.out.println(methods[0].getAnnotation(MyAnnotation.class).value());
            methods[0].invoke(obj);
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    代理类

    你可以通过反射拿到这个注解的代理类,也就是我们这里的 getAnnotation 方法,其实 JDK 通过动态代理机制生成一个实现我们注解(接口)的代理类

    代理类实现接口 Hello 并重写其所有方法,包括 value 方法以及接口 Hello 从 Annotation 接口继承而来的方法

    那这个代理类是如何生成的?直接说结论,用以下方式生成动态代理对象

        public static Annotation annotationForMap(final Class<? extends Annotation> var0, final Map<String, Object> var1) {
    
            return (Annotation)AccessController.doPrivileged(new PrivilegedAction<Annotation>() {
    
                public Annotation run() {
    
                    return (Annotation)Proxy.newProxyInstance(var0.getClassLoader(), new Class[]{var0}, new AnnotationInvocationHandler(var0, var1));
                }
            });
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    InvocationHandler 的实例是 AnnotationInvocationHandler,AnnotationInvocationHandler 是 JAVA 中专门用于处理注解的 Handler

    class AnnotationInvocationHandler implements InvocationHandler, Serializable {
        private static final long serialVersionUID = 6182022883658399397L;
        private final Class<? extends Annotation> type;
        private final Map<String, Object> memberValues;
        ...
    
    • 1
    • 2
    • 3
    • 4
    • 5

    我们的代理类代理了 Hello 接口中所有的方法,所以对于代理类中任何方法的调用都会被转到这里来

        public Object invoke(Object proxy, Method method, Object[] args) {
            String member = method.getName();
            Class<?>[] paramTypes = method.getParameterTypes();
    
            // Handle Object and Annotation methods
            if (member.equals("equals") && paramTypes.length == 1 &&
                paramTypes[0] == Object.class)
                return equalsImpl(args[0]);
            if (paramTypes.length != 0)
                throw new AssertionError("Too many parameters for an annotation method");
    
    		// 如果所调用的方法是父类的方法,直接调用
            switch(member) {
            case "toString":
                return toStringImpl();
            case "hashCode":
                return hashCodeImpl();
            case "annotationType":
                return type;
            }
    
            // memberValues是一个map,里面以方法名为键、对应的数据为值,此时result就已经是需要的值了
            Object result = memberValues.get(member);
    
    		// 异常处理
            if (result == null)
                throw new IncompleteAnnotationException(type, member);
    
            if (result instanceof ExceptionProxy)
                throw ((ExceptionProxy) result).generateException();
    		
    		// 如果拿到的值是数组的话,需要将一个个元素克隆
            if (result.getClass().isArray() && Array.getLength(result) != 0)
                result = cloneArray(result);
    
            return result;
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37

    总之这个方法的作用就是通过方法名返回注解属性值

  • 相关阅读:
    Android Kotlin 高阶详解
    如何利用大数据构建用户画像?
    干货,某大厂小姐姐深夜让我说出了秘密-springboot发邮件 原创
    记录一次Python深浅copy的问题
    Vmvare—windows中打不开摄像头
    java判断是否是鸿蒙系统
    定时发布文章的Typecho采集翻译发布插件
    2021年全国职业院校技能大赛-ruijie网络模块-命令解析-脚本配置
    C# 下载C站和Libu资源实现逻辑
    Wireshark IP实验—Wireshark Lab: IP v7.0(计算机网络自顶向下第七版)
  • 原文地址:https://blog.csdn.net/sekever/article/details/126289857