目录
2.3、程序运行期能够读取到注解,加载后一直存在 JVM 的注解
一、简介
注解是放在 Java 源码类、方法、字段、参数前的一种特殊 ”注释“ 。
注释是被编译器直接忽略,注解则可以被编译器打包进 class 文件,因此,注解是一种用作标注的 ”元数据“。
二、注解分类
从 JVM 的角度看,注解本身对代码逻辑没有任何影响,如何使用注解完全由工具决定。
2.1、由编译器使用的注解
例如:
@Override:让编译器检查该方法是否正确地实现了覆写。
@SuppressWarnings:告诉编译器忽略此处代码产生的警告。
这类注解救不活被编译进入 .class 文件,它们在编译后就被编译器扔掉了。
示例
Main.java 类
public class Main{
public static void main(String[] args) {
A a = new B();
a.say();
}
}
interface A{
void say();
}
class B implements A{
@Override
public void say() {
}
}
编译后的文件
Main.class
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//
public class Main {
public Main() {
}
public static void main(String[] args) {
A a = new B();
a.say();
}
}
A.class
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//
interface A {
void say();
}
B.class
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//
class B implements A {
B() {
}
public void say() {
}
}
B.class 文件里 @Override 注解编译后就被编译器扔掉了。
2.2、由工具处理.class 文件的注解
比如工具会在加载class的时候,对class做动态修改,实现一些特殊功能。
例如:
@Data IntelliJ IDEA 工具可以自动生成get与set方法并且重写equals和toString方法
示例
Person.java
import lombok.Data;
@Data
public class Person {
private String username;
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
}
Person.class
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//
public class Person {
private String username;
public String getUsername() {
return this.username;
}
public void setUsername(String username) {
this.username = username;
}
public Person() {
}
public boolean equals(Object o) {
if (o == this) {
return true;
} else if (!(o instanceof Person)) {
return false;
} else {
Person other = (Person)o;
if (!other.canEqual(this)) {
return false;
} else {
Object this$username = this.getUsername();
Object other$username = other.getUsername();
if (this$username == null) {
if (other$username != null) {
return false;
}
} else if (!this$username.equals(other$username)) {
return false;
}
return true;
}
}
}
protected boolean canEqual(Object other) {
return other instanceof Person;
}
public int hashCode() {
int PRIME = true;
int result = 1;
Object $username = this.getUsername();
int result = result * 59 + ($username == null ? 43 : $username.hashCode());
return result;
}
public String toString() {
return "Person(username=" + this.getUsername() + ")";
}
}
2.3、程序运行期能够读取到注解,加载后一直存在 JVM 的注解
在程序运行期能够读取的注解,它们加载后一直存在于 JVM 中, 这也是最常用的注解。
例如:
@Controller Spring 标记控制类注解
@Service Spring 标记业务层注解
@Controller
public class ApiController {
}
ApiController.java文件 和 编译后文件ApiController.class 是一样的。@Controller 也一直存在。
三、定义注解
Java 语言使用 @Interface 语法来定义注解(Annotation)。
示例
public @interface Report {
int type() default 0;
String level() default "info";
String value() default "";
}
注解参数类似无参数方法,可以用 default 设置一个默认值(推荐)。比较常用参数名为value 。
3.1、元注解
有一些注解可以修饰其他的注解,这些注解就称为元注解(meta annotation)。Java 标准库已经定义了一些元注解,只需使用注解,通常不需要自己写元注解。
3.2、注解配置参数类型
定义一个注解时,还可以配置参数,配置参数可以包括
- Java基本类型
- String
- 枚举类型
- 基本类型、String、Class以及枚举的数组
3.3、@Target
最常用的元注解是 @Target 。作用是定义的注解能够应用源码的哪些位置。
- 类或接口:ElementType.TYPE
- 字段:ElementType.FIELD
- 方法:ElementType.METHOD
- 构造方法:ElementType.CONSTRUCTOR
- 方法参数:ElementType.PARAMETER
示例
import java.lang.annotation.ElementType;
import java.lang.annotation.Target;
@Target({
ElementType.METHOD,
ElementType.FIELD
})
public @interface Report {
}
定义注解 Report 可以使用在方法和字段上。
3.4、@Retention
@Retention 作用是定义注解的生命周期。
- 仅编译期:RetentionPolicy.SOURCE
- 仅class文件:RetentionPolicy.CLASS
- 运行期:RetentionPolicy.RUNTIME
如果注解@Retention不存在,默认 仅class文件:RetentionPolicy.CLASS 。通常自定义注解都是运行期RetentionPolicy.RUNTIME这个元注解。
示例
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@Retention(RetentionPolicy.RUNTIME)
public @interface Report {
int type() default 0;
String level() default "info";
String value() default "";
}
3.5、@Repeatable
@Repeatable作用是这个注解是否可以重复使用。
示例
import java.lang.annotation.ElementType;
import java.lang.annotation.Repeatable;
import java.lang.annotation.Target;
@Report(type=1, level="debug")
@Report(type=2, level="warning")
public class Main{
public static void main(String[] args) {
}
}
@Repeatable(Reports.class)
@Target(ElementType.TYPE)
@interface Report {
int type() default 0;
String level() default "info";
String value() default "";
}
@Target(ElementType.TYPE)
@interface Reports {
Report[] value();
}
3.6、@Inherited
@Inherited作用 子类是否可以继承父类定义的注解。注意@Inherited只对@Target(ElementType.TYPE)类型的注解有效,并且对继承的类有效,对接口的继承无效。
示例
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Target;
@Inherited
@Target(ElementType.TYPE)
public @interface Report {
int type() default 0;
String level() default "info";
String value() default "";
}
在使用的时候,如果一个类用到了@Report
@Report(type=1)
public class Person {
}
则它的子类默认也定义了该注解:
public class Student extends Person {
}
3.7、定义注解
3.7.1、用@interface 定义注解
public @interface Report {
}
3.7.2、添加参数和默认值
public @interface Report {
int type() default 0;
String level() default "info";
String value() default "";
}
3.7.3、用元注解配置注解
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)
public @interface Report {
int type() default 0;
String level() default "info";
String value() default "";
}
其中必须设置@Target 和@Retention 。 @Retention 一般设置为运行期(RetentionPolicy.RUNTIME),@Inherited 和 @ Repeatable一般可以不写。
四、处理注解
判断某个注解是否存在于类、字段、方法或构造器。
- Class.isAnnotationPresent(Class)
- Field.isAnnotationPresent(Class)
- Method.isAnnotationPresent(Class)
- Constructor.isAnnotationPresent(Class)
示例
// 判断@Report是否存在于Person类:
Person.class.isAnnotationPresent(Report.class);
使用反射API读取注解
- Class.getAnnotation(Class)
- Field.getAnnotation(Class)
- Method.getAnnotation(Class)
- Constructor.getAnnotation(Class)
示例
// 获取Person定义的@Report注解:
Report report = Person.class.getAnnotation(Report.class);
int type = report.type();
String level = report.level();
4.1、使用注解
定义一个Range.java注解类。
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Range {
int min() default 0;
int max() default 255;
}
Person.java使用注解
public class Person {
@Range(min=1, max=20)
public String name;
@Range(max=10)
public String city;
}
测试
import java.lang.reflect.Field;
public class Main{
public static void main(String[] args) {
Person p = new Person();
p.name="张无忌";
p.city="深圳";
try {
Main.check(p);
} catch (ReflectiveOperationException e) {
e.printStackTrace();
}
}
static void check(Person person) throws IllegalArgumentException, ReflectiveOperationException {
// 遍历所有Field:
for (Field field : person.getClass().getFields()) {
// 获取Field定义的@Range:
Range range = field.getAnnotation(Range.class);
// 如果@Range存在:
if (range != null) {
// 获取Field的值:
Object value = field.get(person);
// 如果值是String:
if (value instanceof String) {
String s = (String) value;
// 判断值是否满足@Range的min/max:
if (s.length() < range.min() || s.length() > range.max()) {
throw new IllegalArgumentException("Invalid field: " + field.getName());
}
}
}
}
}
}
通过@Range 注解,配合 check() 方法就可以完成Person 示例的检查。