Annotations, a form of metadata, provide data about a program that is not part of the program itself. Annotations have no direct effect on the operation of the code they annotate.

注解的定义

注解(Annotation)是Java语言中一种特殊的修饰符,它可以用于类、方法、参数、变量、构造器以及包声明中,用于为Java代码提供元数据。相对于其他修饰符如public、final等,注解并不直接影响代码的语义,但却能被某些工具软件(如编译器、框架)所读取和利用。

注解的作用

  1. 编译检查 - 如@Override放在方法前,如果该方法不是重写父类的方法,则编译器会发出警告。
  2. 代码分析 - 通过代码里标识的注解,程序可以在编译时进行一些基于注解的处理。注解信息和JAVA的反射功能在一起时会使得程序的功能更加强大。
  3. 编译时动态处理 - 如常见的Java框架Spring、Hibernate、JUnit等,会在编译时读取注解的信息,然后根据注解的信息进行一些其他处理。
  4. 生成额外的文件 - 如Javadoc工具会根据源码中的注解来生成API文档。

内置注解

Java提供了一些预定义的注解,如:

  1. @Override: 限定重写父类方法。
  2. @Deprecated: 表示某个程序元素(如方法)已经过时。
  3. @SuppressWarnings: 告诉编译器忽略指定的警告。
  4. @SafeVarargs: 抑制堆污染警告。
  5. @FunctionalInterface: 标记一个接口为函数式接口。

元注解:
用于注解其他注解的注解称为元注解,简单理解就是用于注解其它新定义的注解。
Java提供了以下几种元注解:

  1. @Target: 表明该注解可以被应用于什么地方(如方法、类、字段等)。
  2. @Retention: 表明该注解的生命周期(仅源代码、编译期、运行期)。
  3. @Documented: 表示使用该注解的元素应被Javadoc或其他工具文档化。
  4. @Inherited: 表示该注解可以被子类继承

注解分类

  1. 由编译器使用的注解
    含义:这类注解不会被编译进入 .class 文件,在编译后它们就被编译器丢弃了。
    示例
    @Override:此注解让编译器检查该方法是否正确地实现了覆写。
    @SuppressWarnings:此注解告诉编译器忽略此处代码产生的警告。

  2. 由工具处理 .class 文件使用的注解
    含义:有些工具在加载 class 的时候,会对 class 文件做动态修改,以实现一些特殊的功能。这类注解会被编译进入 .class 文件,但在加载完成后并不会存在于内存中。这类注解主要被一些底层库使用,一般我们不需要自己处理。
    示例:可以参考 lombok。

  3. 在程序运行期能够读取的注解
    含义:这些注解在加载后会一直存在于 JVM 中,是最常用的注解。
    示例:配置了 @PostConstruct 的方法会在调用构造方法后自动被调用。这是 Java 代码通过读取该注解实现的功能,JVM 本身并不会识别该注解。

注解的基本语法

注解的声明类似于接口的声明,但是前面多了一个@符号:

1
2
3
4
//定义注解
public @interface MyAnnotation {
String value() default ""; //定义value属性
}
1
2
3
4
5
//使用注解
@MyAnnotation(value="This is my custom annotation")
public class MyClass {
// class body
}

注解的实现原理与底层机制

在Java中,注解的实现基于反射。注解本身被编译器处理为接口,其方法则对应注解的属性。当代码运行时,可以通过反射访问这些注解和它们的属性。

注解的属性值在编译时被嵌入到使用注解的类的字节码中。当运行时环境加载这个类时,注解数据成为类的一部分,可以通过Java反射API来查询。

注解的提取

结合注解和反射可以实现强大的动态处理能力。你可以在运行时查询一个类、方法或字段上的注解信息,并据此做出相应的处理。这在很多框架中被广泛应用,比如Spring和Hibernate。

例如,下面的代码演示了如何使用反射来读取方法上的注解:

1
2
3
4
5
6
7
8
9
public class AnnotationProcessor {
public void process(Object obj) throws Exception {
for (Method m : obj.getClass().getDeclaredMethods()) {
if (m.isAnnotationPresent(MyAnnotation.class)) {
// 处理带有@MyAnnotation的方法
}
}
}
}

注解实际用例

注解实际使用是更加简化编码,更方便,简洁去维护一类代码。如以下使用都可以有替方法,但是用了注解后,代码看起来更加简洁,代码的耦合度也更加小。

  1. lombok 编译时处理构造方法、set/get 方法等
    1
    2
    3
    4
    5
    6
    7
    8
    import lombok.Data;
    //增加lombok的@data注解,就不需要额外写set、get方法
    @Data
    public class Student {

    private String name;
    private Integer age;
    }
  2. @autowire/@resource spring中注入对应的service
1
2
3
//controller中注入对应的service
@Autowired
private StudentService studentService;
  1. 数据库读写分离 @Write @read 方便

  2. 数据缓存,如Redis 缓存

  3. Spring web 中 @RestController @ResponseBody

自定义注解的使用

自定义一个注解,作用于service方法,用于打印出不同的方法耗时并在控制台输出。 所有demo见 Github

1. 定义注解

1
2
3
public @interface CustomLog {

}

2. 在对应service中使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
import com.technotes.annotationdemo.annotation.CustomLog;
import com.technotes.annotationdemo.model.Student;
import com.technotes.annotationdemo.service.StudentService;
import org.springframework.stereotype.Service;

import java.util.Random;

@Service
public class StudentServiceImpl implements StudentService {

@Override
//增加刚刚写的 CustomLog
@CustomLog
public Student findByName(String name) {
//模拟查找
Student student = new Student();
student.setName(name);
student.setAge(new Random().nextInt(40));
return student;
}
@Override
//增加刚刚写的 CustomLog
@CustomLog
public Student findByName2(String name) {
//模拟查找
Student student = new Student();
student.setName(name);
student.setAge(new Random().nextInt(40));
return student;
}
}

3.在不同方法中进行调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@RestController
@RequestMapping("/student")
public class StudentController {

@Autowired
private StudentService studentService;

@ResponseBody
@RequestMapping("/find")
public Student findByName(@RequestParam(required = false) String name) {
return studentService.findByName(name);
}

@ResponseBody
@RequestMapping("/find2")
public Student findByName2(@RequestParam(required = false) String name) {
return studentService.findByName2(name);
}
}

4.访问查看结果

1
2
3
4
访问:
http://localhost:8080/student/find?name=123
输出:
com.technotes.annotationdemo.service.impl.StudentServiceImpl findByName Time 3 ms
1
2
3
4
访问:
http://localhost:8080/student/find2?name=123
输出:
com.technotes.annotationdemo.service.impl.StudentServiceImpl findByName2 Time 1 ms