@Import与@ImportResource注解的解读

https://www.cnblogs.com/zzq6032010/p/11029641.html

前言

    在使用Spring-Cloud微服务框架的时候,对于@Import和@ImportResource这两个注解想必大家并不陌生。我们会经常用@Import来导入配置类或者导入一个带有@Component等注解要放入Spring容器中的类;用@ImportResource来导入一个传统的xml配置文件。另外,在启用很多组件时,我们会用到一个形如@EnableXXX的注解,比如@EnableAsync、@EnableHystrix、@EnableApollo等,点开这些注解往里追溯,你也会发现@Import的身影。如此看来,这两个注解与我们平时的开发关系密切,但大家知道它们是如何发挥作用的吗?下面就一起探索一下。

正文

    首先看这两个注解的路径,它们都位于org.springframework.context.annotation包下,可以说是根正苗红的Spring注解,所以对这两个注解的处理,更多的也是在原有的Spring框架中进行的。在Spring-Cloud启动类的run方法中,通过简单的追溯我们可以定位到这个run方法(仅部分代码):

1 public ConfigurableApplicationContext run(String... args) {
 2         StopWatch stopWatch = new StopWatch();
 3         stopWatch.start();
 4         ConfigurableApplicationContext context = null;
 5         Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList();
 6         this.configureHeadlessProperty();
 7         SpringApplicationRunListeners listeners = this.getRunListeners(args);
 8         listeners.starting();
 9 
10         Collection exceptionReporters;
11         try {
12             ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
13             ConfigurableEnvironment environment = this.prepareEnvironment(listeners, applicationArguments);
14             this.configureIgnoreBeanInfo(environment);
15             Banner printedBanner = this.printBanner(environment);
16             context = this.createApplicationContext();
17             exceptionReporters = this.getSpringFactoriesInstances(SpringBootExceptionReporter.class, new Class[]{ConfigurableApplicationContext.class}, context);
18             this.prepareContext(context, environment, listeners, applicationArguments, printedBanner);
19             this.refreshContext(context);
20             this.afterRefresh(context, applicationArguments);
21             stopWatch.stop();

可以看到,在19行的位置,调用 refreshContext方法,看到这里,想必都会想到Spring中大名鼎鼎的refresh方法,确实如此,正是在这个方法里面完成了对refresh方法的调用。对这两个注解的处理,应该还是落在refresh方法中。

这时就需要参考之前一篇博文中的内容了(地址Spring源码解读之BeanFactoryPostProcessor的处理 - 淡墨痕 - 博客园)。我们知道在初始化ApplicationContext容器的时候,会初始化AnnotationBeanDefinitionReader类,在初始化此类的时候Spring会通过硬编码的形式强行给容器中注入一个元处理器类ConfigurationClassPostProcessor。而Spring Cloud中是在哪里注入的此元处理器类?回到上面的run方法中,点开第16行的代码就会发现如下代码:

 1 protected ConfigurableApplicationContext createApplicationContext() {
 2         Class<?> contextClass = this.applicationContextClass;
 3         if (contextClass == null) {
 4             try {
 5                 switch(this.webApplicationType) {
 6                 case SERVLET:
 7                     contextClass = Class.forName("org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext");
 8                     break;
 9                 case REACTIVE:
10                     contextClass = Class.forName("org.springframework.boot.web.reactive.context.AnnotationConfigReactiveWebServerApplicationContext");
11                     break;
12                 default:
13                     contextClass = Class.forName("org.springframework.context.annotation.AnnotationConfigApplicationContext");
14                 }
15             } catch (ClassNotFoundException var3) {
16                 throw new IllegalStateException("Unable create a default ApplicationContext, please specify an ApplicationContextClass", var3);
17             }
18         }
19 
20         return (ConfigurableApplicationContext)BeanUtils.instantiateClass(contextClass);
21     }

可以看到这个方法可能会创建三种ApplicationContext,而分别对这三种容器的构造方法进行查看,发现每个构造方法中都初始化了一个AnnotationBeanDefinitionReader,所以元处理器类ConfigurationClassPostProcessor就是这样加载到容器中的。

同样通过那篇博文我们知道,是在refresh方法中的第五个方法invokeBeanFactoryPostProcessors(beanFactory)完成了对类ConfigurationClassPostProcessor中postProcessBeanDefinitionRegistry方法的调用。我们重点关注对parse.parse()方法的调用,如下图所示:

 1         // 初始化解析器
 2         ConfigurationClassParser parser = new ConfigurationClassParser(
 3                 this.metadataReaderFactory, this.problemReporter, this.environment,
 4                 this.resourceLoader, this.componentScanBeanNameGenerator, registry);
 5 
 6         Set<BeanDefinitionHolder> candidates = new LinkedHashSet<BeanDefinitionHolder>(configCandidates);
 7         Set<ConfigurationClass> alreadyParsed = new HashSet<ConfigurationClass>(configCandidates.size());
 8         do {
 9             // 解析,此方法是这个后置处理方法的核心   经过了漫长的解析 复杂的一批
10             parser.parse(candidates);

此方法异常复杂,但是这不能阻挡我们前进的脚步,继续查看之。发现后面调到了org.springframework.context.annotation.ConfigurationClassParser#doProcessConfigurationClass方法,此方法内容量较大,分别对@PropertySource、@Import、@ImportSource、@Bean进行了处理,我们就以@ImportResource为例追溯,因为@Import相比@ImportResource只是少了一步解析Xml文件。

定位到处理@ImportResource的地方:

 1         // 将解析结果添加到ConfigurationClass的importedResources中
 2         if (sourceClass.getMetadata().isAnnotated(ImportResource.class.getName())) {
 3             AnnotationAttributes importResource = AnnotationConfigUtils.attributesFor(sourceClass.getMetadata(), ImportResource.class);
 4             String[] resources = importResource.getAliasedStringArray("locations", ImportResource.class, sourceClass);
 5             Class<? extends BeanDefinitionReader> readerClass = importResource.getClass("reader");
 6             for (String resource : resources) {
 7                 String resolvedResource = this.environment.resolveRequiredPlaceholders(resource);
 8                 configClass.addImportedResource(resolvedResource, readerClass);
 9             }
10         }

可以知道,此处是将@ImportResource中每一个xml资源配置项提取出来,跟reader一起放入了configClass的一个map中。有放入就有取出,取出的地方在parse方法的下面,如下所示:

 1 parser.parse(candidates);
 2             parser.validate();
 3 
 4             Set<ConfigurationClass> configClasses = new LinkedHashSet<ConfigurationClass>(parser.getConfigurationClasses());
 5             configClasses.removeAll(alreadyParsed);
 6 
 7             // Read the model and create bean definitions based on its content
 8             if (this.reader == null) {
 9                 this.reader = new ConfigurationClassBeanDefinitionReader(
10                         registry, this.sourceExtractor, this.resourceLoader, this.environment,
11                         this.importBeanNameGenerator, parser.getImportRegistry());
12             }
13             // 将BeanDefinition加载进容器中
14             this.reader.loadBeanDefinitions(configClasses);

第14行代码点进去追溯,就会发现下面的方法:

 1 private void loadBeanDefinitionsForConfigurationClass(ConfigurationClass configClass,
 2             TrackedConditionEvaluator trackedConditionEvaluator) {
 3 
 4         if (trackedConditionEvaluator.shouldSkip(configClass)) {
 5             String beanName = configClass.getBeanName();
 6             if (StringUtils.hasLength(beanName) && this.registry.containsBeanDefinition(beanName)) {
 7                 this.registry.removeBeanDefinition(beanName);
 8             }
 9             this.importRegistry.removeImportingClassFor(configClass.getMetadata().getClassName());
10             return;
11         }
12 
13         if (configClass.isImported()) {
14             registerBeanDefinitionForImportedConfigurationClass(configClass);
15         }
16         for (BeanMethod beanMethod : configClass.getBeanMethods()) {
17             loadBeanDefinitionsForBeanMethod(beanMethod);
18         }
19         loadBeanDefinitionsFromImportedResources(configClass.getImportedResources());
20         loadBeanDefinitionsFromRegistrars(configClass.getImportBeanDefinitionRegistrars());
21     }

第19行代码处就是取出了之前put进去的数据,调用XmlBeanDefinitionReader的loadBeanDefinitions方法进行载入处理。而且此处还可以看到对@Import的处理,对ImportBeanDefinitionRegistrars的处理。

到这里,Spring容器就完成了对@Import、@ImportResource注解的处理,将所有涉及到的类都存入了容器中。其中还有一点需要提一下,就是在对@Import注解处理的时候,使用了递归跟循环调用,因为@Import引入的类上可能还有@Import、@ImportResource等注解,这样做就能保证不会漏掉。

好了,基本解读就到这里,如果其中有不准确之处,还请各位道友指正,我们下期再见?

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
@Import注解是Spring框架中的一个注解,它用于将其他类或配置导入到当前类中。@Import可以单独使用,也可以和其他注解一起使用,例如@Configuration注解、ImportSelector接口和ImportBeanDefinitionRegistrar接口。 当@Import注解单独使用时,它可以直接将其他普通的类导入到当前类中,以便在当前类中可以使用被导入的类。 当@Import注解结合@Configuration注解、ImportSelector接口和ImportBeanDefinitionRegistrar接口使用时,在Spring Boot中是最常见的用法之一。举个例子,如果我们在一个类上使用@EnableAutoConfiguration注解,那么在该注解的源码中会使用@Import注解来导入AutoConfigurationImportSelector类。这样,通过@EnableAutoConfiguration注解,我们可以自动配置应用程序的一些默认设置。 总结来说,@Import注解是用来将其他类或配置导入到当前类中的注解,可以单独使用,也可以和其他注解一起使用,常见的用法是结合@Configuration注解、ImportSelector接口和ImportBeanDefinitionRegistrar接口在Spring Boot中实现自动配置。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* [@Import注解的四种使用方式](https://blog.csdn.net/bluemysky/article/details/128827769)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] - *3* [@Import注解使用](https://blog.csdn.net/m0_55806905/article/details/127967036)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值