文章目录

Java 注解:自定义注解与反射获取

发布于 2026-04-05 03:36:51 · 浏览 25 次 · 评论 0 条

Java 注解:自定义注解与反射获取


注解是 Java 提供的一种元编程机制,它允许在代码中嵌入附加信息,这些信息可以在编译时、类加载时或运行时被读取和处理。注解本身不直接影响代码的运行逻辑,但它为工具、框架和开发者提供了描述代码行为的标准化方式。

这篇文章将系统讲解注解的核心概念、手把手教你自定义注解,并通过反射机制在运行时获取注解信息。


一、注解的本质与作用

1.1 什么是注解

注解(Annotation)是一种特殊的接口,以 @interface 关键字定义。它的本质是 元数据——关于数据的数据。注解可以附加在类、方法、字段、参数等程序元素上,为这些元素提供额外的信息标记。

举一个最常见的例子:@Override 注解。编译器在编译时会检查被该注解标记的方法是否确实重写了父类方法,如果不存在匹配的方法签名,编译将报错。这个过程展示了注解的核心价值:将额外信息与代码元素绑定,并在特定时机被工具或框架读取和利用

1.2 注解的三大应用场景

注解广泛应用于以下场景:

编译检查 — 注解在编译阶段发挥作用,如 @Override@Deprecated@SuppressWarnings 等,帮助编译器发现潜在问题或抑制警告。

代码生成 — 注解可以指导工具自动生成代码。Lombok 通过 @Data@Getter 等注解在编译时自动生成 getter/setter 方法;MyBatis 通过 @Mapper@Select 等注解简化映射配置。

运行时处理 — 通过反射机制在运行时读取注解信息,实现灵活的逻辑处理。Spring 框架的依赖注入、AOP 切面配置等都大量依赖这一机制。


二、Java 内置注解

Java 提供了一组内置注解,直接可用:

注解 作用
@Override 标记方法重写父类方法,编译器验证重写合法性
@Deprecated 标记元素已废弃,不推荐使用
@SuppressWarnings 抑制编译器警告
@FunctionalInterface 标记函数式接口,编译器验证接口是否符合函数式规范
@Override
public String toString() {
    return "CustomClass{}";
}

@Deprecated
public void oldMethod() {
    // 该方法已废弃
}

@SuppressWarnings("unchecked")
public void legacyCode() {
    List rawList = new ArrayList();
}

三、自定义注解

3.1 注解定义语法

自定义注解使用 @interface 关键字:

public @interface MyAnnotation {
    // 注解属性定义
    String value() default "";
    int number() default 0;
}

3.2 注解属性

注解属性以类似抽象方法的形式定义,但实际作用是声明该注解可以携带的数据。属性的类型可以是:

  • 基本类型(int、long、boolean 等)
  • String
  • Class 及其数组
  • 枚举类型
  • 其他注解类型
  • 以上类型的数组
public @interface Entity {
    String tableName();
    String primaryKey() default "id";
}

3.3 使用自定义注解

@Entity(tableName = "users")
public class User {
    // 类上使用注解
}

@Entity(tableName = "orders", primaryKey = "order_id")
public class Order {
    // 多次使用时指定不同属性值
}

四、元注解:注解的注解

元注解是用于修饰其他注解的注解。Java 提供四个标准元注解:

4.1 @Target:限定使用范围

@Target 指定注解可以应用在哪些程序元素上。如果未指定,默认可以应用于所有元素。

import java.lang.annotation.ElementType;
import java.lang.annotation.Target;

// 该注解只能用于方法和类
@Target({ElementType.METHOD, ElementType.TYPE})
public @interface限TargetAnnotation {
    String description();
}

ElementType 可选值包括:TYPE(类/接口)、FIELD(字段)、METHOD(方法)、PARAMETER(参数)、CONSTRUCTOR(构造器)、LOCAL_VARIABLE(局部变量)、ANNOTATION_TYPE(注解)、PACKAGE(包)、TYPE_PARAMETER(类型参数,Java 8+)、TYPE_USE(所有类型使用场景,Java 8+)。

4.2 @Retention:保留策略

@Retention 指定注解在哪个阶段保留:

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

public @interface限RetentionAnnotation {
    String name();
}

RetentionPolicy 有三个取值:

SOURCE — 只在源码阶段保留,编译后被丢弃。如 @Override@SuppressWarnings

CLASS — 在编译时保留,但运行时不可通过反射获取。这是默认行为。

RUNTIME — 在运行时仍保留,可以通过反射机制读取。通常用于需要运行时处理的场景。

4.3 @Documented:文档生成

@Documented 使注解包含在 Javadoc 生成的文档中:

import java.lang.annotation.Documented;

@Documented
public @interface DocumentedAnnotation {
    String info();
}

4.4 @Inherited:子类继承

@Inherited 使被它修饰的注解自动被子类继承:

import java.lang.annotation.Inherited;

@Inherited
@Retention(RetentionPolicy.RUNTIME)
public @interface InheritedAnnotation {
    String value() default "inherited";
}

当父类被 @InheritedAnnotation 标记时,子类自动继承该注解。需要注意的是,这个继承只发生在类级别,对方法或字段的注解不会继承。


五、反射获取注解信息

这是注解发挥实际作用的核心环节。通过反射 API,程序可以在运行时读取注解信息并执行相应逻辑。

5.1 获取 Class 对象的注解

import java.lang.annotation.Annotation;

@Retention(RetentionPolicy.RUNTIME)
@Entity(tableName = "t_users")
public class User {
    private Long id;
    private String name;

    // getter/setter 省略
}

public class AnnotationProcessor {
    public static void main(String[] args) throws Exception {
        // 获取 User 类的 Class 对象
        Class<?> clazz = User.class;

        // 检查类上是否有指定注解
        if (clazz.isAnnotationPresent(Entity.class)) {
            // 获取注解实例
            Entity entity = clazz.getAnnotation(Entity.class);
            String tableName = entity.tableName();

            System.out.println("对应的表名: " + tableName);
        }
    }
}

5.2 获取字段的注解

import java.lang.reflect.Field;

@Retention(RetentionPolicy.RUNTIME)
public @interface Column {
    String name();
    boolean unique() default false;
}

public class User {
    @Column(name = "user_id", unique = true)
    private Long id;

    @Column(name = "user_name")
    private String name;
}

public class FieldProcessor {
    public static void processFields(Class<?> clazz) {
        for (Field field : clazz.getDeclaredFields()) {
            if (field.isAnnotationPresent(Column.class)) {
                Column column = field.getAnnotation(Column.class);
                System.out.println("字段: " + field.getName() + 
                                   " -> 列名: " + column.name() + 
                                   ", 唯一: " + column.unique());
            }
        }
    }
}

5.3 获取方法的注解

import java.lang.reflect.Method;

@Retention(RetentionPolicy.RUNTIME)
public @interface Action {
    String name();
    String method() default "GET";
}

public class UserService {
    @Action(name = "查询用户", method = "GET")
    public User getUser(Long id) {
        return new User();
    }

    @Action(name = "创建用户", method = "POST")
    public User createUser(User user) {
        return user;
    }
}

public class MethodProcessor {
    public static void processMethods(Class<?> clazz) {
        for (Method method : clazz.getDeclaredMethods()) {
            if (method.isAnnotationPresent(Action.class)) {
                Action action = method.getAnnotation(Action.class);
                System.out.println("方法: " + method.getName() + 
                                   " -> 动作: " + action.name() + 
                                   ", HTTP方法: " + action.method());
            }
        }
    }
}

5.4 完整案例:基于注解的表结构自动生成

以下是一个综合示例,展示如何通过注解和反射实现根据实体类自动生成建表 SQL:

import java.lang.annotation.*;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;

// 实体注解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Table {
    String name();
}

// 字段注解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Column {
    String name();
    String type() default "VARCHAR(255)";
    boolean primaryKey() default false;
    boolean notNull() default false;
}

// 实体类定义
@Table(name = "t_products")
public class Product {
    @Column(name = "id", type = "BIGINT", primaryKey = true)
    private Long id;

    @Column(name = "product_name", notNull = true)
    private String name;

    @Column(name = "price", type = "DECIMAL(10,2)")
    private Double price;

    @Column(name = "stock", type = "INT")
    private Integer stock;
}

// SQL 生成器
public class SqlGenerator {
    public static String generateCreateTableSql(Class<?> clazz) {
        if (!clazz.isAnnotationPresent(Table.class)) {
            return null;
        }

        Table table = clazz.getAnnotation(Table.class);
        String tableName = table.name();

        List<String> columnDefs = new ArrayList<>();
        List<String> primaryKeys = new ArrayList<>();

        for (Field field : clazz.getDeclaredFields()) {
            if (!field.isAnnotationPresent(Column.class)) {
                continue;
            }

            Column column = field.getAnnotation(Column.class);
            StringBuilder def = new StringBuilder();
            def.append(column.name()).append(" ").append(column.type());

            if (column.notNull()) {
                def.append(" NOT NULL");
            }

            columnDefs.add(def.toString());

            if (column.primaryKey()) {
                primaryKeys.add(column.name());
            }
        }

        if (!primaryKeys.isEmpty()) {
            columnDefs.add("PRIMARY KEY (" + String.join(", ", primaryKeys) + ")");
        }

        return String.format("CREATE TABLE %s (\n    %s\n);", 
                           tableName, 
                           String.join(",\n    ", columnDefs));
    }

    public static void main(String[] args) {
        String sql = generateCreateTableSql(Product.class);
        System.out.println(sql);
    }
}

运行结果:

CREATE TABLE t_products (
    id BIGINT NOT NULL,
    product_name VARCHAR(255) NOT NULL,
    price DECIMAL(10,2),
    stock INT,
    PRIMARY KEY (id)
);

六、注解处理器与编译时处理

除了运行时通过反射处理注解,注解还可以在编译时通过注解处理器(Annotation Processor)进行处理。javax.annotation.processing 包提供了相关 API,允许在编译阶段生成新代码或报告错误。

import javax.annotation.processing.*;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.TypeElement;
import java.util.Set;

@SupportedAnnotationTypes("com.example.Table")
@SupportedSourceVersion(SourceVersion.RELEASE_8)
public class TableProcessor extends AbstractProcessor {

    @Override
    public boolean process(Set<? extends TypeElement> annotations, 
                          RoundEnvironment roundEnv) {
        // 遍历所有被 @Table 注解的元素
        for (TypeElement element : annotations) {
            // 生成相应代码
            System.out.println("Processing: " + element.getQualifiedName());
        }
        return true;
    }
}

七、实践建议与注意事项

选择正确的保留策略 — 如果需要在运行时通过反射获取注解,必须将 @Retention 设置为 RetentionPolicy.RUNTIME。这是最容易被忽视的错误点之一。

合理使用 @Target — 明确注解的应用范围不仅可以避免误用,还能让编译器和 IDE 给出更准确的提示。未指定 @Target 的注解可以应用于任何元素,这有时会导致意外的行为。

避免过度使用注解 — 注解提高了代码的简洁性,但也会增加理解和维护成本。对于简单的配置,直接硬编码可能比使用注解更清晰。

注意注解的继承限制 — Java 的注解默认不会被继承。即使在父类上标记了注解,子类也不会自动继承。如果需要继承行为,必须使用 @Inherited 元注解,且该注解只能作用于类。

性能考量 — 反射操作Annotation虽然方便,但相比普通方法调用有性能开销。在对性能敏感的场景(如高频调用的核心方法)中,应当评估是否需要缓存反射获取的结果。


注解是 Java 语言中强大的元编程特性。通过自定义注解,开发者可以为自己的代码添加语义标记;通过反射机制,这些标记可以在运行时被读取和处理,从而实现灵活的配置、自动化和框架集成功能。掌握注解与反射的结合使用,是深入理解 Spring、MyBatis 等主流框架内部原理的必经之路。

评论 (0)

暂无评论,快来抢沙发吧!

扫一扫,手机查看

扫描上方二维码,在手机上查看本文