还在写丑陋的反射?AnnotationUtils 带你解锁注解处理的优雅姿势

67 阅读3分钟

点击上方“程序员蜗牛g”,选择“设为星标”

跟蜗牛哥一起,每天进步一点点

程序员蜗牛g

大厂程序员一枚 跟蜗牛一起 每天进步一点点

33篇原创内容

**

公众号

Apache Commons Lang 提供了一个神器 —— AnnotationUtils

为什么选择 AnnotationUtils?

传统的注解处理方式通常像这样:

// 查找注解并获取属性MyAnnotation annotation = myClass.getAnnotation(MyAnnotation.class);if (annotation != null) {    String value = annotation.value();}
// 遍历父类寻找继承的注解Class<?> clazz = myClass;while (clazz != null) {    MyAnnotation ann = clazz.getAnnotation(MyAnnotation.class);    if (ann != null) {        break;    }    clazz = clazz.getSuperclass();}
// 比较两个注解boolean isSame = annotation.equals(anotherAnnotation); // 可能失败

图片

核心 API 使用详解

  1. 查找与获取注解

路径:/src/main/java/com/icoderoad/demo/annotation/AnnotationFinder.java

@RestController@RequestMapping("/api")public class MyController { }
// 支持继承查找RequestMapping mapping =    AnnotationUtils.findAnnotation(MyController.class, RequestMapping.class);

即便注解定义在父类上,也能轻松找到。 同样适用于 字段与方法

public class User {    @NotBlank(message = "用户名不能为空")    private String username;
    @Override    @Transactional    public String toString() {        return username;    }}
Field field = User.class.getDeclaredField("username");NotBlank notBlank = AnnotationUtils.getAnnotation(field, NotBlank.class);
Method method = User.class.getMethod("toString");Transactional tx = AnnotationUtils.getAnnotation(method, Transactional.class);

2. 注解属性访问

路径:/src/main/java/com/icoderoad/demo/annotation/AnnotationValueReader.java

@RestController@RequestMapping(value = "/api", name = "userApi")public class UserController { }
// 获取属性RequestMapping mapping =    AnnotationUtils.findAnnotation(UserController.class, RequestMapping.class);
String path = AnnotationUtils.getValue(mapping, "value");  // "/api"String name = AnnotationUtils.getValue(mapping, "name");   // "userApi"
// 检查默认值boolean isDefault = AnnotationUtils.isDefaultValue(mapping, "produces"); // true
// 获取所有属性Map<String, Object> attrs = AnnotationUtils.getAnnotationAttributes(mapping);// {value=["/api"], name="userApi", produces="", ...}

3. 注解比较与验证

路径:/src/main/java/com/icoderoad/demo/annotation/AnnotationComparer.java

@RequestMapping("/api")public class ControllerA {}
@RequestMapping("/api")public class ControllerB { }
// 普通比较可能失败Annotation ann1 = ControllerA.class.getAnnotation(RequestMapping.class);Annotation ann2 = ControllerB.class.getAnnotation(RequestMapping.class);
// AnnotationUtils 安全比较boolean equal = AnnotationUtils.equals(ann1, ann2); // trueint hash1 = AnnotationUtils.hashCode(ann1);int hash2 = AnnotationUtils.hashCode(ann2);

用于属性验证时:

@Validatedpublic class User {    @Size(min = 3, max = 20)    private String username;}
Size size = AnnotationUtils.getAnnotation(    User.class.getDeclaredField("username"), Size.class);
if (size != null) {    int min = (Integer) AnnotationUtils.getValue(size, "min");    int max = (Integer) AnnotationUtils.getValue(size, "max");}

实战场景演示

场景一:自定义权限注解

路径:/src/main/java/com/icoderoad/demo/security/PermissionAnnotationProcessor.java

@Retention(RetentionPolicy.RUNTIME)@Target(ElementType.METHOD)public @interface RequiresPermission {    String[] value();    Logical logical() default Logical.AND;}
@Componentpublic class PermissionAnnotationProcessor {    public boolean checkPermission(Method method, User user) {        RequiresPermission perm =            AnnotationUtils.findAnnotation(method, RequiresPermission.class);
        if (perm != null) {            String[] required = (String[]) AnnotationUtils.getValue(perm, "value");            Logical logic = (Logical) AnnotationUtils.getValue(perm, "logical");            return checkUserPermissions(user, required, logic);        }        return true;    }}

场景二:条件化配置

路径:/src/main/java/com/icoderoad/demo/config/ConfigurationProcessor.java

@Retention(RetentionPolicy.RUNTIME)@Target(ElementType.TYPE)public @interface ConditionalOnProperty {    String value();    String havingValue() default "true";}
public class ConfigurationProcessor {    private final Environment env;
    public ConfigurationProcessor(Environment env) {        this.env = env;    }
    public boolean shouldProcess(Class<?> configClass) {        ConditionalOnProperty condition =            AnnotationUtils.findAnnotation(configClass, ConditionalOnProperty.class);        if (condition != null) {            String key = (String) AnnotationUtils.getValue(condition, "value");            String expected = (String) AnnotationUtils.getValue(condition, "havingValue");            String actual = env.getProperty(key);            return expected.equals(actual);        }        return true;    }}

场景三:组合注解与元注解

路径:/src/main/java/com/icoderoad/demo/api/ApiScanner.java

@RestController@RequestMapping("/api/v1")@ResponseBody@CrossOrigin@Target(ElementType.TYPE)@Retention(RetentionPolicy.RUNTIME)public @interface RestApiEndpoint {    String value() default "";}
@RestApiEndpoint("users")public class UserController {    @GetMapping    public List<User> getUsers() { return userService.findAll(); }}
public class ApiScanner {    public void scanControllers(Class<?>... classes) {        for (Class<?> clazz : classes) {            RestApiEndpoint endpoint =                AnnotationUtils.findAnnotation(clazz, RestApiEndpoint.class);            if (endpoint != null) {                String path = (String) AnnotationUtils.getValue(endpoint, "value");                RequestMapping mapping =                    AnnotationUtils.findAnnotation(clazz, RequestMapping.class);                String[] basePaths = (String[]) AnnotationUtils.getValue(mapping, "value");                System.out.println("发现 API: " + basePaths[0] + "/" + path);            }        }    }}

高级玩法与最佳实践

  • 重复注解处理:通过 AnnotationUtils.getAnnotationsByType 轻松解析 @Repeatable 注解。
  • 属性合并:遍历类层次结构并合并注解属性,常用于继承场景。
  • 缓存优化:在频繁调用时对注解进行缓存,减少反射消耗。

如果这篇文章对您有所帮助,或者有所启发的话,求一键三连:点赞、转发、在看。

关注公众号:woniuxgg,在公众号中回复:笔记  就可以获得蜗牛为你精心准备的java实战语雀笔记,回复面试、开发手册、有超赞的粉丝福利!