注解
注解(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();
}
生成汇编代码
- 先编译 java 源文件
javac com/csthink/annotation/Table.java,得到Table.class。 - 再执行反编译
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 2024年1月11日; 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;
}
package com.csthink.validator;
import com.csthink.validator.impl.PhoneNumberValidator;
import jakarta.validation.Constraint;
import jakarta.validation.Payload;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
@Target({FIELD})
@Retention(RUNTIME)
@Documented
@Constraint(validatedBy = PhoneNumberValidator.class)
public @interface Phone {
String locale() default "86";
String message() default "Invalid phone number";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
package com.csthink.validator.impl;
import com.csthink.validator.Phone;
import jakarta.validation.ConstraintValidator;
import jakarta.validation.ConstraintValidatorContext;
public class PhoneNumberValidator implements ConstraintValidator<Phone, String> {
private String locale;
@Override
public void initialize(Phone constraintAnnotation) {
this.locale = constraintAnnotation.locale();
}
@Override
public boolean isValid(String value, ConstraintValidatorContext context) {
if (value == null) {
return false;
}
return isMobile(this.locale, value);
}
// 以下逻辑可以单独放在工具类中
private static boolean isMobile(String locale, String phone) {
String regex = "^[1]\\d{10}$";
if (!"86".equals(locale)) {
regex = "\\d{1,14}";
}
Pattern p = Pattern.compile(regex);
Matcher m = p.matcher(phone);
return m.matches();
}
}
元注解
用于修饰注解的注解,通常用在注解的定义上
@Target
用法: @Target({ElementType.METHOD, ElementType.TYPE})
作用: 标明注解的作用域(作用目标),支持下列后选值
CONSTRUCTOR构造方法声明FIELD字段声明LOCAL_VARIABLE局部变量声明METHOD方法声明PACKAGE包声明PARAMETER参数声明 参考 Spring 的 @RequestParamTYPE类、接口、枚举、Annotation 类型
@Retention
用法: @Retention(RetentionPolicy.RUNTIME)
作用: 标明注解的生命周期
SOURCE只在源码中显示,编译时丢弃CLASS编译时会记录到.class文件中,运行时忽略RUNTIME运行时存在,可以通过反射读取
@Inherited
用法: @Inherited
作用: 是否允许子类继承该注解
- 父类使用类注解或方法注解,子类可以不用声明任何注解便可以直接使用父类的类注解
- 父类声明的类注解或方法注解,子类只可以继承到父类的类注解
- 只能用于继承关系中,接口不可以
@Documented
用法: @Documented
作用: 注解是否应当被包含在 JavaDoc 文档中