前言

在使用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等注解,这样做就能保证不会漏掉。

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

@Import与@ImportResource注解的解读的更多相关文章

  1. Spring框架中的@Import、@ImportResource注解

    spring@Import @Import注解在4.2之前只支持导入配置类 在4.2,@Import注解支持导入普通的java类,并将其声明成一个bean 使用场景: import注解主要用在基于ja ...

  2. SpringBoot开发使用@ImportResource注解影响拦截器

    问题描述 今天在给SpringBoot项目配置拦截器的时候发现怎么都进不到拦截器的方法里面,在搜索引擎上看了无数篇关于配置拦截器的文章都没有找到解决方案. 就在我准备放弃的时候,在 CSDN 上发现了 ...

  3. spring ioc 源码分析之-- beanDefinition的加载过程以及ComponentScan,@componet,@import @Bean等注解解析过程

    背景:我们启动主启动类后,相应的bean就被扫描进来了,原理是啥? 实现该功能的主要核心类就是:ConfigurationClassPostProcessor,我们看看他的继承体系: 它实现了Bean ...

  4. 14 - springboot的@Configuration、@Bean、@Import()、@ImportResource()、@Conditional说明

    1.@Configuration.@Bean.@Import().@ImportResource().@Conditional 分析源码的时候总会见到标题中的这几个注解,因此:弄一篇博客来说明一下吧, ...

  5. spring源码分析之@ImportSelector、@Import、ImportResource工作原理分析

    1. @importSelector定义: /** * Interface to be implemented by types that determine which @{@link Config ...

  6. SpringMVC源码解读 - RequestMapping注解实现解读 - RequestMappingInfo

    使用@RequestMapping注解时,配置的信息最后都设置到了RequestMappingInfo中. RequestMappingInfo封装了PatternsRequestCondition, ...

  7. SpringMVC源码解读 - RequestMapping注解实现解读 - RequestCondition体系

    一般我们开发时,使用最多的还是@RequestMapping注解方式. @RequestMapping(value = "/", param = "role=guest& ...

  8. SpringMVC源码解读 - RequestMapping注解实现解读

    SpringMVC源码解读 - RequestMapping注解实现解读 - RequestCondition体系  https://www.cnblogs.com/leftthen/p/520840 ...

  9. spring 中的@Import注解和@ImportResource注解

    概述:@Import注解是引入带有@Configuration的java类. @ImportResource是引入spring配置文件.xml 案例的核心代码如下: package com.timo. ...

随机推荐

  1. Java修饰符public,protected,default,private访问权限

    public 具有最大的访问权限.所有类可访问. protected 主要是用来保护子类.自身.子类及同一个包中类可以访问 default 没有加修饰符的.有时候也称为friendly,它是针对本包访 ...

  2. python基础入门while循环 格式化 编码初识

    一.while循环 1.格式 while+空格+条件+英文冒号: ​ 缩进+结果(循环体) ​ #若条件为真则一直执行,条件为假则不执行 while True: print('痒') print('. ...

  3. hdu 6318 Swaps and Inversions (线段树求逆序对数)

    Swaps and Inversions Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Oth ...

  4. getX,getY,getScrollX,getScrollY,ScrollTo(),ScrollBy()辨析

    前言:前两天看了自定义控件,其中有一些东西我觉得有必要深入理解一下 以下图为例: getX(),getY()返回的是触摸点A相对于view的位置 getRaw(),getRawY()返回的是触摸点B相 ...

  5. 《Java基础知识》Java正则表达式

    正则表达式定义了字符串的模式. 正则表达式可以用来搜索.编辑或处理文本. 正则表达式并不仅限于某一种语言,但是在每种语言中有细微的差别. 正则表达式实例 一个字符串其实就是一个简单的正则表达式,例如  ...

  6. VS2017 无法修改代码编辑区的项背景颜色问题

    以前都是好好的,安装  ClaudiaIDE: https://github.com/buchizo/ClaudiaIDE 之后也没啥问题,用着用着代码编辑区自定义的颜色就没有了,好几台电脑都是这样, ...

  7. .NET Core的JWT认证的学习

    今天学习下JWT,遇到了两个坑爹问题,这里记录下.在 ASP.NET Core 中,授权的设置方式有两种,可以使用角色,也可以使用策略,这里也将简单举例角色.策略的使用. JWT这里不做介绍,如果想了 ...

  8. SAP QA32试图做UD,系统报错-工厂 BTYC中的 QM 基选设置需要维护

    SAP QA32 试图做UD,系统报错 - 工厂 BTYC 中的 QM 基选设置需要维护 - 检验批 10000062593,试图做使用决策,系统报错, 工厂 BTYC 中的 QM 基选设置需要维护 ...

  9. iOS开发-APP图标、启动页、名字的设置

    APP图标.启动页.名字的设置:(较全面,但是APP启动页讲述的有漏洞) 参考链接:https://www.jianshu.com/p/2c7e181276ff APP启动页:(弥补上一文的漏洞) 参 ...

  10. 18.JAVA-pull解析XML

    1.pull解析介绍 pull解析其实和我们上章学的sax解析原理非常类似,在Android中非常常用. 在java中,需自己获取xmlpull所依赖的类库分别为:kxml2-2.3.0.jar,xm ...