@Import與@ImportResource註解的解讀

前言html

    在使用Spring-Cloud微服務框架的時候,對於@Import和@ImportResource這兩個註解想必你們並不陌生。咱們會常常用@Import來導入配置類或者導入一個帶有@Component等註解要放入Spring容器中的類;用@ImportResource來導入一個傳統的xml配置文件。另外,在啓用不少組件時,咱們會用到一個形如@EnableXXX的註解,好比@EnableAsync、@EnableHystrix、@EnableApollo等,點開這些註解往裏追溯,你也會發現@Import的身影。如此看來,這兩個註解與咱們平時的開發關係密切,但你們知道它們是如何發揮做用的嗎?下面就一塊兒探索一下。react

正文web

    首先看這兩個註解的路徑,它們都位於org.springframework.context.annotation包下,能夠說是根正苗紅的Spring註解,因此對這兩個註解的處理,更多的也是在原有的Spring框架中進行的。在Spring-Cloud啓動類的run方法中,經過簡單的追溯咱們能夠定位到這個run方法(僅部分代碼):spring

 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方法中。app

這時就須要參考以前一篇博文中的內容了(地址http://www.javashuo.com/article/p-bsnnyolk-er.html)。咱們知道在初始化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就是這樣加載到容器中的。less

一樣經過那篇博文咱們知道,是在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文件。post

定位處處理@ImportResource的地方:ui

 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等註解,這樣作就能保證不會漏掉。

好了,基本解讀就到這裏,若是其中有不許確之處,還請各位道友指正,咱們下期再見?

相關文章
相關標籤/搜索