第三章 基于注解的Java代码配置 —— Spring Cloud技术初探系列

前言

在经典的Spring应用中,我们通常是在xml文件中定义Bean的,虽然可以通过配置<context:component-scan/>标签来扫描业务代码中的bean来简化配置,但是一些框架的集成或者是配置在多数情况下还是在xml中定义。其实从Spring 3开始,就已经支持使用Configuration注解在java代码中配置bean,相应的还有Import、ComponentScan等注解来对应xml配置方式中相应标签的功能。但是这些基于注解的功能好像没有像Service、Controller这些注解被我们所熟悉。直到Spring Boot和Spring Cloud出现以后,“约定大于配置”的思想以及开箱即用的特性在这里面得到加强以后,通过注解在java代码中配置的优势就慢慢体现出来,以至于在这两个框架的官方文档中很难再看到使用xml配置的示例。这也对刚接触Spring Boot的用户造成了一些小困惑,为什么我加了几个注解就可以启动一个web容器,做到以前要配置很久才能做到的事情?为什么我把某个框架的starter依赖加进去,什么都不用做就可以把这个框架整合进来?。其实在我们的认知里,它能实现这么多功能,无非就是Spring容器实例化了各式各样的bean罢了,以前要我们手动通过xml配置才能做到的事情它现在帮助我们自动完成了。所以一系列的问题,估计要从这个Configuration机制开始说起了。

3.1 一些回顾

在第二章中介绍启动流程的时候,分析了load方法加载BeanDefinition的大致原理,在这里SpringBoot使用到了BeanDefinitionLoader来加载Bean的配置,里面使用到了多种类型的BeanDefinition获取方式,其中之一就是AnnotatedBeanDefinitionReader。而在这个Reader的构造方法中向ApplicationContext注入了很多PostProcessor。相关代码如下:

/**
	 * Create a new {@code AnnotatedBeanDefinitionReader} for the given registry and using
	 * the given {@link Environment}.
	 * @param registry the {@code BeanFactory} to load bean definitions into,
	 * in the form of a {@code BeanDefinitionRegistry}
	 * @param environment the {@code Environment} to use when evaluating bean definition
	 * profiles.
	 * @since 3.1
	 */
	public AnnotatedBeanDefinitionReader(BeanDefinitionRegistry registry, Environment environment) {
		Assert.notNull(registry, "BeanDefinitionRegistry must not be null");
		Assert.notNull(environment, "Environment must not be null");
		this.registry = registry;
		this.conditionEvaluator = new ConditionEvaluator(registry, environment, null);
		AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);
	}

关键初始化过程在AnnotationConfigUtils.registerAnnotationConfigProcessors()这个方法里面,下面节选了部分代码:

......
	if (!registry.containsBeanDefinition(CONFIGURATION_ANNOTATION_PROCESSOR_BEAN_NAME)) {
		RootBeanDefinition def = new RootBeanDefinition(ConfigurationClassPostProcessor.class);
		def.setSource(source);.add(registerPostProcessor(registry, def, CONFIGURATION_ANNOTATION_PROCESSOR_BEAN_NAME));
	}
	if (!registry.containsBeanDefinition(AUTOWIRED_ANNOTATION_PROCESSOR_BEAN_NAME)) {
		RootBeanDefinition def = new RootBeanDefinition(AutowiredAnnotationBeanPostProcessor.class);
		def.setSource(source);
		beanDefs.add(registerPostProcessor(registry, def, AUTOWIRED_ANNOTATION_PROCESSOR_BEAN_NAME));
	}
	if (!registry.containsBeanDefinition(REQUIRED_ANNOTATION_PROCESSOR_BEAN_NAME)) {
		RootBeanDefinition def = new RootBeanDefinition(RequiredAnnotationBeanPostProcessor.class);
		def.setSource(source);
		beanDefs.add(registerPostProcessor(registry, def, REQUIRED_ANNOTATION_PROCESSOR_BEAN_NAME));
	}
......

以上代码是不是觉得有些似曾相识的感觉?比如ConfigurationClass、AutowiredAnnotation、RequiredAnnotation等,是不是和@Configuration、@Autowired、@Required很像?其中ConfigurationClassPostProcessor这个则是我们本章的重点。这里如果对BeanFactoryPostProcessor机制还不太熟悉的同学们可能需要补充相关的知识了。

其实SrpingBoot中启动的Spring容器是AnnotationConfigReactiveWebServerApplicationContext或者其他类似AnnotationConfig开头的容器,在这些容器内部也会使用到AnnotatedBeanDefinitionReader,类似于下面代码这样:

/**
	 * Create a new {@link AnnotationConfigReactiveWebServerApplicationContext} with the
	 * given {@code DefaultListableBeanFactory}. The context needs to be populated through
	 * {@link #register} calls and then manually {@linkplain #refresh refreshed}.
	 * @param beanFactory the DefaultListableBeanFactory instance to use for this context
	 */
	public AnnotationConfigReactiveWebServerApplicationContext(
			DefaultListableBeanFactory beanFactory) {
		super(beanFactory);
		this.reader = new AnnotatedBeanDefinitionReader(this);
		this.scanner = new ClassPathBeanDefinitionScanner(this);
	}

只不过在SpringBoot的启动过程中,又使用了BeanDefinitionLoader将BenaDefinition的读取方式重新整合了一遍。

3.2 ConfigurationClassPostProcessor

通过对前面内容的一些回归,终于找到@Configuration功能真正的实现所在了——ConfigurationClassPostProcessor。没想到Spring在注入这个BeanFactoryPostProcessor的时候是在AnnotatedBeanDefinitionReader的构造方法中完成的,确实藏得有些隐蔽。

ConfigurationClassPostProcessor做为BeanDefinitionRegistryPostProcessor的实现,可以在容器开始实例化Bean之前操控容器中的BeanDefinition,例如新增或者修改Bean定义。由此我们可以大致猜到BeanDefinitionRegistryPostProcessor所做的事情就是扫描容器中已经注册的BeanDefinition,然后通过查看这些定义所对应的Class上面是否有指定的注解,然后再根据注解的标识去扫描额外的BeanDefinition。具体实现是在postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry)方法里。这个方法的机制稍微有些复杂,代码量有点多,不过逻辑很清晰。对于这个方法的繁琐细节就没有必要仔细了解了,主要流程可以通过下图来说明:
在这里插入图片描述

  1. 在实现里面,会遍历出Spring容器中在正常的启动逻辑中已经读取到的BeanDefinition——在第一章的实例代码中就是SpringApplication.run(Application.class,args)这个run方法里面的Application.class,其余就是还有Spring框架本身注入的一些框架Bean。

  2. 然后读取这些BeanDefinition对应的Class上是否有相关的注解。在这里,注解被分为了两种类型。一种是@Configuration这种已经确定是一个Bean定义的修饰注解(源码中称之为full configuration),还有一种就是@Component、@ComponentScan、@Import、@ImportResource这些表示对其他定义的引入(源码中称之为lite configuration)。后续在处理中会对这两种不同类型的注解区分处理。其实这些注解在我们熟悉的xml类型配置文件中都能找到与之功能相对应的标签。

  3. 然后ConfigurationClassPostProcessor会将解析的过程委派给ConfigurationClassParser去完成。这里主要就是递归地查看指定Class上的注解。这里的递归主要包含两个方面:一个是递归查看Class和它的父类上的注解;一个是递归查看像@Import这样的注解再次引入的其他Bean定义来源。在查看Class是否被某个注解标注的时候,还支持了注解嵌套的情况,具体实现可以参考AnnotatedElementUtils.isAnnotated()和nnotatedElementUtils.getMergedAnnotationAttributes()方法。聊到这里就可以回过来看一下我们示例代码中用来启动SpringBoot那个类上面添加的@SpringBootApplication注解。这个注解是被@SpringBootConfiguration、@EnableAutoConfiguration、@ComponentScan等注解标注的,因此它集合了这些注解的功能于一身。是不是有一种豁然开朗的感觉?

到这里值得一提的就是当它在处理@Import注解的时候,Spring还提供了一个ImportSelector接口扩展机制,用来扩展在Import的时候,除了可以指定具体的常规类,还可以根据ImportSelector的返回结果来扫描更多的组件集合。这个就是@EnableAutoConfiguration注解功能的实现基础,在后续章节中会继续分析到。

  1. ConfigurationClassParser最终会将在配置范围内的所有带有配置属性的Class进行读取,得到ConfigurationClass集合。然后继续由ConfigurationClassBeanDefinitionReader来从这些读取到的ConfigurationClass中读取带有@Bean注解的方法,解析真正的Bean定义并封装成BeanDefinition并注册到Spring容器中。在这里面,使用到了TrackedConditionEvaluator来实现@Conditional注解的功能。该注解接受Condition接口的实现,根据Condition返回的boolean来决定被标注的BeanDefinition是否确实要注入到Spring容器里。关于该扩展的具体用法可以参考这篇文章。

小结

其实可以说通过java代码配置和使用xml配置是定义Bean的两种不同实现,因为通过上面的介绍可以发现两者的功能之间是相互对应的,如果对照起来学习,那么思路可能就清晰多了,因为xml是我们所熟悉的。因此整个Spring的Bean的配置体系可以用下图来描述(虚线框和箭头表示的是XML配置方式中与之对应的功能):
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值