熊大
话说公司入职一个妹子妹子长相如何呢?那是增之一分则太长,减之一分则太短;著粉则太白,施朱则太赤;眉如翠羽,肌如白雪。
一天妹子过来问我熊哥你知道spring boot是如何装配得呢?当然知道在 spring boot 启动加载spring-boot-autaoconfigure模块然后解析sring.factories
文件。里面维护了db、mq、es等一些常用得中间件他会根据你的spring-boot-stater找到你所需要得类通过配置文件自动注入到ioc容器中省却了各种xml配置简直是谁用谁知道。
[如果想具体了解]添加链接描述
妹子看我吐沫乱飞默默得问了一句那你知道他是什么时候加载得嘛?
我。。。。。。。
古语云:知耻而后勇!
今天咱就搞明白争取下次在妹子面前表现一把!
上文所知他是实现了spring 扩展了DeferredImportSelector.selectImports();这个方法那这个方法究竟实在那一步调用得呢?
打开启动一步一步往下找启动类方法调用图如下:
它先创建了上下文、然后刷新上下文重点看下refreshContext这个方法干了什么?
protected void refresh(ApplicationContext applicationContext){
((AbstractApplicationContext) applicationContext).refresh();
}
看到AbstractApplicationConext.refresh()这个方法是不是眼前一亮呢?
因为refresh里面干得事情比较多而且这次只是探讨
spring在哪一步对DeferredImportSelector这个接口处理故舍弃了部分方法调用直奔主题下面是方法调用图:
最后发现它最后调用了ConfigurationClassParser.parse 这个方法。
备注:ConfigurationClassParser这个类是spring
比较重要的一个类他是专门处理@Import和@Configuration注解的。
为什么这样说先买一个关子。
我们先看下parse方法里面干了什么
for (BeanDefinitionHolder holder : configCandidates) {
BeanDefinition bd = holder.getBeanDefinition();
try {
if (bd instanceof AnnotatedBeanDefinition) {
parse(((AnnotatedBeanDefinition) bd).getMetadata(), holder.getBeanName());
}
else if (bd instanceof AbstractBeanDefinition && ((AbstractBeanDefinition) bd).hasBeanClass()) {
parse(((AbstractBeanDefinition) bd).getBeanClass(), holder.getBeanName());
}
else {
parse(bd.getBeanClassName(), holder.getBeanName());
}
}
catch (BeanDefinitionStoreException ex) {
throw ex;
}
catch (Throwable ex) {
throw new BeanDefinitionStoreException(
"Failed to parse configuration class [" + bd.getBeanClassName() + "]", ex);
}
}
processDeferredImportSelectors();
}
可以看到这个parse方法里面会根据不同得BeanDefinition类型走不同得解析方法。因为parse源码比较多我把里面重要得调用的关系罗列下然后我们再看其中几个重要方法得代码。
说明下其实getImport是processImpots里面得其中一个方法而collectImport是getImports里面得方法。为什么把collectImports单独列出来呢?我们看下源码就知道了。
private void collectImports(SourceClass sourceClass,
Set<SourceClass> imports, Set<SourceClass> visited)
throws IOException {
if (visited.add(sourceClass)) {
for (SourceClass annotation : sourceClass.getAnnotations()) {
String annName = annotation.getMetadata().getClassName();
if (!annName.startsWith("java")&&!annName.equals(
Import.class.getName())) {
collectImports(annotation, imports, visited);
}
}
imports.addAll(
sourceClass.getAnnotationAttributes(
Import.class.getName(),
"value"));
}
}
可以看出这里处理@Import注解并且将它得value值放到了一个集合里面这个value值其实就是import那个bean。
此时翻过来想一想spring boot 自动装配利用了组合注解但是最后@Import导入了一个bean 而这个bean则是实现了spring 提供给我们得扩展接口。
解析完毕继续往下看processDeferredImportSelectors()这个方法。通过名称其实可以猜出来他是专门处理DeferredImportSelector接口得方法得。废话不多说直接放源码!
for (DeferredImportSelectorHolder deferredImport :deferredImports) {
ConfigurationClass configClass=
deferredImport.getConfigurationClass();
try {
String[] imports = deferredImport.getImportSelector()
.selectImports(configClass.getMetadata());
}
catch (BeanDefinitionStoreException ex) {
throw ex;
}
}
到此终于看到了deferredImport.getImportSelector().selectImports(configClass.getMetadata());这个就是真正处理spring boot 自动装配得地方。
这个也就说spring boot 自动装配其实还是利用了spring 提供给我们可扩展得接口DeferredImportSelector。其中这个接口是延迟加载上面那个方法调用图在processDeferredImportSelectors()这个方法调用之后会循环调用再次解析
当然这个接口加载得顺序实可以控制的它可以通过实现oder接口来定义加载顺序。spring boot 自动装配则是默认级别。如果我们用了spring cloud 其中eurake client 加载顺序要高于自动装配得加载顺序。