@Import与@ImportResource注解的分析
发布时间:2021-12-05 10:46:45 所属栏目:教程 来源:互联网
导读:前言 在使用Spring-Cloud微服务框架的时候,对于@Import和@ImportResource这两个注解想必大家并不陌生。我们会经常用@Import来导入配置类或者导入一个带有@Component等注解要放入Spring容器中的类;用@ImportResource来导入一个传统的xml配置文件。另外,在
前言 在使用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.linuxidc.com/Linux/2019-08/160330.htm)。我们知道在初始化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等注解,这样做就能保证不会漏掉。 好了,基本解读就到这里,如果其中有不准确之处,还请各位道友指正。 ![]() (编辑:青岛站长网) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |