zoukankan      html  css  js  c++  java
  • 反射与注解

    反射

    反射的基本概念

    反射是 Java 语言的一个特性,允许程序在运行时进行自我检查并且对内部成员进行操作。

    反射主要是指程序可以访问,检测和修改它本身状态或行为的一种能力,并能根据自身行为的状态和结果,调整或修改应用所描述行为的状态和相关的语义。也就是说,在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为 Java 语言的反射机制。

    Oracle 官方对反射的解释:

    Reflection enables Java code to discover information about the fields, methods and constructors of loaded classes, and to use reflected fields, methods, and constructors to operate on their underlying counterparts, within security restrictions.
    The API accommodates applications that need access to either the public members of a target object (based on its runtime class) or the members declared by a given class. It also allows programs to suppress default reflective access control.

    反射机制优缺点:

    • 优点: 运行期类型的判断,动态加载类,提高代码灵活度。
    • 缺点: 性能瓶颈:反射相当于一系列解释操作,通知 JVM 要做的事情,性能比直接的 Java 代码要慢很多。

    反射是框架设计的灵魂,在我们平时的项目开发过程中,很少会直接使用到反射机制。但很多设计、开发都与反射机制有关,例如模块化的开发,通过反射去调用对应的字节码;动态代理设计模式也采用了反射机制,还有我们日常使用的 Spring 等框架也大量使用到了反射机制。举例:

    • JDBC 连接数据库时,使用Class.forName()通过反射加载数据库的驱动程序;
    • Spring 框架也用到很多反射机制,最经典的就是 XML 的配置模式。Spring 通过 XML 配置模式装载 Bean 的过程:1)将程序内所有 XML 或 Properties 配置文件加载入内存中; 2)Java 类里面解析 XML 或 Properties 里面的内容,得到对应实体类的字节码字符串以及相关的属性信息;3)使用反射机制,根据这个字符串获得某个类的Class实例;4)动态配置实例的属性。

    反射机制的作用:

    • 在运行时判断任意一个对象所属的类;
    • 在运行时获取类的对象;
    • 在运行时访问 Java 对象的属性,方法,构造方法等。

    在 Java 中,Class 类和java.lang.reflect类库一起对反射提供了支持,该类库主要包含以下类:

    • Field:表示类中的成员变量;
    • Method:表示类中的方法;
    • Constructor:表示类的构造方法;
    • Array:该类提供了动态创建数组和访问数组元素的静态方法。

    获取类的 Class 对象

    Class 类是用来表示运行时实例对象类型信息的对应类:

    • 在运行区间,一个类,只有一个与之相对应的 Class 对象;
    • Class 类为类类型,Class 对象为类类型对象。

    Class 类的特点:

    • Class 类也是类的一种,class 则是关键字;

    • Class 类只有一个私有的构造函数,只有 JVM 能够创建 Class 类的实例;

    • JVM 中只有唯一一个和类相对应的 Class 对象来描述其类型信息。

    Java 提供了以下三种获取 Class 对象的方式,如下所示:

    package com.example;
    
    public class ReflectTest {
        public static void main(String[] args) throws ClassNotFoundException {
            // 方式一:调用实例对象的 getClass() 方法
            ReflectTest r1 = new ReflectTest();
            Class cl1 = r1.getClass();
            System.out.println(cl1.getName()); // com.example.ReflectTest
    
            // 方式二:通过 "类名.class" 方式获取
            // 任何数据类型(包括基本数据类型)都有一个“静态”的 class 属性
            Class cl2 = ReflectTest.class;
            System.out.println(cl2.getName()); // com.example.ReflectTest
    
            // 方式三:调用 Class.forName(String className) 静态方法(最常用)
            Class cl3 = Class.forName("com.example.ReflectTest");
            System.out.println(cl3.getName()); // com.example.ReflectTest
            
            // 三种方式返回的 Class 是同一个对象
            System.out.println(cl1 == cl2);  // true
            System.out.println(cl2 == cl3);  // true
        }
    }
    

    获取类的构造方法

    获取 Constructor 对象

    Constructor 表示类的构造方法,可以通过 Class 对象的方法获取类的构造方法(Constructor 对象):

    // 批量获取的方法
    // 获取所有被 public 修饰的构造方法
    public Constructor<?>[] getConstructors();  
    // 获取所有的构造方法(包括 public、protected、默认、private)
    public Constructor<?>[] getDeclaredConstructors();
    
    // 单个获取的方法
    // 获取单个被 public 修饰的构造方法
    public Constructor<T> getConstructor(Class<?>... parameterTypes);
    // 获取单个构造方法(可以为 public、protected、默认、private 中的任意一种)
    public Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes)
    

    使用代码对上述方法进行说明:

    package com.example;
    
    import java.lang.reflect.Constructor;
    
    public class ReflectTest {
    
        ReflectTest(String str) {
            System.out.println("默认的带一个参数的构造方法, str = " + str);
        }
    
        public ReflectTest() {
            System.out.println("公有的无参的构造方法...");
        }
    
        public ReflectTest(char name) {
            System.out.println("公有的带一个参数的构造方法,name =" + name);
        }
    
        public ReflectTest(String name, int index) {
            System.out.println("公有的带多个参数的构造方法,name=" + name + ";index=" + index);
        }
    
        protected ReflectTest(boolean n) {
            System.out.println("受保护的带一个参数的构造方法,n=" + n);
        }
    
        private ReflectTest(int index) {
            System.out.println("私有的带一个参数的构造方法,index=" + index);
        }
    }
    
    package com.example;
    
    import java.lang.reflect.Constructor;
    import java.util.Arrays;
    
    public class Test {
    
        public static void main(String[] args) throws Exception {
            // 获取 Class 对象
            Class clazz = Class.forName("com.example.ReflectTest");
    
            // 1> 获取所有被 public 修饰的构造方法"
            Constructor[] conArray = clazz.getConstructors();
            Arrays.stream(conArray).forEach(System.out::println);
            // public com.example.ReflectTest(java.lang.String,int)
            // public com.example.ReflectTest()
            // public com.example.ReflectTest(char)
    
            // 2> 获取所有构造方法(包括 public、protected、默认、private)"
            Constructor[] conArray1 = clazz.getDeclaredConstructors();
            Arrays.stream(conArray1).forEach(System.out::println);
            // private com.example.ReflectTest(int)
            // protected com.example.ReflectTest(boolean)
            // public com.example.ReflectTest(java.lang.String,int)
            // com.example.ReflectTest(java.lang.String)
            // public com.example.ReflectTest()
            // public com.example.ReflectTest(char)
    
            // 3> 获取被 public 修饰的带两个参数的构造方法"
            Constructor con = clazz.getConstructor(String.class, int.class);
            System.out.println(con);
            // public com.example.ReflectTest(java.lang.String,int)
    
            // 4> 获取被 private 修饰的带一个参数的构造方法
            Constructor con1 = clazz.getDeclaredConstructor(int.class);
            System.out.println(con1);
            // private com.example.ReflectTest(int)
        }
    }
    
    

    创建实例对象

    Constructor 是类的构造器对象,可以调用该对象的 newInstance() 方法创建类的实例对象:

    public T newInstance(Object ... initargs);
    
    package com.example;
    
    import java.lang.reflect.Constructor;
    
    public class Test {
        public static void main(String[] args) throws Exception {
            // 获取 Class 对象
            Class clazz = Class.forName("com.example.ReflectTest");
    
            // 1> 获取被 public 修饰的带两个参数的构造方法
            Constructor<ReflectTest> con = clazz.getConstructor(String.class, int.class);
            // 使用 con 所代表的公有构造方法进行类实例化
            ReflectTest r = (ReflectTest) con.newInstance("A", 1);
    
            // 2> 获取被 private 修饰的带一个参数的构造方法
            Constructor con1 = clazz.getDeclaredConstructor(int.class);
            // 暴力访问,私有构造方法不能直接访问(忽略掉访问修饰符)
            con1.setAccessible(true);
            // 使用 con1 所代表的私有构造方法对象进行类实例化
            ReflectTest r1 = (ReflectTest) con1.newInstance(1);
        }
    }
    
    公有的带多个参数的构造方法,name=A;index=1
    私有的带一个参数的构造方法,index=1
    

    获取类的成员变量

    获取 Field 对象

    Field 表示类的成员变量,可以通过 Class 对象的方法获取类的成员变量(Field 对象):

    // 批量获取的方法
    // 获取所有被 public 修饰的成员变量,包含继承的成员变量
    public Field[] getFields();
    // 获取所有成员变量(包括 public、protected、默认、private),不包含继承的成员变量
    public Field[] getDeclaredFields();
    
    // 获取单个的方法
    // 获取单个被 public 修饰的成员变量,包含继承的成员变量
    public Field getField(String name);   // name 是成员变量名,下同
    // 获取单个成员变量(包括 public、protected、默认、private),不包含继承的成员变量
    public Field getDeclaredField(String name);
    

    使用代码对上述方法进行说明:

    package com.example;
    
    public class ReflectTest {
        public String name;
        protected int index;
        char type;
        private String targetInfo;
    }
    
    package com.example;
    
    import java.lang.reflect.Field;
    import java.util.Arrays;
    
    public class Test {
        public static void main(String[] args) throws Exception {
            // 获取 Class 对象
            Class clazz = Class.forName("com.example.ReflectTest");
    
            // 1> 获取所有被 public 修饰的成员变量,包含继承的成员变量
            Field[] fields = clazz.getFields();
            Arrays.stream(fields).forEach(System.out::println);
            // public java.lang.String com.example.ReflectTest.name
    
            // 2> 获取所有的成员变量(包括 public、protected、默认、private),不包含继承的成员变量
            Field[] fields1 = clazz.getDeclaredFields();
            Arrays.stream(fields1).forEach(System.out::println);
            // public java.lang.String com.example.ReflectTest.name
            // protected int com.example.ReflectTest.index
            // char com.example.ReflectTest.type
            // private java.lang.String com.example.ReflectTest.targetInfo
    
            // 3> 获取单个被 public 修饰的成员变量,包含继承的成员变量
            Field f = clazz.getField("name");
            System.out.println(f);
            // public java.lang.String com.example.ReflectTest.name
    
            // 4> 获取单个被 private 修饰的成员变量,不包含继承的成员变量
            Field f1 = clazz.getDeclaredField("targetInfo");
            System.out.println(f1);
            // private java.lang.String com.example.ReflectTest.targetInfo
        }
    }
    

    访问和设置对象的成员变量

    Field 是类的成员变量对象,可以使用该对象的 get() 和 set() 方法对一个实例对象的成员变量进行访问和设置:

    // obj 是实例对象,value 是设置的值
    public Object get(Object obj);
    public void set(Object obj, Object value);
    
    package com.example;
    
    import java.lang.reflect.Field;
    
    public class Test {
        public static void main(String[] args) throws Exception {
            // 创建 ReflectTest 对象
            ReflectTest reflectTest = new ReflectTest();
            // 获取 Class 对象
            Class clazz = reflectTest.getClass();
    
            // 1> 获取单个被 public 修饰的成员变量
            Field f = clazz.getField("name");
            // 将 reflectTest 对象的公有字段 name 设置为 A
            f.set(reflectTest, "A");
            // 访问 reflectTest 对象的公有字段 name
            String name = (String)f.get(reflectTest);
            System.out.println(name);     // A
    
            // 2> 获取单个被 private 修饰的成员变量
            Field f1 = clazz.getDeclaredField("targetInfo");
            // 暴力访问,私有成员变量不能直接访问和设置(忽略掉访问修饰符)
            f1.setAccessible(true);
            // 将 reflectTest 对象的私有字段 targetInfo 设置为 B
            f1.set(reflectTest, "B");
            // 访问 reflectTest 对象的私有字段 targetInfo
            String targetInfo = (String)f1.get(reflectTest);
            System.out.println(targetInfo);   // B
        }
    }
    

    获取类的成员方法

    获取 Method 对象

    Method 表示类的成员方法,可以通过 Class 对象的方法获取类的成员方法(Method 对象):

    // 批量获取的方法
    // 获取所有被 public 修饰的成员方法,包含继承的成员方法
    public Method[] getMethods();
    // 获取所有成员方法(包括 public、protected、默认、private),不包含继承的成员方法
    public Method[] getDeclaredMethods();
    
    // 获取单个的方法
    // 获取单个被 public 修饰的成员方法,包含继承的成员方法
    public Method getMethod(String name, Class<?>... parameterTypes);  // name 是方法名,parameterTypes 是方法的形参类型,下同
    // 获取单个成员方法(包括 public、protected、默认、private),不包含继承的成员方法
    public Method getDeclaredMethod(String name, Class<?>... parameterTypes);
    

    使用代码对上述方法进行说明:

    package com.example;
    
    public class ReflectTest {
    
        public void show1(String s) {
            System.out.println("调用了公有的有参的方法,s=" + s);
        }
    
        protected void show2() {
            System.out.println("调用了受保护的无参方法");
        }
    
        void show3() {
            System.out.println("调用了默认的无参方法");
        }
    
        private void show4(int index) {
            System.out.println("调用了私有的有参方法,index=" + index);
        }
    }
    
    package com.example;
    
    import java.lang.reflect.Method;
    import java.util.Arrays;
    
    public class Test {
        public static void main(String[] args) throws Exception {
            // 获取 Class 对象
            Class clazz = Class.forName("com.example.ReflectTest");
    
            // 1> 获取所有被 public 修饰的成员方法,包含继承的成员方法
            Method[] method = clazz.getMethods();
            Arrays.stream(method).forEach(System.out::println);
            // public void com.example.ReflectTest.show1(java.lang.String)
            // 此处还会将父类 Object 的公有成员方法打印出,这里略写
    
            // 2> 获取所有的成员方法(包括 public、protected、默认、private),不包含继承的成员方法
            Method[] method1 = clazz.getDeclaredMethods();
            Arrays.stream(method1).forEach(System.out::println);
            // protected void com.example.ReflectTest.show2()
            // public void com.example.ReflectTest.show1(java.lang.String)
            // void com.example.ReflectTest.show3()
            // private void com.example.ReflectTest.show4(int)
    
            // 3> 获取单个被 public 修饰的成员方法,包含继承的成员方法
            Method m = clazz.getMethod("show1", String.class);
            System.out.println(m);
            // public void com.example.ReflectTest.show1(java.lang.String)
    
            // 4> 获取单个被 private 修饰的成员方法,不包含继承的成员方法
            Method m1 = clazz.getDeclaredMethod("show4", int.class);
            System.out.println(m1);
            // private void com.example.ReflectTest.show4(int)
        }
    }
    

    执行对象的成员方法

    Method 是类的成员方法类,可以使用它的 invoke() 方法执行一个实例对象的成员方法:

    // obj 是实例对象,args 是方法的形参类型
    public Object invoke(Object obj, Object... args);
    
    package com.example;
    
    import java.lang.reflect.Method;
    
    public class Test {
        public static void main(String[] args) throws Exception {
            // 创建 ReflectTest 对象
            ReflectTest reflectTest = new ReflectTest();
            // 获取 Class 对象
            Class clazz = reflectTest.getClass();
    
            // 1> 获取单个被 public 修饰的成员方法,包含继承的成员方法
            Method m = clazz.getMethod("show1", String.class);
            // 执行 reflectTest 对象的公有方法 show1()
            m.invoke(reflectTest,"A");
    
            // 2> 获取单个被 private 修饰的成员方法,不包含继承的成员方法
            Method m1 = clazz.getDeclaredMethod("show4", int.class);
            // 暴力访问,私有方法不能直接访问(忽略掉访问修饰符)
            m1.setAccessible(true);
            // 执行 reflectTest 对象的私有方法 show4()
            m1.invoke(reflectTest,1);
        }
    }
    
    调用了公有的有参的方法,s=A
    调用了私有的有参方法,index=1
    

    注解

    注解的基本概念

    注解提供了一种为程序元素设置元数据的方法:

    • 元数据指的是添加到程序元素如方法、字段、类和包上的额外信息。
    • 注解是一种分散式的元数据设置方式,XML 是集中式的设置方式。
    • 注解不能直接干扰程序代码的运行。

    Java 中的所有注解都继承自 Annotation 接口,该接口定义如下:

    package java.lang.annotation;
    
    public interface Annotation {
        
        boolean equals(Object obj);
    
        int hashCode();
    
        String toString();
    
        Class<? extends Annotation> annotationType();
    }
    

    注解是由注解接口定义的,格式如下:

    modifiers @interface AnnotaionName {
        type elementName1();     // 注解属性
        type elementName2() defaule value;   // 带有默认值的注解属性
        ...    
    }
    

    注解的功能:

    • 作为特定的标记,用于告诉编译器一些信息。例如,@Override注解向编译器表明修饰的方法是一个重写的方法。
    • 编译时动态处理,如动态生成代码。例如,Lombok 的@Data注解在编译时动态为实体类生成 get()、set()、equals() 和 hashCode() 等方法。
    • 运行时动态处理,作为额外信息的载体,如获取注解信息。例如,SpringMVC 将路径信息写在注解中,通过获取注解信息来获取路径,从而指定哪个 Controller 来处理具体请求。

    注解的分类:

    • 标准注解:@Override、@Deprecated、@SuppressWarnings;
    • 元注解:用于修饰注解的注解,通常用在注解的定义上,例如 @Retention、@Target、@Inherited、@Documented;
    • 自定义注解:自定义注解一般要搭配元注解实现。

    元注解

    用于修饰注解的注解,通常用在注解的定义上,常使用的元注解含义如下:

    • @Target:指定注解的作用目标;
    • @Retention:指定注解的保留策略;
    • @Documented:指定注解是否应当被包含在 Javadoc 文档中;
    • @Inherited:指定是否允许子类继承该注解。

    以 @Override 注解为例:

    // 表明 @Override 注解只能作用在成员方法上
    @Target(ElementType.METHOD) 
    // 表明 @Override 注解仅保留在源文件中,在编译之后的 class 文件中会被去除
    @Retention(RetentionPolicy.SOURCE)  
    public @interface Override {
    }
    

    @Target 注解

    @Target 是用于描述注解的作用目标,即定义注解用于什么地方,查看其源码:

    @Documented
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.ANNOTATION_TYPE)
    public @interface Target {
        // 指定注解的作用目标(使用 ElementType 枚举)
        ElementType[] value();     
    }
    
    public enum ElementType {
        TYPE,        // 类,接口(包括注解类型),enum 常量
    
        FIELD,       // 成员变量(包括 enum 常量)
    
        METHOD,      // 成员方法
     
        PARAMETER,   // 方法或构造器参数
        
        CONSTRUCTOR,     // 构造器
    
        LOCAL_VARIABLE,  // 局部变量
    
        ANNOTATION_TYPE, // 注解类型
    
        PACKAGE,         // 包
    
        TYPE_PARAMETER,  // 类型参数(泛型类的参数)
    
        TYPE_USE         // 类型用法
    }
    

    @Retention 注解

    @Retention 用来指定注解的保留策略,即定义注解在什么范围内保留,查看其源码:

    @Documented
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.ANNOTATION_TYPE)
    public @interface Retention {
        // 指定注解的保留策略(使用 RetentionPolicy 枚举)
        RetentionPolicy value();
    }
    
    public enum RetentionPolicy {
        // 注解仅保留在源文件中,而在编译之后的 class 文件中会被去除
        SOURCE,
    
        // 注解会保留在源文件中,也会保留在编译之后的 class 文件中,但 JVM 不会将它们载入
        // 默认的保留策略
        CLASS,
    
        // 注解不但保留在源文件和编译之后的 class 文件中,还会由 JVM 将它们载入。
        // 因此,在运行时可以通过反射 API 获取使用该保留策略的注解
        RUNTIME
    }
    

    @Documented 注解

    @Documented 用来指定注解是否应当被包含在 Javadoc 文档中,查看其源码:

    @Documented
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.ANNOTATION_TYPE)
    public @interface Documented {
    }
    

    用法如下所示:

    @Documented
    @Target({ElementType.TYPE,ElementType.METHOD}) 
    public @interface MyDocumentedtAnnotation {
        // 自定义一个被 @Documented 修饰的注解
    }
    
    @MyDocumentedtAnnotation    
    public class MyDocumentedTest {
    	
        @MyDocumentedtAnnotation
    	public String toString() {
    		return this.toString();
    	}
    }
    

    如果 MyDocumentedTest 类生成 Javadoc 文档,类和方法上会保留了 @MyDocumentedtAnnotation 注解信息。

    @Inherited 注解

    @Inherited 用来指定注解是否允许子类继承,查看其源码:

    @Documented
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.ANNOTATION_TYPE)
    public @interface Inherited {
    }
    

    用法如下所示:

    @Inherited
    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    public @interface MyInheritedAnnotation {
        // 自定义一个被 @Inherited 修饰的注解
        // 声明该注解具有默认值为 "MyInheritedAnnotation" 的 name 属性
        String name() default "MyInheritedAnnotation";
    }
    
    @MyInheritedAnnotation(name = "parent")
    public class Parent {
        // 定义一个被 @MyInheritedAnnotation 注解修饰的父类,
        // 并将该注解的 name 属性值设置为 "parent"
    }
    
    public class Child extends Parent {
        // 定义一个 Child 子类继承 Parent 父类
        public static void main(String[] args) {
            // 获取 Class 对象
            Class<Child> clazz = Child.class;
            
            // 获取 Child 类上的指定注解
            MyInheritedAnnotation annotation = clazz.getAnnotation(MyInheritedAnnotation.class);
            // 打印注解上的 name 属性
            System.out.println(annotation.name());  // parent
        }
    }
    

    从上可以发现发现,Child 类继承了 Parent 类的@MyInheritedAnnotation注解,并将其属性也一起继承了。


    自定义注解及其使用

    自定义注解一般的格式如下:

    // 注解接口的修饰符一般定义为 public
    public @interface 注解名 {
        // 注解属性支持的数据类型有:
        //      1> 8 种基本数据类型   2> String 类型
        //      3> Class 类型        4> Enum 类型
        //      5> Annotation 类型   6> 以上所有类型的数组
        修饰符 返回值 属性名() 默认值;	
        修饰符 返回值 属性名() 默认值;
    	...
    }
    

    具体使用如下:

    package com.example;
    
    import java.lang.annotation.ElementType;
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.Target;
    
    @Target(ElementType.FIELD)     // 表明该注解仅能作用在成员变量上
    @Retention(RetentionPolicy.RUNTIME) // 保留策略设置为 RUNTIME,可以通过反射获取该注解信息
    public @interface PersonInfoAnnotation {
        String name();   // 名字
        int age() default 19;  // 年龄
        String gender() default "男";  // 性别
        String[] jobs();    // 兴趣
    }
    
    package com.example;
    
    import java.lang.annotation.ElementType;
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.Target;
    
    @Target({ElementType.TYPE, ElementType.METHOD})  // 表明该注解仅能作用在成员变量上
    @Retention(RetentionPolicy.RUNTIME) // 保留策略设置为 RUNTIME,可以通过反射获取该注解信息
    public @interface CourseInfoAnnotation {
        String courseName();    // 课程名称
        String courseTag();     // 课程标签
        String courseProfile(); // 课程简介
        int courseIndex() default 303;  // 课程序号
    }
    
    package com.example;
    
    @CourseInfoAnnotation(courseName = "数据结构", courseTag = "专业课",
            courseProfile = "数据结构是指相互之间存在一种或多种特定关系的数据元素的集合")
    public class Course {
        
        @PersonInfoAnnotation(name = "Bob", jobs = {"篮球", "围棋"})
        private String teacher;
    
        @CourseInfoAnnotation(courseName = "算法", courseTag = "选修课",
        courseProfile = "算法是指解题方案的准确而完整的描述", courseIndex = 101)
        public void getCourse() {
    
        }
    }
    

    注解中定义了属性,在使用过程中可以给属性赋值:

    • 如果定义属性时,使用 default 关键字给属性赋予默认值,在使用可以不对该属性赋值。

    • 如果只有一个属性需要赋值,并且该属性的名称是 value,则 value 可以省略,直接定义值即可。

    • 数组赋值时,值使用{}包裹。如果数组中只有一个值,则{}可以省略

    注解作用在目标对象后,可以通过反射机制获取注解的属性信息,与反射相关的 Class、Method、Field 等类都实现了 AnnotatedElement 接口,该接口定义获取注解信息的方法:

    public interface AnnotatedElement {
        // 如果指定类型的注解存在于此元素上,则返回 true,否则返回 false
        default boolean isAnnotationPresent(Class<? extends Annotation> annotationClass);
        
        // 返回此元素上存在的所有注解(包括继承的注解)
        Annotation[] getAnnotations();
        
        // 返回此元素上存在的所有注解(不包括继承的注解)
        Annotation[] getDeclaredAnnotations();
        
        // 如果该元素上的指定类型的注解存在,则返回该注解,否则返回 null(包括继承的注解)
        <T extends Annotation> T getAnnotation(Class<T> annotationClass);
        
        // 如果该元素上的指定类型的注解存在,则返回该注解,否则返回 null(不包括继承的注解)
        default <T extends Annotation> T getDeclaredAnnotation(Class<T> annotationClass);
        //...
    }
    

    下面使用上述方法获取 Course 类中的注解信息:

    • 获取类上注解
    public static void main(String[] args) throws Exception {
        // 获取 Class 对象
        Class clazz = Class.forName("com.example.Course");
        // 获取 class 对象上的所有注解
        Annotation[] annotations = clazz.getAnnotations();
        Arrays.stream(annotations).forEach(annotation -> {
            CourseInfoAnnotation cia = (CourseInfoAnnotation) annotation;
            System.out.println("courseName:" + cia.courseName());
            System.out.println("courseTag:" + cia.courseTag());
            System.out.println("courseProfile:" + cia.courseProfile());
            System.out.println("courseIndex:" + cia.courseIndex());
        });
    }
    
    courseName:数据结构
    courseTag:专业课
    courseProfile:数据结构是指相互之间存在一种或多种特定关系的数据元素的集合
    courseIndex:303
    
    • 获取成员变量上的注解
    public static void main(String[] args) throws Exception {
        // 获取 Class 对象
        Class clazz = Class.forName("com.example.Course");
        // 获取指定成员变量
        Field field = clazz.getDeclaredField("teacher");
        // 判断是否存在指定注解
        if(field.isAnnotationPresent(PersonInfoAnnotation.class)) {
            // 获取指定类型的注解
            PersonInfoAnnotation pia = field.getAnnotation(PersonInfoAnnotation.class);
            System.out.println("name:" + pia.name());
            System.out.println("age:" + pia.name());
            System.out.println("gender:" + pia.gender());
            System.out.println("jobs:" + Arrays.toString(pia.jobs()));
        }else {
            System.out.println(false);
        }
    }
    
    name:Bob
    age:Bob
    gender:男
    jobs:[篮球, 围棋]
    
    • 获取成员方法上的注解
    public static void main(String[] args) throws Exception {
        // 获取 Class 对象
        Class clazz = Class.forName("com.example.Course");
        // 获取指定成员方法
        Method method = clazz.getDeclaredMethod("getCourse");
        // 判断是否存在指定注解
        if(method.isAnnotationPresent(CourseInfoAnnotation.class)) {
            // 获取指定类型的注解
            CourseInfoAnnotation cia = method.getAnnotation(CourseInfoAnnotation.class);
            System.out.println("courseName:" + cia.courseName());
            System.out.println("courseTag:" + cia.courseTag());
            System.out.println("courseProfile:" + cia.courseProfile());
            System.out.println("courseIndex:" + cia.courseIndex());
        }else {
            System.out.println(false);
        }
    }
    
    courseName:算法
    courseTag:选修课
    courseProfile:算法是指解题方案的准确而完整的描述
    courseIndex:101
    

    注解的工作原理

    • 使用时,通过键值对的形式为注解属性赋值;
    • 编译时,编译器会检査注解的使用范围,将注解信息写入 Class 文件中;
    • 运行时,JVM 将单个 Class 文件中保留策略为 RUNTIME 的所有注解的属性取出,并最终存入 map 里;
    • 创建 AnnotationInyocationhandler 实例并传入前面的 map;
    • JVM 使用动态代理为注解生成代理类,并初始化处理器;
    • 调用 invoke 方法,通过传入方法名返回注解对应的属性值。
  • 相关阅读:
    如何设置项目encoding为utf-8
    如何使用Navicat恢复数据库脚本
    如何使用Navicat备份数据库脚本
    表单编辑组件使用指南
    Maven如何打包本地依赖包
    怎么替换jar包里面的文件?
    如何开始创建第一个基于Spring MVC的Controller
    怎么将数据库从Oracle迁移到SQL Server,或从Oracle迁移到MySQL
    如何使用Eclipse调试framework
    windows下安装oracle,sqlplus连接启动oracle(oracle 主机字符串输入是什么)
  • 原文地址:https://www.cnblogs.com/zongmin/p/13424650.html
Copyright © 2011-2022 走看看