杰瑞科技汇

Java注解如何实现功能扩展?

什么是 Java 注解?

注解是 Java 提供的一种元数据(Metadata)机制,元数据是“关于数据的数据”,它不属于程序本身逻辑的一部分,但可以用来对代码进行说明、配置或约束。

Java注解如何实现功能扩展?-图1
(图片来源网络,侵删)

你可以把它想象成给代码贴上的“标签”或“注释”,但这些标签可以被编译器、工具程序或运行时的虚拟机读取,并据此执行特定的操作。

核心思想

  • 它不是代码:注解本身不包含任何业务逻辑。
  • 它是一种标记:它标记了类、方法、字段、参数等,告诉其他工具“我是一个什么类型的组件”或“我需要某种特殊处理”。
  • 它需要被处理:注解本身是“死的”,必须配合相应的“处理器”(Processor)才能发挥价值,这个处理器可以是编译器、IDE、运行时环境或一个独立的程序。

注解的语法

定义注解

使用 @interface 关键字来定义一个注解。

// 定义一个名为 MyAnnotation 的注解
public @interface MyAnnotation {
    // 注解的成员(看起来像方法)
    String value(); // 成员名为 value,类型为 String
    int count() default 0; // 成员名为 count,类型为 int,并有一个默认值 0
}
  • 成员:注解中的“方法”被称为成员,成员的类型只能是基本类型、String、Class、枚举、注解,以及这些类型的数组。
  • 默认值:可以使用 default 关键字为成员指定默认值,如果一个成员有默认值,那么在使用该注解时就可以不提供该成员的值。
  • value 成员:如果注解只有一个名为 value 的成员,那么在使用时可以直接赋值,而不需要写成 value = "xxx" 的形式。

使用注解

在需要的地方使用 符号来应用注解。

@MyAnnotation(value = "TestClass", count = 10)
public class TestClass {
    @MyAnnotation("testMethod")
    public void testMethod() {
        // ...
    }
}
  • 在上面的例子中,TestClass 类被标记为 @MyAnnotation,并提供了 valuecount 的值。
  • testMethod 方法被标记为 @MyAnnotation,由于它只提供了 value 成员,所以可以简写。

内置注解

Java 提供了一些内置的注解,用于在编译和运行时进行特殊处理。

Java注解如何实现功能扩展?-图2
(图片来源网络,侵删)
注解 作用 例子
@Override 告诉编译器,这个方法覆写了父类的方法,如果拼写错误或方法签名不匹配,编译器会报错。 @Override public String toString() { ... }
@Deprecated 标记一个方法、类或字段为“过时的”,不推荐使用,使用这些元素时,编译器会给出警告。 @Deprecated public void oldMethod() { ... }
@SuppressWarnings 抑制编译器产生的特定警告。 @SuppressWarnings("unchecked")
@SafeVarargs 告诉编译器,这个可变参数方法是类型安全的,可以抑制“堆污染”相关的警告。 @SafeVarargs static <T> List<T> asList(T... a) { ... }

元注解

元注解是“注解的注解”,它们用来修饰注解的定义,控制注解的行为。

元注解 作用
@Target 指定被修饰的注解可以应用在哪些程序元素上。ElementType.TYPE(类、接口、枚举),ElementType.METHOD(方法),ElementType.FIELD(字段)等。
@Retention 指定被修饰的注解的生命周期,有三个级别:
1. RetentionPolicy.SOURCE:只在源码中存在,编译时被丢弃。
2. RetentionPolicy.CLASS:在 .class 文件中存在,但运行时被 JVM 丢弃(默认级别)。
3. RetentionPolicy.RUNTIME:在运行时也存在,可以通过反射读取。(最常用)
@Documented 指定该注解会被包含在 JavaDoc 文档中。
@Inherited 指定被修饰的注解具有继承性,如果一个类被 @Inherited 注解的注解标记,那么它的子类也会自动拥有这个注解。

示例:使用元注解定义一个注解

import java.lang.annotation.*;
// 1. @Target: 这个注解只能用在类和方法上
// 2. @Retention: 这个注解在运行时仍然可用,可以通过反射读取
// 3. @Documented: 这个注解会出现在 Javadoc 中
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyAnnotation {
    String value() default "";
    int count() default 0;
}

注解的处理器

这是注解发挥威力的关键,处理器读取注解信息并执行相应操作。

编译时处理器

这是最常见的一种处理器,它在代码编译阶段执行,用于生成额外的代码(如 getter/setterequals/hashCode)或进行代码检查。

经典例子:Lombok

Java注解如何实现功能扩展?-图3
(图片来源网络,侵删)

Lombok 是一个通过注解来简化 Java 代码的库,你只需要在类上添加几个注解,Lombok 的编译时处理器就会在编译时自动生成相应的代码(如 @Getter, @Setter, @ToString, @Data 等)。

import lombok.Data;
@Data // Lombok 的注解,编译时会自动生成 getter, setter, toString, equals, hashCode
public class User {
    private String name;
    private int age;
}
// 编译后,User.class 文件中会包含以下方法(虽然你看不到源码):
// public String getName() { ... }
// public void setName(String name) { ... }
// public String toString() { ... }
// ...

运行时处理器

在程序运行时,通过反射机制读取注解信息,并据此执行逻辑。

经典例子:JUnit 5

JUnit 框架就是通过运行时注解来识别和执行测试用例的。

import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
public class MyTest {
    @Test // JUnit 的注解,告诉测试运行器这是一个测试方法
    public void testAddition() {
        int result = 2 + 2;
        assertEquals(4, result); // 断言
    }
}

当 JUnit 测试运行器启动时,它会通过反射扫描你的测试类,找到所有被 @Test 注解标记的方法,然后逐一执行它们。

运行时处理器(Spring Framework)

Spring 框架大量使用注解来实现其“控制反转”和“依赖注入”的核心思想。

import org.springframework.stereotype.Component;
@Component // Spring 的注解,告诉 Spring "我是一个 Bean,请把我管理起来"
public class MyService {
    public void doSomething() {
        System.out.println("Service is doing something.");
    }
}

当 Spring 应用启动时,它的容器会扫描被 @Component(或 @Service, @Repository 等)注解标记的类,将它们实例化并放入容器中,以便在其他地方通过 @Autowired 注入使用。


自定义一个运行时处理器示例

让我们创建一个简单的例子,定义一个注解,然后在运行时通过反射读取它。

第 1 步:定义注解

import java.lang.annotation.*;
@Retention(RetentionPolicy.RUNTIME) // 必须是 RUNTIME,否则反射读不到
@Target(ElementType.METHOD)
public @interface MyAction {
    String name();
}

第 2 步:在代码中使用注解

public class MyController {
    @MyAction(name = "sayHello")
    public void hello() {
        System.out.println("Hello, World!");
    }
    @MyAction(name = "sayGoodbye")
    public void goodbye() {
        System.out.println("Goodbye, World!");
    }
}

第 3 步:编写处理器(通过反射读取)

import java.lang.reflect.Method;
public class AnnotationProcessor {
    public static void main(String[] args) throws Exception {
        // 1. 获取 MyController 的 Class 对象
        Class<?> clazz = MyController.class;
        // 2. 获取所有声明的方法
        Method[] methods = clazz.getDeclaredMethods();
        // 3. 遍历方法,查找带有 @MyAction 注解的方法
        for (Method method : methods) {
            // 检查方法上是否有 @MyAction 注解
            if (method.isAnnotationPresent(MyAction.class)) {
                // 4. 获取注解实例
                MyAction action = method.getAnnotation(MyAction.class);
                // 5. 读取注解的属性
                String actionName = action.name();
                System.out.println("Found action: " + actionName);
                // 6. 调用被注解的方法
                // 需要先创建对象实例,因为方法是实例方法
                MyController controller = (MyController) clazz.getDeclaredConstructor().newInstance();
                method.invoke(controller);
            }
        }
    }
}

运行结果

Found action: sayHello
Hello, World!
Found action: sayGoodbye
Goodbye, World!

特性 描述
本质 一种元数据,用于为代码添加信息,而不是逻辑。
语法 @interface 定义, 使用。
分类 内置注解@Override等)、元注解@Target等)、自定义注解
生命周期 @Retention 控制,分为 SOURCECLASSRUNTIME
核心价值 简化代码(如 Lombok)、提供框架支持(如 Spring、JUnit)、实现编译时检查
工作原理 通过处理器在编译时或运行时读取注解信息,并执行相应操作,处理器通常依赖反射(运行时)或Java Compiler API(编译时)。

理解注解是掌握现代 Java 框架和工具的关键一步,它极大地提高了 Java 代码的灵活性和可维护性。

分享:
扫描分享到社交APP
上一篇
下一篇