SpringBoot中的自动装载

(1)ImportSelector接口

ImportSelector接口是Spring导入外部配置的核心接口,在SpringBoot的自动化配置和@EnableXXX(功能性注解)中起到了决定性的作用。当在@Configuration标注的Class上使用@Import引入了一个ImportSelector实现类后,会把ImportSelector实现类中返回的Class名称都定义为bean。

public interface ImportSelector {String[] selectImports(AnnotationMetadata var1);
}

DeferredImportSelector接口继承ImportSelector,他和ImportSelector的区别在于装载bean的时机上,DeferredImportSelector需要等所有的@Configuration都执行完毕后才会进行装载

public interface DeferredImportSelector extends ImportSelector {//...省略
}

(2)模拟SpringBoot自动装配的实现

接下来我们写一个小例子,看下ImportSelector接口的用法

通过模拟实现ImportSelector接口来还原SpringBoot自动装配的原理(SpringBoot的自动装配原理就是通过封装ImportSelector接口等一些列操作来实现的)

1)定义Bean对象

这个类模拟的是我们要自动专配在SpringBoot容器中的Bean实例

public class User {private String username;private Integer age;//省略..
}

2)定义配置类Configuration

//定义一个configuration
//注意这里并没有使用spring注解
//spring扫描的时候并不会装载该类
public class UserConfiguration {@Beanpublic User getUser() {return new User("张三",18);}
}

3 ) 定义ImportSelector接口的实现类

ImportSelector 接口中的selectImports方法会将加载的类当作一个配置类来初始化,就是相当于@Configuration注解的功能。

public class UserImportSelector implements ImportSelector {@Overridepublic String[] selectImports(AnnotationMetadata importingClassMetadata) {//获取配置类名称return new String[]{UserConfiguration.class.getName()};}
}

4) 定义EnableXXX注解

在SpringBoot在自动装配正式通过注解@EnableAutoCofiguration注解实现的,这里通过自定义EnableXXX注解模拟SpringBoot中的@EnableAutoConfiguration注解实现自动装配。

@SpringBoot源码:

@Retention(RetentionPolicy.RUNTIME)
@Documented
@Target(ElementType.TYPE)
@Import(UserImportSelector.class)
public @interface EnableUserBean {}

5 ) 测试

/**
* 通过在类上声明@EnableUserBean,会自动的加载所有对象
*/
@EnableUserBean
public class TestApplication {public static void main(String[] args) {//获取sprinboot注解驱动的容器对象AnnotationConfigApplicationContext applicationContext =new AnnotationConfigApplicationContext(TestApplication.class);//加载通过实现ImportSelector接口模拟SpringBoot自动装载功能,将声明的User对象的实例装配到SpringBoot的容器中//获取SpringBoot容器中的User的实例对象User user = applicationContext.getBean(User.class);System.out.println(user);}
}

由此可见,User对象并没有使用Spring的对象创建注解声明(@Controller,@Service,@Repostiroty),而是使用编程的方式动态的载入bean。

这个接口在哪里调用呢?我们可以来看一下ConfigurationClassParser这个类的processImports方法

private void processImports(ConfigurationClass configClass, SourceClass
currentSourceClass,Collection<SourceClass> importCandidates, boolean
checkForCircularImports) {if (importCandidates.isEmpty()) {return;}if (checkForCircularImports && isChainedImportOnStack(configClass)) {this.problemReporter.error(new CircularImportProblem(configClass,
this.importStack));}else {this.importStack.push(configClass);try {for (SourceClass candidate : importCandidates)
{ //对ImportSelector的处理if (candidate.isAssignable(ImportSelector.class)) {// Candidate class is an ImportSelector -> delegate to
it to determine importsClass<?> candidateClass = candidate.loadClass();ImportSelector selector =
BeanUtils.instantiateClass(candidateClass, ImportSelector.class);ParserStrategyUtils.invokeAwareMethods(selector, this.environment, this.resourceLoader,
this.registry);if (this.deferredImportSelectors != null && selector
instanceof DeferredImportSelector) { //如果为延迟导入处理
则加入集合当中this.deferredImportSelectors.add(new
DeferredImportSelectorHolder(configClass, (DeferredImportSelector) selector));}else { //根据ImportSelector方法
的返回值来进行递归操作String[] importClassNames =
selector.selectImports(currentSourceClass.getMetadata());Collection<SourceClass> importSourceClasses =
asSourceClasses(importClassNames);processImports(configClass, currentSourceClass,
importSourceClasses, false);}}else if
(candidate.isAssignable(ImportBeanDefinitionRegistrar.class)) {// Candidate class is an ImportBeanDefinitionRegistrar -
>// delegate to it to register additional bean
definitionsClass<?> candidateClass = candidate.loadClass();ImportBeanDefinitionRegistrar registrar =BeanUtils.instantiateClass(candidateClass,
ImportBeanDefinitionRegistrar.class);ParserStrategyUtils.invokeAwareMethods(registrar, this.environment,
this.resourceLoader, this.registry);configClass.addImportBeanDefinitionRegistrar(registrar,
currentSourceClass.getMetadata());}else { // 如果当前的类既不是
ImportSelector也不是ImportBeanDefinitionRegistar就进行@Configuration的解析处理// Candidate class not an ImportSelector or
ImportBeanDefinitionRegistrar ->// process it as an @Configuration classthis.importStack.registerImport(currentSourceClass.getMetadata(),
candidate.getMetadata().getClassName());processConfigurationClass(candidate.asConfigClass(configClass));}}}catch (BeanDefinitionStoreException ex) {throw ex;}catch (Throwable ex) {throw new BeanDefinitionStoreException("Failed to process import candidates for configuration
class [" +configClass.getMetadata().getClassName() + "]", ex);}finally {this.importStack.pop();}}}

(3)springBoot自动装载原理解析

SpringBoot开箱即用的特点,很大程度上归功于ImportSelector接口。接下来我们看下springBoot是如何在spring的基础上做扩展的。
在SpringBoot中最重要的一个注解SpringBootApplication

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = {@Filter(type = FilterType.CUSTOM,classes = {TypeExcludeFilter.class}
), @Filter(type = FilterType.CUSTOM,classes = {AutoConfigurationExcludeFilter.class}
)}
)
public @interface SpringBootApplication {//...
}

在SpringBootApplication注解中声明了一个 @EnableAutoConfiguration注解

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import({AutoConfigurationImportSelector.class})
public @interface EnableAutoConfiguration {String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";Class<?>[] exclude() default {};String[] excludeName() default {};
}

在EnableAutoConfiguration接口中通过@Import注解引入了SpringBoot定义的AutoConfigurationImportSelector类,这个类内容比较多,我们只需看下最主要的逻辑代码即可

public class AutoConfigurationImportSelectorimplements DeferredImportSelector, BeanClassLoaderAware,
ResourceLoaderAware,BeanFactoryAware, EnvironmentAware, Ordered {@Overridepublic String[] selectImports(AnnotationMetadata annotationMetadata) {if (!isEnabled(annotationMetadata)) {return NO_IMPORTS;}AutoConfigurationMetadata autoConfigurationMetadata =
AutoConfigurationMetadataLoader.loadMetadata(this.beanClassLoader);//主要逻辑在getAutoConfigurationEntry这个方法AutoConfigurationEntry autoConfigurationEntry =
getAutoConfigurationEntry(autoConfigurationMetadata, annotationMetadata);return
StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());}protected AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata,AnnotationMetadata annotationMetadata) {if (!isEnabled(annotationMetadata)) {return EMPTY_ENTRY;}AnnotationAttributes attributes = getAttributes(annotationMetadata);//通过getCandidateConfigurations方法获取所有需要加载的beanList<String> configurations =
getCandidateConfigurations(annotationMetadata,attributes);//去重处理configurations = removeDuplicates(configurations);//获取不需要加载的bean,这里我们可以通过spring.autoconfigure.exclude人为配置Set<String> exclusions = getExclusions(annotationMetadata, attributes);checkExcludedClasses(configurations, exclusions);configurations.removeAll(exclusions);configurations = filter(configurations, autoConfigurationMetadata);//发送事件,通知所有的AutoConfigurationImportListener进行监听fireAutoConfigurationImportEvents(configurations, exclusions);return new AutoConfigurationEntry(configurations, exclusions);}//这里是获取bean渠道的地方,重点看SpringFactoriesLoader#loadFactoryNamesprotected List<String> getCandidateConfigurations(AnnotationMetadata
metadata,AnnotationAttributes attributes) {//这里的getSpringFactoriesLoaderFactoryClass()最终返回
EnableAutoConfiguration.classList<String> configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader());Assert.notEmpty(configurations,"No auto configuration classes found in METAINF/spring.factories. If you "+ "are using a custom packaging, make sure that file is
correct.");return configurations;}
}

从上面的逻辑可以看出,最终获取bean的渠道在SpringFactoriesLoader.loadFactoryNames

public final class SpringFactoriesLoader {public static final String FACTORIES_RESOURCE_LOCATION = "METAINF/spring.factories";private static final Log logger =
LogFactory.getLog(SpringFactoriesLoader.class);private static final Map<ClassLoader, MultiValueMap<String, String>> cache =
new ConcurrentReferenceHashMap();public static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable
ClassLoader classLoader) {String factoryClassName = factoryClass.getName();//通过factoryClassName获取相应的bean全称//上面传入的factoryClass是EnableAutoConfiguration.classreturn
(List)loadSpringFactories(classLoader).getOrDefault(factoryClassName,
Collections.emptyList());}private static Map<String, List<String>> loadSpringFactories(@Nullable
ClassLoader classLoader) {MultiValueMap<String, String> result = (MultiValueMap)cache.get(classLoader);if (result != null) {return result;} else {try {//获取工程中所有META-INF/spring.factories文件,将其中的键值组合成MapEnumeration<URL> urls = classLoader != null ?
classLoader.getResources("META-INF/spring.factories") :
ClassLoader.getSystemResources("META-INF/spring.factories");LinkedMultiValueMap result = new LinkedMultiValueMap();while(urls.hasMoreElements()) {URL url = (URL)urls.nextElement();UrlResource resource = new UrlResource(url);Properties properties =
PropertiesLoaderUtils.loadProperties(resource);Iterator var6 = properties.entrySet().iterator();while(var6.hasNext()) {Entry<?, ?> entry = (Entry)var6.next();String factoryClassName =
((String)entry.getKey()).trim();String[] var9 =
StringUtils.commaDelimitedListToStringArray((String)entry.getValue());int var10 = var9.length;for(int var11 = 0; var11 < var10; ++var11) {String factoryName = var9[var11];result.add(factoryClassName, factoryName.trim());}}}cache.put(classLoader, result);return result;} catch (IOException var13) {throw new IllegalArgumentException("Unable to load factories
from location [META-INF/spring.factories]", var13);}}}private static <T> T instantiateFactory(String instanceClassName, Class<T>
factoryClass, ClassLoader classLoader) {try {Class<?> instanceClass = ClassUtils.forName(instanceClassName,
classLoader);if (!factoryClass.isAssignableFrom(instanceClass)) {throw new IllegalArgumentException("Class [" + instanceClassName
+ "] is not assignable to [" + factoryClass.getName() + "]");} else {return ReflectionUtils.accessibleConstructor(instanceClass, new
Class[0]).newInstance();}} catch (Throwable var4) {throw new IllegalArgumentException("Unable to instantiate factory
class: " + factoryClass.getName(), var4);}}
}

每个jar都可以定义自己的META-INF/spring.factories ,jar被加载的同时 spring.factories里面定义的bean就可以自动被加载。

SpringBoot自动装载相关推荐

  1. SpringBoot的自动装载

    目录 核心逻辑 作用 实现过程 图解自动装配流程 核心逻辑 pom文件中添加starter起步依赖,如果你自己有配置,那就用你的配置,如果没有配置文件,就帮你配置,并且帮你创建对象放到IoC容器中 作 ...

  2. java 自动装载_springboot自动装载

    代码分析版本为2.1.4.RELEASE ! V: P8 _  m  d% `' d: C$ N. ~springboot是spring的拓展框架,不需要再写xml文件,来配置bean的加载,使开发更 ...

  3. springboot自动配置原理

    概述 Springboot的基本认识 对于Spring框架,我们接触得比较多的是Spring mvc,Spring IOC.AOP.DI.而这框架如果在使用过程中,随着项目越来越大,引入的技术越来越多 ...

  4. linux系统u盘自动升级,Archlinux 的U盘自动装载(二)升级到 udisks2

    在安装 mysql-gui-tools 过程中,发现需要用到 udisks2.udisks和udisks2可以互相替换也可以共用.最后决定换用 udisks2 安装 先卸载原来的软件,如果有的话. p ...

  5. SpringBoot自动装箱原理

    一.自动装箱流程图(关键注解) 二.SpringBoot中常用到的注解说明 @Configuration `表名这是一个配置类,将会被注入到容器中,等同于先前使用的xml配置文件. @Configur ...

  6. Feign底层原理分析-自动装载动态代理

    本篇文章仅介绍Feign的核心机制,包括如何交由Spring容器托管.动态代理机制等内容,不会过分深入细节. 1.什么是Feign? 这里套用Feign官方Github上的介绍:"Feign ...

  7. SpringBoot自动配置的原理及实现

    SpringBoot的核心就是自动配置,自动配置是基于条件判断配置Bean 自动配置的源码在spring-boot-autoconfigure-2.2.13.RELEASE SpringBoot运行原 ...

  8. 这样讲 SpringBoot 自动配置原理,你应该能明白了吧

    点击上方"方志朋",选择"设为星标" 回复"666"获取新整理的面试资料 作者:你在我家门口 juejin.im/post/5ce5effb ...

  9. 【Spring框架家族】SpringBoot自动配置基本实现

    SpringBoot自动配置-Condition_1 Condition是Spring4.0后引入的条件化配置接口,通过实现Condition接口可以完成有条件的加载相应的Bean @Conditio ...

最新文章

  1. 2021年大数据Spark(四十三):SparkStreaming整合Kafka 0.10 开发使用
  2. SAP PM 入门系列6 - PM常用表
  3. 一个不定宽高的元素如何在父元素中垂直水平居中
  4. 不符合条件重新输入 c语言,2016年计算机考试上机应试技巧
  5. Linux C :Linux 下第一个C程序
  6. html ajax 数据传送,HTML AJAX 简单数据JS
  7. 怎样修改t3服务器地址,怎样修改t3服务器地址
  8. Ubuntu 12.04 安装配置 Apache2
  9. 算法提高 质因数2(java)
  10. 一生都学不完的计谋(经典)
  11. 第 19 次 CCF CSP 认证 202006-1 线性分类器(line)
  12. 软件测试 - V模型、W模型、H模型、X模型
  13. php留言板验证验证码,留言板7 图形验证码
  14. Nginx工作原理及基本使用
  15. 2021-08-28-n皇后问题
  16. IDC中国大型企业SaaS云服务市场:金蝶位居第一
  17. 小米组织变革:新设三大部门,推进“手机X AIOT”战略落地
  18. 25 - 线程池和指令系统
  19. win10 电脑自带的便签在哪里
  20. 负数在计算机中的存储方式

热门文章

  1. oracle重载操作符的例子
  2. leetcode [114]Flatten Binary Tree to Linked List
  3. spring单元测试报错:Failed to load ApplicationContext 的解决方法
  4. 剑指Offer面试题:2.二维数组中的查找
  5. lintcode-34-N皇后问题 II
  6. 【转】pDc-SelectObject(pOldBrush)恢复画刷
  7. 三步搞定 opencv 初始环境设定
  8. 如何在 Windows 7 中建立逻辑分区
  9. ActiveRecord使用多数据库
  10. 春运又在路上了,火车购票、出行指南了解一下!