开发者

Java 注解与反射实战之自定义注解从入门到精通

目录
  • 前言:注解到底是什么?
  • 一、自定义注解基础:@interface关键字
    • 1.1 最简单的自定义注解
    • 1.2 带属性的注解
  • 二、元注解:注解的 "注解"
    • 2.1@Target:指定注解能修饰哪些元素
    • 2.2@Retention:指定注解的生命周期
    • 2.3@Documented:让注解出现在 API 文档中
    • 2.4@Inherited:让注解可被继承
  • 三、注解 + 反射:让注解 "生效"
    • 实战案例:用注解实现方法权限校验
      • 步骤 1:定义注解
      • 步骤 2:使用注解标注方法
      • 步骤 3:反射 + 注解实现权限校验
    • 执行结果:
    • 四、底层原理:注解本质与反射获取机制
      • 五、应用场景总结
        • 结语

          前言:注解到底是什么?

                  你是否经常在 Java 代码中看到@Override@Deprecated这样的标记?这些就是注解 —— 一种给代码 "贴标签" 的机制。注解本身不直接影响代码执行,但能通过工具(如编译器)或框架(如 Spring)赋予代码额外含义。

               &nbspythonp;  自定义注解则是python让我们根据业务需求创建专属 "标签",结合反射机制能实现强大的动态逻辑(比如日志记录、权限校验、ORM 映射等)。本文将从基础到实战,带你掌握自定义注解的定义、元注解的作用,以及如何通过反射让注解 "生效"。

          一、自定义注解基础:@interface关键字

                  自定义注解使用 @interface 关键字定义,本质上是一种特殊的接口(编译后会生成继承 java.lang.annotation.Annotation 的接口)。

          1.1 最简单的自定义注解

          // 定义一个空注解
          public @interface MyFirstAnnotation {
          }
          

          这个注解没有任何属性,仅作为标记使用。可以直接标注在类、方法等元素上:

          @MyFirstAnnotation
          public class Demo {
              @MyFirstAnnotation
              public void test() {}
          }
          

          1.2 带属性的注解

          注解可以包含 "属性"(类似接口的抽象方法),使用时需要为属性赋值(除非有默认值)。

          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

          2.1@Target:指定注解能修饰哪些元素

             @Target 限制注解可标注的目标(如类、方法、字段等),参数是 ElementType 枚举数组,常用值:

          ElementType作用范围
          TYPE类、接口、枚举
          METHOD方法
          FIELD成员变量(包括枚举常量)
          PARAMETER方法参数
          CONSTRUCTOR构造方法
          LOCAL_VARIAhttp://www.devze.comBLE局部变量

          示例:限制注解只能用于类和方法

          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;
          }
          

          图示:@Target 的作用范围限制

          Java 注解与反射实战之自定义注解从入门到精通

          2.2@Retention:指定注解的生命周期

          @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 级别的注解才能被反射读取,这是注解与反射结合的核心前提。

          图示:注解的生命周期流程

          Java 注解与反射实战之自定义注解从入门到精通

          2.3@Documented:让注解出现在 API 文档中

                  默认情况下,javadoc 生成的文档不会包含注解信息。@Documented 修饰的注解会被包含在文档中。

          示例

          import java.lang.apythonnnotation.Documented;
          @Documented // 生成文档时包含该注解
          public @interface Description {
              String value();
          }
          /**
           * 测试类
           * @Description 这是一个测试类
           */
          @Description("测试类")
          public class Test {}

          生成的 javadoc 中,Test 类的文档会显示 @Description("测试类")

          2.4@Inherited:让注解可被继承

          @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
              }
          }
          

          图示:@Inherited 的继承效果

          Java 注解与反射实战之自定义注解从入门到精通

          三、注解 + 反射:让注解 "生效"

                  注解本身只是标记,必须通过反射获取注解信息并执行逻辑,才能真正发挥作用。反射提供了以下核心方法(在 ClassMethodField 等类中):

          方法作用
          getAnnotation(Class)获取指定类型的注解实例
          getAnnotations()获取所有注解(包括继承的)
          isAnnotationPresent(Class)判断是否存在指定注解

          实战案例:用注解实现方法权限校验

          需求:定义 @RequiresPermission 注解,标记方法需要的权限;通过反射调用方法前检查当前用户是否有权限,无权限则抛出异常。

          步骤 1:定义注解

          import java.lang.annotation.*;
          @Target(ElementType.METHOD) // 仅用于方法
          @Retention(RetentionPolicy.RUNTIME) // 运行时可反射获取
          public @interface RequiresPermission {
              String[] value(); // 所需权限列表
          }

          步骤 2:使用注解标注方法

          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("新增用户成功");
              }
          }

          步骤 3:反射 + 注解实现权限校验

          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;
          }
          
          • 注解实例的生成:当 JVM 加载被注解的类时,会通过动态代理生成注解接口的实现类实例(保存注解属性值)。
          • 反射获取注解的过程:反射通过 getAnnotation() 方法从类 / 方法的元数据中获取代理实例,从而读取属性值。

          五、应用场景总结

          注解 + 反射的组合在框架中被广泛使用:

          • 日志记录:通过注解标记需要记录日志的方法,反射拦截并打印日志(如 Spring 的@Log)。
          • ORM 映射:用注解关联 Java 类与数据库表(如 JPA 的@Entity@Column)。
          • 依赖注入:标记需要注入的对象(如 Spring 的@Autowired)。
          • AOP 切面:通过注解定义切入点(如 Spring 的@Before@After)。
          • 参数校验:验证方法参数合法性(如 Jakarta 的@NotNull@Size)。

          结语

                  自定义注解是 Java 中 "声明式编程" 的核心体现,结合反射能极大简化代码逻辑、提高灵活性。掌握元注解的作用(尤其是@Target@Retention)是定义有效注解的前提,而反射则是让注解从 "标记" 变为 "可执行逻辑" 的桥梁。

          尝试在项目中用注解解决重复逻辑(如日志、权限),你会感受到它的强大!

          到此这篇关于Java 注解与反射实战之自定义注解从入门到精通的文章就介绍到这了,更多相关Java 注解与反射内容请搜索编程客栈(www.devze.com)以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程客栈(www.devze.com)!

          0

          上一篇:

          下一篇:

          精彩评论

          暂无评论...
          验证码 换一张
          取 消

          最新开发

          开发排行榜