Spring注解属性别名

1、@AliasFor介绍

@AliasFor是用于为注解属性声明别名的注解,从Spring Framework 4.2开始,核心Spring中的几个注释已更新为使用@AliasFor配置其内部属性别名。

1.1、使用场景

  • 注解中的显式别名:在同一个注解中,@AliasFor可以在一对属性上声明,以表示它们是彼此可以互换的别名。
  • 元注解中属性的显式别名:如果@AliasFor的注解属性设置为与声明它的注解不同的注解,则该属性将解释为元注解中属性的别名(即显式元注解属性重写)。这使得能够对注解层次结构中重写的属性进行细粒度控制。实际上,使用AliasFor甚至可以为元注解的value属性声明别名。
  • 注解中的隐式别名:如果将注解中的一个或多个属性声明为同一元注解属性的属性替代(直接或传递),则这些属性将被视为彼此的一组隐式别名,从而导致行为类似于注释中显式别名的行为。

1.2、使用要求

与Java中的任何注释一样,仅仅是@AliasFor本身的存在不会强制执行别名语义。要强制执行别名语义,必须通过AnnotationUtils中的方法加载注解。在幕后,Spring将通过将注释包装在一个动态代理中来合成注解,该代理透明地为使用@AliasFor注解的注解属性强制执行属性别名语义。类似地,当在注解层次结构中使用@AliasFor时,AnnotatedElementUtils支持显式元注解属性重写。通常,您不需要自己手动合成注解,因为当在Spring管理的组件上查找注解时,Spring将透明地为您合成注解。

到了Spring5.2则通过MergedAnnotations加载注解。

1.3、实现要求

注解中的显式别名:

  • 组成别名对的每个属性都必须使用@AliasFor进行相互注解。
  • 别名属性必须声明相同的返回类型。
  • 别名属性必须声明一个默认值。
  • 别名属性必须声明相同的默认值。
  • 注解不应声明。

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

  • 作为元注解中的属性别名的属性必须使用@AliasFor进行注解,并且属性必须引用元注解中的属性。
  • 别名属性必须声明相同的返回类型。
  • 注解必须引用元注解。
  • 引用的元注解必须在声明@AliasFor的注解类上是元存在(注解层次结构中存在)的。

注释中的隐式别名:

  • 属于一组隐式别名的每个属性都必须使用@AliasFor进行注解,并且该属性必须在同一元注解中引用相同的属性(通过注解层次结构中的其他显式元注解属性直接覆盖或传递)。
  • 别名属性必须声明相同的返回类型。
  • 别名属性必须声明一个默认值。
  • 别名属性必须声明相同的默认值。
  • 注解必须引用适当的元注释。
  • 引用的元注解必须在声明@AliasFor的注解类上是元存在(注解层次结构中存在)的。

2、@AliasFor使用的例子

2.1、注解中的显式别名

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

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

2.2、元注解中属性的显式别名

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

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

2.3、注释中的隐式别名

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

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

2.4、注解中的传递性隐式别名

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

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

3、@AliasFor注解语义的执行

3.1、MergedAnnotations介绍

提供对合并注解的集合的访问,这些注释通常从类或方法上获得。每个合并的注解表示一个视图,在该视图中可以从不同的源值“合并”属性值,通常是:

  • 注解中一个或多个属性的显式和隐式@AliasFor声明
  • 显式@AliasFor元注解声明
  • 元注解的基于约定的属性别名
  • 来自元注解声明

例如,假设@PostMapping注解的定义如下:

@Retention(RetentionPolicy.RUNTIME)
@RequestMapping(method = RequestMethod.POST)
public @interface PostMapping {
  
    @AliasFor(attribute = "path")
    String[] value() default {};
  
    @AliasFor(attribute = "value")
    String[] path() default {};
}

如果使用@PostMapping("/home")注解方法,则它将包含针对@PostMapping和元注解@RequestMapping的合并注解。 @RequestMapping注解的合并视图将包含以下属性:

NameValueSource
value"/home"在@PostMapping中声明
path"/home"显示别名@AliasFor

可以从任何Java AnnotatedElement获得MergedAnnotations。 它们也可以用于不使用反射的源(例如直接解析字节码的源)。
可以使用不同的搜索策略来查找包含要聚合的注解的相关来源元素。 例如,MergedAnnotations.SearchStrategy.TYPE_HIERARCHY将搜索超类和已实现的接口。

从MergedAnnotations实例,您可以获取单个注解,也可以流式传输所有注解或仅匹配特定类型的注释。 您还可以快速判断是否存在注释。

3.2、典型示例

   // 是否存在该(元)注解
   mergedAnnotations.isPresent(ExampleAnnotation.class);
  
   // 获取ExampleAnnotation的合并的“值”属性(直接注解或元注解)
   mergedAnnotations.get(ExampleAnnotation.class).getString("value");
  
   // 获取所有元注释,但不包括直接注解
   mergedAnnotations.stream().filter(MergedAnnotation::isMetaPresent);
  
   // 获取所有ExampleAnnotation声明(包括任何元注释)并打印合并的“value”属性
   mergedAnnotations.stream(ExampleAnnotation.class)
       .map(mergedAnnotation -> mergedAnnotation.getString("value"))
       .forEach(System.out::println);

3.3、源码分析

MergedAnnotations接口提供了一组静态重载方法来实例化一个MergedAnnotations对象:

static MergedAnnotations from(AnnotatedElement element) {
   return from(element, SearchStrategy.DIRECT);
}
static MergedAnnotations from(AnnotatedElement element, SearchStrategy searchStrategy) {
   return from(element, searchStrategy, RepeatableContainers.standardRepeatables());
}
static MergedAnnotations from(AnnotatedElement element, SearchStrategy searchStrategy,
      RepeatableContainers repeatableContainers) {

   return TypeMappedAnnotations.from(element, searchStrategy, repeatableContainers, AnnotationFilter.PLAIN);
}
//创建一个新的MergedAnnotations实例,该实例包含指定元素的所有注解和元注解,
//并取决于MergedAnnotations.SearchStrategy,相关的继承元素。
static MergedAnnotations from(AnnotatedElement element, SearchStrategy searchStrategy,
      RepeatableContainers repeatableContainers, AnnotationFilter annotationFilter) {
   //调用子类静态方法,返回一个子类的实例对象
   return TypeMappedAnnotations.from(element, searchStrategy, repeatableContainers, annotationFilter);
}

MergedAnnotations内部定义了一个枚举类SearchStrategy,表示查找注解的搜索策略。

  • DIRECT:仅查找直接声明的注释,而无需考虑@Inherited注释,也无需搜索超类或已实现的接口。
  • INHERITED_ANNOTATIONS:查找所有直接声明的注解以及任何@Inherited超类注解。 该策略仅在与Class类型一起使用时才真正有用,因为所有其他带注解的元素都将忽略@Inherited注释。 此策略不搜索已实现的接口。
  • SUPERCLASS:查找所有直接声明的注解和超类注解。 该策略与INHERITED_ANNOTATIONS相似,不同之处在于注解不需要使用@Inherited进行元注解。 此策略不搜索已实现的接口。
  • TYPE_HIERARCHY:对整个类型层次进行完整搜索,包括超类和已实现的接口。 超类注解不需要使用@Inherited进行元注解。
  • TYPE_HIERARCHY_AND_ENCLOSING_CLASSES:对来源和所有封闭的类执行整个类型层次结构的完整搜索。 该策略与TYPE_HIERARCHY相似,不同之处在于还搜索了封闭类。 超类注解不需要使用@Inherited进行元注解。 搜索方法源时,此策略与TYPE_HIERARCHY相同。

AnnotationFilter用于过滤调用指定注解类型的接口,内部定义了4个常用的AnnotationFilter对象的静态变量:

  • PLAIN:与java.lang和org.springframework.lang包及其子包中的注解匹配。
  • JAVA:与java和javax包及其子包中的注解匹配。
  • ALL:始终匹配,可以在根本不存在任何相关注释类型时使用。
  • NONE:永远不匹配,可以在不需要过滤时使用(允许存在任何注释类型)。

MergedAnnotations静态from方法调用子类TypeMappedAnnotations静态from方法:

static MergedAnnotations from(AnnotatedElement element, SearchStrategy searchStrategy,
      RepeatableContainers repeatableContainers, AnnotationFilter annotationFilter) {

   if (AnnotationsScanner.isKnownEmpty(element, searchStrategy)) {
      return NONE;
   }
   return new TypeMappedAnnotations(element, searchStrategy, repeatableContainers, annotationFilter);
}

下面是TypeMappedAnnotations#get(java.lang.Class<A>)实现代码:

//获取指定类型的最接近的匹配注释或元注释;如果不存在,则获取MergedAnnotation.missing()
@Override
public <A extends Annotation> MergedAnnotation<A> get(Class<A> annotationType) {
   return get(annotationType, null, null);
}

@Override
public <A extends Annotation> MergedAnnotation<A> get(Class<A> annotationType,
      @Nullable Predicate<? super MergedAnnotation<A>> predicate) {

   return get(annotationType, predicate, null);
}

@Override
public <A extends Annotation> MergedAnnotation<A> get(Class<A> annotationType,
      @Nullable Predicate<? super MergedAnnotation<A>> predicate,
      @Nullable MergedAnnotationSelector<A> selector) {
   //如果filter命中注解类型,返回一个表示不存在注释的MergedAnnotation
   if (this.annotationFilter.matches(annotationType)) {
      return MergedAnnotation.missing();
   }
   MergedAnnotation<A> result = scan(annotationType,
         new MergedAnnotationFinder<>(annotationType, predicate, selector));
   return (result != null ? result : MergedAnnotation.missing());
}

scan方法参数中传递一个MergedAnnotationFinder对象,MergedAnnotationFinder是接口AnnotationsProcessor的实现类,内部定义了3个用于处理注解的三个方法。

@Nullable
private <C, R> R scan(C criteria, AnnotationsProcessor<C, R> processor) {
   //如果调用了带有Annotation[]参数的TypeMappedAnnotations#from方法
   if (this.annotations != null) {
      R result = processor.doWithAnnotations(criteria, 0, this.source, this.annotations);
      return processor.finish(result);
   }
   //调用了带有AnnotatedElement参数的方法
   if (this.element != null && this.searchStrategy != null) {
      return AnnotationsScanner.scan(criteria, this.element, this.searchStrategy, processor);
   }
   return null;
}

调用上面scan方法时,两个if分支的调用过程分别对应下图的两种实例化TypeMappedAnnotations对象。

通过对代码的跟踪发现,这两个分支最终都会调用TypeMappedAnnotations.MergedAnnotationFinder#process这个方法:

@Nullable
private MergedAnnotation<A> process(
      Object type, int aggregateIndex, @Nullable Object source, Annotation annotation) {

   Annotation[] repeatedAnnotations = repeatableContainers.findRepeatedAnnotations(annotation);
   if (repeatedAnnotations != null) {
      return doWithAnnotations(type, aggregateIndex, source, repeatedAnnotations);
   }
   AnnotationTypeMappings mappings = AnnotationTypeMappings.forAnnotationType(
         annotation.annotationType(), repeatableContainers, annotationFilter);
   for (int i = 0; i < mappings.size(); i++) {
      AnnotationTypeMapping mapping = mappings.get(i);
      if (isMappingForType(mapping, annotationFilter, this.requiredType)) {
         MergedAnnotation<A> candidate = TypeMappedAnnotation.createIfPossible(
               mapping, source, annotation, aggregateIndex, IntrospectionFailureLogger.INFO);
         if (candidate != null && (this.predicate == null || this.predicate.test(candidate))) {
            if (this.selector.isBestCandidate(candidate)) {
               return candidate;
            }
            updateLastResult(candidate);
         }
      }
   }
   return null;
}

而对@AliasFor别名的支持就在AnnotationTypeMappings中实现的,具体参考AnnotationTypeMappings和AnnotationTypeMapping源代码。

  • 3
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
1 Spring基本特征 6 2 Spring的组成 6 2.1 Spring的jar包 6 2.2 Spring配置文件 7 2.3 Spring API 8 3 Spring基本功能详解 8 3.1 SpringIOC 8 3.2别名Alias 11 别名拓展: 11 3.3 Spring容器内部对象的创建 12 Spring容器内部对象创建拓展: 12 3.3.1使用类构造器实例化(默认无参数) 14 3.3.2使用静态工厂方法实例化(简单工厂模式) 14 3.3.3初始化(创建)bean时机 15 Lazy-init初始化bean的时机拓展: 15 3.4 Bean的作用域 16 Scope单例多例作用域拓展: 16 3.4.1 singleton(默认值) 16 3.4.2 prototype 17 3.4.3 Request 17 3.4.4 Session 18 3.4.5 Global session 18 3.4.6 指定Bean的初始化方法和销毁方法 18 Bean的初始化和销毁拓展: 18 Spring的IOC总结: 20 3.5 依赖注入(DI) 20 3.5.1 使用构造器注入 20 3.5.2 使用属性setting方法进行注入 21 3.5.3 装配list集合 22 3.5.4 装配set集合 22 3.5.5 装配map 22 3.5.6 装配Properties 23 3.6 注解注入 23 注解注入拓展: 23 3.6.1 @Autowired 26 3.6.2 @Qualifier 27 3.6.3 @Resource 27 3.6.4 @PostConstruct 28 3.6.5 @PreDestroy 28 注解注入拓展: 28 3.7扫描注入 30 注解扫描拓展: 32 Mvc用注解写: 34 Spring容器IOC和di的整个启动过程: 38 3.8 spring中的继承 38 拓展spring为类中的属性赋值: 40 小结: 47 面向接口编程: 47 4 面向切面编程 52 4.1 代理模式 52 代理模式拓展: 52 4.1.1 JDK动态代理 58 JDK动态代理拓展: 59 4.1.2 CGLIB做代理 66 CGLIB动态代理拓展: 68 4.1.3 Spring的动态代理 71 4.2 AOP编程 71 4.2.1概念: 71 SpringAOP概念拓展: 73 之前实现了目标方法的动态调用,现在来实现切面的动态调用。 74 4.2.2 AOP实现的两种模式 78 4.2.2.1 xml形式 78 XML形式拓展: 81 异常通知处理例子: 91 不用spring异常通知,另一种处理异常 96 4.2.2.2Aop注解形式(了解) 99 注解注入拓展: 103 5 Spring数据库 106 5.1 Spring+JDBC 106 5.1.1 Jdbc编程特点 106 5.1.2引入DataSource 106 5.1.3 核心类JdbcTemplate 106 5.1.4 使用JdbcTemplate 106 5.1.5 继承JdbcDaoSupport 107 5.1.6 使用properties文件 107 5.1.7 RowMapper的使用 107 拓展: 108 DataSource注入的三种方式: 108 5.1.8声明式事务管理 116 5.1.8.1Spring的事务管理器 117 5.1.8.2Spring事务的传播属性 117 5.1.8.3Spring事务的隔离级别 117 拓展: 118 5.1.8.4以XML配置的 形式 119 拓展: 120 5.1.8.5以注解方式配置 125 拓展: 127 5.1.9使用CGLIB以XML形式配置事务 130 5.2 Spring+Hibernate 131 5.2.1 HibernateTemplate模板 131 5.2.2 声明式事务 131 配置XML文件 131 拓展: 132 注解形式: 137 拓展: 138 6 Struts2+spring+hibernate 141 6.1 需要添加的jar包 141 6.2 Spring融合web服务器 141 6.3 struts.xml文件 143 6.4 OpenInSessionView 143 拓展: 144 实例: 146

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值