『Java』注解与反射

17 篇文章 1 订阅

1. 什么是注解

  • Annotation 是从 JDK 5.0 开始引入的技术

  • Annotation 的作用:

    • 不是程序本身,可以对程序作出解释(这点和注释差不多)
    • 可以被其他程序(比如:编译期等)读取
  • Annotation 的格式

    注解是以 @注释名 在代码中存在的,还可以添加一些参数值,例如:@SuppressWarnings(value="unchecked")

  • Annotation 在哪里使用?

    可以附加在 package、class、method、field 等上面,相对于给他们添加了额外的辅助信息,我们可以通过反射机制编程实现对这些元数据的访问

2. 元注解

  • 元注解的作用就是负责注解其他注解,Java 对了 5 个标准的 meta-annotation 类型,他们被用来提供对其他 annotation 类型作说明
  • 这些类型和它们所支持的类在 java.lang.annotation 包中可以找到
    • @Target:用于描述注解的使用范围(被描述的注解可以用在什么地方)
    • @Retention:表示需要在什么级别保存该注释信息,用于描述注解的生命周期
    • @Documented:说明该注解将被包含在 Javadoc 中
    • @Inherited:说明子类可以继承父类中的该注解
    • @Repeatable:表明该注解可重复使用(JDK 8 之后)

2.1 @Target

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Target {
    /**
     * Returns an array of the kinds of elements an annotation type
     * can be applied to.
     * @return an array of the kinds of elements an annotation type
     * can be applied to
     */
    ElementType[] value();
}

可以看到该元注解有一个参数,传递的是数组,元素类型是 ElementType.

该注解用于描述注解的使用范围,由于是数组,说明可以用于多个场景,具体有哪些场景可以进入 ElementType 类查看

public enum ElementType {
    /** 用于描述类、接口(包括注解类型) 或enum声明 Class, interface (including annotation type), or enum declaration */
    TYPE, 
    
    /** 用于描述域 Field declaration (includes enum constants) */
    FIELD, 
    
    /** 用于描述方法 Method declaration */
    METHOD,
    
     /** 用于描述参数 Formal parameter declaration */
    PARAMETER, 
    
     /** 用于描述构造器 Constructor declaration */
    CONSTRUCTOR,
    
    /** 用于描述局部变量 Local variable declaration */
    LOCAL_VARIABLE,
    
    /** Annotation type declaration */
    ANNOTATION_TYPE,
    
    /** 用于描述包 Package declaration */
    PACKAGE, 
    
	/**
     * 用来标注类型参数 Type parameter declaration
     * @since 1.8
     */
    TYPE_PARAMETER,
    
    /**
     * 能标注任何类型名称 Use of a type
     * @since 1.8
     */
    TYPE_USE
}

其中,ElementType.TYPE_PARAMETER(Type parameter declaration) 用来标注类型参数, 栗子如下:

@Target(ElementType.TYPE_PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
public @interface TypeParameterAnnotation {
     
}
 
// 如下是该注解的使用例子
public class TypeParameterClass<@TypeParameterAnnotation T> {
    public <@TypeParameterAnnotation U> T foo(T t) {
        return null;
    }   
}

lementType.TYPE_USE(Use of a type) 能标注任何类型名称,包括上面这个ElementType.TYPE_PARAMETER的,栗子如下:

public class TestTypeUse {
 
    @Target(ElementType.TYPE_USE)
    @Retention(RetentionPolicy.RUNTIME)
    public @interface TypeUseAnnotation {
         
    }
     
    public static @TypeUseAnnotation class TypeUseClass<@TypeUseAnnotation T> extends @TypeUseAnnotation Object {
        public void foo(@TypeUseAnnotation T t) throws @TypeUseAnnotation Exception {
             
        }
    }
     
    // 如下注解的使用都是合法的
    @SuppressWarnings({ "rawtypes", "unused", "resource" })
    public static void main(String[] args) throws Exception {
        TypeUseClass<@TypeUseAnnotation String> typeUseClass = new @TypeUseAnnotation TypeUseClass<>();
        typeUseClass.foo("");
        List<@TypeUseAnnotation Comparable> list1 = new ArrayList<>();
        List<? extends Comparable> list2 = new ArrayList<@TypeUseAnnotation Comparable>();
        @TypeUseAnnotation String text = (@TypeUseAnnotation String)new Object();
        java.util. @TypeUseAnnotation Scanner console = new java.util.@TypeUseAnnotation Scanner(System.in);
    }
}

注意:当注解未指定 Target 值时,或未被 Target 注释时,则说明该注解可以用于任何元素之上.

如果同时要传递多个作用域时,栗子如下:

@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
@Target(value = {ElementType.ANNOTATION_TYPE,ElementType.CONSTRUCTOR})

2.2 @Retention

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Retention {
    /**
     * Returns the retention policy.
     * @return the retention policy
     */
    RetentionPolicy value();
}

该注解表示需要在什么级别保存该注释信息,用于描述注解的生命周期,只能传递单值,RetentionPolicy 类型,进入该类型查看:

public enum RetentionPolicy {
    SOURCE,    // 编译时舍弃
    CLASS,     // 编译时保留,但是 JVN 运行时不保留,该选项是默认值
    RUNTIME    // 运行时也可保留,可以被反射获取,所以一般都用这个选项
}

2.3 @Documented

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

明该注解将被包含在 Javadoc 中,方便生成文档信息的

2.4 @Inherited

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

@Inherited 注解加在某个类 A 上时,假如类 B 继承了 A,则 B 也会带上该注解

  • 接口用上 @Inherited 修饰的注解,其实现类不会继承这个注解
  • 父类的方法用了@Inherited 修饰的注解,子类也不会继承这个注解

用了 @Inherited 修饰的注解的 @RetentionRetentionPolicy.RUNTIME,则增强了继承性,在反射中可以获取得到

2.5 @Repeatable

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Repeatable {
    /**
     * Indicates the <em>containing annotation type</em> for the
     * repeatable annotation type.
     * @return the containing annotation type
     */
    Class<? extends Annotation> value();
}

@Repeatable 注解是用于声明其它类型注解的元注解,来表示这个声明的注解是可重复的.

@Repeatable 的值是另一个注解,其可以通过这个另一个注解的值来包含这个可重复的注解.

示例

【Value 注解】

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Repeatable(Values.class)
public @interface Value {
    String value() default "value";
}

【Values 注解】

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Values {
    Value[] value();
}

其中,@Value 注解上的元注解 @Repeatable 中的值,使用了@Values 注解,@Values 注解中包含的值类型是一个 @Value 注解的数组!
这就解释了官方文档中 @Repeatable 中值的使用.

【测试】

package com.ice.annotation;

import org.junit.Test;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.Arrays;

public class AnnotationClass {

    @Value("hello")
    @Value("world")
    public void test(String var1, String var2) {
        System.out.println(var1 + " " + var2);
    }

    // 获取使用`@Value`注解的`test`方法,并打印这个方法上的注解长度和信息
    @Test
    public void testValue() {
        Method[] methods = AnnotationClass.class.getMethods();
        for (Method method : methods){
            if (method.getName().equals("test")) {
                Annotation[] annotations = method.getDeclaredAnnotations();
                System.out.println(annotations.length);
                System.out.println(method.getName() + " = " + Arrays.toString(annotations));
            }
        }
    }
}

【输出结果】

1
test = [@com.ice.annotation.Values(value=[@com.ice.annotation.Value(value=hello), @com.ice.annotation.Value(value=world)])]

结果显示,test() 方法上的注解长度为 1 , 且打印信息为 @Values 注解,它的值包含了使用的两个注解. 因此可知在 JDK 8 中,相同注解只是以集合的方式进行了保存,原理并没有变化.

注意事项:

  • @Repeatable 所声明的注解,其元注解 @Target 的使用范围要比 @Repeatable 的值声明的注解中的 @Target 的范围要大或相同,否则编译器错误,显示@Repeatable 值所声明的注解的元注解 @Target 不是 @Repeatable 声明的注解的 @Target 的子集

    // Value
    @Target({ElementType.METHOD})
    @Retention(RetentionPolicy.RUNTIME)
    @Repeatable(Values.class)
    public @interface Value {
        String value() default "value";
    }
    
    // Values
    @Target({ElementType.METHOD, ElementType.TYPE})
    @Retention(RetentionPolicy.RUNTIME)
    public @interface Values {
        Value[] value();
    }
    

    此时会报错:

    在这里插入图片描述

  • @Repeatable注解声明的注解的元注解@Retention的周期要比@Repeatable的值指向的注解的@Retention得周期要小或相同

    // Value 注意 @Retention的值
    @Target({ElementType.METHOD})
    @Retention(RetentionPolicy.RUNTIME)
    @Repeatable(Values.class)
    public @interface Value {
        String value() default "value";
    }
    // Values
    @Target({ElementType.METHOD})
    @Retention(RetentionPolicy.CLASS)
    public @interface Values {
        Value[] value();
    }
    

    在这里插入图片描述

3. 自定义注解

使用 @interface 自定义注解时,自动实现了 java.lang.annotation.Annotation 接口

  • @interface 用来声明一个注解,格式为

    public @interface 注解名{
    	// 定义内容
    }
    
  • 其中的每一个方法实际上是声明了一个配置参数,方法的名称就是参数的名称,返回值类型就是参数的类型

  • 可以通过 default 来声明参数的默认值

  • 如果只有一个参数成员,一般参数名定为 value

  • 注解元素必须要有值,我们定义注解元素时,经常使用空串或 0 作为默认值

【示例】

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {

	String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";

	Class<?>[] exclude() default {};

	String[] excludeName() default {};

}

4. Java 反射机制

Reflection(反射)允许程序在执行期间家住与 Reflection API 取得任何类的内部信息,并能直接操作任意对象的内部属性及方法.

加载完类之后,在堆内存的方法区中就产生了一个 Class 类型的对象(一个类只有一个 Class 对象),这个对象就包含了完整的类的结构信息. 我们可以通过这个对象看到类的结构.

Java 反射机制主要提供了以下功能:

  • 在运行时判断任意一个对象所属的类
  • 在运行时构造任意一个类的对象
  • 在运行时判断任意一个类所具有的成员变量和方法
  • 在运行时调用任意一个对象的方法
  • 生成动态代理

5. 反射机制的相关类

与 Java 反射相关的类如下:

类名用途
Class代表类的实体,在运行的Java应用程序中表示类和接口
Constructor代表类的构造方法
Field代表类的成员变量(成员变量也称为类的属性)
Method代表类的方法

5.1 Class 类

  • 获得类相关的方法

    方法用途
    asSubclass(Class<U> clazz)把传递的该类的 Class 对象转换成代表其子类的对象
    cast(Object obj)把对象转换成该类的类型
    getClassLoader()获得该类的加载器
    getClasses()返回一个 Class 数组,数组中包含该类中所有 public 类和接口类的对象
    getDeclaredClasses()返回一个 Class 数组,数组中包含该类中所有类和接口类的对象
    Class.forName(String className)利用全限定名返回类的 Class 对象
    getName()获得该类的全限定名
    newInstance()创建该类的实例
    getPackage()获得该 类的包
    getSimpleName()获得该类的名字
    getSuperclass()获得该类的父类的全限定名
    getInterfaces()返回一个 Class 数组,数组中包含该类直接实现的所有接口(父类实现的接口与它无关)

    其中,asSubclass() 方法我们不太常见,其创建应用应用:Class.forName("xxx.xxx.xxx").asSubclass(List.class).newInstance();

    xxx.xxx.xxxList 的子类时,正常执行,当不是 List 的子类时,抛出ClassCastException,这时我们可以做些别的处理

  • 获得类中属性相关的方法

    方法用途
    getField(String name)返回一个 Field 对象,根据给定属性名返回该类的 public 属性
    (包括继承得到的属性)
    getFields()返回一个 Field 数组,数组元素包含该类的所有 public 属性
    (包括继承得到的属性)
    getDeclaredField(String name)返回一个 Field 对象,根据给定属性名返回该类的属性
    getDeclaredFields()返回一个 Field 数组,数组元素包含该类的所有属性
  • 获得类中注解相关的方法

    方法用途
    getAnnotation(Class<A> annotationClass)返回一个 Annotation 对象,根据参数(如Deprecated.class)类型返回注解
    (包括继承得到的注解)
    getAnnotations()返回一个 Annotation 数组,数组元素是该类所有的注解
    (包括继承得到的注解)
    getDeclaredAnnotation(Class<A> annotationClass)返回一个 Annotation 对象,根据参数(如Deprecated.class)类型返回直接声明的注解
    getDeclaredAnnotations()返回一个 Annotation 数组,数组元素是该类所有直接声明的注解
  • 获得类中构造器相关的方法

    方法用途
    getConstructor(Class<?>... parameterTypes)返回一个 Constructor 对象,获得该类中与参数类型匹配的 public 构造方法
    (获取无参构造则不用传参数)
    getConstructors()返回一个 Constructor 数组,数组元素是该类所有的 public 构造方法
    getDeclaredConstructor(Class<?>... parameterTypes)返回一个 Constructor 对象,获得该类中与参数类型匹配的构造方法
    (获取无参构造则不用传参数)
    getDeclaredConstructors()返回一个 Constructor 数组,数组元素是该类所有的构造方法
  • 获得类中方法相关的方法

    方法用途
    getMethod(String name, Class<?>... parameterTypes)返回一个 Method 对象,获得该类中与方法签名匹配的 public 方法
    包括继承得到的方法
    getMethods()返回一个 Method 数组,数组元素是该类中所有 public 方法
    包括继承得到的方法
    getDeclaredMethod(String name, Class<?>... parameterTypes)返回一个 Method 对象,获得该类中直接声明的与方法签名匹配的方法
    getDeclaredMethods()返回一个 Method 数组,数组元素是该类中直接声明的所有方法
  • 类中其他重要的方法

    方法用途
    isAnnotation()如果该类是注解类型则返回 true
    isAnnotationPresent(Class<? extends Annotation> annotationClass)如果该类声明了指定类型注解类型则返回 true
    isAnonymousClass()如果是匿名类则返回 true
    isArray()如果是一个数组类则返回 true
    isEnum()如果是枚举类则返回 true
    isInstance(Object obj)如果 obj 是该类的实例则返回 true
    isInterface()如果该类是接口类则返回 true
    isLocalClass()如果是局部类则返回 true
    isMemberClass()如果是内部类则返回 true

5.2 Field 类

方法用途
equals(Object obj)属性与 obj 相等则返回 true
get(Object obj)获得 obj 中对应的属性值
set(Object obj, Object value)设置 obj 中对应属性值为 value

equals() 的源码如下:

public boolean equals(Object obj) {
    if (obj != null && obj instanceof Field) {
        Field other = (Field)obj;
        return (getDeclaringClass() == other.getDeclaringClass())
            && (getName() == other.getName())
            && (getType() == other.getType());
    }
    return false;
}

5.3 Method 类

主要就一个调用方法:invoke(Object obj, Object... args),传递 obj 对象及参数调用该对象对应的方法

5.4 Constructor 类

也就是一个主要方法,用反射的方式创建对象:newInstance(Object... initargs),根据传递的参数创建类的对象

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值