admin管理员组

文章数量:1561848

注解驱动发展史

注解驱动启蒙时代:Spring Framework 1.X

在Spring Framework 1.X时代,其中1.2.0版本是这个时代的分水岭。当时Java5刚刚出炉,业界正刮起使用Annotation的技术风,Spring Framework自然予以支持。
虽然框架层面均已支持@ManagedResource和@Transactional等Annotation,然而被注解的Spring Bean的装配仍需要使用XML方式,由于Spring Framework 1.X实现的局限性,XML配置方式是唯一的选择。

注解驱动过渡时代:Spring Framework 2.X

虽然Spring Framework 2.0在Annotation支持方面新增了新的成员,比如@Required、@Respository、@Aspect等,但同时提升了XML配置能力,即可扩展的XML编写。

同样作为重要分水岭的Spring Framework 2.5新引入了一些骨架式的Annotation:

  • 依赖注入 Annotation:@Autowired
  • 依赖查找 Annotation:@Qualifier
  • 组件声明 Annotation:@Component、@Service
  • SpringMVC Annotation:@Controller、@RequestMapping、@ModelAttribute等。

无论@Autowird注入单个还是Spring Bean集合其依赖查找的实现均属于限定类型(Class)的方式。如需在相同类型细粒度地筛选则需要@Qualifier的配合。除此之外@Qualifier也支持"逻辑类型"限定。如Spring Boot中外部化配置注解@ConfigurationPropertiesBinding:

/**
 * Qualifier for beans that are needed to configure the binding of
 * {@link ConfigurationProperties @ConfigurationProperties} (e.g. Converters).
 *
 * @author Dave Syer
 * @since 1.3.0
 */
@Qualifier(ConfigurationPropertiesBinding.VALUE)
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ConfigurationPropertiesBinding {

	/**
	 * Concrete value for the {@link Qualifier @Qualifier}.
	 */
	String VALUE = "org.springframework.boot.context.properties.ConfigurationPropertiesBinding";

}

以及Spring Cloud中的负载均衡注解@LoadBalanced:

/**
 * Annotation to mark a RestTemplate bean to be configured to use a LoadBalancerClient.
 * @author Spencer Gibb
 */
@Target({ ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Qualifier
public @interface LoadBalanced {

}

两者均标注了@Qualifier均属于"逻辑类型”限定。在Spring应用上下文生命周期中,@ConifgurationPropertiesBinding或@LoadBalanced Bean的处理实现先通过@Qualifier筛选在对其加工处理。

@ConfigurationPropertiesBinding的处理实现在org.springframework.boot.context.properties.ConfigurationPropertiesBindingPostProcessor中。而@LoadBalanced的处理则对应org.springframework.cloud.client.loadbalancer.LoadBalancerAutoConfiguration。

除了@Autowired注入,Spring Framework 2.5也支持JSR-250 @Resource注入。

尽管Spring Framework 2.X时代提供了为数不少的注解,然而编程手段却不多,最主要原因在于框架层面仍未直接提供驱动注解的Spring应用上下文,并且仍需要XML配置驱动。

注解驱动黄金时代:Spring Framework 3.X

Spring Framework 3.X其功能特性出现了井喷包括全面拥抱Java5(泛型、变量参数等),除了提升Spring模式注解"派生"的层次性,首要任务是替换XML配置方式,所以引入了配置类注解@Configuration,该注解也是内建的@Component"派生"注解。

遗憾的是Spring Framework 3.0还是没有引入替换XML元素<context:component-scan>的注解,而是选择过渡方案——@ImportResource和@Import。

Spring Framework 3.0又引入了新的Spring应用上下文AnnotationConfigurationApplicationContext,作为前时代ApplicationContext实现的替代。

Spring Framework 3.1新引入的注解@ComponentScan替换XML元素<context:component-scan>,在Bean定义允许使用@Role设置其角色同时注解@Profile使得Spring应用上下文具备条件化Bean的能力。

Spring Framework 3.X在Web方面的提升更是突飞猛进,如请求处理注解@RequestHeader、@CookieValue和@RqquestPart的出现使得Spring MVC @Controller类不必直接使用Servlet API。除此之外Spring Framework 3.0提供了REST开发,如@PathVariable便于REST动态路径的开发,@RequestBody能够直接反序列化请求内容对应相应方面@ResponseBody表示将处理方法返回对象序列化为REST主题内容,并且@ResponseStatus可以补充HTTP响应状态信息。更重要的是Spring Web整合了Servlet 3.0+规范,利用javax.servlet.ServletContainerInitializer API实现在传统Servlet容器中自动装配的能力。

更为精妙的是Spring Framework 3.1抽象了一套全新并统一的配置属性API,包括配置属性存储接口Enviroment,以及配置属性源抽象PropertySources,这两个核心API奠定了Spring Boot外部化配置的基础也是Spring Cloud分布式配置的基石,其次部分是缓存抽象主要API包括缓存Cache及缓存管理器CacheManager,在异步方面引入异步操作注解@Async、周期异步执行注解@Scheduled及异步Web请求处理DeferredResult,在校验方面新增了校验注解@Validated。

注解驱动完善时代:Spring Framework 4.X

首先要完善的任务是提升条件装配能力,条件化注解@Conditional被引入,通过与自定义Condition实现整合,弥补了之前版本条件化装配的短板,以至于@Profile从Spring Framework 4.0开始重新声明,通过@Conditional实现。注解@Conditional的出现自然也对Spring Boot的条件装配产生了影响,Spring Boot的所有@ConditionalOn*注解均基于@Conditional“派生”注解,其抽象类SpringBootCondition也是Condition的实现。

Spring Framework 4.X对Java语言特性的支持也是与时俱进的,尽管它并不强制使用Java8,然而巧妙地兼容了Java Time API(JSR-310)、@Repeatable及参数名臣发现,@Repeatable的出现解决了以往Annotation无法重复标注在同一类上的限制。

Spring Framework 4.2新增了事件监听器注解@EventListener,作为ApplicationListener接口编程的第二选择。

由于Java注解之间不存在继承关系,因此Spring模式注解"派生"特性从Spring Framework 2.5诞生,然而这种“派生”特性需要确保注解之间的属性方法签名完全一致。例如@Component与@Repository:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Indexed
public @interface Component {

	/**
	 * The value may indicate a suggestion for a logical component name,
	 * to be turned into a Spring bean in case of an autodetected component.
	 * @return the suggested component name, if any (or empty String otherwise)
	 */
	String value() default "";

}
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Repository {

	/**
	 * The value may indicate a suggestion for a logical component name,
	 * to be turned into a Spring bean in case of an autodetected component.
	 * @return the suggested component name, if any (or empty String otherwise)
	 */
	@AliasFor(annotation = Component.class)
	String value() default "";

}

@Repository作为@Component的派生注解,两者均存在相同的属性方法:value(),同样的限制也适用于@Component和@Configuration之间。这种限制被Spring Framework 4.2新注解@AliasFor解除,同时他还能在同一注解内实现属性方法的别名,如Spring Web MVC中的@RequestMapping:

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

	String name() default "";

	@AliasFor("path")
	String[] value() default {};

	@AliasFor("value")
	String[] path() default {};

}

而Spring Framework 4.3引入的@GetMapping作为@RequestMapping的派生注解同样利用@AliasFor实现了不同注解之间的属性方法别名:

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

Spring Framework 4.X在Web注解驱动编程方面也有小幅提升,除了上文提到的@GetMapping最显著的注解莫过于@RestController,其次值得关注的是@RestControllerAdvice,Spring Framework 4.2开始引入@CrossOrigin作为CorsRegistry替换注解方案,前者更加集中在@Controller的处理方法上,后者则封关注请求URL。

除此之外Spring Framework 4.X新增了依赖查找注解@Lookup,不过他无论在Spring Framework还是Spring Boot均处于边缘化的境地,知道存在即可。

注解驱动当下时代:Spring Framework 5.X

Spring Framework 5.0作为Spring Boot 2.0的底层核心框架。在Spring Boot应用场景中,大量使用注解@ComponentScan扫描指定的package,当扫描的package所包含的类越多时,Soring模式注解解析的耗时就越长,面对这个问题Spring Framework 5.0引入了注解@Indexed为Spring模式注解添加索引,以提升应用启动性能,例如

@Indexed
@Configuration
public class AnnotationIndexedConfiguration{
}

注解@Indexed不能孤立存在,需要在工程pom.xml中增加依赖:

<dependencies>
	<dependency>
		<groupId>org.springframework</groupId>
		<artifactId>spring-context-indexer</artifactId>
		<version>5.0.6.RELEASE</version>
	</dependency>
</dependencies>

当工程打包为Jar或在IDE工具中重新构建后,META-INF/springponents文件将自动生成。换言之该文件在编译时生成。当Spring应用上下文执行ComponentScan扫描时,META-INF/springponents将被CandidateComponentsIndexLoader读取并加载,转化为CandidateComponentsIndex对象,进而@ComponentScan不再扫描指定的package,而是读取CandidateComponentsIndex对象从而达到提升性能的目的。不过这种方式存在缺陷:
假设Spring应用存在一个包含META-INF/springponents资源的a.jar,b.jar包仅存在模式注解,那么@ComponentScan扫描这两个jar中的package时,b.jar中的模式注解不会被识别。

如果对org.springframework:spring-context-indexer处理机制感兴趣,则可参考org.springframework.context.index.CandidateComponentsIndexer的实现,其底层技术为“Java Annotation Processor”。

Spring Framework 5.0也引入了JSR-305适配注解,如NonNull、Nullable等,为Java与Kotlin之间提供技术杠杆。

Spring 核心注解场景分类

Spring模式注解

Spring注解场景说明起始版本
@Repository数据仓储模式注解2.0
@Component通用组件模式注解2.5
@Service服务模式注解2.5
@ControllerWeb控制器模式注解2.5
@Configuration配置类模式注解3.0

装配注解

Spring注解场景说明起始版本
@ImportResource替换XML元素<import>2.5
@Import导入@Configuration类3.0
@ComponentScan扫描指定package下标注Spring模式注解的类3.1

依赖注入注解

Spring注解场景说明起始版本
@AutowiredBean依赖注入,支持多种依赖查找方式2.5
@Qualifier细粒度@Autowired依赖查找2.5
@Resource(Java注解)Bean依赖注入,优先名称依赖查找方式2.5

Bean定义注解

Spring注解场景说明起始版本
@Bean替换XML元素<bean>3.0
@DependsOn替换XML元素<bean depends-on="…">
@Lazy替换XML元素<bean lazy-init="truefalse">
@Primary替换XML元素<bean primary="truefalse">
@Role替换XML元素<bean role="…">3.1
@Lookup替换XML元素<bean lookup-method="…">4.1

Spring条件装配注解

Spring注解场景说明起始版本
@Profile配置化条件装配3.1
@Conditional编程条件装配4.0

配置属性注解

Spring注解场景说明起始版本
@PropertySource配置属性抽象PropertySource3.1
@PropertySources@PropertySource集合注解4.0

生命周期回调注解

Spring注解场景说明起始版本
@PostConstruct替换XML元素<bean init-method="…">或InitializingBean2.5
@PreDestroy替换XML元素<bean destroy-method="…">或DisposableBean2.5

注解属性注解

Spring注解场景说明起始版本
@AliasFor别名注解属性,实现复用的目的4.2

性能注解

Spring注解场景说明起始版本
@Indexed提升Spring模式注解的扫描效率5.0

Spring 注解编程模型

该主题出现在Spring Framework GitHub的标题为“Spring Annotation Programming Model”的Wiki,按其议题逐一讨论,包括:

  • 元注解(Meta-Annotations)
  • Spring 模式注解(Stereotype Annotations)
  • Spring 组合注解(Composed Annotations)
  • Spring 注解属性别名和覆盖(Attribute Aliases and Overrides)

元注解(Meta-Annotations)

元注解是注解编程模型的首要术语,所谓元注解是指一个能声明在其他注解上的注解,如果一个注解标注在其他注解上,那么他就是元注解。Java标准注解@Inherited、@Repeatable均属于元注解。在Spring使用场景中,@Component就是标准的元注解,它在@Repository、@Service等注解上均有标注。不过在Spring注解编程模型中,@Component及被其标注的注解有一种专属术语描述——“Spring模式注解”。

Spring 模式注解(Stereotype Annotations)

@Component作为一种由Spring容器托管的通用模式组件,任何被@Component标注的组件均为组件扫描的候选对象。类似地凡是被@Component元标注的注解,如@Service,所标注的任何组件,也被视作组件扫描的候选对象,由于@Service元标注@Component,当类A标注@Service注解后,A也被认为是候选组件。

Spring模式注解即@Component“派生”注解。由于Java语言规范的规定,Annotation不允许继承,没有类派生子类的能力。因此Spring Framework采用元标注方式实现注解之间的“派生”。

理解@Component“派生性”

自Spring Framework 2.5起,@Repository不但是标记注解,而且作为Spring Framework的组件(Component)。值得关注的是,@Repository在注解定义上,也被注解@Component标注,@Repository就是@Component派生Annotation。

为了检验@Component派生性正确性,下面通过自定义@Component派生注解方式验证。

自定义@Component"派生"注解

自定义注解@StringRepository

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component // 测试多层次 @Component派生,请将当前注释
// @Repository // 测试多层次 @Component派生,请将当前反注释,并且将 spring-context 升级到 3.0.0.RELEASE
public @interface StringRepository {

    /**
     * 属性方法必须与 {@link Component#value()} 保持一致
     * @return Bean 的名称
     */
    String value() default "";
}

标注@StringRepository

@StringRepository("chineseNameRepository")
public class NameRepository {

    /**
     * 查找所有的名字
     *
     * @return non-null List
     */
    public List<String> findAll() {
        return Arrays.asList("张三", "李四", "小马哥");
    }
}

部署@StringRepository Bean

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework/schema/beans"
       xmlns:xsi="http://www.w3/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework/schema/context"
       xsi:schemaLocation="http://www.springframework/schema/beans http://www.springframework/schema/beans/spring-beans.xsd
    http://www.springframework/schema/context http://www.springframework/schema/context/spring-context.xsd">

    <!-- 激活注解驱动特性 -->
    <context:annotation-config />

    <!-- 找寻被@Component或者其派生 Annotation 标记的类(Class),将它们注册为 Spring Bean -->
    <context:component-scan base-package="thinking.in.spring.boot.samples.spring25" />

</beans>

实现@StringRepository引导类

public static void main(String[] args) {
    // 构建 XML 配置驱动 Spring 上下文
    ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext();
    // 设置 XML 配置文件的位置
    context.setConfigLocation("classpath:/META-INF/spring/context.xml");
    // 启动上下文
    context.refresh();
    // 获取名称为 "chineseNameRepository" Bean 对象
    NameRepository nameRepository = (NameRepository) context.getBean("chineseNameRepository");
    // 输出用户名称:[张三, 李四, 小马哥]
    System.out.printf("nameRepository.findAll() = %s \n", nameRepository.findAll());
}

上述示例表明@StringRepository作为@Component派生的Annotation经Spring Framework扫描后,它所标注的目标类被Spring应用上下文初始化为Spring Bean,被Spring上下文管理。

@Component"派生性"原理
  • ClassPathBeanDefinitionScanner
  • AnnotationTypeFilter
多层次@Component"派生性"

构建在Spring Framework 4.x及更高版本上的Spring Boot自然也继承了Spring Framework 2.5以来的@Component派生性能力。比如常见的@SpringBootApplication,等价于@Configuration、@EnableAutoConfiguration和@ComponentScan的联合注解。

  • Spring Framework 2.5并不支持多层次@Component派生性
  • Spring Framework 3.0仅支持两层@Component派生性
  • Spring Framework 4.0开始支持多层次@Component派生性

以上三个版本@Component派生性差别在于AnnotationAttributesReadingVisitor#visitEnd方法的差异。

组合注解

所谓组合注解是指某个注解“元标注”一个或多个其他注解,其目的在于将这些关联的注解行为组合成单个自定义注解。举例说明@TransactionalService标注了@Transactional和@Service注解,因此@TransactionalService组合了这两个注解的语义,同时@TransactionalService也是一个自定义Spring 模式注解。

/**
 * {@link Transactional @Transactional} 和 {@link Service @Service} 组合注解
 */
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Transactional
@Service(value = "transactionalService")
public @interface TransactionalService {
	/**
     * @return 服务 Bean 名称
     */
    @AliasFor(attribute = "value")
    String name() default "";

    /**
     * 覆盖 {@link Transactional#value()} 默认值
     *
     * @return {@link PlatformTransactionManager} Bean 名称
     */
    @AliasFor("name")
    String value() default "";

    /**
     * 建立 {@link Transactional#transactionManager()} 别名
     *
     * @return {@link PlatformTransactionManager} Bean 名称,默认关联 "txManager" Bean
     */
    @AliasFor(attribute = "transactionManager", annotation = Transactional.class)
    String manager() default "txManager";
}

值得注意的是TransactionalService元标注了@Transactional和@Service注解,其中@Service是模式注解,而@Transactional则是Spring事务注解。言外之意,Spring组合注解中的元注解允许是Spring模式注解和其他Spring功能性注解的任意组合。

理解Spring组合注解

Spring Framework必然提供@Transactional的处理实现,问题在于Spring容器如何通过@TransactionalService感知元注解@Transactional,Spring并没有考虑使用Java 反射的手段来解析元注解信息,而是抽象出AnnotationMetadata接口,其实现类为AnnotationMetadataReadingVisitor,并且从Spring Framework 4.0开始AnnotationMetadataReadingVisitor所关联的AnnotationAttributesReadingVisitor采用递归查找元注解,使得多层次元注解信息保存在AnnotationMetadataReadingVisitor的metaAnnotationMap字段中。

表面上metaAnnotationMap字段被AnnotationAttributesReadingVisitor关联并处理,实际上该字段来自AnnotationMetadataReadingVisitor,通过AnnotationAttributesReadingVisitor构造器参数与其关联:new AnnotationAttributesReadingVisitor(
className, this.attributesMap, this.metaAnnotationMap, this.classLoader);

在Java中Class对象是类的元信息载体,承载了其成员的元信息对象,包括字段、方法、构造器以及注解等,而Class的加载通过ClassLoader#loadClass(String)方法实现。而Spring Framework的类加载则通过ASM实现,如ClassReader。相对于ClassLoader体系,Spring ASM更为底层,读取的是类资源,直接操作其中的字节码,获取相关元信息,同时便于Spring相关的字节码提升。在读取元信息方面,Spring抽象出MetadataReader接口:

/**
 * Simple facade for accessing class metadata,
 * as read by an ASM {@link org.springframework.asm.ClassReader}.
 * @since 2.5
 */
public interface MetadataReader {
	/**
	 * Return the resource reference for the class file.
	 */
	Resource getResource();
	/**
	 * Read basic class metadata for the underlying class.
	 */
	ClassMetadata getClassMetadata();
	/**
	 * Read full annotation metadata for the underlying class,
	 * including metadata for annotated methods.
	 */
	AnnotationMetadata getAnnotationMetadata();
}

getClassMetadata()用于读取类的元信息,注解的元信息由getAnnotationMetadata()方法获取。无论ClassMetadata 还是AnnotationMetadata 均没有Java Class和Annotation API那样丰富的关联属性。
MetadataReader 有明显的资源特性,getResource()方法关联了类资源的Resource信息,它在Spring Framework中仅存在一个非公开的实现,即SimpleMetadataReader ,其关联的ClassMetadata和AnnotationMetadata 信息在构造的阶段完成初始化:

SimpleMetadataReader(Resource resource, @Nullable ClassLoader classLoader) throws IOException {
	InputStream is = new BufferedInputStream(resource.getInputStream());
	ClassReader classReader;
	try {
		classReader = new ClassReader(is);
	}
	catch (IllegalArgumentException ex) {
		throw new NestedIOException("ASM ClassReader failed to parse class file - " +
				"probably due to a new Java class file version that isn't supported yet: " + resource, ex);
	}
	finally {
		is.close();
	}

	AnnotationMetadataReadingVisitor visitor = new AnnotationMetadataReadingVisitor(classLoader);
	classReader.accept(visitor, ClassReader.SKIP_DEBUG);

	this.annotationMetadata = visitor;
	// (since AnnotationMetadataReadingVisitor extends ClassMetadataReadingVisitor)
	this.classMetadata = visitor;
	this.resource = resource;
}

然而AnnotationMetadataReadingVisitor 同时实现了ClassMetadata 和AnnotationMetadata接口,因此在Spring注解编程模型中,元注解的实现仍集中在AnnotationMetadataReadingVisitor 及AnnotationAttributesReadingVisitor之中。

下面结合示例加以说明,先回顾ClassPathScanningCandidateComponentProvider#findCandidateComponents(String)方法中读取MetadataReader实现的方式。

其中MetaReader对象通过MetadataReaderFactory#getMetadataReader(Resource)方法获取,而方法中的this.metadataReaderFactory对象默认是CachingMetadataReaderFactory实例,并且MetadataReaderFactory提供另一个重载方法getMetadataReader(String)

/**
 * Factory interface for {@link MetadataReader} instances.
 * Allows for caching a MetadataReader per original resource.
 *
 * @author Juergen Hoeller
 * @since 2.5
 * @see SimpleMetadataReaderFactory
 * @see CachingMetadataReaderFactory
 */
public interface MetadataReaderFactory {

	/**
	 * Obtain a MetadataReader for the given class name.
	 * @param className the class name (to be resolved to a ".class" file)
	 * @return a holder for the ClassReader instance (never {@code null})
	 * @throws IOException in case of I/O failure
	 */
	MetadataReader getMetadataReader(String className) throws IOException;

	/**
	 * Obtain a MetadataReader for the given resource.
	 * @param resource the resource (pointing to a ".class" file)
	 * @return a holder for the ClassReader instance (never {@code null})
	 * @throws IOException in case of I/O failure
	 */
	MetadataReader getMetadataReader(Resource resource) throws IOException;
}

使用示例:

@TransactionalService
public class TransactionalServiceAnnotationMetadataBootstrap {

    public static void main(String[] args) throws IOException {
        // @TransactionalService 标注在当前类 TransactionalServiceAnnotationMetadataBootstrap
        String className = TransactionalServiceAnnotationMetadataBootstrap.class.getName();
        // 构建 MetadataReaderFactory 实例
        MetadataReaderFactory metadataReaderFactory = new CachingMetadataReaderFactory();
        // 读取 @TransactionService MetadataReader 信息
        MetadataReader metadataReader = metadataReaderFactory.getMetadataReader(className);
        // 读取 @TransactionService AnnotationMetadata 信息
        AnnotationMetadata annotationMetadata = metadataReader.getAnnotationMetadata();

        annotationMetadata.getAnnotationTypes().forEach(annotationType -> {

            Set<String> metaAnnotationTypes = annotationMetadata.getMetaAnnotationTypes(annotationType);

            metaAnnotationTypes.forEach(metaAnnotationType -> {
                System.out.printf("注解 @%s 元标注 @%s\n", annotationType, metaAnnotationType);
            });

        });
    }
}

输出:

注解 @thinking.in.spring.boot.samples.spring5.annotation.TransactionalService 元标注 @org.springframework.transaction.annotation.Transactional
注解 @thinking.in.spring.boot.samples.spring5.annotation.TransactionalService 元标注 @org.springframework.stereotype.Service
注解 @thinking.in.spring.boot.samples.spring5.annotation.TransactionalService 元标注 @org.springframework.stereotype.Component
注解 @thinking.in.spring.boot.samples.spring5.annotation.TransactionalService 元标注 @org.springframework.stereotype.Indexed

最后分析AnnotationMetadata#getMetaAnnotationTypes(String)的实现即AnnotationMetadataReadingVisitor:

@Override
public AnnotationVisitor visitAnnotation(final String desc, boolean visible) {
	String className = Type.getType(desc).getClassName();
	this.annotationSet.add(className);
	return new AnnotationAttributesReadingVisitor(
			className, this.attributesMap, this.metaAnnotationMap, this.classLoader);
}

@Override
public Set<String> getAnnotationTypes() {
	return this.annotationSet;
}

@Override
public Set<String> getMetaAnnotationTypes(String annotationName) {
	return this.metaAnnotationMap.get(annotationName);
}

AnnotationMetadataReadingVisitor#getMetaAnnotationTypes(String)方法实际上返回的是底层metaAnnotationMap字段的结果,已知该字段的初始化在AnnotationAttributesReadingVisitor中完成。

分析至此得出以下结论,无论Spring模式注解(多层次@Component派生性)的元信息还是Spring组合注解的元信息均有AnnotationMetadata API抽象表达,具体某个注解的元注解信息则通过getMetaAnnotationTypes(String)方法查询。

之所以不提AnnotationMetadataReadingVisitor的原因是该类并非AnnotationMetadata的唯一实现,另外一处实现为StandardAnnotationMetadata。两者读取注解元信息的手段不同,前者利用ASM方式读取,后者的读取方式使用Java反射。

AnnotationMetadata API不仅通过getMetaAnnotationTypes(String)方法暴露元注解信息,而且提供getAnnotationAttributes(String)方法抽象指定注解的属性方法,该方法也是下一节的讨论重点。

Spring注解属性别名和覆盖

在Java反射编程模型中,注解之间无法继承也不能实现接口,不过Java语言默认将所有注解实现Annotation接口,被标注的对象用AnnotatedElement表达。通过AnnotatedElement#getAnnotation(Class)方法返回指定类型的注解对象,获取注解属性则需要显示地调用对应的属性方法。

理解Spring注解元信息抽象AnnotationMetadata

仍以@TransactionalService为例,利用Java反射API实现获取@TransactionalService属性方法。

/**
 * {@link TransactionalService} 注解反射引导类
 */
@TransactionalService(name = "test") // name 属性内容
public class TransactionalServiceAnnotationReflectionBootstrap {

    public static void main(String[] args) {
        // Class 实现了 AnnotatedElement 接口
        AnnotatedElement annotatedElement = TransactionalServiceAnnotationReflectionBootstrap.class;
        // 从 AnnotatedElement 获取 TransactionalService
        TransactionalService transactionalService = annotatedElement.getAnnotation(TransactionalService.class);
        // 显示地调用属性方法 TransactionalService#name() 获取属性
//        String nameAttribute = transactionalService.name();
//        System.out.println("@TransactionalService.name() = " + nameAttribute);

        // 输出 @TransactionalService 属性
//        printAnnotationAttribute(transactionalService);
        // 获取 transactionalService 的所有的元注解
        Set<Annotation> metaAnnotations = getAllMetaAnnotations(transactionalService);
        // 输出结果
        metaAnnotations.forEach(TransactionalServiceAnnotationReflectionBootstrap::printAnnotationAttribute);
    }

    private static Set<Annotation> getAllMetaAnnotations(Annotation annotation) {
        Annotation[] metaAnnotations = annotation.annotationType().getAnnotations();
        if (ObjectUtils.isEmpty(metaAnnotations)) { // 没有找到,返回空集合
            return Collections.emptySet();
        }
        // 获取所有非 Java 标准元注解结合
        Set<Annotation> metaAnnotationsSet = Stream.of(metaAnnotations)
                // 排除 Java 标准注解,如 @Target,@Documented 等,它们因相互依赖,将导致递归不断
                // 通过 java.lang.annotation 包名排除
                .filter(metaAnnotation -> !Target.class.getPackage().equals(metaAnnotation.annotationType().getPackage()))
                .collect(Collectors.toSet());

        // 递归查找元注解的元注解集合
        Set<Annotation> metaMetaAnnotationsSet = metaAnnotationsSet.stream()
              .map(TransactionalServiceAnnotationReflectionBootstrap::getAllMetaAnnotations)
                .collect(HashSet::new, Set::addAll, Set::addAll);

        // 添加递归结果
        metaMetaAnnotationsSet.add(annotation);
        return metaMetaAnnotationsSet;
    }

    private static void printAnnotationAttribute(Annotation annotation) {
        Class<?> annotationType = annotation.annotationType();
        // 完全 Java 反射实现(ReflectionUtils 为 Spring 反射工具类)
        ReflectionUtils.doWithMethods(annotationType,
                method -> System.out.printf("@%s.%s() = %s\n", annotationType.getSimpleName(),
                        method.getName(), ReflectionUtils.invokeMethod(method, annotation)) // 执行 Method 反射调用
//                , method -> method.getParameterCount() == 0); // 选择无参数方法
                , method -> !method.getDeclaringClass().equals(Annotation.class));// 选择非 Annotation 方法
    }
}

输出

@Transactional.value() = 
@Transactional.timeout() = -1
@Transactional.readOnly() = false
@Transactional.transactionManager() = 
@Transactional.rollbackFor() = [Ljava.lang.Class;@1ee0005
@Transactional.isolation() = DEFAULT
@Transactional.noRollbackFor() = [Ljava.lang.Class;@75a1cd57
@Transactional.propagation() = REQUIRED
@Transactional.noRollbackForClassName() = [Ljava.lang.String;@3d012ddd
@Transactional.rollbackForClassName() = [Ljava.lang.String;@6f2b958e
@Component.value() = 
@TransactionalService.name() = test
@TransactionalService.value() = 
@TransactionalService.manager() = txManager
@Service.value() = transactionalService

日志内容中出现了@TransactionalService所以元注解的属性方法。很明显基于Java反射API获取元(嵌套)注解及属性信息的实现颇为复杂,需要递归地获取元注解。从Spring Framework 4.0开始,AnnotationMetadata#getMetaAnnotationTypes(String)方法返回注解所关联的属性信息,以Map结构存储。同时AnnotationMetadata存在两种实现:基于ASM的AnnotationMetadataReadingVisitor和Java 反射API的StandardAnnotationMetadata。下面利用StandardAnnotationMetadata将示例代码重新实现:

/**
 * {@link TransactionalService @TransactionalService} {@link StandardAnnotationMetadata}实现 引导类
 */
@TransactionalService
public class TransactionalServiceStandardAnnotationMetadataBootstrap {

    public static void main(String[] args) throws IOException {
        // 读取 @TransactionService AnnotationMetadata 信息
        AnnotationMetadata annotationMetadata = new StandardAnnotationMetadata(TransactionalServiceStandardAnnotationMetadataBootstrap.class);

        // 获取所有的元注解类型(全类名)集合
        Set<String> metaAnnotationTypes = annotationMetadata.getAnnotationTypes()
                .stream() // TO Stream
                .map(annotationMetadata::getMetaAnnotationTypes) // 读取单注解的元注解类型集合
                .collect(LinkedHashSet::new, Set::addAll, Set::addAll); // 合并元注解类型(全类名)集合

        metaAnnotationTypes.forEach(metaAnnotation -> { // 读取所有元注解类型
            // 读取元注解属性信息
            Map<String, Object> annotationAttributes = annotationMetadata.getAnnotationAttributes(metaAnnotation);
            if (!CollectionUtils.isEmpty(annotationAttributes)) {
                annotationAttributes.forEach((name, value) ->
                        System.out.printf("注解 @%s 属性 %s = %s\n", ClassUtils.getShortName(metaAnnotation), name, value));
            }
        });
    }
}

对比重构前的示例,经过AnnotationMetadata API重构的示例将复杂的递归查找元注解的逻辑“扁平化”降低了开发成本。

在AnnotationMetadata语义上,基于Java反射API实现的StandardAnnotationMetadata与AnnotationMetadataReadingVisitor保持一致。那么Spring Framework为什么并存两套实现?
首先两者适用于不同的使用场景,凡是基于Java反射API的实现必然需要一个大前提,即被反射的Class必须被ClassLoader加载。当Spring应用指定Java Package扫描Spring模式注解时,StandardAnnotationMetadata显然不适应了,因为应用不需要、更不应该把这些package下的Class悉数加载。这也解释了为什么AnnotationMetadataReadingVisitor会出现在ClassPathScanningCandidateComponentProvider的实现中。反之当Class已被加载并能够被程序获取时,再通过ASM读取就显得多此一举了。
另一方面两者的性能表现差异较大,在getAnnotationTypes()的性能上,AnnotationMetadataReadingVisitor明显要优于StandardAnnotationMetadata。

@Override
public Set<String> getMetaAnnotationTypes(String annotationName) {
	return (this.annotations.length > 0 ?
			AnnotatedElementUtils.getMetaAnnotationTypes(getIntrospectedClass(), annotationName) :
			Collections.emptySet());
}


其中searchWithGetSemanticsInAnnotations方法通过AnnotationUtils#isInJavaLangAnnotationPackage(java.lang.Class<? extends java.lang.annotation.Annotation>)方法过滤Java标准注解,同时递归调用hasSearchableMetaAnnotations方法获取所有元注解。

同样地StandardAnnotationMetadata#getAnnotationAttributes(java.lang.String)实际调用了AnnotatedElementUtils#getMergedAnnotationAttributes(AnnotatedElement,String, boolean, boolean)方法,大意是指在指定element的元注解层次中查找第一个类型名为annotationName的注解,将它及更底层的元注解属性合并到AnnotationAttributes之中。

理解Spring注解属性抽象AnnotationAttributes

以@TransactionalService为例,假设当annotationName为org.springframework.stereotype.Service时,按照层析性分析,@Service更底层的元注解为@Component,@Component.value()与@Service.value()将发生合并,所以最终的AnnotationAttributes对象仅包含一个元素即value:""。同理当annotationName为org.springframework.transaction.annotation.Transactional时,由于它更底层的元注解仅为Java标准注解,所以AnnotationAttributes只包含@Transactional自身的注解。稍作示例检验:

@TransactionalService(name = "transactionalServiceBean")
public class TransactionalServiceBean {

    public void save() {
        System.out.println("保存操作...");
    }
}
/**
 * {@link AnnotationAttributes} 引导类
 */
public class AnnotationAttributesBootstrap {

    public static void main(String[] args) {
        AnnotatedElement annotatedElement = TransactionalServiceBean.class;
        // 获取 @Service 注解属性独享
        AnnotationAttributes serviceAttributes =
                AnnotatedElementUtils.getMergedAnnotationAttributes(annotatedElement, Service.class);
        // 获取 @Transactional 注解属性独享
        AnnotationAttributes transactionalAttributes =
                AnnotatedElementUtils.getMergedAnnotationAttributes(annotatedElement, Transactional.class);
        // 输出
        print(serviceAttributes);
        print(transactionalAttributes);
    }

    private static void print(AnnotationAttributes annotationAttributes) {
        System.out.printf("注解 @%s 属性集合 : \n", annotationAttributes.annotationType().getName());
        annotationAttributes.forEach((name, value) ->
                System.out.printf("\t属性 %s : %s \n", name, value)
        );
    }
}

运行结果:

注解 @org.springframework.stereotype.Service 属性集合 : 
	属性 value : transactionalService 
注解 @org.springframework.transaction.annotation.Transactional 属性集合 : 
	属性 value : txManager 
	属性 timeout : -1 
	属性 readOnly : false 
	属性 rollbackForClassName : [Ljava.lang.String;@5fdef03a 
	属性 noRollbackForClassName : [Ljava.lang.String;@3b22cdd0 
	属性 transactionManager : txManager 
	属性 rollbackFor : [Ljava.lang.Class;@1e81f4dc 
	属性 noRollbackFor : [Ljava.lang.Class;@4d591d15 
	属性 propagation : REQUIRED 
	属性 isolation : DEFAULT 

运行结果与预期一致。值得注意的是,Spring Framework将注解属性抽象为AnnotationAttributes 类,它直接扩展了LinkedHashMap,目的不言而喻,既要使用key-value的数据结构,又要确保其顺序保持与属性方法声明一致。按照@TransactionalService的声明,其元注解分别为@Transactional和@Service,而@Service的元注解是@Component,并且两者均有属性方法value(),以及默认值""。那么AnnotationAttributes 中的value属性到底是@Component.value()覆盖@Service.value()还是反过来呢?较低层注解属性将覆盖叫高层的,及@Service.value()覆盖@Component.value()。

理解Spring注解属性覆盖

较低层注解能够覆盖其元注解的同名属性,并且AnnotationAttributes采用注解就近覆盖的设计原则,@Service较@Component而言,距@TransactionalService更近,所以它是较低层注解。同理@TransactionalService也可以覆盖@Service的同名属性。

理解Spring注解属性别名

https://blog.csdn/shenchaohao12321/article/details/103746124

Spring @Enable模块驱动

Spring Framework 3.1开始支持“@Enable模块驱动”。所谓模块是指具备相同领域的功能组件集合组合所形成的一个独立的单元。

理解@Enable模块驱动

@Enable模块驱动在后续Spring Framework、Spring Boot和Spring Cloud中一以贯之,这种模块化的Annotation均以@Enable作为前缀:

框架实现@Enable注解模块激活模块
Spring Framework@EnableWebMvcWeb Mvc模块
@EnableTransactionManagement事务管理模块
@EnableCachingCaching模块
@EnableMBeanExportJMX模块
@EnableAsync异步处理模块
@EnableWebFluxWeb Flux模块
@EnableAspectJAutoProxyAspectJ代理模块
Spring Boot@EnableAutoConfiguration自动装配模块
@EnableManagementContextActuator管理模块
@EnableConfigurationProperties配置属性绑定模块
@EnableOAuth2SsoOAuth2单点登录模块
Spring Cloud@EnableEurekaServerEureka服务器模块
@EnableConfigServer配置服务器模块
@EnableFeignClientsFeign客户端模块
@EnableZuulProxy服务网关Zuul模块
@EnableCircuitBreaker服务熔断模块

引入“@Enable模块驱动”的意义在于能够简化装配步骤,实现了“按需装配”同时屏蔽组件集合装配的细节。但该模式必须手动触发,也就是说Annotation必须标注在某个配置Bean中。

自定义@Enable模块驱动

@Import指示要导入的一个或多个@Configuration类。提供与spring xml中的元素等效的功能。允许导入@Configuration类、ImportSelector和ImportBeanDefinitionRegistrator实现,以及常规组件类(从4.2开始,类似于AnnotationConfigApplicationContext.register)。

如果XML或其他非@Configuration需要导入bean定义资源,请改用@ImportResource注释。

基于“注解驱动”实现@Enable模块

1、实现Configuration类

@Configuration
public class HelloWorldConfiguration {

    @Bean
    public String helloWorld() { // 创建名为"helloWorld" String 类型的Bean
        return "Hello,World";
    }
}

2、实现“@Enable模块驱动”Annotation

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(HelloWorldConfiguration.class) // 导入 HelloWorldConfiguration
public @interface EnableHelloWorld {
}

3、标注@EnableHelloWorld到引导类

@EnableHelloWorld
@Configuration
public class EnableHelloWorldBootstrap {

    public static void main(String[] args) {
        // 构建 Annotation 配置驱动 Spring 上下文
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
        // 注册 当前引导类(被 @Configuration 标注) 到 Spring 上下文
        context.register(EnableHelloWorldBootstrap.class);
        // 启动上下文
        context.refresh();
        // 获取名称为 "helloWorld" Bean 对象
        String helloWorld = context.getBean("helloWorld", String.class);
        // 输出用户名称:"Hello,World"
        System.out.printf("helloWorld = %s \n", helloWorld);
        // 关闭上下文
        context.close();
    }
}
基于“接口编程”实现@Enable模块

接口编程需要实现ImportSelector或@ImportBeanDefinitionRegistrar接口:

  • ImportSelector:接口相对简单,使用Spring注解元信息抽象AnnotationMetadata作为方法参数,该参数的内容为导入ImportSelector实现的@Configuration类元信息,进而动态地选择一个或多个其他@Configuration类进行导入。
  • ImportBeanDefinitionRegistrar:相对于ImportSelector而言编程复杂度高,除注解元信息AnnotationMetadata作为入参外,接口将BeanDefinition的注册交给开发人员决定。

本文标签: 注解Spring