Java 注解学习总结

Java 注解 (Annotation)

注解是什么

《Java 核心技术 卷2》 中对注解的说明:

注解是那些插入到源代码中使用其他工具对其进行处理的标签。这些工具可以在源代码层次上进行操作,或者可以处理编译器在其中放置了注解的类文件。

注解不会改变程序的编译方式。Java 编译器对于包含注解和不包含注解的代码会生成相同的虚拟机指令。

JavaGuide 网站上对注解的说明,个人认为这个说明比较通俗易懂:

Annotation (注解)是 Java 5 开始引入的新特性,可以看作是一种特殊的注释,主要用于修饰类、方法或者变量,提供某些信息供程序在编译或者运行时使用。

注解本质上是一个继承了 Annotaion 的特殊接口。

Java 为什么要支持注解

如果只定义注解,然后在代码中插入注解,这样并没有什么作用,注解只有被程序解析处理之后才会起作用。

《Java 编程的逻辑》 中两段话很好地说明了注解的作用:

注解似乎有某种神奇的力量,通过简单的声明,就可以达到某种效果。这是声明式编程风格,这种风格降低了编程的难度,为应用程序员提供了更为高级的语言,使得程序员可以在更高的抽象层次上思考和解决问题,而不是限于底层的细节实现。

注解提升了 Java 语言的表达能力,有效地实现了应用功能和底层功能的分离,框架/库的程序员可以专注于底层实现,借助反射实现通用功能,提供注解给应用程序员使用,应用程序员可以专注于应用功能,通过简单的声明式注解与框架/库进行协作。

如何使用注解

定义注解

注解接口

如果是使用框架/库提供的注解,就不需要自己定义注解。但是也需要能看懂框架/库提供的注解的定义,才能知道注解应该加在什么地方。

注解是由注解接口 @interface 来定义的,例如下面定义了一个名为 Label 注解:

@Target({ElementType.ANNOTATION_TYPE, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Label {
    String value() default "";
}

所有的注解接口都隐式地扩展自 java.lang.annotation.Annotation 接口,这个接口是一个常规接口,不是注解接口。

package java.lang.annotation;

/**
 * @author  Josh Bloch
 * @since   1.5
 */
public interface Annotation {
    boolean equals(Object obj);

    int hashCode();

    String toString();

    Class<? extends Annotation> annotationType();
}
元注解

定义注解时会用到元注解,例如上面例子中的 @Target@Retention ,这是两个必须要声明的元注解(如果没有声明这两个注解其实使用的是默认值),元注解是用来定义注解的注解。

@Target 表示注解的目标,即表示注解可以应用到哪些项上,取值是枚举类型 ElementType ,主要可选值有:

元素类型注解适用场合
TYPE类(包括枚举)、接口(包括注解)
FIELD成员变量(包括枚举常量)
METHOD方法
CONSTRUCTOR构造方法
PARAMETER方法中的参数
LOCAL_VARIABLE局部变量
ANNOTATION_TYPE注解

@Target 有一个值时写为:@Target(ElementType.FIELD) ,有多个值时写为:@Target({ElementType.ANNOTATION_TYPE, ElementType.FIELD})

如果没有声明 @Target,默认为适用所有类型。

@Retention 表示注解信息保留到什么时候,取值只能有 1 个,类型为枚举类型 RetentionPolicy,可选值只有 3 个:

枚举值描述
SOURCE只在源代码中保留,编译器将代码编译为字节码文件之后就会丢掉。例如 JDK 提供的 @Override,Lombok 提供的 @Getter、@Setter 等注解。
CLASS保留在字节码文件中,但 Java 虚拟机不需要将它们载入。
RUNTIME一直保留到运行时,通过反射 API 可获得它们。例如 Spring 提供的注解、通过反射解析的自定义注解。

如果没有声明 @Retention ,默认为CLASS。

元注解除了 @Target@Retention,常用的还有:

  • @Document,表示注解信息包含到 Javadoc 生成的文档中。
  • @Inherited,表示如果某个注解用@Inherited声明了,父类使用了此注解,则子类就自动有了此注解。
注解元素

例子中 String value() default ""; 就是注解元素。

注解元素的类型为下列之一:

  • 基本类型
  • String
  • Class
  • 枚举类型
  • 注解类型
  • 由前面所述类型组成的数组

对于名为 value 的注解元素赋值时,如果没有其它注解元素要赋值,就可以忽略掉元素名称 value 和等号,只写 value 元素的值,例如 @Label(“姓名”) 等价于 @Label(value = “姓名”)。

在代码中使用注解

根据注解 @Target 的定义,注解应用于哪些目标上,就在对应目标上插入注解,并根据需要给注解元素赋值。例如上面定义的 @Label 注解可以应用于成员变量上,就可以在类的成员变量上使用此注解,如:

public class Student {
    @Label("姓名")
    private String name;
    
    @Label("分数")
    private Double score;
    
    public Student(String name, Double score) {
        this.name = name;
        this.score = score;
    }
}

解析注解

如果是框架/库提供的注解,注解的解析由框架/库完成,我们只需要根据注解的功能在代码中插入注解即可。如果是自定义注解,我们就需要通过对注解进行解析。类 Class、Field、Method、Constructor 都实现了 java.lang.reflect.AnnotatedElement 接口,有如下方法:

/**
 * @author Josh Bloch
 */
public interface AnnotatedElement {
    /**
     * Returns true if an annotation for the specified type
     * is <em>present</em> on this element, else false.  This method
     * is designed primarily for convenient access to marker annotations.
     */
    default boolean isAnnotationPresent(Class<? extends Annotation> annotationClass) {
        return getAnnotation(annotationClass) != null;
    }

    /**
     * Returns this element's annotation for the specified type if
     * such an annotation is <em>present</em>, else null.
     */
    <T extends Annotation> T getAnnotation(Class<T> annotationClass);

    /**
     * Returns annotations that are <em>present</em> on this element.
     */
    Annotation[] getAnnotations();

    /**
     * Returns annotations that are <em>associated</em> with this element.
     */
    default <T extends Annotation> T[] getAnnotationsByType(Class<T> annotationClass) {
         T[] result = getDeclaredAnnotationsByType(annotationClass);

         if (result.length == 0 && // Neither directly nor indirectly present
             this instanceof Class && // the element is a class
             AnnotationType.getInstance(annotationClass).isInherited()) { // Inheritable
             Class<?> superClass = ((Class<?>) this).getSuperclass();
             if (superClass != null) {
                 // Determine if the annotation is associated with the
                 // superclass
                 result = superClass.getAnnotationsByType(annotationClass);
             }
         }

         return result;
     }

    /**
     * Returns this element's annotation for the specified type if
     * such an annotation is <em>directly present</em>, else null.
     */
    default <T extends Annotation> T getDeclaredAnnotation(Class<T> annotationClass) {
         Objects.requireNonNull(annotationClass);
         // Loop over all directly-present annotations looking for a matching one
         for (Annotation annotation : getDeclaredAnnotations()) {
             if (annotationClass.equals(annotation.annotationType())) {
                 // More robust to do a dynamic cast at runtime instead
                 // of compile-time only.
                 return annotationClass.cast(annotation);
             }
         }
         return null;
     }

    /**
     * Returns this element's annotation(s) for the specified type if
     * such annotations are either <em>directly present</em> or
     * <em>indirectly present</em>. This method ignores inherited
     * annotations.
     */
    default <T extends Annotation> T[] getDeclaredAnnotationsByType(Class<T> annotationClass) {
        Objects.requireNonNull(annotationClass);
        return AnnotationSupport.
            getDirectlyAndIndirectlyPresent(Arrays.stream(getDeclaredAnnotations()).
                                            collect(Collectors.toMap(Annotation::annotationType,
                                                                     Function.identity(),
                                                                     ((first,second) -> first),
                                                                     LinkedHashMap::new)),
                                            annotationClass);
    }

    /**
     * Returns annotations that are <em>directly present</em> on this element.
     * This method ignores inherited annotations.
     */
    Annotation[] getDeclaredAnnotations();
}

可以使用 Filed 类的 getAnnotation 方法对上面定义的 @Label 进行解析:

public class SimpleFormatter {
    public static String format(Object obj) {
        try {
            Class<?> cls = obj.getClass();
            StringBuilder sb = new StringBuilder();
            for (Field f : cls.getDeclaredFields()) {
                if (!f.isAccessible()) {
                    f.setAccessible(true);
                }
                Label label = f.getAnnotation(Label.class);
                String name = label != null ? label.value() : f.getName();
                Object value = f.get(obj);
                sb.append(name + ": " + value + "\n");
            }
            return sb.toString();
        } catch (IllegalAccessException e){
            throw new RuntimeException(e);
        }
    }
}

这样在上一步使用注解时就可以用 SimpleFormatter#format 方法对一个对象输出成员变量标记后的信息,如:

        Student student = new Student("张三", 80.9d);
        System.out.println(SimpleFormatter.format(student));

输出为:

姓名:张三

分数:80.9

Spring 中的注解说明

组合注解 (composed annotation)原理

@RestController 就是一个组合注解,使用它就相当于同时使用了 @Controller@ResponseBody 两个注解。

/**
 * A convenience annotation that is itself annotated with
 * {@link Controller @Controller} and {@link ResponseBody @ResponseBody}.
 * <p>
 * Types that carry this annotation are treated as controllers where
 * {@link RequestMapping @RequestMapping} methods assume
 * {@link ResponseBody @ResponseBody} semantics by default.
 */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Controller
@ResponseBody
public @interface RestController {

	/**
	 * The value may indicate a suggestion for a logical component name,
	 * to be turned into a Spring bean in case of an autodetected component.
	 * @return the suggested component name, if any (or empty String otherwise)
	 * @since 4.0.1
	 */
	@AliasFor(annotation = Controller.class)
	String value() default "";

}

Spring 解析被 @RestController 注解的类时,可以先获取到 @RestController 注解,再获取 @RestController 的注解,就能获取到 @Controller 和 @ResponseBody 注解,通过递归就能获取到一个类上的所有注解。获取注解的注解时,要用 annotation.annotationType().getAnnotations()

for (Annotation annotation : f.getAnnotations()) {
                for (Annotation annotation1 : annotation.annotationType().getAnnotations()) {
                    System.out.println("annotation1 = " + annotation1);
                }
            }

一个注解要能在加在别的注解上面,它的 @Target 必须是 ElementType.TYPE 或者 ElementType.ANNOTATION_TYPE,否则编译器就会报错。

上面 @AliasFor(annotation = Controller.class) 表示 @RestController 的 value 值会同时设置 @Controller 的value 值,@AliasFor 同时也可以设置同一个注解内两个不同的注解元素,表示这两个注解元素的值会相互设置。

参考资料

  • 《Java 核心技术 卷2》 Cay S. Horstmann 著

  • 《Java 编程的逻辑》马俊昌 著

  • JavaGuide 网站:https://javaguide.cn

  • https://github.com/spring-projects/spring-framework/wiki/Spring-Annotation-Programming-Model

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值