注解

发布于 2026-07-29 10:56 更新于 2026-07-29 10:56 1787 字 9 min read ... 访问量

本文介绍了 Java 注解的基本概念、元注解、自定义注解的定义与使用,以及注解在权限校验和数据库映射中的实际应用。通过反射机制,注解可以被解析并用于实现业务逻辑,但其本质是元数据,实际功能需由框架或程序实现。文章强调注解需配合运行时保留策略(RUNTIME)和安全校验,且在实际项目中应避免重复实现 ORM 功能,建议使用成熟框架。

Java Annotation(注解)

注解概述

注解是 Java 5 引入的一种元数据机制。它可以为类、方法、字段、构造器、参数等程序元素添加说明或配置信息。

注解本身通常不直接执行业务逻辑,而是由编译器、JVM、反射代码或框架读取并处理。常见注解包括 @Override@Deprecated@SuppressWarnings

注解的本质

自定义注解使用 @interface 声明。编译后,注解类型本质上是继承了 java.lang.annotation.Annotation 接口的特殊接口。

public @interface MyAnnotation {
    String value() default "";
}

注解中的成员声明形式类似无参数方法。成员类型只能使用基本类型、StringClass、枚举、其他注解,以及这些类型的一维数组。

元注解

元注解是用于修饰注解类型的注解。

@Retention

@Retention 用于指定注解的保留阶段。

取值说明
RetentionPolicy.SOURCE仅存在于源代码中,编译后丢弃
RetentionPolicy.CLASS写入字节码文件,但运行时通常不能通过反射读取
RetentionPolicy.RUNTIME运行时保留,可以通过反射读取

需要在程序运行期间解析的注解,必须使用 RetentionPolicy.RUNTIME

@Target

@Target 用于限制注解可以修饰的位置。

取值适用位置
ElementType.TYPE类、接口、枚举或注解类型
ElementType.METHOD方法
ElementType.FIELD字段
ElementType.PARAMETER方法参数
ElementType.CONSTRUCTOR构造器
ElementType.ANNOTATION_TYPE注解类型

其他常用元注解

  • @Documented:生成 API 文档时包含该注解。
  • @Inherited:允许类级注解被子类继承,但不作用于方法和字段。
  • @Repeatable:允许同一位置重复使用同一种注解。

定义并使用自定义注解

定义注解

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.TYPE, ElementType.METHOD})
public @interface MyAnnotation {
    String value() default "";

    int age() default 18;

    String[] tags() default {};
}

使用注解

@MyAnnotation(
        value = "用户类",
        age = 20,
        tags = {"测试", "业务"}
)
public class User {
    @MyAnnotation("打印方法")
    public void print() {
        System.out.println("执行 print() 方法");
    }
}

当注解只有一个名为 value 的成员需要赋值时,可以省略成员名。

@MyAnnotation("用户类")
public class User {
}

通过反射解析注解

运行时注解可以通过反射读取。常用方法包括 isAnnotationPresent()getAnnotation()getDeclaredAnnotations()

import java.lang.reflect.Method;
import java.util.Arrays;

public class AnnotationTest {
    public static void main(String[] args) throws NoSuchMethodException {
        Class<User> userClass = User.class;

        if (userClass.isAnnotationPresent(MyAnnotation.class)) {
            MyAnnotation classAnnotation = userClass.getAnnotation(MyAnnotation.class);

            System.out.println("value:" + classAnnotation.value());
            System.out.println("age:" + classAnnotation.age());
            System.out.println("tags:" + Arrays.toString(classAnnotation.tags()));
        }

        Method printMethod = userClass.getDeclaredMethod("print");
        MyAnnotation methodAnnotation = printMethod.getAnnotation(MyAnnotation.class);

        if (methodAnnotation != null) {
            System.out.println("方法注解:" + methodAnnotation.value());
        }
    }
}

注解只负责保存元数据。真正的功能必须由编译器、反射程序或框架实现。

自定义权限校验

定义权限注解

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.METHOD)
public @interface RequirePermission {
    String value();
}

在业务方法上使用注解

public class OrderService {
    @RequirePermission("order:delete")
    public void deleteOrder(Long id) {
        System.out.println("删除订单:" + id);
    }
}

解析注解并执行方法

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class PermissionInterceptor {
    private static final String CURRENT_PERMISSION = "order:delete";

    public static void invokeMethod(
            Object target,
            Method method,
            Object... arguments
    ) throws InvocationTargetException, IllegalAccessException {
        RequirePermission annotation = method.getAnnotation(RequirePermission.class);

        if (annotation != null) {
            String requiredPermission = annotation.value();

            if (!requiredPermission.equals(CURRENT_PERMISSION)) {
                throw new SecurityException(
                        "权限不足,需要权限:" + requiredPermission
                );
            }
        }

        method.invoke(target, arguments);
    }

    public static void main(String[] args) throws ReflectiveOperationException {
        OrderService orderService = new OrderService();
        Method method = OrderService.class.getMethod("deleteOrder", Long.class);

        invokeMethod(orderService, method, 10L);
    }
}

该示例只演示了“注解加反射”的基本思路。真实项目中的用户身份、角色、权限集合和异常处理通常由安全框架统一管理。

使用注解映射数据库表

整体思路

可以使用类级注解记录表名,使用字段级注解记录列名,再通过反射生成 SQL 并完成对象映射。

表名注解

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 Table {
    String value();
}

字段注解

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

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Column {
    String value();

    boolean primaryKey() default false;
}

实体类绑定注解

@Table("book")
public class MyBook {
    @Column(value = "id", primaryKey = true)
    private Integer bookId;

    @Column("isbn")
    private Integer isbn;

    @Column("name")
    private String name;

    @Column("price")
    private Integer price;

    public MyBook() {
    }

    public MyBook(Integer bookId, Integer isbn, String name, Integer price) {
        this.bookId = bookId;
        this.isbn = isbn;
        this.name = name;
        this.price = price;
    }

    @Override
    public String toString() {
        return "MyBook{" +
                "bookId=" + bookId +
                ", isbn=" + isbn +
                ", name='" + name + '\'' +
                ", price=" + price +
                '}';
    }
}

JDBC 连接工具

连接对象不应作为全局单例长期复用。每次数据库操作获取连接,并在操作结束后关闭资源,更利于并发使用和异常恢复。

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;

public final class DBUtil {
    private static final String URL =
            "jdbc:mysql://localhost:3306/java2506?serverTimezone=UTC&characterEncoding=utf8";
    private static final String USERNAME = "root";
    private static final String PASSWORD = "1234";

    private DBUtil() {
    }

    static {
        try {
            Class.forName("com.mysql.cj.jdbc.Driver");
        } catch (ClassNotFoundException e) {
            throw new ExceptionInInitializerError(e);
        }
    }

    public static Connection getConnection() throws SQLException {
        return DriverManager.getConnection(URL, USERNAME, PASSWORD);
    }
}

注解解析与通用 JDBC 工具

新增对象

import java.lang.reflect.Field;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;

public class AnnotationJdbcUtil {
    public static int insert(Object entity) {
        Class<?> entityClass = entity.getClass();
        Table table = entityClass.getAnnotation(Table.class);

        if (table == null) {
            throw new IllegalArgumentException("实体类缺少 @Table 注解");
        }

        List<String> columns = new ArrayList<>();
        List<Object> values = new ArrayList<>();

        for (Field field : entityClass.getDeclaredFields()) {
            Column column = field.getAnnotation(Column.class);

            if (column == null) {
                continue;
            }

            field.setAccessible(true);
            columns.add(column.value());

            try {
                values.add(field.get(entity));
            } catch (IllegalAccessException e) {
                throw new RuntimeException("读取字段失败:" + field.getName(), e);
            }
        }

        String placeholders = String.join(",", java.util.Collections.nCopies(
                columns.size(),
                "?"
        ));

        String sql = "insert into " + table.value() +
                "(" + String.join(",", columns) + ") values(" +
                placeholders + ")";

        try (Connection connection = DBUtil.getConnection();
             PreparedStatement statement = connection.prepareStatement(sql)) {

            for (int index = 0; index < values.size(); index++) {
                statement.setObject(index + 1, values.get(index));
            }

            return statement.executeUpdate();
        } catch (SQLException e) {
            throw new RuntimeException("执行新增操作失败", e);
        }
    }
}

根据主键查询对象

import java.lang.reflect.Field;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;

public class AnnotationJdbcQueryUtil {
    public static <T> T getById(Class<T> entityClass, Object id) {
        Table table = entityClass.getAnnotation(Table.class);

        if (table == null) {
            throw new IllegalArgumentException("实体类缺少 @Table 注解");
        }

        List<Field> mappedFields = new ArrayList<>();
        List<String> columnNames = new ArrayList<>();
        String primaryKeyColumn = null;

        for (Field field : entityClass.getDeclaredFields()) {
            Column column = field.getAnnotation(Column.class);

            if (column == null) {
                continue;
            }

            mappedFields.add(field);
            columnNames.add(column.value());

            if (column.primaryKey()) {
                primaryKeyColumn = column.value();
            }
        }

        if (primaryKeyColumn == null) {
            throw new IllegalArgumentException("实体类未标记主键字段");
        }

        String sql = "select " + String.join(",", columnNames) +
                " from " + table.value() +
                " where " + primaryKeyColumn + " = ?";

        try (Connection connection = DBUtil.getConnection();
             PreparedStatement statement = connection.prepareStatement(sql)) {

            statement.setObject(1, id);

            try (ResultSet resultSet = statement.executeQuery()) {
                if (!resultSet.next()) {
                    return null;
                }

                T entity = entityClass.getDeclaredConstructor().newInstance();

                for (Field field : mappedFields) {
                    Column column = field.getAnnotation(Column.class);
                    field.setAccessible(true);
                    field.set(entity, resultSet.getObject(column.value()));
                }

                return entity;
            }
        } catch (SQLException | ReflectiveOperationException e) {
            throw new RuntimeException("查询对象失败", e);
        }
    }
}

测试调用

public class JdbcAnnotationTest {
    public static void main(String[] args) {
        MyBook book = new MyBook(
                6,
                1006,
                "Java 反射与注解程序设计",
                66
        );

        int affectedRows = AnnotationJdbcUtil.insert(book);
        System.out.println("受影响行数:" + affectedRows);

        MyBook result = AnnotationJdbcQueryUtil.getById(MyBook.class, 6);
        System.out.println("查询结果:" + result);
    }
}

使用注意事项

  • 注解需要配合解析程序才能产生实际功能。
  • 运行时通过反射读取的注解必须使用 RetentionPolicy.RUNTIME
  • 表名和列名不能通过 PreparedStatement 的占位符传入,因此映射值必须来自可信注解,不能直接使用用户输入。
  • 通用映射工具还需要处理类型转换、空值、继承字段、自增主键和事务等问题。
  • 生产项目通常使用成熟的持久层框架,不建议重复实现完整 ORM 功能。

喜欢的话,留下你的评论吧~

... 访问量
© 2026 跨越星轨的客 @Hoshiumi
Powered by theme astro-koharu · Inspired by Shoka