springboot自动配置原理

172 阅读2分钟

在SpringBoot3中spring.factories配置的自动装配不生效

  • 在Spring Boot 2.7还是可以兼容使用spring.factories
  • 到了SpringBoot3 spring.factories就不兼容使用了。

spring.factories其实是SpringBoot提供的SPI机制,底层实现是基于SpringFactoriesLoader检索ClassLoader中所有jar(包括ClassPath下的所有模块)引入的META-INF/spring.factories文件,基于文件中的接口(或者注解)加载对应的实现类并且注册到IOC容器。这种方式对于@ComponentScan不能扫描到的并且想自动注册到IOC容器的使用场景十分合适,基本上绝大多数第三方组件甚至部分spring-projects中编写的组件都是使用这种方案。

原文链接:blog.csdn.net/BASK2311/ar…

image.png

image.png

image.png

如何扫描到的呢 springboot默认只能扫描到自己主程序所在的包及其子包,扫描不到springboot-autoconfigure包中官方提供好的配置类

@EnableAutoConfiguration

springboot开启自动配置的核心注解 image.png

@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {

   /**
    * Environment property that can be used to override when auto-configuration is
    * enabled.
    */
   String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";

   /**
    * Exclude specific auto-configuration classes such that they will never be applied.
    * @return the classes to exclude
    */
   Class<?>[] exclude() default {};

   /**
    * Exclude specific auto-configuration class names such that they will never be
    * applied.
    * @return the class names to exclude
    * @since 1.3.0
    */
   String[] excludeName() default {};

}
@Import(AutoConfigurationImportSelector.class) 批量给容器导入组件
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
   List<String> configurations = ImportCandidates.load(AutoConfiguration.class, getBeanClassLoader())
      .getCandidates();
   Assert.notEmpty(configurations,
         "No auto configuration classes found 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;
}
public final class ImportCandidates implements Iterable<String> {

   private static final String LOCATION = "META-INF/spring/%s.imports";

   private static final String COMMENT_START = "#";

   private final List<String> candidates;

   private ImportCandidates(List<String> candidates) {
      Assert.notNull(candidates, "'candidates' must not be null");
      this.candidates = Collections.unmodifiableList(candidates);
   }

   @Override
   public Iterator<String> iterator() {
      return this.candidates.iterator();
   }

   /**
    * Returns the list of loaded import candidates.
    * @return the list of import candidates
    */
   public List<String> getCandidates() {
      return this.candidates;
   }

   /**
    * Loads the names of import candidates from the classpath.
    *
    * The names of the import candidates are stored in files named
    * {@code META-INF/spring/full-qualified-annotation-name.imports} on the classpath.
    * Every line contains the full qualified name of the candidate class. Comments are
    * supported using the # character.
    * @param annotation annotation to load
    * @param classLoader class loader to use for loading
    * @return list of names of annotated classes
    */
   public static ImportCandidates load(Class<?> annotation, ClassLoader classLoader) {
      Assert.notNull(annotation, "'annotation' must not be null");
      ClassLoader classLoaderToUse = decideClassloader(classLoader);
      String location = String.format(LOCATION, annotation.getName());
      Enumeration<URL> urls = findUrlsInClasspath(classLoaderToUse, location);
      List<String> importCandidates = new ArrayList<>();
      while (urls.hasMoreElements()) {
         URL url = urls.nextElement();
         importCandidates.addAll(readCandidateConfigurations(url));
      }
      return new ImportCandidates(importCandidates);
   }

   private static ClassLoader decideClassloader(ClassLoader classLoader) {
      if (classLoader == null) {
         return ImportCandidates.class.getClassLoader();
      }
      return classLoader;
   }

   private static Enumeration<URL> findUrlsInClasspath(ClassLoader classLoader, String location) {
      try {
         return classLoader.getResources(location);
      }
      catch (IOException ex) {
         throw new IllegalArgumentException("Failed to load configurations from location [" + location + "]", ex);
      }
   }

   private static List<String> readCandidateConfigurations(URL url) {
      try (BufferedReader reader = new BufferedReader(
            new InputStreamReader(new UrlResource(url).getInputStream(), StandardCharsets.UTF_8))) {
         List<String> candidates = new ArrayList<>();
         String line;
         while ((line = reader.readLine()) != null) {
            line = stripComment(line);
            line = line.trim();
            if (line.isEmpty()) {
               continue;
            }
            candidates.add(line);
         }
         return candidates;
      }
      catch (IOException ex) {
         throw new IllegalArgumentException("Unable to load configurations from location [" + url + "]", ex);
      }
   }

   private static String stripComment(String line) {
      int commentStart = line.indexOf(COMMENT_START);
      if (commentStart == -1) {
         return line;
      }
      return line.substring(0, commentStart);
   }

}

image.png

总结 通过@Import(AutoConfigurationImportSelector.class) 将META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports文件中定义的xxxAutoConfiguration类导入容器中

springboot3 由于 Java EE 已经变更为 Jakarta EE,包名以 javax开头的需要相应地变更为jakarta

image.png