跳至主要內容

注解

Mars大约 6 分钟JAVA注解annotationJAVA

注解(Annotation),也叫元数据。一种代码级别的说明,可以理解为一种特殊的“注释”。它是 JDK 1.5 及以后版本引入的一个特性。它可以声明在包、类、字段、方法、局部变量、 方法参数等的前面,用来对这些元素进行说明,注释。

什么是注解

注解提供了另一种为程序元素(参数、字段、方法、类和包等)设置元数据的方法,不同于 XML 这种集中式的设置方式,注解是一种分散式的元数据设置方式。

  • 下面是一段在 SpringBoot 项目中使用到的注解代码示例
@Slf4j
@Api(tags = "Test 模块")
@RestController
@RequestMapping("/redis")
public class RedisTestController {

    @Resource
    private RedisTemplate<String, User> redisTemplate;

    @GetMapping("find/{id}")
    public User findUserById(@PathVariable("id") String userId) {
        return redisTemplate.opsForValue().get(userId);
    }
}

JDK 1.5 提供了 java.lang.annotation.Annotation 所有的注解都是继承自该接口

package java.lang.annotation;

/**
 * The common interface extended by all annotation interfaces.  Note that an
 * interface that manually extends this one does <i>not</i> define
 * an annotation interface.  Also note that this interface does not itself
 * define an annotation interface.
 *
 * More information about annotation interfaces can be found in section
 * {@jls 9.6} of <cite>The Java Language Specification</cite>.
 *
 * The {@link java.lang.reflect.AnnotatedElement} interface discusses
 * compatibility concerns when evolving an annotation interface from being
 * non-repeatable to being repeatable.
 *
 * @author  Josh Bloch
 * @since   1.5
 */
public interface Annotation {
    // ....
}

这里我先简单定义了一个自定义注解,这里可以先不要过于纠结如何自定义注解,先走马观花的看一下这个注解,后面我们详细描述如何自定义注解,通过先编译 java源文件生成字节码(class)文件,然后再通过反编译查看生成的 class 字节码文件可以验证: "所有的注解类都是继承自 java.lang.annotation.Annotation"。

package com.csthink.annotation;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Table {

    String value();
}

生成汇编代码

  1. 先编译 java 源文件 javac com/csthink/annotation/Table.java,得到 Table.class
  2. 再执行反编译 javap -verbose com.csthink.annotation.Table 查看编译得到的字节码文件 (Table.class), 可以查看 java 编译器生成的字节码。
Classfile /Users/mars/stack/code/java-study/src/main/java/com/csthink/annotation/Table.class
Last modified 2024111; size 455 bytes
MD5 checksum fd1ab01940965cd72965fa27a2a1cef2
Compiled from "Table.java"
public interface com.csthink.annotation.Table extends java.lang.annotation.Annotation
minor version: 0
major version: 55
flags: (0x2601) ACC_PUBLIC, ACC_INTERFACE, ACC_ABSTRACT, ACC_ANNOTATION
this_class: #1                          // com/csthink/annotation/Table
super_class: #2                         // java/lang/Object
interfaces: 1, fields: 0, methods: 1, attributes: 2
Constant pool:
        #1 = Class              #16            // com/csthink/annotation/Table
        #2 = Class              #17            // java/lang/Object
        #3 = Class              #18            // java/lang/annotation/Annotation
        #4 = Utf8               value
   #5 = Utf8               ()Ljava/lang/String;
   #6 = Utf8               SourceFile
   #7 = Utf8               Table.java
   #8 = Utf8               RuntimeVisibleAnnotations
   #9 = Utf8               Ljava/lang/annotation/Target;
  #10 = Utf8               Ljava/lang/annotation/ElementType;
  #11 = Utf8               TYPE
  #12 = Utf8               Ljava/lang/annotation/Retention;
  #13 = Utf8               Ljava/lang/annotation/RetentionPolicy;
  #14 = Utf8               RUNTIME
  #15 = Utf8               Ljava/lang/annotation/Documented;
  #16 = Utf8               com/csthink/annotation/Table
  #17 = Utf8               java/lang/Object
  #18 = Utf8               java/lang/annotation/Annotation
{
  public abstract java.lang.String value();
  descriptor: ()Ljava/lang/String;
  flags: (0x0401) ACC_PUBLIC, ACC_ABSTRACT
}
SourceFile: "Table.java"
RuntimeVisibleAnnotations:
        0: #9(#4=[e#10.#11])
        java.lang.annotation.Target(
        value=[Ljava/lang/annotation/ElementType;.TYPE]
        )
        1: #12(#4=e#13.#14)
        java.lang.annotation.Retention(
        value=Ljava/lang/annotation/RetentionPolicy;.RUNTIME
    )
            2: #15()
java.lang.annotation.Documented

通过分析上面的字节码文件,我们可以看出注解是继承自 java.lang.annotation.Annotation

javap 工具

javap 是 jdk 自带的反解析工具,它可以根据 class 字节码文件,反解析出类对应的 code 区(字节码指令)、局部变量表、 异常表和代码行偏移量映射表、常量池等信息。

常用参数选项:

  • -l : 输出行号和本地变量表信息;
  • -c : 对当前 class 字节码进行反编译生成汇编代码;
  • -v : 除了包含 -c 参数的内容外,还会输出行号、局部变量表信息、常量池等信息;

javap 有许多参数,-verbose 或者简写 -v

注解的功能

  • 作为特定的标记,用于告诉编译器一些信息(比如 @Override 注解可以检查父类或接口类中是否包含有需要重写的方法)
  • 编译时动态处理,如动态生成代码(比如 Lombok 工具)
  • 运行时动态处理,作为额外信息的载体,如获取注解信息

注解的分类

按照运行机制分

源码注解

注解只在源码中存在,编译成 .class 文件就不存在了

编译时注解

注解在源码和 .class 文件中都存在,比如 @Override@Deprecated@SuppressWarnings

运行时注解

在运行阶段也起作用,甚至会影响运行逻辑的注解,比如 @Autowired

按照来源分

由于数量较多,无法罗列完所有的注解,仅选取部分作为示例

JDK 自带的注解

  • @Override
  • @Deprecated
  • @SuppressWarnings("deprecated")
    • 调用添加了 @Deprecated 注解的方法时,若不希望看到中划线这类的警告,可以在调用方类使用这个 @SuppressWarnings("deprecated") 注解

第三方注解

Spring

  • @Autowired
  • @Service
  • @Repository
  • @Component

MyBatis

  • @InsertProvider
  • @UpdateProvider
  • @Options

自定义注解

自定义注解语法规范
  • 使用 @interface 关键字定义注解
  • 成员以无参数无异常方式声明
  • 可以用 default 为成员指定默认值
  • 成员类型是受限的,合法的类型包括原始(基本类型,不能使用包装类)类型以及 String、Class、Annotation、Enumeration,以及上述所有类型的数组
  • 若注解只有一个成员,则成员约定取名为 value(), 在使用是可以忽略成员名和赋值号(=)
  • 注解类可以没有成员,没有成员的注解称为标识注解
一个简单的自定义注解示例

该注解的功能是用于校验客户端参数是否为一个有效的手机号

使用自定义注解
package com.csthink.req;


import com.csthink.validator.Phone;

public class LoginReq {
    
    @Phone
    private String mobile;
}

元注解

用于修饰注解的注解,通常用在注解的定义上

@Target

用法: @Target({ElementType.METHOD, ElementType.TYPE})

作用: 标明注解的作用域(作用目标),支持下列后选值

  • CONSTRUCTOR 构造方法声明
  • FIELD 字段声明
  • LOCAL_VARIABLE 局部变量声明
  • METHOD 方法声明
  • PACKAGE 包声明
  • PARAMETER 参数声明 参考 Spring 的 @RequestParam
  • TYPE 类、接口、枚举、Annotation 类型

@Retention

用法: @Retention(RetentionPolicy.RUNTIME)

作用: 标明注解的生命周期

  • SOURCE 只在源码中显示,编译时丢弃
  • CLASS 编译时会记录到 .class 文件中,运行时忽略
  • RUNTIME 运行时存在,可以通过反射读取

@Inherited

用法: @Inherited

作用: 是否允许子类继承该注解

  • 父类使用类注解或方法注解,子类可以不用声明任何注解便可以直接使用父类的类注解
  • 父类声明的类注解或方法注解,子类只可以继承到父类的类注解
  • 只能用于继承关系中,接口不可以

@Documented

用法: @Documented

作用: 注解是否应当被包含在 JavaDoc 文档中

参考

Oracle Tutorialsopen in new window

廖雪峰的注解教程open in new window