欢迎光临
我们一直在努力

JAVA 注解(Annotation):从原理到实战应用

JAVA 注解(Annotation):从原理到实战应用

在这里插入图片描述

1.1 本章学习目标与重点

💡 掌握注解的核心概念与分类,理解注解在Java开发中的核心价值。
💡 熟练使用JDK内置注解,掌握自定义注解的定义、解析与使用流程。
💡 掌握注解的元注解配置方式,理解不同元注解对自定义注解的约束作用。
💡 结合反射机制实现注解的实战应用,掌握注解在框架开发中的核心用法。
⚠️ 本章重点是 自定义注解的开发流程 和 注解与反射结合的实战应用,这是Java高级开发与框架设计的必备技能。

1.2 注解的核心概念与价值

1.2.1 什么是注解

💡 注解(Annotation) 是Java 5引入的一种特殊标记,它可以在编译期、类加载期、运行时被读取,并执行相应的处理逻辑。注解本身不直接影响代码的执行逻辑,而是通过元数据的方式为程序提供额外信息,这些信息可以被编译器、虚拟机或自定义的注解处理器解析和使用。

注解的本质是一个继承了 java.lang.annotation.Annotation 接口的特殊接口,我们定义的每一个注解,最终都会被编译器生成对应的接口实现类,供程序在运行时调用。

1.2.2 注解的核心价值

在传统Java开发中,我们需要通过配置文件(如XML)来描述程序的元数据信息,而注解的出现,让元数据可以直接嵌入到代码中,实现了代码与配置的一体化。注解的核心价值主要体现在以下几个方面:

  • 简化配置:替代XML等外部配置文件,减少配置文件的数量,降低维护成本。
  • 增强代码可读性:元数据与代码紧密结合,开发者可以直接在代码中看到配置信息。
  • 编译期检查:部分注解可以在编译期进行语法检查,提前发现潜在问题。
  • 框架开发基石:是Spring、MyBatis等主流框架的核心技术,用于实现依赖注入、AOP、ORM映射等功能。
  • 1.2.3 注解的分类

    根据注解的使用范围和生命周期,我们可以将注解分为以下三类:

    分类标准具体类型特点典型示例
    JDK内置注解 基本注解 用于修饰代码元素(类、方法、属性等) @Override、@Deprecated、@SuppressWarnings
    元注解 注解的注解 用于修饰自定义注解,约束注解的使用范围和生命周期 @Target、@Retention、@Documented、@Inherited
    自定义注解 业务注解 开发者根据业务需求自行定义的注解,用于实现特定功能 @RequestMapping、@Autowired、@Test

    ✅ 核心结论:注解是一种元数据标记,本身不执行任何逻辑,需要通过注解处理器(如反射、APT工具)解析后,才能发挥实际作用。

    1.3 JDK内置注解详解

    JDK提供了三个最基础的内置注解,用于辅助代码开发,这三个注解也是我们日常开发中使用频率最高的注解。

    1.3.1 @Override:重写检查注解

    💡 作用:用于标记方法是重写自父类或接口的方法,编译器会检查该方法的签名是否与父类完全一致,如果不一致则编译报错。
    ⚠️ 注意事项:该注解只能修饰方法,且只能用于重写场景,不能用于重载。

    代码实操

    /**
    * @Override 注解使用示例
    */

    class ParentClass {
    public void sayHello() {
    System.out.println("父类的sayHello方法");
    }
    }

    class ChildClass extends ParentClass {
    // 使用@Override标记该方法是重写自父类
    @Override
    public void sayHello() {
    System.out.println("子类重写的sayHello方法");
    }

    // 错误示例:方法签名与父类不一致,编译报错
    // @Override
    // public void sayHello(String name) {
    // System.out.println("Hello " + name);
    // }
    }

    public class OverrideDemo {
    public static void main(String[] args) {
    ParentClass child = new ChildClass();
    child.sayHello(); // 输出:子类重写的sayHello方法
    }
    }

    1.3.2 @Deprecated:过时标记注解

    💡 作用:用于标记类、方法、属性等元素已过时,不推荐使用。编译器会对使用该元素的代码发出警告,提示开发者更换新的API。
    ⚠️ 注意事项:该注解标记的元素仍然可以使用,但存在被移除的风险,建议开发者使用替代方案。

    代码实操

    /**
    * @Deprecated 注解使用示例
    */

    public class DeprecatedDemo {
    // 标记该方法已过时
    @Deprecated
    public static void oldMethod() {
    System.out.println("这个方法已经过时了,请使用newMethod替代");
    }

    // 新的替代方法
    public static void newMethod() {
    System.out.println("这是新的推荐方法");
    }

    public static void main(String[] args) {
    // 使用过时方法,编译器会发出警告
    oldMethod();
    // 使用推荐方法
    newMethod();
    }
    }

    1.3.3 @SuppressWarnings:抑制警告注解

    💡 作用:用于抑制编译器产生的特定类型警告,让代码编译时不再输出警告信息。
    ⚠️ 注意事项:该注解需要传入参数,指定要抑制的警告类型,常用参数有 unchecked(抑制未检查转换警告)、deprecation(抑制过时元素警告)、all(抑制所有警告)。

    代码实操

    import java.util.ArrayList;
    import java.util.List;

    /**
    * @SuppressWarnings 注解使用示例
    */

    public class SuppressWarningsDemo {
    public static void main(String[] args) {
    // 抑制未检查转换警告
    @SuppressWarnings("unchecked")
    List<String> list = new ArrayList(); // 原始类型ArrayList,默认会产生警告
    list.add("Hello Annotation");
    System.out.println(list.get(0));

    // 抑制过时方法警告
    @SuppressWarnings("deprecation")
    void test() {
    DeprecatedDemo.oldMethod(); // 调用过时方法,不会产生警告
    }
    test();
    }
    }

    ✅ 核心结论:JDK内置注解主要用于代码检查和提示,帮助开发者写出更规范的代码,提升代码质量。

    1.4 元注解详解

    元注解是用于修饰自定义注解的注解,JDK提供了四个标准元注解,它们分别从不同维度约束自定义注解的行为。

    1.4.1 @Target:注解目标约束

    💡 作用:指定自定义注解可以修饰的代码元素类型,如类、方法、属性等。
    ⚠️ 注意事项:如果不指定 @Target,则该注解可以修饰所有类型的代码元素。

    @Target 的参数是一个 ElementType 枚举数组,常用枚举值如下:

    枚举值可修饰的代码元素
    TYPE 类、接口、枚举
    METHOD 方法
    FIELD 属性(成员变量)
    PARAMETER 方法参数
    CONSTRUCTOR 构造方法
    LOCAL_VARIABLE 局部变量
    ANNOTATION_TYPE 注解

    1.4.2 @Retention:注解生命周期约束

    💡 作用:指定注解的生命周期,即注解在哪个阶段有效。
    ⚠️ 注意事项:这是自定义注解必须指定的元注解,否则注解默认在编译期就会被丢弃,无法在运行时通过反射获取。

    @Retention 的参数是一个 RetentionPolicy 枚举值,三种枚举值对应注解的三个生命周期:

    枚举值生命周期特点适用场景
    SOURCE 源码阶段 注解仅存在于源码中,编译成字节码后被丢弃 编译器检查,如 @Override
    CLASS 字节码阶段 注解存在于源码和字节码中,类加载时被丢弃 类加载器处理,如字节码增强
    RUNTIME 运行时阶段 注解存在于源码、字节码和运行时,可通过反射获取 框架开发,如Spring注解

    1.4.3 @Documented:文档生成标记

    💡 作用:指定该注解会被 javadoc 工具提取到API文档中。
    ⚠️ 注意事项:默认情况下,注解不会出现在API文档中,添加该元注解后,注解信息会被包含在文档里。

    1.4.4 @Inherited:注解继承标记

    💡 作用:指定该注解可以被子类继承,如果父类使用了该注解,子类会自动继承该注解。
    ⚠️ 注意事项:该注解仅对类级别注解有效,对方法、属性等注解无效。

    代码实操:元注解使用示例

    import java.lang.annotation.*;

    /**
    * 自定义注解 + 元注解使用示例
    * 该注解用于标记类的作者信息
    */

    // 该注解只能修饰类
    @Target(ElementType.TYPE)
    // 注解在运行时有效,可通过反射获取
    @Retention(RetentionPolicy.RUNTIME)
    // 注解信息会被javadoc提取
    @Documented
    // 注解可以被子类继承
    @Inherited
    public @interface Author {
    // 注解属性:作者姓名,必填
    String name();
    // 注解属性:作者邮箱,可选,默认值为空字符串
    String email() default "";
    }

    ✅ 核心结论:元注解是自定义注解的规则约束器,通过元注解可以精准控制自定义注解的使用范围和生命周期。

    1.5 自定义注解的开发流程

    自定义注解是注解开发的核心,掌握自定义注解的定义、使用和解析流程,是实现注解驱动开发的关键。

    1.5.1 步骤1:定义自定义注解

    定义自定义注解的语法格式与定义接口类似,只是在 interface 关键字前加了一个 @ 符号。注解中可以定义属性,属性的定义方式与接口方法类似,支持指定默认值。

    语法格式

    [元注解]
    public @interface 注解名称 {
    // 注解属性定义
    属性类型 属性名() [default 默认值];
    }

    代码实操:定义两个常用自定义注解

    import java.lang.annotation.*;

    /**
    * 注解1:用于标记业务日志,记录方法的操作信息
    */

    @Target(ElementType.METHOD) // 仅修饰方法
    @Retention(RetentionPolicy.RUNTIME) // 运行时有效
    public @interface Log {
    // 操作描述
    String desc();
    // 操作类型,默认值为"OTHER"
    String type() default "OTHER";
    }

    /**
    * 注解2:用于标记数据校验,校验方法参数的合法性
    */

    @Target(ElementType.PARAMETER) // 仅修饰方法参数
    @Retention(RetentionPolicy.RUNTIME) // 运行时有效
    public @interface Validate {
    // 参数不能为空
    boolean notNull() default true;
    // 参数长度最小值,默认值为0
    int minLength() default 0;
    }

    1.5.2 步骤2:使用自定义注解

    定义好自定义注解后,就可以在对应的代码元素上使用该注解,使用时需要为注解的必填属性赋值,可选属性可以使用默认值。

    代码实操:使用自定义注解

    /**
    * 自定义注解使用示例:用户服务类
    */

    public class UserService {
    // 使用@Log注解标记方法,记录操作日志
    @Log(desc = "根据ID查询用户", type = "QUERY")
    public String getUserById(
    // 使用@Validate注解标记参数,校验参数合法性
    @Validate(notNull = true, minLength = 1) String userId) {
    if (userId == null || userId.length() < 1) {
    return "参数不合法";
    }
    return "用户ID:" + userId + ",用户名:张三";
    }

    @Log(desc = "添加用户", type = "ADD")
    public String addUser(
    @Validate(notNull = true, minLength = 2) String username) {
    if (username == null || username.length() < 2) {
    return "用户名长度不能小于2";
    }
    return "添加用户成功:" + username;
    }
    }

    1.5.3 步骤3:解析自定义注解

    注解本身不会执行任何逻辑,必须通过注解解析器来读取注解信息,并执行相应的处理逻辑。在Java中,最常用的注解解析方式是反射机制。

    解析流程

    ① 获取目标类的 Class 对象。
    ② 获取目标方法或参数的注解对象。
    ③ 读取注解的属性值,执行自定义业务逻辑。

    代码实操:反射解析自定义注解

    import java.lang.reflect.Method;
    import java.lang.reflect.Parameter;

    /**
    * 注解解析器:解析@Log和@Validate注解
    */

    public class AnnotationParser {
    /**
    * 解析@Log注解,输出方法操作日志
    */

    public static void parseLogAnnotation(Class<?> clazz) {
    // 获取类中所有方法
    Method[] methods = clazz.getDeclaredMethods();
    for (Method method : methods) {
    // 判断方法是否有@Log注解
    if (method.isAnnotationPresent(Log.class)) {
    Log log = method.getAnnotation(Log.class);
    System.out.println("===== 方法操作日志 =====");
    System.out.println("方法名:" + method.getName());
    System.out.println("操作描述:" + log.desc());
    System.out.println("操作类型:" + log.type());
    System.out.println("———————–");
    }
    }
    }

    /**
    * 解析@Validate注解,校验方法参数合法性
    */

    public static Object invokeAndValidate(Object obj, String methodName, Object... args) throws Exception {
    // 获取目标方法
    Method method = obj.getClass().getDeclaredMethod(methodName, getParameterTypes(args));
    Parameter[] parameters = method.getParameters();

    // 校验参数
    for (int i = 0; i < parameters.length; i++) {
    Parameter param = parameters[i];
    if (param.isAnnotationPresent(Validate.class)) {
    Validate validate = param.getAnnotation(Validate.class);
    Object arg = args[i];
    String paramName = param.getName();

    // 非空校验
    if (validate.notNull() && arg == null) {
    throw new IllegalArgumentException(paramName + " 不能为null");
    }

    // 长度校验
    if (arg instanceof String && ((String) arg).length() < validate.minLength()) {
    throw new IllegalArgumentException(paramName + " 长度不能小于" + validate.minLength());
    }
    }
    }

    // 调用目标方法
    return method.invoke(obj, args);
    }

    // 获取参数类型数组
    private static Class<?>[] getParameterTypes(Object... args) {
    if (args == null || args.length == 0) {
    return new Class[0];
    }
    Class<?>[] types = new Class[args.length];
    for (int i = 0; i < args.length; i++) {
    types[i] = args[i].getClass();
    }
    return types;
    }

    // 测试方法
    public static void main(String[] args) throws Exception {
    UserService userService = new UserService();

    // 解析@Log注解
    parseLogAnnotation(UserService.class);

    // 解析@Validate注解并调用方法
    System.out.println("正常调用:" + invokeAndValidate(userService, "getUserById", "1001"));
    System.out.println("参数非法调用:" + invokeAndValidate(userService, "getUserById", ""));
    }
    }

    输出结果

    ===== 方法操作日志 =====
    方法名:getUserById
    操作描述:根据ID查询用户
    操作类型:QUERY
    ———————–
    ===== 方法操作日志 =====
    方法名:addUser
    操作描述:添加用户
    操作类型:ADD
    ———————–
    正常调用:用户ID:1001,用户名:张三
    Exception in thread "main" java.lang.IllegalArgumentException: arg0 长度不能小于1

    ✅ 核心结论:自定义注解的开发流程是 定义 → 使用 → 解析,其中解析是核心步骤,反射是最常用的解析方式。

    1.6 注解的高级应用场景

    注解在Java开发中应用广泛,尤其是在框架开发和业务系统设计中,以下是几个典型的高级应用场景。

    1.6.1 场景1:基于注解的AOP实现

    💡 核心思想:通过注解标记需要增强的方法,结合动态代理实现AOP(面向切面编程),在方法执行前后添加通用逻辑(如日志、事务、权限校验)。

    代码实操:注解+动态代理实现日志切面

    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Method;
    import java.lang.reflect.Proxy;

    /**
    * 基于注解的AOP日志切面
    */

    // 定义切面注解
    @Target(ElementType.METHOD)
    @Retention(RetentionPolicy.RUNTIME)
    @interface LogAspect {
    }

    // 业务接口
    interface OrderService {
    @LogAspect
    void createOrder(String orderId);
    }

    // 业务实现类
    class OrderServiceImpl implements OrderService {
    @Override
    public void createOrder(String orderId) {
    System.out.println("创建订单:" + orderId);
    }
    }

    // 日志切面处理器
    class LogAspectHandler implements InvocationHandler {
    private Object target;

    public LogAspectHandler(Object target) {
    this.target = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    // 方法执行前:判断是否有@LogAspect注解
    if (method.isAnnotationPresent(LogAspect.class)) {
    System.out.println("[前置日志] 方法 " + method.getName() + " 开始执行,参数:" + args[0]);
    }

    // 执行目标方法
    Object result = method.invoke(target, args);

    // 方法执行后
    if (method.isAnnotationPresent(LogAspect.class)) {
    System.out.println("[后置日志] 方法 " + method.getName() + " 执行完毕");
    }
    return result;
    }

    // 创建代理对象
    public static <T> T createProxy(T target) {
    return (T) Proxy.newProxyInstance(
    target.getClass().getClassLoader(),
    target.getClass().getInterfaces(),
    new LogAspectHandler(target)
    );
    }
    }

    // 测试类
    public class AnnotationAopDemo {
    public static void main(String[] args) {
    OrderService orderService = new OrderServiceImpl();
    OrderService proxy = LogAspectHandler.createProxy(orderService);
    proxy.createOrder("ORDER_20260129");
    }
    }

    输出结果

    [前置日志] 方法 createOrder 开始执行,参数:ORDER_20260129
    创建订单:ORDER_20260129
    [后置日志] 方法 createOrder 执行完毕

    1.6.2 场景2:基于注解的ORM映射

    💡 核心思想:通过注解标记实体类与数据库表的映射关系,结合反射实现自动生成SQL语句,简化数据库操作。

    代码实操:注解实现ORM映射

    import java.lang.annotation.*;

    /**
    * 基于注解的ORM映射示例
    */

    // 表映射注解
    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    @interface Table {
    String name();
    }

    // 字段映射注解
    @Target(ElementType.FIELD)
    @Retention(RetentionPolicy.RUNTIME)
    @interface Column {
    String name();
    String type() default "VARCHAR";
    int length() default 255;
    }

    // 实体类
    @Table(name = "t_user")
    class User {
    @Column(name = "user_id", type = "INT", length = 10)
    private Integer userId;

    @Column(name = "user_name", type = "VARCHAR", length = 50)
    private String userName;

    @Column(name = "age", type = "INT", length = 3)
    private Integer age;

    // 省略getter/setter方法
    public Integer getUserId() { return userId; }
    public void setUserId(Integer userId) { this.userId = userId; }
    public String getUserName() { return userName; }
    public void setUserName(String userName) { this.userName = userName; }
    public Integer getAge() { return age; }
    public void setAge(Integer age) { this.age = age; }
    }

    // ORM工具类:生成SQL语句
    class OrmUtil {
    public static String generateSelectSql(Class<?> clazz) {
    // 获取表名
    Table table = clazz.getAnnotation(Table.class);
    if (table == null) {
    throw new IllegalArgumentException("实体类未添加@Table注解");
    }
    String tableName = table.name();

    // 获取字段名
    StringBuilder columns = new StringBuilder();
    java.lang.reflect.Field[] fields = clazz.getDeclaredFields();
    for (java.lang.reflect.Field field : fields) {
    Column column = field.getAnnotation(Column.class);
    if (column != null) {
    columns.append(column.name()).append(",");
    }
    }
    // 移除最后一个逗号
    columns.deleteCharAt(columns.length() 1);

    // 生成查询SQL
    return "SELECT " + columns + " FROM " + tableName;
    }

    public static void main(String[] args) {
    System.out.println("生成的查询SQL:" + generateSelectSql(User.class));
    }
    }

    输出结果

    生成的查询SQL:SELECT user_id,user_name,age FROM t_user

    1.6.3 场景3:基于注解的单元测试

    💡 核心思想:通过注解标记测试方法,测试框架自动识别并执行这些方法,实现自动化测试。这正是JUnit框架的核心实现原理。

    代码实操:简化版JUnit框架

    import java.lang.annotation.*;
    import java.lang.reflect.Method;

    /**
    * 简化版JUnit框架:基于注解的单元测试
    */

    // 测试方法注解
    @Target(ElementType.METHOD)
    @Retention(RetentionPolicy.RUNTIME)
    @interface TestCase {
    }

    // 测试类
    class MathTest {
    @TestCase
    public void testAdd() {
    int result = 1 + 2;
    assert result == 3 : "加法测试失败";
    System.out.println("testAdd 测试通过");
    }

    @TestCase
    public void testMultiply() {
    int result = 2 * 3;
    assert result == 6 : "乘法测试失败";
    System.out.println("testMultiply 测试通过");
    }

    // 非测试方法,不会被执行
    public void normalMethod() {
    System.out.println("这是普通方法,不会被测试");
    }
    }

    // 测试运行器
    class TestRunner {
    public static void run(Class<?> testClass) throws Exception {
    Object obj = testClass.getDeclaredConstructor().newInstance();
    Method[] methods = testClass.getDeclaredMethods();

    int passCount = 0;
    int failCount = 0;

    for (Method method : methods) {
    if (method.isAnnotationPresent(TestCase.class)) {
    try {
    method.invoke(obj);
    passCount++;
    } catch (Exception e) {
    failCount++;
    System.out.println(method.getName() + " 测试失败:" + e.getCause().getMessage());
    }
    }
    }

    System.out.println("\\n===== 测试结果 ======");
    System.out.println("总测试数:" + (passCount + failCount));
    System.out.println("通过数:" + passCount);
    System.out.println("失败数:" + failCount);
    }

    public static void main(String[] args) throws Exception {
    TestRunner.run(MathTest.class);
    }
    }

    输出结果

    testAdd 测试通过
    testMultiply 测试通过

    ===== 测试结果 ======
    总测试数:2
    通过数:2
    失败数:0

    1.7 注解的优缺点与最佳实践

    1.7.1 注解的优点

  • 简化开发:减少冗余配置,提高开发效率。
  • 增强代码可读性:元数据与代码紧密结合,一目了然。
  • 提高代码灵活性:通过注解可以动态控制程序行为,无需修改核心代码。
  • 框架友好:是现代Java框架的标配,便于集成主流框架。
  • 1.7.2 注解的缺点

  • 侵入性强:注解直接嵌入代码,修改注解需要修改源码。
  • 可读性差:过多的注解会导致代码臃肿,影响阅读。
  • 性能损耗:运行时注解需要通过反射解析,存在一定的性能开销。
  • 调试困难:注解的逻辑在运行时执行,调试时难以跟踪。
  • 1.7.3 注解的最佳实践

  • 合理选择注解生命周期:仅在需要反射解析时使用 RUNTIME,否则使用 SOURCE 或 CLASS,减少性能损耗。
  • 避免过度使用注解:不要用注解替代所有配置,对于经常变化的配置,建议使用外部配置文件。
  • 做好注解文档:为自定义注解添加详细的JavaDoc注释,说明注解的作用和使用方式。
  • 结合设计模式使用:如结合工厂模式、代理模式,实现注解的灵活解析和扩展。
  • 优先使用成熟框架的注解:如Spring的 @Autowired、@RequestMapping,避免重复造轮子。
  • 1.8 实战案例:基于注解的权限校验框架

    1.8.1 需求分析

    💡 实现一个轻量级的权限校验框架,要求:

  • 通过注解标记需要权限校验的方法。
  • 支持指定方法所需的权限角色。
  • 在方法执行前自动校验用户权限,无权限则抛出异常。
  • 框架具备良好的扩展性,支持自定义权限校验逻辑。
  • 1.8.2 代码实现

    import java.lang.annotation.*;
    import java.lang.reflect.Method;
    import java.util.Arrays;
    import java.util.HashSet;
    import java.util.Set;

    /**
    * 基于注解的权限校验框架
    */

    // 权限校验注解
    @Target(ElementType.METHOD)
    @Retention(RetentionPolicy.RUNTIME)
    public @interface RequiresPermission {
    // 所需权限角色
    String[] value();
    }

    // 用户上下文类:存储当前登录用户信息
    class UserContext {
    private static ThreadLocal<String> currentUser = new ThreadLocal<>();
    private static Set<String> userRoles = new HashSet<>();

    // 设置当前用户
    public static void setCurrentUser(String username, String... roles) {
    currentUser.set(username);
    userRoles.clear();
    userRoles.addAll(Arrays.asList(roles));
    }

    // 获取当前用户角色
    public static Set<String> getUserRoles() {
    return userRoles;
    }

    // 清除当前用户
    public static void clear() {
    currentUser.remove();
    userRoles.clear();
    }
    }

    // 权限校验切面类
    class PermissionAspect {
    /**
    * 执行方法并校验权限
    */

    public static Object invoke(Object obj, String methodName, Object... args) throws Exception {
    Method method = obj.getClass().getDeclaredMethod(methodName, getParameterTypes(args));

    // 权限校验
    if (method.isAnnotationPresent(RequiresPermission.class)) {
    RequiresPermission annotation = method.getAnnotation(RequiresPermission.class);
    String[] requiredRoles = annotation.value();
    Set<String> userRoles = UserContext.getUserRoles();

    // 检查是否有匹配的角色
    boolean hasPermission = false;
    for (String role : requiredRoles) {
    if (userRoles.contains(role)) {
    hasPermission = true;
    break;
    }
    }

    if (!hasPermission) {
    throw new SecurityException("无权限访问该方法,所需角色:" + Arrays.toString(requiredRoles));
    }
    }

    // 执行目标方法
    return method.invoke(obj, args);
    }

    private static Class<?>[] getParameterTypes(Object... args) {
    if (args == null || args.length == 0) {
    return new Class[0];
    }
    Class<?>[] types = new Class[args.length];
    for (int i = 0; i < args.length; i++) {
    types[i] = args[i].getClass();
    }
    return types;
    }
    }

    // 业务服务类
    class AdminService {
    @RequiresPermission({"ADMIN", "SUPER_ADMIN"})
    public String deleteUser(String userId) {
    return "删除用户成功:" + userId;
    }

    @RequiresPermission({"USER", "ADMIN", "SUPER_ADMIN"})
    public String queryUser(String userId) {
    return "查询用户信息:" + userId;
    }
    }

    // 测试类
    public class PermissionFrameworkDemo {
    public static void main(String[] args) throws Exception {
    AdminService adminService = new AdminService();

    // 测试1:管理员用户,拥有ADMIN角色
    UserContext.setCurrentUser("admin", "ADMIN");
    System.out.println(PermissionAspect.invoke(adminService, "deleteUser", "1001"));
    System.out.println(PermissionAspect.invoke(adminService, "queryUser", "1001"));

    // 测试2:普通用户,只有USER角色
    UserContext.setCurrentUser("user", "USER");
    System.out.println(PermissionAspect.invoke(adminService, "queryUser", "1001"));
    try {
    PermissionAspect.invoke(adminService, "deleteUser", "1001");
    } catch (Exception e) {
    System.out.println(e.getCause().getMessage());
    }

    UserContext.clear();
    }
    }

    输出结果

    删除用户成功:1001
    查询用户信息:1001
    查询用户信息:1001
    无权限访问该方法,所需角色:[ADMIN, SUPER_ADMIN]

    1.8.3 案例总结

    ✅ 这个权限校验框架综合运用了注解定义、反射解析、线程本地存储等技术,核心亮点如下:

  • 使用 @RequiresPermission 注解标记需要权限校验的方法,配置简单。
  • 通过 UserContext 存储当前用户角色,支持多线程环境。
  • 利用反射解析注解,在方法执行前自动校验权限,无侵入性。
  • 框架扩展性强,可通过修改 PermissionAspect 类实现自定义权限校验逻辑。
  • 1.9 本章总结

  • 注解是Java的一种元数据标记,本身不执行逻辑,需要通过解析器(反射、APT)处理后才能发挥作用。
  • JDK内置注解用于代码检查,元注解用于约束自定义注解,自定义注解是业务开发的核心。
  • 自定义注解的开发流程是 定义 → 使用 → 解析,反射是最常用的运行时解析方式。
  • 注解是框架开发的基石,广泛应用于AOP、ORM、权限校验、单元测试等场景。
  • 注解的优点是简化配置、增强可读性,缺点是侵入性强、有性能损耗,使用时需平衡利弊。
  • 注解的最佳实践是“合理选择生命周期、避免过度使用、做好文档注释”,结合设计模式提升扩展性。
  • 赞(0)
    未经允许不得转载:171主机测评 » JAVA 注解(Annotation):从原理到实战应用
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址