注解的继承性回顾

  1. 被@Inherited元注解标注的注解标注在类上的时候,子类可以继承父类上的注解。
  2. 注解未被@Inherited元注解标注的,该注解标注在类上时,子类不会继承父类上标注的注解。
  3. 注解标注在接口上,其子类及子接口都不会继承该注解
  4. 注解标注在类或接口方法上,其子类重写该方法不会继承父类或接口中方法上标记的注解

根据注解继承的特性,我们再做AOP切面拦截的时候会遇到拦截不到的问题,今天我们就讲解下对这些特殊情况如何解决,对源码不做过渡深入的讲解。

一、注解的继承

注解继承分为两种情况:

  • 类级别 Type (Class, Interface),
  • 属性和方法级别 (Property, Method)

类级别 (Type): 注解 仅 在 类 Class 上且注解上含有 元注解 @Inherited 时, 才会被继承;(在 jdk 8 中, 接口Interface 无法继承任何Type类型注解)

属性和方法级别 (Property, Method): 注解无论何时都会被子类或子接口继承, 除非子类或子接口重写.

注意以上说的是继承(extends), 不属于注解合并 (叠加)。

测试

public class IterInheritedTest {

    @Inherited
    @Target({ElementType.TYPE})
    @Retention(RetentionPolicy.RUNTIME)
    public @interface InheritedAnnotationType {}

    @Inherited
    @Target({ElementType.FIELD,ElementType.METHOD})
    @Retention(RetentionPolicy.RUNTIME)
    public @interface ABC {
        String value() default "";
    }

    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    public @interface UnInheritedAnnotationType {}

    @UnInheritedAnnotationType
    static
    class A {}

    @InheritedAnnotationType
    static
    class B extends A {}

    static class C extends B {}

    @UnInheritedAnnotationType
    interface Z {
        @ABC()
        void he();
    }

    @InheritedAnnotationType
    interface Y extends Z {
        @ABC("hhhh")
        void he();
    }

    interface X extends Y {}

    public static void main(String[] args) throws NoSuchMethodException, NoSuchFieldException {
        System.out.println(X.class.getAnnotation(InheritedAnnotationType.class));
        System.out.println(Y.class.getAnnotation(InheritedAnnotationType.class));
        System.out.println(Z.class.getAnnotation(InheritedAnnotationType.class));
        System.out.println("_________________________________");
        System.out.println(X.class.getAnnotation(UnInheritedAnnotationType.class));
        System.out.println(Y.class.getAnnotation(UnInheritedAnnotationType.class));
        System.out.println(Z.class.getAnnotation(UnInheritedAnnotationType.class));
        System.out.println("_________________________________");
        System.out.println(Arrays.toString(Z.class.getMethod("he").getAnnotations()));
        System.out.println(Arrays.toString(Y.class.getMethod("he").getAnnotations()));
        System.out.println(Arrays.toString(X.class.getMethod("he").getAnnotations()));
    }
}

输出

null
@org.pzone.crypto.IterInheritedTest$InheritedAnnotationType()
null
_________________________________
null
null
@org.pzone.crypto.IterInheritedTest$UnInheritedAnnotationType()
_________________________________
[@org.pzone.crypto.IterInheritedTest$ABC(value=)]
[@org.pzone.crypto.IterInheritedTest$ABC(value=hhhh)]
[@org.pzone.crypto.IterInheritedTest$ABC(value=hhhh)]

二、注解的合并

java 注解原本只是一种能被获取信息的 注释 。本身对代码逻辑没有任何影响(作用: 判断是否存在 + 读取内容信息),使用效果完全由使用者决定。

注解被很多规范使用,作为标记或者约定,如jsr303 参数校验

1.注解合并的含义

在 springboot 中注解发挥了很大的作用,而这些作用仅在springboot中有用,就好比@AliasFor。

注解本身并不能被注解继承,而 springboot 中却看到大量的合并注解就好比

@RestController = @Controller + @ResponseBody

这就是 @AliasFor 的功劳。需要注意的是,离开 Spring 就无法使用了。

spring 的注解都是由下面这个类读取的,所以 spring 的注解可以进行注解合并(仅限 Spring注解 Only Spring Annotations)

2. @AliasFor 的作用

@AliasFor 有四个作用:

2.1 注释中的显式别名:

public @interface ContextConfiguration {
  
  @AliasFor("locations")
  String[] value() default {};
  
  @AliasFor("value")
  String[] locations() default {};
}

在@ContextConfiguration中, value和locations是彼此的显式别名。

2.2 元注释中属性的显式别名:

@ContextConfiguration
public @interface XmlTestConfig {

   @AliasFor(annotation = ContextConfiguration.class, attribute = "locations")
   String[] xmlFiles();
}

在@XmlTestConfig中, xmlFiles是@ContextConfiguration中locations的显式别名。换句话说, xmlFiles覆盖了@ContextConfiguration中的locations属性。

2.3 注释中的隐式别名:

@ContextConfiguration
public @interface MyTestConfig {
  @AliasFor(annotation = ContextConfiguration.class, attribute = "locations")
  String[] value() default {};
  
  @AliasFor(annotation = ContextConfiguration.class, attribute = "locations")
  String[] groovyScripts() default {};
  
  @AliasFor(annotation = ContextConfiguration.class, attribute = "locations")
  String[] xmlFiles() default {};
}

在@MyTestConfig中, value 、 groovyScripts和xmlFiles都是@ContextConfiguration中locations属性的显式元注释属性覆盖。因此,这三个属性也是彼此的隐式别名。

2.4 注释中的传递隐式别名:

@MyTestConfig
public @interface GroovyOrXmlTestConfig {
  @AliasFor(annotation = MyTestConfig.class, attribute = "groovyScripts")
  String[] groovy() default {};
  
  @AliasFor(annotation = ContextConfiguration.class, attribute = "locations")
  String[] xml() default {};
}

在@GroovyOrXmlTestConfig中, groovy是对 @MyTestConfig 中 groovyScripts 属性的显式覆盖;而xml是对 @ContextConfiguration 中的 locations 属性的显式覆盖。此外, groovy 和 xml 是彼此的可传递隐式别名,因为它们都有效地覆盖了 @ContextConfiguration 中的 locations 属性。

三、注解合并的应用

spring中有时候一个类上面标记很多注解。

实际上Java注解可以进行继承(也就是把多个注解合并成1个)

比如说SpringMVC的注解

@RestController
@RequestMapping("/person")

可以合并为一个

@PathRestController("/user")

实现是:

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
 
import org.springframework.core.annotation.AliasFor;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
 
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@RestController
@RequestMapping
public @interface PathRestController {
    @AliasFor("path")
    String[] value() default {};
 
    @AliasFor("value")
    String[] path() default {};
}
Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐