springboot 自动配置原理

@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 里面跟没没有这个文件

随便拎一个 org.springframework.boot.autoconfigure.data.cassandra.CassandraDataAutoConfiguration 类,进去看看

posted @ 2024-07-05 23:46  CyrusHuang  阅读(2)  评论(0编辑  收藏  举报