引言
Java注解(Annotation)是Java编程语言的一种形式,用于在代码中添加元数据。注解可以用于类、方法、变量、参数、包等元素,提供有关这些元素的附加信息。
以下是Java官方文档中对注解的定义(来自Java SE Documentation):
Annotations are a form of metadata that provide data about a program but are not part of the program itself. Annotations have no direct effect on the operation of the code they annotate.
一、注解的基本概念
1.1 什么是注解
注解是一种特殊的Java类型,它为程序元素(类、方法、变量等)提供元数据。注解本身不会直接影响程序的逻辑,但可以通过工具或框架在编译时、类加载时或运行时进行处理。
Java注解的主要用途有:
- 编译时信息:注解可以用于在编译时给编译器提供信息。例如,
@Override
注解告诉编译器该方法是重写父类方法。 - 编译时和运行时的处理:注解可以用于在编译时或运行时进行处理。例如,Java的注解处理工具(APT)可以在编译时处理注解,Spring框架可以在运行时处理注解。
- 代码生成:注解可以用于代码生成工具,例如Lombok,通过注解生成样板代码。
- 配置:注解可以用作配置的一种形式,替代传统的XML配置文件。例如,在Spring框架中,注解可以用来配置依赖注入。
1.2 注解的语法
注解的定义和使用非常简单。定义一个注解需要使用@interface
关键字:
public @interface MyAnnotation {
String value();
}
使用注解时,只需在目标元素前加上@注解名
:
@MyAnnotation("example")
public class MyClass {
// ...
}
二、标准注解
Java提供了一些常用的标准注解,这些注解在日常开发中非常有用。
2.1 @Override
@Override
用于标识一个方法是重写父类方法的。编译器会检查该方法是否正确地重写了父类的方法。
public class Parent {
public void display() {
System.out.println("Parent display");
}
}
public class Child extends Parent {
@Override
public void display() {
System.out.println("Child display");
}
}
2.2 @Deprecated
@Deprecated
表示某个元素(类、方法、字段等)已经过时,不建议使用。编译器会发出警告,提醒开发者该元素已过时。
@Deprecated
public void oldMethod() {
// ...
}
2.3 @SuppressWarnings
@SuppressWarnings
用于抑制编译器产生的警告信息。常见的参数有"unchecked"
、"deprecation"
等。
@SuppressWarnings("unchecked")
public void test() {
List rawList = new ArrayList();
List<String> list = rawList; // unchecked warning
}
2.4 @SafeVarargs
@SafeVarargs
用于抑制对使用了泛型可变参数方法的警告。只能用于静态方法、构造方法和最终方法。
@SafeVarargs
public static <T> void print(T... args) {
for (T arg : args) {
System.out.println(arg);
}
}
2.5 @FunctionalInterface
@FunctionalInterface
用于标识一个接口是函数式接口(即只有一个抽象方法的接口)。编译器会检查该接口是否符合函数式接口的要求。
@FunctionalInterface
public interface MyFunctionalInterface {
void execute();
}
三、元注解
元注解是用于注解其他注解的注解,常见的元注解有:
3.1 @Retention
@Retention
指定注解的保留策略。参数为RetentionPolicy
枚举类型,常见值有SOURCE
、CLASS
、RUNTIME
。
SOURCE
:注解只在源代码中保留,编译时会被丢弃。CLASS
:注解在编译时保留在类文件中,但不会在运行时保留。RUNTIME
:注解在运行时保留,可以通过反射获取。
@Retention(RetentionPolicy.RUNTIME)
public @interface MyRuntimeAnnotation {
// ...
}
3.2 @Target
@Target
指定注解可以应用的程序元素。参数为ElementType
枚举类型,常见值有TYPE
、FIELD
、METHOD
、PARAMETER
、CONSTRUCTOR
、LOCAL_VARIABLE
、ANNOTATION_TYPE
、PACKAGE
。
@Target(ElementType.METHOD)
public @interface MyMethodAnnotation {
// ...
}
@Target
注解的 value
元素是一个数组,用于指定可以应用该注解的程序元素类型。ElementType
是一个枚举,定义了各种可能的程序元素类型,包括:
ElementType.ANNOTATION_TYPE
:可以应用于注解类型。ElementType.CONSTRUCTOR
:可以应用于构造函数。ElementType.FIELD
:可以应用于字段或属性。ElementType.LOCAL_VARIABLE
:可以应用于局部变量。ElementType.METHOD
:可以应用于方法。ElementType.PACKAGE
:可以应用于包声明。ElementType.PARAMETER
:可以应用于参数。ElementType.TYPE
:可以应用于类、接口(包括注解类型)或枚举声明。ElementType.TYPE_PARAMETER
:可以应用于类型参数(从 Java 8 开始)。ElementType.TYPE_USE
:可以应用于任何类型的使用(从 Java 8 开始)。
3.3 @Documented
@Documented
指定注解是否包含在Javadoc中。
@Documented
public @interface MyDocumentedAnnotation {
// ...
}
3.4 @Inherited
@Inherited
指定注解是否可以被子类继承。
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface MyInheritedAnnotation {
// ...
}
四、注解处理器
注解处理器(Annotation Processor)是一种在编译时处理注解的工具。通过注解处理器,可以生成代码、检查注解的使用等。
定义一个注解处理器通常涉及以下几个步骤:
- 创建自定义注解:定义你希望处理的注解。
- 实现注解处理器:编写注解处理器类,继承
javax.annotation.processing.AbstractProcessor
。 - 注册注解处理器:在
META-INF/services
目录下创建一个服务提供者配置文件,将注解处理器注册到编译器中。
4.1 定义注解处理器
注解处理器需要继承javax.annotation.processing.AbstractProcessor
类,并重写process
方法。
@SupportedAnnotationTypes("com.example.MyAnnotation")
@SupportedSourceVersion(SourceVersion.RELEASE_8)
public class MyAnnotationProcessor extends AbstractProcessor {
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
for (Element element : roundEnv.getElementsAnnotatedWith(MyAnnotation.class)) {
// 处理注解
}
return true;
}
}
4.2 注册注解处理器
在META-INF/services
目录下创建文件javax.annotation.processing.Processor
,并在文件中写入注解处理器的全限定类名。
com.example.MyAnnotationProcessor
4.3 编译和运行
使用javac
编译时,注解处理器会自动执行。
javac -processor com.example.MyAnnotationProcessor MyClass.java
五、实际应用
5.1 JUnit中的注解
JUnit是一个流行的Java单元测试框架,广泛使用注解来标识测试方法和生命周期方法。
@Test
:标识一个方法是测试方法。@Before
:在每个测试方法执行之前执行。@After
:在每个测试方法执行之后执行。@BeforeClass
:在所有测试方法执行之前执行,仅执行一次。@AfterClass
:在所有测试方法执行之后执行,仅执行一次。
public class MyTest {
@BeforeClass
public static void setUpBeforeClass() {
// 初始化资源
}
@AfterClass
public static void tearDownAfterClass() {
// 清理资源
}
@Before
public void setUp() {
// 每个测试方法之前执行
}
@After
public void tearDown() {
// 每个测试方法之后执行
}
@Test
public void testMethod() {
// 测试方法
}
}
5.2 Spring中的注解
Spring是一个流行的Java企业级开发框架,广泛使用注解来简化配置和依赖注入。
@Component
:标识一个类是Spring管理的组件。@Service
:标识一个类是服务层组件。@Repository
:标识一个类是数据访问层组件。@Controller
:标识一个类是Spring MVC控制器。@Autowired
:自动注入依赖。@Qualifier
:指定注入的具体实现。@Value
:注入属性值。@Configuration
:标识一个类是配置类。@Bean
:标识一个方法返回一个Spring管理的Bean。@Primary
:指定首选的Bean。@Scope
:指定Bean的作用域。@RequestMapping
:映射HTTP请求到处理方法。@GetMapping
:映射HTTP GET请求到处理方法。@PostMapping
:映射HTTP POST请求到处理方法。@PutMapping
:映射HTTP PUT请求到处理方法。@DeleteMapping
:映射HTTP DELETE请求到处理方法。
@Service
public class MyService {
@Autowired
private MyRepository myRepository;
public void performService() {
// 业务逻辑
}
}
@Controller
public class MyController {
@Autowired
private MyService myService;
@GetMapping("/hello")
public String sayHello() {
return "Hello, World!";
}
}
@Qualifier
是 Spring 框架中的一个注解,用于在依赖注入中消除歧义。当有多个候选 Bean 可供注入时,@Qualifier
允许你指定要注入的具体 Bean。
例如,假设你有两个实现了同一个接口的 Bean:
public interface Animal {
void speak();
}
@Component
@Qualifier("dog")
public class Dog implements Animal {
@Override
public void speak() {
System.out.println("Woof");
}
}
@Component
@Qualifier("cat")
public class Cat implements Animal {
@Override
public void speak() {
System.out.println("Meow");
}
}
如果你在某个地方需要注入 Animal
类型的 Bean,但不指定具体的实现,Spring 会抛出一个异常,因为它无法确定该注入哪个 Bean。这时,你可以使用 @Qualifier
注解来明确指定要注入的 Bean:
@Component
public class AnimalService {
private final Animal animal;
// 在引入animal类时通过注解指定想注入的子类是dog而不是cat
@Autowired
public AnimalService(@Qualifier("dog") Animal animal) {
this.animal = animal;
}
public void makeAnimalSpeak() {
animal.speak();
}
}
在这个例子中,AnimalService
类中的 animal
字段会被注入 Dog
实现,而不是 Cat
实现。@Qualifier("dog")
明确指定了要注入的 Bean。
六、注解的高级用法
6.1 自定义注解
自定义注解可以根据需要定义自己的注解,并通过反射或注解处理器进行处理。
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface LogExecutionTime {
// ...
}
6.2 反射获取注解
通过反射可以在运行时获取注解,并进行相应的处理。
public class AnnotationProcessor {
public static void processAnnotations(Class<?> clazz) {
for (Method method : clazz.getDeclaredMethods()) {
if (method.isAnnotationPresent(LogExecutionTime.class)) {
// 处理注解
}
}
}
}
6.3 注解组合
注解可以组合使用,以实现更复杂的功能。例如,Spring中的@RestController
注解就是@Controller
和@ResponseBody
注解的组合。
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Controller
@ResponseBody
public @interface RestController {
// ...
}
七、总结
注解是Java中非常强大和灵活的元数据机制,可以极大地简化代码和配置。通过本文的介绍,我们深入了解了注解的基本概念、标准注解、元注解、注解处理器以及实际应用。
相关文档
https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/lang/annotation/package-summary.html