引言
注解是 Java 中给代码'贴标签'的机制,本身不直接影响执行,但能通过工具或框架赋予额外含义。自定义注解结合反射可实现日志记录、权限校验等动态逻辑。
介绍 Java 注解与反射机制。首先讲解自定义注解的定义方式及属性设置,随后阐述四大元注解(@Target、@Retention 等)的作用范围与生命周期。通过权限校验实战案例,演示如何结合反射获取注解信息并执行逻辑。最后总结注解在日志、ORM、AOP 等框架场景中的应用,帮助开发者掌握声明式编程的核心技术。

注解是 Java 中给代码'贴标签'的机制,本身不直接影响执行,但能通过工具或框架赋予额外含义。自定义注解结合反射可实现日志记录、权限校验等动态逻辑。
自定义注解使用 @interface 关键字定义,本质上是一种特殊的接口(编译后会生成继承 java.lang.annotation.Annotation 的接口)。
// 定义一个空注解
public @interface MyFirstAnnotation { }
这个注解没有任何属性,仅作为标记使用。可以直接标注在类、方法等元素上:
@MyFirstAnnotation
public class Demo {
@MyFirstAnnotation
public void test() {}
}
注解可以包含'属性'(类似接口的抽象方法),使用时需要为属性赋值(除非有默认值)。
public @interface UserInfo {
// 字符串属性
String name();
// 整数属性,带默认值
int age() default 18;
// 数组属性
String[] hobbies() default {"coding"};
}
使用时的语法(属性名 = 值):
@UserInfo(name = "张三", age = 20, hobbies = {"篮球", "游戏"})
public class Person {}
💡 特殊规则:
若属性名是 value,且只有这一个属性需要赋值,可省略属性名:@MyAnnotation("test") 数组属性若只有一个元素,可省略大括号:hobbies = "足球"
元注解是用于修饰注解的注解,规定了自定义注解的使用范围、生命周期等特性。Java 内置了 4 种元注解:@Target、@Retention、@Documented、@Inherited。
@Target 限制注解可标注的目标(如类、方法、字段等),参数是 ElementType 枚举数组,常用值:
| ElementType | 作用范围 |
|---|---|
| TYPE | 类、接口、枚举 |
| METHOD | 方法 |
| FIELD | 成员变量(包括枚举常量) |
| PARAMETER | 方法参数 |
| CONSTRUCTOR | 构造方法 |
| LOCAL_VARIABLE | 局部变量 |
示例:限制注解只能用于类和方法
import java.lang.annotation.Target;
import java.lang.annotation.ElementType;
@Target({ElementType.TYPE, ElementType.METHOD}) // 可修饰类和方法
public @interface Log { }
如果把 @Log 标注在字段上,编译器会直接报错:
public class Demo {
@Log // 编译错误:@Log 不适用于字段
private String name;
}
@Retention 决定注解保留到哪个阶段(源码、字节码、运行时),参数是 RetentionPolicy 枚举,必须指定:
| RetentionPolicy | 生命周期说明 | 能否被反射获取 |
|---|---|---|
| SOURCE | 仅存在于源码中,编译后丢弃(如@Override) | 不能 |
| CLASS | 保留到字节码中,但 JVM 运行时不加载(默认值) | 不能 |
| RUNTIME | 保留到运行时,JVM 加载,可通过反射获取 | 能 |
示例:让注解在运行时可被反射获取
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@Retention(RetentionPolicy.RUNTIME) // 关键:保留到运行时
public @interface Permission {
String value();
}
💡 为什么 RUNTIME 重要?反射是在程序运行时动态获取类信息的机制,只有 RUNTIME 级别的注解才能被反射读取,这是注解与反射结合的核心前提。
默认情况下,javadoc 生成的文档不会包含注解信息。@Documented 修饰的注解会被包含在文档中。
示例:
import java.lang.annotation.Documented;
@Documented // 生成文档时包含该注解
public @interface Description {
String value();
}
/**
* 测试类
* @Description 这是一个测试类
*/
@Description("测试类")
public class Test {}
生成的 javadoc 中,Test 类的文档会显示 @Description("测试类")。
@Inherited 表示注解具有继承性:如果父类被该注解标注,子类会自动继承该注解(仅对类注解有效,方法 / 字段注解不继承)。
示例:
import java.lang.annotation.Inherited;
@Inherited // 允许继承
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface InheritedAnnotation {}
// 父类标注注解
@InheritedAnnotation
class Parent {}
// 子类未标注,但会继承父类的@InheritedAnnotation
class Child extends Parent {}
通过反射验证:
public class Test {
public static void main(String[] args) {
System.out.println(Child.class.isAnnotationPresent(InheritedAnnotation.class)); // 输出:true
}
}
注解本身只是标记,必须通过反射获取注解信息并执行逻辑,才能真正发挥作用。反射提供了以下核心方法(在 Class、Method、Field 等类中):
| 方法 | 作用 |
|---|---|
| getAnnotation(Class) | 获取指定类型的注解实例 |
| getAnnotations() | 获取所有注解(包括继承的) |
| isAnnotationPresent(Class) | 判断是否存在指定注解 |
需求:定义 @RequiresPermission 注解,标记方法需要的权限;通过反射调用方法前检查当前用户是否有权限,无权限则抛出异常。
import java.lang.annotation.*;
@Target(ElementType.METHOD) // 仅用于方法
@Retention(RetentionPolicy.RUNTIME) // 运行时可反射获取
public @interface RequiresPermission {
String[] value(); // 所需权限列表
}
public class UserService {
// 需要"user:query"权限
@RequiresPermission("user:query")
public void queryUser() {
System.out.println("查询用户成功");
}
// 需要"user:add"或"admin"权限
@RequiresPermission({"user:add", "admin"})
public void addUser() {
System.out.println("新增用户成功");
}
}
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
public class PermissionChecker {
// 模拟当前用户拥有的权限
private static final Set<String> CURRENT_USER_PERMISSIONS = new HashSet<>(Arrays.asList("user:query"));
// 反射调用方法并校验权限
public static void invokeWithCheck(Object obj, String methodName) throws Exception {
// 1. 获取方法对象
Method method = obj.getClass().getMethod(methodName);
// 2. 检查方法是否有@RequiresPermission 注解
if (method.isAnnotationPresent(RequiresPermission.class)) {
// 3. 获取注解实例
RequiresPermission annotation = method.getAnnotation(RequiresPermission.class);
// 4. 获取注解的权限列表
String[] requiredPermissions = annotation.value();
// 5. 校验权限
boolean hasPermission = false;
for (String permission : requiredPermissions) {
if (CURRENT_USER_PERMISSIONS.contains(permission)) {
hasPermission = true;
break;
}
}
if (!hasPermission) {
throw new SecurityException("权限不足,需要:" + Arrays.toString(requiredPermissions));
}
}
// 6. 权限通过,调用方法
method.invoke(obj);
}
public static void main(String[] args) throws Exception {
UserService service = new UserService();
invokeWithCheck(service, "queryUser"); // 成功:查询用户成功
invokeWithCheck(service, "addUser"); // 失败:抛出 SecurityException
}
}
查询用户成功
Exception in thread "main" java.lang.SecurityException: 权限不足,需要:[user:add, admin]
注解的本质:@interface 编译后会生成一个继承 java.lang.annotation.Annotation 的接口,例如:
// 编译后自动生成的代码(简化)
public interface MyAnnotation extends Annotation {
String value();
int age() default 18;
}
注解 + 反射的组合在框架中被广泛使用:
日志记录:通过注解标记需要记录日志的方法,反射拦截并打印日志(如 Spring 的@Log)。 ORM 映射:用注解关联 Java 类与数据库表(如 JPA 的@Entity、@Column)。 依赖注入:标记需要注入的对象(如 Spring 的@Autowired)。 AOP 切面:通过注解定义切入点(如 Spring 的@Before、@After)。 参数校验:验证方法参数合法性(如 Jakarta 的@NotNull、@Size)。
自定义注解是 Java 中'声明式编程'的核心体现,结合反射能极大简化代码逻辑、提高灵活性。掌握元注解的作用(尤其是@Target 和@Retention)是定义有效注解的前提,而反射则是让注解从'标记'变为'可执行逻辑'的桥梁。

微信公众号「极客日志」,在微信中扫描左侧二维码关注。展示文案:极客日志 zeeklog
查找任何按下的键的javascript键代码、代码、位置和修饰符。 在线工具,Keycode 信息在线工具,online
JavaScript 字符串转义/反转义;Java 风格 \uXXXX(Native2Ascii)编码与解码。 在线工具,Escape 与 Native 编解码在线工具,online
使用 Prettier 在浏览器内格式化 JavaScript 或 HTML 片段。 在线工具,JavaScript / HTML 格式化在线工具,online
Terser 压缩、变量名混淆,或 javascript-obfuscator 高强度混淆(体积会增大)。 在线工具,JavaScript 压缩与混淆在线工具,online
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online