Spring注解工具类

前言

在看Spring源码的时候,经常会有处理注解的时候,比如从方法上获取注解,类上获取注解,注解属性别名。JDK中自带的获取注解API有点简单,不会从父类方法或者接口上的方法去查找,不能为属性定义别名等,因此Spring封装了一个便利的工具类,更加方便的去获取注解信息。

JDK自带方法

AnnotatedElement为获取注解的顶层接口,ClassMethod都实现了这个接口。

关于元注解@Inherited

@Inherited标记的注解是可以被子类继承的,注意必须在类上,接口或者方法上都是不能被继承的。

下面看下API的使用

先准备几个注解用来测试

/**
 * 被@Inherited标记
 */
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface InheriteClassCase {
}

/**
 * 被@Inherited标记
 */
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface InheriteInterfaceCase {
}

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface NoInheriteCase {
}

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface OtherCase {
}

测试类如下

public class JdkAnnotationTest {

    @InheriteClassCase
    @NoInheriteCase
    private static abstract class ParentClassCase {

        @InheriteClassCase
        abstract void invokeClass();
    }

    @InheriteInterfaceCase
    private interface ParentInterfaceCase {

        void invokeInterface();
    }

    @OtherCase
    private class SonCase extends ParentClassCase implements ParentInterfaceCase {

        @Override
        public void invokeClass() {

        }

        @Override
        public void invokeInterface() {

        }
    }

    @Test
    public void testApi() throws NoSuchMethodException {
        // 获取自己声明的注解, 只能拿到OtherCase
        Annotation[] declaredAnnotations = SonCase.class.getDeclaredAnnotations();
        Assert.assertEquals(1, declaredAnnotations.length);
        Assert.assertEquals(OtherCase.class, declaredAnnotations[0].annotationType());

        /*
         * 获取存在的注解(自己声明的或者继承的)
         * 目标注解必须被@Inherited标记才能被继承
         * 且必须在类上
         */
        InheriteClassCase inheriteClassCase = SonCase.class.getAnnotation(InheriteClassCase.class);
        Assert.assertNotNull(inheriteClassCase);

        /*
         * NoInheriteCase虽然在父类存在, 但是没有被@Inherited标记, 不能被继承
         */
        NoInheriteCase noInheriteCase = SonCase.class.getAnnotation(NoInheriteCase.class);
        Assert.assertNull(noInheriteCase);

        // InheriteInterfaceCase虽然被@Inherited标记, 但是在接口上, 也不能获取到
        InheriteInterfaceCase inheriteInterfaceCase = SonCase.class.getAnnotation(InheriteInterfaceCase.class);
        Assert.assertNull(inheriteInterfaceCase);

        Method invokeClassMethod = SonCase.class.getMethod("invokeClass");
        // InheriteClassCase在父类方法中, 子类方法并没有被标注, 因此不能获取到
        InheriteClassCase classCase = invokeClassMethod.getAnnotation(InheriteClassCase.class);
        Assert.assertNull(classCase);
    }
}

Spring AnnotationUtils

该类主要增强了以下几点

  • 查找元注解,getAnnotationfindAnnotation都支持。
  • 查找范围扩大,可在整个继承体系上查找(类 + 接口),仅findAnnotation支持。
  • 属性别名,通过@AliasFor注解,给属性起一个别名,这样指定一个属性,获取别名属性时也能拿到一样的值,getAnnotationfindAnnotation都支持。

何为元注解,一个注解A可以在注解B上注解,那么注解A称为注解B的元注解。一个类上存在注解B,该工具类可以直接获取到元注解A。

API使用

public class AnnotationUtilsTest {

    private static class MetaAnnotationCase {
        @GetMapping("/request")
        public void request(){}
    }

    @InheriteClassCase
    @NoInheriteCase
    private static abstract class ParentClassCase {

        @GetMapping
        @InheriteClassCase
        abstract void invokeClass();
    }

    @InheriteInterfaceCase
    private interface ParentInterfaceCase {

        @GetMapping
        void invokeInterface();
    }

    @OtherCase
    private class SonCase extends ParentClassCase implements ParentInterfaceCase {

        @Override
        public void invokeClass() {

        }

        @Override
        public void invokeInterface() {

        }
    }

    /**
     * getAnnotation基本使用和AnnotatedElement.getAnnotation一样
     * 不过扩展了属性别名、元注解查找
     */
    @Test
    public void testGetAnnotation() throws NoSuchMethodException {
        Method requestMethod = MetaAnnotationCase.class.getMethod("request");

        // 直接存在
        GetMapping getMapping = AnnotationUtils.getAnnotation(requestMethod, GetMapping.class);
        Assert.assertNotNull(getMapping);

        // 查找元注解(RequestMapping注解了GetMapping)
        RequestMapping requestMapping = AnnotationUtils.getAnnotation(requestMethod, RequestMapping.class);
        Assert.assertNotNull(requestMapping);

        // JDK @Inherited语义
        InheriteClassCase inheriteClassCase = AnnotationUtils.getAnnotation(SonCase.class, InheriteClassCase.class);
        Assert.assertNotNull(inheriteClassCase);

        // 接口, @Inherited不生效
        InheriteInterfaceCase inheriteInterfaceCase = AnnotationUtils.getAnnotation(SonCase.class, InheriteInterfaceCase.class);
        Assert.assertNull(inheriteInterfaceCase);

        // 方法上, @Inherited不生效
        Method invokeClassMethod = SonCase.class.getDeclaredMethod("invokeClass");
        InheriteClassCase inheriteClassCaseAtMethod = AnnotationUtils.getAnnotation(invokeClassMethod, InheriteClassCase.class);
        Assert.assertNull(inheriteClassCaseAtMethod);
    }

    /**
     * 在getAnnotation基础上,扩展了搜索范围(类+接口)
     */
    @Test
    public void testFindAnnotation() throws NoSuchMethodException {
        Method invokeClassMethod = SonCase.class.getDeclaredMethod("invokeClass");
        Method invokeInterfaceMethod = SonCase.class.getDeclaredMethod("invokeInterface");

        // 命中父类上的注解(即便没有@Inherited)
        NoInheriteCase noInheriteCase = AnnotationUtils.findAnnotation(SonCase.class, NoInheriteCase.class);
        Assert.assertNotNull(noInheriteCase);

        // 命中接口上的注解
        InheriteInterfaceCase inheriteInterfaceCase = AnnotationUtils.findAnnotation(SonCase.class, InheriteInterfaceCase.class);
        Assert.assertNotNull(inheriteInterfaceCase);

        // 命中父类或者接口中的方法上的注解
        GetMapping getMapping = AnnotationUtils.findAnnotation(invokeInterfaceMethod, GetMapping.class);
        Assert.assertNotNull(getMapping);

        // 查找元注解(父类方法存在GetMapping)
        RequestMapping requestMapping = AnnotationUtils.findAnnotation(invokeClassMethod, RequestMapping.class);
        Assert.assertNotNull(requestMapping);
    }

    /**
     * isAnnotationXXX
     */
    @Test
    public void testIsAnnotation() {
        // Class直接声明的注解,不会考虑@Inherited语义,也不会在继承体系上搜索
        Assert.assertFalse(AnnotationUtils.isAnnotationDeclaredLocally(InheriteClassCase.class, SonCase.class));
        Assert.assertTrue(AnnotationUtils.isAnnotationDeclaredLocally(InheriteClassCase.class, ParentClassCase.class));

        // 指定注解存在且是通过@Inherited继承的,不是直接声明的
        Assert.assertTrue(AnnotationUtils.isAnnotationInherited(InheriteClassCase.class, SonCase.class));
        Assert.assertFalse(AnnotationUtils.isAnnotationInherited(InheriteClassCase.class, ParentClassCase.class));

        // 判断一个注解类型是不是另外一个注解类型的元注解
        Assert.assertTrue(AnnotationUtils.isAnnotationMetaPresent(GetMapping.class, RequestMapping.class));
    }

    @Test
    public void testAttrAliasFor() throws NoSuchMethodException {
        Method requestMethod = MetaAnnotationCase.class.getDeclaredMethod("request");
        String[] value = new String[] {"/request"};
        GetMapping getMapping = AnnotationUtils.getAnnotation(requestMethod, GetMapping.class);
        Objects.requireNonNull(getMapping);
        Assert.assertArrayEquals(value, getMapping.value());
        // 虽然只指定了value的值, 但是path属性=value属性, 因为value和path通过@AliasFor互相指定了别名
        Assert.assertArrayEquals(value, getMapping.path());

        AnnotationAttributes annotationAttributes = AnnotationUtils.getAnnotationAttributes(requestMethod, getMapping);
        Assert.assertArrayEquals(value, annotationAttributes.getStringArray("value"));
        Assert.assertArrayEquals(value, annotationAttributes.getStringArray("path"));
    }
}

Spring AnnotatedElementUtils

此类进一步增强了@AliasFor注解,可以合并属性

还是以@GetMapping、@RequestMapping为例

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@RequestMapping(method = RequestMethod.GET)
public @interface GetMapping {

    /**
     * Alias for {@link RequestMapping#name}.
     */
    @AliasFor(annotation = RequestMapping.class)
    String name() default "";

    /**
     * Alias for {@link RequestMapping#value}.
     */
    @AliasFor(annotation = RequestMapping.class)
    String[] value() default {};
}

@GetMapping的定义对于value属性,是@RequestMapping注解的value属性别名,SpringMVC中真实起作用的其实是@RequestMapping注解,但是我们使用@GetMapping指定value属性时,SpringMVC在解析时需要拿到@RequestMapping注解的value属性,此时可以通过该工具类来获取。

例子:

public class AnnotatedElementUtilsTest {

    private static class MetaAnnotationCase {
        @GetMapping("/request")
        public void request() {
        }
    }

    @Test
    public void testGetMetaAnnotationTypes() throws NoSuchMethodException {
        Method requestMethod = MetaAnnotationCase.class.getMethod("request");

        String[] res = {RequestMapping.class.getName(), Mapping.class.getName()};
        // 获取元注解类型, 会排除java.lang包下面的注解, 会递归搜索
        Set<String> metaAnnotationTypes = AnnotatedElementUtils.getMetaAnnotationTypes(requestMethod, GetMapping.class);
        Assert.assertArrayEquals(res, metaAnnotationTypes.toArray());
    }

    /**
     * 合并@AliasFor指定的别名属性
     */
    @Test
    public void testGetMergedAnnotationAttributes() throws NoSuchMethodException {
        Method requestMethod = MetaAnnotationCase.class.getDeclaredMethod("request");
        String[] value = new String[] {"/request"};
        String[] defaultValue = new String[] {};
        RequestMapping requestMapping = AnnotationUtils.getAnnotation(requestMethod, RequestMapping.class);
        Objects.requireNonNull(requestMapping);
        // 指定了GetMapping的value属性, 但是通过AnnotationUtils方式拿不到值
        Assert.assertArrayEquals(defaultValue, requestMapping.value());

        // 通过AnnotatedElementUtils的merge系列方法可以合并属性值
        // getXXX, 只会搜索自己和@Inherited语义继承的注解
        requestMapping = AnnotatedElementUtils.getMergedAnnotation(requestMethod, RequestMapping.class);
        Assert.assertNotNull(requestMapping);
        Assert.assertArrayEquals(value, requestMapping.value());

        // findXXX会寻找父类+接口
        requestMapping = AnnotatedElementUtils.findMergedAnnotation(requestMethod, RequestMapping.class);
        Assert.assertNotNull(requestMapping);
        Assert.assertArrayEquals(value, requestMapping.value());

        AnnotationAttributes mergedAnnotationAttributes = AnnotatedElementUtils.getMergedAnnotationAttributes(requestMethod, RequestMapping.class);
        Assert.assertNotNull(mergedAnnotationAttributes);
        Assert.assertArrayEquals(value, mergedAnnotationAttributes.getStringArray("value"));

        mergedAnnotationAttributes = AnnotatedElementUtils.findMergedAnnotationAttributes(requestMethod, RequestMapping.class, false, false);
        Assert.assertNotNull(mergedAnnotationAttributes);
        Assert.assertArrayEquals(value, mergedAnnotationAttributes.getStringArray("value"));

    }

}

getOrFindAll系列

public class AnnotatedElementUtilsTest {

    private static class MetaAnnotationCase {
        @GetMapping("/request")
        public void request() {
        }

        @GetMapping("/getMapping")
        @RequestMapping("/requestMapping")
        public void repeat() {
        }
    }

    /**
     * 拿到所有指定注解列表
     */
    @Test
    public void testGetOrFindAll() throws NoSuchMethodException {
        Method repeatMethod = MetaAnnotationCase.class.getDeclaredMethod("repeat");
        // getXXX, 只会搜索自己和@Inherited语义继承的注解, 返回所有的, 不仅仅是第一个
        // 返回的是LinkedHashSet, 因此有顺序
        Set<RequestMapping> requestMappingSet = AnnotatedElementUtils.getAllMergedAnnotations(repeatMethod, RequestMapping.class);
        String[][] value = {{"/requestMapping"}, {"/getMapping"}};
        int i = 0;
        for (RequestMapping requestMapping : requestMappingSet) {
            Assert.assertArrayEquals(value[i++], requestMapping.value());
        }

        // findXXX, 会搜索类+接口
        requestMappingSet = AnnotatedElementUtils.findAllMergedAnnotations(repeatMethod, RequestMapping.class);
        i = 0;
        for (RequestMapping requestMapping : requestMappingSet) {
            Assert.assertArrayEquals(value[i++], requestMapping.value());
        }
    }

}
posted on 2023-09-19 22:06  wastonl  阅读(269)  评论(0编辑  收藏  举报