springboot 自動配置原理

CyrusHuang發表於2024-07-05

@SpringBootApplication

發現是一個複合註解 @SpringBootConfiguration、@EnableAutoConfiguration、@ComponentScan 由三個註解組合而來

@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 {
    ...
}
  1. @SpringBootConfiguration 等效於 @Configuration 註解(@Configuration 如果不清楚去看看前面 spring 的文章)

    @Target({ElementType.TYPE})
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Configuration
    public @interface SpringBootConfiguration {
        @AliasFor(
            annotation = Configuration.class
        )
        boolean proxyBeanMethods() default true;
    }
    
  2. @ComponentScan 是用來配置哪些包能被掃描,注意這裡只是規定哪些被掃描,並不會把這些包下面的類沒有註冊到容器(@EnableAutoConfiguration 會讀取 @ComponentScan 配置的資訊,然後根據配置進行註冊)

    簡言之就是 @ComponentScan 只管配置,@EnableAutoConfiguration 會根據配置來註冊

  3. @EnableAutoConfiguration 這個是核心,完成自動配置的註解,專門來說說這個

@EnableAutoConfiguration

可以看出是由兩個註解的複合註解 @AutoConfigurationPackage、@Import(AutoConfigurationImportSelector.class)

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

}

@AutoConfigurationPackage

  1. 這個註解的元註解可以看出,使用 @Import 匯入了一個 AutoConfigurationPackages.Registrar 型別的 bean

    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Inherited
    @Import(AutoConfigurationPackages.Registrar.class)
    public @interface AutoConfigurationPackage {
    
    }
    
  2. @Import 前面說的很清楚了,有三種用法

    @Import(A.class)                               // 匯入普通類
    @Import(MyImportSelector.class)                // 匯入實現了 ImportSelector 的類(可以批次註冊 bean,方法返回的陣列就是要匯入的 bean)
    @Import(MyImportBeanDefinitionRegister.class)  // 匯入實現了 ImportBeanDefinitionRegistrar 的類(也可以批次,透過註冊 BeanDefinition 的方式)
    

    @Import(AutoConfigurationPackages.Registrar.class) 就是使用的第三種方式,看看原始碼(這是個內部類)

    // org.springframework.boot.autoconfigure.AutoConfigurationPackages.Registrar
    static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {
    
        @Override
        public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
            // metadata 表示註解元資訊,獲取使用了這個註解的那個類上的註解資訊(我們是從 SpringBootApplication 跟進來的,也就是啟動類)
            register(registry, new PackageImports(metadata).getPackageNames().toArray(new String[0]));
        }
    
        @Override
        public Set<Object> determineImports(AnnotationMetadata metadata) {
            return Collections.singleton(new PackageImports(metadata));
        }
    
    }
    

    register 方法做了什麼(spring 的 方方面面詳細講了 @Import 怎麼註冊 BeanDefinition 的,這裡再大概看下)

    // org.springframework.boot.autoconfigure.AutoConfigurationPackages#register
    public static void register(BeanDefinitionRegistry registry, String... packageNames) {
        // 如果容器中的 beanFactory.beanDefinitionMap 已經有這個 bean 了(初識起動不會有)
        if (registry.containsBeanDefinition(BEAN)) {
            BeanDefinition beanDefinition = registry.getBeanDefinition(BEAN);
            ConstructorArgumentValues constructorArguments = beanDefinition.getConstructorArgumentValues();
            constructorArguments.addIndexedArgumentValue(0, addBasePackages(constructorArguments, packageNames));
        } else {
            // BeanDefinition 物件
            GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
            beanDefinition.setBeanClass(BasePackages.class);
            beanDefinition.getConstructorArgumentValues().addIndexedArgumentValue(0, packageNames);
            beanDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
            // 註冊 BeanDefinition
            registry.registerBeanDefinition(BEAN, beanDefinition);
        }
    }
    

    new PackageImports(metadata) 做了什麼,是在讀取 @ComponentScan 配置的資訊

    // org.springframework.boot.autoconfigure.AutoConfigurationPackages.PackageImports#PackageImports
    PackageImports(AnnotationMetadata metadata) {
        AnnotationAttributes attributes = AnnotationAttributes
                .fromMap(metadata.getAnnotationAttributes(AutoConfigurationPackage.class.getName(), false));
        List<String> packageNames = new ArrayList<>();
        // 先找 basePackages 屬性
        for (String basePackage : attributes.getStringArray("basePackages")) {
            packageNames.add(basePackage);
        }
        // 再找 basePackageClasses 屬性
        for (Class<?> basePackageClass : attributes.getClassArray("basePackageClasses")) {
            packageNames.add(basePackageClass.getPackage().getName());
        }
        // 如果都沒配置,那就使用啟動類所在的包
        if (packageNames.isEmpty()) {
            // metadata.getClassName() 啟動類的包
            packageNames.add(ClassUtils.getPackageName(metadata.getClassName()));
        }
        this.packageNames = Collections.unmodifiableList(packageNames);
    }
    

    這裡有一點要注意,如果什麼都不配置,只會匯入啟動類這一個包,並不是往上說的子包及以下的包

    比如我定義一個包裡面放一個 Controller(UserController)為什麼這個也會被註冊到容器,剛不是說子包不會被掃描到嗎?

    因為這只是 spring 的容器,springMVC 初始化的時候,自己還有個容器,會把這些再進行註冊!

@Import(AutoConfigurationImportSelector.class)

剛才的 @Import 是匯入了一個實現了 ImportBeanDefinitionRegistrar 的類,這個 @Import 匯入了一個實現了 ImportSelector 的類AutoConfigurationImportSelector 類繼承和實現關係:AutoConfigurationImportSelector > DeferredImportSelector > ImportSelector

// org.springframework.boot.autoconfigure.AutoConfigurationImportSelector#selectImports
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
    if (!isEnabled(annotationMetadata)) {
        return NO_IMPORTS;
    }
    // 主要看這個方法返回的什麼(返回的陣列都要被匯入容器)
    AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(annotationMetadata);
    return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}

org.springframework.boot.autoconfigure.AutoConfigurationImportSelector#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);
}

org.springframework.boot.autoconfigure.AutoConfigurationImportSelector#getCandidateConfigurations

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;
}

org.springframework.core.io.support.SpringFactoriesLoader#loadFactoryNames

public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
    String factoryTypeName = factoryType.getName();
    // 呼叫了本類的 loadSpringFactories 方法,就是下面的方法
    return (List)loadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList());
}

// org.springframework.core.io.support.SpringFactoriesLoader#loadSpringFactories
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 {
            // 找所有 jar 的 META-INF/spring.factories 檔案,裡面配置的
            Enumeration<URL> urls = classLoader != null ? classLoader.getResources("META-INF/spring.factories") : ClassLoader.getSystemResources("META-INF/spring.factories");
           ....
        } catch (IOException var13) {
            IOException ex = var13;
            throw new IllegalArgumentException("Unable to load factories from location [META-INF/spring.factories]", ex);
        }
    }
}

找到的所有的類都要註冊到容器嗎,答案肯定不是的,不然太多了,這裡就需要按需載入了

用 spring-boot-autoconfigure 這個 jar 為例,這個很典型,springboot 自己的,裡面類容也是最多的,有的 jar 裡面跟沒沒有這個檔案

springboot 自動配置原理

隨便拎一個 org.springframework.boot.autoconfigure.data.cassandra.CassandraDataAutoConfiguration 類,進去看看

springboot 自動配置原理

相關文章