前言
在使用 Spring-Cloud 微服务框架的时候, 对于 @Import 和 @ImportResource 这两个注解想必大家并不陌生. 我们会经常用 @Import 来导入配置类或者导入一个带有 @Component 等注解要放入 Spring 容器中的类; 用 @ImportResource 来导入一个传统的 xml 配置文件. 另外, 在启用很多组件时, 我们会用到一个形如 @EnableXXX 的注解, 比如 @EnableAsync,@EnableHystrix,@EnableApollo 等, 点开这些注解往里追溯, 你也会发现 @Import 的身影. 如此看来, 这两个注解与我们平时的开发关系密切, 但大家知道它们是如何发挥作用的吗? 下面就一起探索一下.
正文
首先看这两个注解的路径, 它们都位于 org.springframework.context.annotation 包下, 可以说是根正苗红的 Spring 注解, 所以对这两个注解的处理, 更多的也是在原有的 Spring 框架中进行的. 在 Spring-Cloud 启动类的 run 方法中, 通过简单的追溯我们可以定位到这个 run 方法 (仅部分代码):
- public ConfigurableApplicationContext run(String... args) {
- StopWatch stopWatch = new StopWatch();
- stopWatch.start();
- ConfigurableApplicationContext context = null;
- Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList();
- this.configureHeadlessProperty();
- SpringApplicationRunListeners listeners = this.getRunListeners(args);
- listeners.starting();
- Collection exceptionReporters;
- try {
- ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
- ConfigurableEnvironment environment = this.prepareEnvironment(listeners, applicationArguments);
- this.configureIgnoreBeanInfo(environment);
- Banner printedBanner = this.printBanner(environment);
- context = this.createApplicationContext();
- exceptionReporters = this.getSpringFactoriesInstances(SpringBootExceptionReporter.class, new Class[]{ConfigurableApplicationContext.class}, context);
- this.prepareContext(context, environment, listeners, applicationArguments, printedBanner);
- this.refreshContext(context);
- this.afterRefresh(context, applicationArguments);
- stopWatch.stop();
可以看到, 在 19 行的位置, 调用 refreshContext 方法, 看到这里, 想必都会想到 Spring 中大名鼎鼎的 refresh 方法, 确实如此, 正是在这个方法里面完成了对 refresh 方法的调用. 对这两个注解的处理, 应该还是落在 refresh 方法中.
这时就需要参考之前一篇博文中的内容了 (地址 https://www.cnblogs.com/zzq6032010/p/11031214.html). 我们知道在初始化 ApplicationContext 容器的时候, 会初始化 AnnotationBeanDefinitionReader 类, 在初始化此类的时候 Spring 会通过硬编码的形式强行给容器中注入一个元处理器类 ConfigurationClassPostProcessor. 而 Spring Cloud 中是在哪里注入的此元处理器类? 回到上面的 run 方法中, 点开第 16 行的代码就会发现如下代码:
- protected ConfigurableApplicationContext createApplicationContext() {
- Class<?> contextClass = this.applicationContextClass;
- if (contextClass == null) {
- try {
- switch(this.webApplicationType) {
- case SERVLET:
- contextClass = Class.forName("org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext");
- break;
- case REACTIVE:
- contextClass = Class.forName("org.springframework.boot.web.reactive.context.AnnotationConfigReactiveWebServerApplicationContext");
- break;
- default:
- contextClass = Class.forName("org.springframework.context.annotation.AnnotationConfigApplicationContext");
- }
- } catch (ClassNotFoundException var3) {
- throw new IllegalStateException("Unable create a default ApplicationContext, please specify an ApplicationContextClass", var3);
- }
- }
- return (ConfigurableApplicationContext)BeanUtils.instantiateClass(contextClass);
- }
可以看到这个方法可能会创建三种 ApplicationContext, 而分别对这三种容器的构造方法进行查看, 发现每个构造方法中都初始化了一个 AnnotationBeanDefinitionReader, 所以元处理器类 ConfigurationClassPostProcessor 就是这样加载到容器中的.
同样通过那篇博文我们知道, 是在 refresh 方法中的第五个方法 invokeBeanFactoryPostProcessors(beanFactory) 完成了对类 ConfigurationClassPostProcessor 中 postProcessBeanDefinitionRegistry 方法的调用. 我们重点关注对 parse.parse() 方法的调用, 如下图所示:
- // 初始化解析器
- ConfigurationClassParser parser = new ConfigurationClassParser(
- this.metadataReaderFactory, this.problemReporter, this.environment,
- this.resourceLoader, this.componentScanBeanNameGenerator, registry);
- Set<BeanDefinitionHolder> candidates = new LinkedHashSet<BeanDefinitionHolder>(configCandidates);
- Set<ConfigurationClass> alreadyParsed = new HashSet<ConfigurationClass>(configCandidates.size());
- do {
- // 解析, 此方法是这个后置处理方法的核心 经过了漫长的解析 复杂的一批
- parser.parse(candidates);
此方法异常复杂, 但是这不能阻挡我们前进的脚步, 继续查看之. 发现后面调到了 org.springframework.context.annotation.ConfigurationClassParser#doProcessConfigurationClass 方法, 此方法内容量较大, 分别对 @PropertySource,@Import,@ImportSource,@Bean 进行了处理, 我们就以 @ImportResource 为例追溯, 因为 @Import 相比 @ImportResource 只是少了一步解析 xml 文件.
定位到处理 @ImportResource 的地方:
- // 将解析结果添加到 ConfigurationClass 的 importedResources 中
- if (sourceClass.getMetadata().isAnnotated(ImportResource.class.getName())) {
- AnnotationAttributes importResource = AnnotationConfigUtils.attributesFor(sourceClass.getMetadata(), ImportResource.class);
- String[] resources = importResource.getAliasedStringArray("locations", ImportResource.class, sourceClass);
- Class<? extends BeanDefinitionReader> readerClass = importResource.getClass("reader");
- for (String resource : resources) {
- String resolvedResource = this.environment.resolveRequiredPlaceholders(resource);
- configClass.addImportedResource(resolvedResource, readerClass);
- }
- }
可以知道, 此处是将 @ImportResource 中每一个 xml 资源配置项提取出来, 跟 reader 一起放入了 configClass 的一个 map 中. 有放入就有取出, 取出的地方在 parse 方法的下面, 如下所示:
- parser.parse(candidates);
- parser.validate();
- Set<ConfigurationClass> configClasses = new LinkedHashSet<ConfigurationClass>(parser.getConfigurationClasses());
- configClasses.removeAll(alreadyParsed);
- // Read the model and create bean definitions based on its content
- if (this.reader == null) {
- this.reader = new ConfigurationClassBeanDefinitionReader(
- registry, this.sourceExtractor, this.resourceLoader, this.environment,
- this.importBeanNameGenerator, parser.getImportRegistry());
- }
- // 将 BeanDefinition 加载进容器中
- this.reader.loadBeanDefinitions(configClasses);
第 14 行代码点进去追溯, 就会发现下面的方法:
- private void loadBeanDefinitionsForConfigurationClass(ConfigurationClass configClass,
- TrackedConditionEvaluator trackedConditionEvaluator) {
- if (trackedConditionEvaluator.shouldSkip(configClass)) {
- String beanName = configClass.getBeanName();
- if (StringUtils.hasLength(beanName) && this.registry.containsBeanDefinition(beanName)) {
- this.registry.removeBeanDefinition(beanName);
- }
- this.importRegistry.removeImportingClassFor(configClass.getMetadata().getClassName());
- return;
- }
- if (configClass.isImported()) {
- registerBeanDefinitionForImportedConfigurationClass(configClass);
- }
- for (BeanMethod beanMethod : configClass.getBeanMethods()) {
- loadBeanDefinitionsForBeanMethod(beanMethod);
- }
- loadBeanDefinitionsFromImportedResources(configClass.getImportedResources());
- loadBeanDefinitionsFromRegistrars(configClass.getImportBeanDefinitionRegistrars());
- }
第 19 行代码处就是取出了之前 put 进去的数据, 调用 XmlBeanDefinitionReader 的 loadBeanDefinitions 方法进行载入处理. 而且此处还可以看到对 @Import 的处理, 对 ImportBeanDefinitionRegistrars 的处理.
到这里, Spring 容器就完成了对 @Import,@ImportResource 注解的处理, 将所有涉及到的类都存入了容器中. 其中还有一点需要提一下, 就是在对 @Import 注解处理的时候, 使用了递归跟循环调用, 因为 @Import 引入的类上可能还有 @Import,@ImportResource 等注解, 这样做就能保证不会漏掉.
好了, 基本解读就到这里, 如果其中有不准确之处, 还请各位道友指正, 我们下期再见?
来源: https://www.cnblogs.com/zzq6032010/p/11029641.html