Java Annotation(注解)
注解概述
注解是 Java 5 引入的一种元数据机制。它可以为类、方法、字段、构造器、参数等程序元素添加说明或配置信息。
注解本身通常不直接执行业务逻辑,而是由编译器、JVM、反射代码或框架读取并处理。常见注解包括 @Override、@Deprecated 和 @SuppressWarnings。
注解的本质
自定义注解使用 @interface 声明。编译后,注解类型本质上是继承了 java.lang.annotation.Annotation 接口的特殊接口。
public @interface MyAnnotation {
String value() default "";
}
注解中的成员声明形式类似无参数方法。成员类型只能使用基本类型、String、Class、枚举、其他注解,以及这些类型的一维数组。
元注解
元注解是用于修饰注解类型的注解。
@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 功能。
喜欢的话,留下你的评论吧~