首页 > 编程语言 >Java 注解 (Annotation) 学习总结

Java 注解 (Annotation) 学习总结

时间:2024-09-04 14:56:13浏览次数:10  
标签:Class Java value annotation 注解 annotationClass Annotation

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 同时也可以设置同一个注解内两个不同的注解元素,表示这两个注解元素的值会相互设置。

参考资料

标签:Class,Java,value,annotation,注解,annotationClass,Annotation
From: https://www.cnblogs.com/moguxiong/p/18396467

相关文章

  • WebDriver API剖析----执行JavaScript脚本
    页面上的操作有时通过Selenium是无法实现的,如滚动条、时间控件等,此时就需要借助JavaScript来完成。WebDriver提供了一个内置方法来操作JavaScript,代码如下:driver.execute_script(self,script,args)可以通过两种方式在浏览器中执行JavaScript。1、在文档根级别执行JavaScr......
  • 【精选】基于JAVA大学生日常行为评分管理系统的设计与实现(全网最新,独一无二)
    博主介绍:  ✌我是阿龙,一名专注于Java技术领域的程序员,全网拥有10W+粉丝。作为CSDN特邀作者、博客专家、新星计划导师,我在计算机毕业设计开发方面积累了丰富的经验。同时,我也是掘金、华为云、阿里云、InfoQ等平台的优质作者。通过长期分享和实战指导,我致力于帮助更多学生......
  • Java中用于格式化文本消息的工具类MessageFormat.format,使用{x}占位符
    MessageFormat.format 是Java中用于格式化文本消息的工具类。它允许你定义一个模板字符串,并使用一组参数来填充模板中的占位符。这个类是用于处理复杂消息格式的,例如多语言环境下的消息。MessageFormat.format 的使用方式如下:publicstaticStringformat(Stringpattern,......
  • Java对象的四种引用
    在Java中,对象的引用管理可以通过四种不同的引用类型来实现,这些引用类型提供了不同程度的内存回收控制。这些引用类型分别是强引用(StrongReference)、软引用(SoftReference)、弱引用(WeakReference)和虚引用(PhantomReference)。每种引用类型都有其特定的应用场景,下面详细介绍每一种......
  • android java BufferedWriter writer 需要关闭资源吗?
    在Android开发中,使用Java的`BufferedWriter`或其他类似的I/O资源时,**确实需要关闭资源**。这是因为:1.**释放系统资源**:关闭`BufferedWriter`会释放与之关联的底层资源,如文件句柄。2.**保证数据完整性**:确保所有写入操作完成并刷新缓冲区,这样数据才能被完整地写入到文件中。......
  • 【Java基础】填空题
    这个系列主要是对历年的考试题目中容易模糊的点进行汇总,其中很多内容也附带的了解析。这个系列的所有内容应该是全网最详细的内容了,希望可以帮助大家考试顺利。2024-042023-102023-042022-102022-042021-102021-042020-102020-082019-102019-04求三连!!感谢~~......
  • java+vue计算机毕设中小学家校联系系统的设计与实现【源码+开题+论文】
    本系统(程序+源码)带文档lw万字以上文末可获取一份本项目的java源码和数据库参考。系统程序文件列表开题报告内容研究背景随着信息技术的飞速发展和教育改革的不断深入,家校联系作为教育过程中不可或缺的一环,其形式与效率正经历着前所未有的变革。传统家校沟通方式,如家长会......
  • java+vue计算机毕设自助料理网上订餐系统【源码+开题+论文】
    本系统(程序+源码)带文档lw万字以上文末可获取一份本项目的java源码和数据库参考。系统程序文件列表开题报告内容研究背景随着互联网的飞速发展和人们生活节奏的加快,线上订餐服务已成为现代都市人不可或缺的生活方式之一。传统餐饮行业面临着转型升级的迫切需求,而自助料理......
  • java+vue计算机毕设心理健康管理系统【源码+开题+论文】
    本系统(程序+源码)带文档lw万字以上文末可获取一份本项目的java源码和数据库参考。系统程序文件列表开题报告内容研究背景随着现代社会的快速发展,生活节奏日益加快,人们面临的压力与挑战也愈发复杂多样。心理健康问题逐渐成为影响个体生活质量和社会稳定的重要因素。然而,传......
  • 基于Java+SpringBoot+Mysql在线众筹系统功能设计与实现五
    一、前言介绍:1.1项目摘要随着互联网的普及和人们消费观念的转变,众筹作为一种创新的融资方式,逐渐受到社会各界的关注和青睐。它打破了传统融资模式的限制,为初创企业、艺术家、公益项目等提供了更为灵活和便捷的融资渠道。因此,开发众筹系统旨在满足这一市场需求,促进创新项......