首页 > 其他分享 >从 @Import 注解到自动配置

从 @Import 注解到自动配置

时间:2024-11-30 20:11:35浏览次数:6  
标签:spring org boot autoconfigure 自动 springframework 注解 Import configurations

推荐阅读:Spring @Import 注解用法Spring Boot Starter 是什么

@Import 注解用于把实例加入 Spring IOC 容器中。

打开 @SpringBootApplication 注解(Spring Boot 2.6.13 为例),会发现其被 @EnableAutoConfiguration 标注:

@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 {
}

打开 @EnableAutoConfiguration 注解,发现其被 @Import 注解:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
}

提供给 @Import 注解的参数为 AutoConfigurationImportSelector.class,它实现了 DeferredImportSelector 接口,DeferredImportSelector 接口继承自 ImportSelector 接口,其核心为 selectImports 方法,用来返回需要由容器管理的类的全类名。下面是 AutoConfigurationImportSelector 的方法实现:

@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
  if (!isEnabled(annotationMetadata)) {
    return NO_IMPORTS;
  }
  AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(annotationMetadata);
  return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}

其中的 getAutoConfigurationEntry 方法,返回找到的自动配置类。方法如下:

protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
  if (!isEnabled(annotationMetadata)) {
    return EMPTY_ENTRY;
  }
  AnnotationAttributes attributes = getAttributes(annotationMetadata);
  List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
  configurations = removeDuplicates(configurations);
  Set<String> exclusions = getExclusions(annotationMetadata, attributes);
  checkExcludedClasses(configurations, exclusions);
  configurations.removeAll(exclusions);
  configurations = getConfigurationClassFilter().filter(configurations);
  fireAutoConfigurationImportEvents(configurations, exclusions);
  return new AutoConfigurationEntry(configurations, exclusions);
}

重点是 getCandidateConfigurations 方法,该方法会扫描到所有自动配置类。返回值经过去重、过滤等操作后,封装到 AutoConfigurationEntry 对象中返回。进入方法内部:

protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
  List<String> configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(),
      getBeanClassLoader());
  Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you "
      + "are using a custom packaging, make sure that file is correct.");
  return configurations;
}

getSpringFactoriesLoaderFactoryClass 方法返回 EnableAutoConfiguration.class,后面用来找自动配置类:

protected Class<?> getSpringFactoriesLoaderFactoryClass() {
  return EnableAutoConfiguration.class;
}

SpringFactoriesLoader.loadFactoryNames 方法会返回一个 List,这个 List 中包含的是所有自动配置类的全类名。进入方法:

public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
  ClassLoader classLoaderToUse = classLoader;
  if (classLoaderToUse == null) {
    classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();
  }
  String factoryTypeName = factoryType.getName();
  return loadSpringFactories(classLoaderToUse).getOrDefault(factoryTypeName, Collections.emptyList());
}

loadSpringFactories 方法会返回一个 Map,其 key 为全类名,value 为全类名对应的类名集合。进入方法:

private static Map<String, List<String>> loadSpringFactories(ClassLoader classLoader) {
  Map<String, List<String>> result = cache.get(classLoader);
  if (result != null) {
    return result;
  }

  result = new HashMap<>();
  try {
    Enumeration<URL> urls = classLoader.getResources(FACTORIES_RESOURCE_LOCATION);
    while (urls.hasMoreElements()) {
      URL url = urls.nextElement();
      UrlResource resource = new UrlResource(url);
      Properties properties = PropertiesLoaderUtils.loadProperties(resource);
      for (Map.Entry<?, ?> entry : properties.entrySet()) {
        String factoryTypeName = ((String) entry.getKey()).trim();
        String[] factoryImplementationNames =
            StringUtils.commaDelimitedListToStringArray((String) entry.getValue());
        for (String factoryImplementationName : factoryImplementationNames) {
          result.computeIfAbsent(factoryTypeName, key -> new ArrayList<>())
              .add(factoryImplementationName.trim());
        }
      }
    }

    // Replace all lists with unmodifiable lists containing unique elements
    result.replaceAll((factoryType, implementations) -> implementations.stream().distinct()
        .collect(Collectors.collectingAndThen(Collectors.toList(), Collections::unmodifiableList)));
    cache.put(classLoader, result);
  }
  catch (IOException ex) {
    throw new IllegalArgumentException("Unable to load factories from location [" +
        FACTORIES_RESOURCE_LOCATION + "]", ex);
  }
  return result;
}

重点关注Enumeration<URL> urls = classLoader.getResources(FACTORIES_RESOURCE_LOCATION),其中FACTORIES_RESOURCE_LOCATION的值为META-INF/spring.factories

public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";

打开spring-boot-autoconfigure-2.6.13.jar的 META-INF/spring.factories 文件,可以看到包含如下内容:

# Auto Configuration Import Filters
org.springframework.boot.autoconfigure.AutoConfigurationImportFilter=\
org.springframework.boot.autoconfigure.condition.OnBeanCondition,\
org.springframework.boot.autoconfigure.condition.OnClassCondition,\
org.springframework.boot.autoconfigure.condition.OnWebApplicationCondition

# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\
org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration,\
org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration,\
...

loadSpringFactories 方法的作用就是,扫描 META-INF/spring.factories 文件,将文件中的配置信息加载为一个 Map,其中 key 为全类名,value 为全类名对应的类名集合。

最终 loadFactoryNames 中的loadSpringFactories(classLoaderToUse).getOrDefault(factoryTypeName, Collections.emptyList())的返回值就是所有自动配置类的全类名。

注意 loadSpringFactories 方法会扫描到类路径下所有 jar 包中的 META-INF/spring.factories 文件,包括但不限于spring-boot-autoconfigure-2.6.13.jar

这样,Spring 容器就知道要加载哪些自动配置类了。


Spring Boot 2.7 之后,改为通过 META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports 来声明自动配置类。其 getCandidateConfigurations 方法如下(以 Spring Boot 2.7.18 为例):

protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
  List<String> configurations = new ArrayList<>(
      SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader()));
  ImportCandidates.load(AutoConfiguration.class, getBeanClassLoader()).forEach(configurations::add);
  Assert.notEmpty(configurations,
      "No auto configuration classes found in META-INF/spring.factories nor in META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports. If you "
          + "are using a custom packaging, make sure that file is correct.");
  return configurations;
}

可以看到它除了调用 SpringFactoriesLoader.loadFactoryNames 方法外,还调用了 ImportCandidates.load 方法,也就是说此时可以通过 META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports 和 META-INF/spring.factories 两种方法来声明自动配置类。

标签:spring,org,boot,autoconfigure,自动,springframework,注解,Import,configurations
From: https://www.cnblogs.com/Higurashi-kagome/p/18578804

相关文章

  • 类的析构函数自动调用
    类的析构函数会自动调用。析构函数是在对象的生命周期结束时由系统自动调用的特殊成员函数,主要用于释放对象占用的资源,执行清理工作。具体情况自动调用的时机:局部对象:当对象离开其作用域时,析构函数会自动调用。#include<iostream>classMyClass{public:~MyClass(......
  • 深度学习1:从图像识别到自动驾驶:深度学习如何引领未来出行新趋势?
    文章目录导读一、机器学习与经验积累:一场智慧的碰撞二、深度学习的四大基石:数据模型目标函数与算法三、深度学习的应用场景:从多标签分类到强化学习四、深度学习的发展历程:从“重新发现”到“寒武纪大爆发”五、深度学习的成功案例:从智能助理到自动驾驶结语:迎接智......
  • 【3分钟学会】一招禁用表单中input输入框回车键自动触发提交事件!
    知其然知其所以然在前端项目开发中,偶尔会有表单提交的问题:用户输入表单后,不小心按了回车键,导致提前触发了提交事件?问题概述当表单中仅有一个input输入框时,按下回车键就会自动触发提交事件,这是为什么呢?这里就要提到一个标准:W3C文中最后一句话已经解释了原因:当表单中只有......
  • python实现自动计算排工期
    一、需求说明如果大家涉及到项目管理的话,那么一定逃不掉规划工期。由于在规划工期的时候需要考虑将法定节假日和周六日进行去除,所以许多人都会打开日历,一天一天的去计算。这样效率即慢,同时也容易出现错误,那么如何快速实现工期的排定呢?假设开发的过程和顺序如下(其他行业的当......
  • 微信消息塞爆?自动回复功能让你轻松应对!
    在商业竞争日趋白热化的今天,客户信息成百上千纷至沓来的时候。我们常常会显得应接不暇。这时,一款多微管理系统的出现给我们带来了极大的便利。 首先,登录你的微信号。如果此时你拥有20甚至是30个微信号时,告诉你,这款系统它能支持同时登录多个账号,完全无需担心会受到数量的......
  • KUKA机器人中断编程5—自动回原点功能的编程
        在KUKA机器人的调试中,每次当工作流程中断后需要重新进行测试,机器人就要回到程序的原点即Home点,这时就需要手动操作先将机器人移动到安全位置,再回到Home点。有时会觉得每次都通过手动操作回原点比较麻烦,想通过一个信号给机器人,机器人就能自动回原点。对于这种需求也......
  • 修复 Docker Ubuntu 容器中 Tab 自动补全与上下键历史命令失效问题
    1简介在使用Docker容器运行Ubuntu系统时,有时会遇到Tab键自动补全和上下键历史命令失效的问题。这通常是由于终端模拟器的设置不当引起的。2解决方案2.1安装bash-completebash-completion是一个增强的命令补全工具,能够为许多常用的命令提供智能补全。这在复杂的命令行操作......
  • Docker Compose 优先拉取镜像,若无镜像则自动构建并启动服务
    言简意赅的讲解DockerComposebuild解决的痛点之前我介绍了Docker的一些基础使用和常见问题。后续遇见了拉取不到镜像的网络问题,解决了网络问题之后,有些童鞋反馈很多项目现场无法拉取镜像,甚至有内网限制的情况,需要优先拉取镜像否则本地构建,大家的需求越来越复杂,那本文......
  • ImportError: /nvidia/cusparse/lib/libcusparse.so.12: undefined symbol: __nvJitLi
      大家好,我是爱编程的喵喵。双985硕士毕业,现担任全栈工程师一职,热衷于将数据思维应用到工作与生活中。从事机器学习以及相关的前后端开发工作。曾在阿里云、科大讯飞、CCF等比赛获得多次Top名次。现为CSDN博客专家、人工智能领域优质创作者。喜欢通过博客创作的方式对所学......
  • 手动批量注入service 自动依赖注入 C# asp.net dontet 依赖注入
    手动批量注入service自动依赖注入C#asp.netdontet依赖注入publicstaticclassServiceCollectionExtensions{//批量注入所有的继承IBaseService的ServicepublicstaticvoidAddPDAServices(thisIServiceCollectionservices){varassemblies......