Springboot 自动装配置

Posted 愚蠢的猴子

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Springboot 自动装配置相关的知识,希望对你有一定的参考价值。

  Spring Boot 相对于传统的Spring引入了自动配置功能,简化了项目中繁琐的配置,让开发者利用起来更加的简便、快捷。比如内嵌的tomcat容器等,这些都属于Spring Boot自动配置的范畴。

  其中@EnableAutoConfiguration注解下的AutoConfigurationImportSelector类就是自动装配的核心。在2.0.6.RELEASE版本中没有getAutoConfigurationEntry方法,在2.1.4.RELEASE版本对其做了优化变成了我们现在看到的样子。

@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
    if (!isEnabled(annotationMetadata)) {
        return NO_IMPORTS;
    }
    AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
            .loadMetadata(this.beanClassLoader);
    AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(
            autoConfigurationMetadata, annotationMetadata);
    return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}

/**
 * Return the {@link AutoConfigurationEntry} based on the {@link AnnotationMetadata}
 * of the importing {@link Configuration @Configuration} class.
 * @param autoConfigurationMetadata the auto-configuration metadata
 * @param annotationMetadata the annotation metadata of the configuration class
 * @return the auto-configurations that should be imported 应该导入的自动配置
 */
protected AutoConfigurationEntry getAutoConfigurationEntry(
        AutoConfigurationMetadata autoConfigurationMetadata,
        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);
    //对参数exclusions进行验证,exclusion必须为自动装配的类,否则抛出异常
    checkExcludedClasses(configurations, exclusions);
    //移除exclusions
    configurations.removeAll(exclusions);
    //根据maven导入的启动器过滤出 需要导入的配置类
    configurations = filter(configurations, autoConfigurationMetadata);
    //配置监听事件
    fireAutoConfigurationImportEvents(configurations, exclusions);
    return new AutoConfigurationEntry(configurations, exclusions);
}

1、其中AutoConfigurationMetadataLoader.loadMetadata(this.beanClassLoader),会加载META-INF/spring-autoconfigure-metadata.properties下的所有配置信息。

final class AutoConfigurationMetadataLoader {

    protected static final String PATH = "META-INF/spring-autoconfigure-metadata.properties";

    private AutoConfigurationMetadataLoader() {
    }

    public static AutoConfigurationMetadata loadMetadata(ClassLoader classLoader) {
        return loadMetadata(classLoader, PATH);
    }
    
    static AutoConfigurationMetadata loadMetadata(ClassLoader classLoader, String path) {
        try {
            Enumeration<URL> urls = (classLoader != null) ? classLoader.getResources(path)
                    : ClassLoader.getSystemResources(path);
            Properties properties = new Properties();
            while (urls.hasMoreElements()) {
                properties.putAll(PropertiesLoaderUtils
                        .loadProperties(new UrlResource(urls.nextElement())));
            }
            return loadMetadata(properties);
        }
        catch (IOException ex) {
            throw new IllegalArgumentException(
                    "Unable to load @ConditionalOnClass location [" + path + "]", ex);
        }
    }
}

2. getCandidateConfigurations(annotationMetadata,attributes),会加载所有包下META-INF/spring.factories的信息并组装成Map,然后读取key为org.springframework.boot.autoconfigure.EnableAutoConfiguration的数组,并将这个数组返回。

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;
}
# Auto configure从spring.factories中读取到的文件内容
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,\\
org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration,\\
org.springframework.boot.autoconfigure.cassandra.CassandraAutoConfiguration,\\
org.springframework.boot.autoconfigure.cloud.CloudAutoConfiguration,\\
org.springframework.boot.autoconfigure.context.ConfigurationPropertiesAutoConfiguration,\\

 

3. removeDuplicates(configurations),去除重复的配置类,若我们自己写的starter 可能存主重复的。

protected final <T> List<T> removeDuplicates(List<T> list) {
    return new ArrayList<>(new LinkedHashSet<>(list));
}

4. getExclusions(annotationMetadata, attributes),获取限制候选配置的所有排除项(找到不希望自动装配的配置类)。

/**
 * Return any exclusions that limit the candidate configurations.
 * @param metadata the source metadata
 * @param attributes the {@link #getAttributes(AnnotationMetadata) annotation
 * attributes}
 * @return exclusions or an empty set
 */
protected Set<String> getExclusions(AnnotationMetadata metadata,
        AnnotationAttributes attributes) {
    Set<String> excluded = new LinkedHashSet<>();
    excluded.addAll(asList(attributes, "exclude"));
    excluded.addAll(Arrays.asList(attributes.getStringArray("excludeName")));
    excluded.addAll(getExcludeAutoConfigurationsProperty());
    return excluded;
}

private List<String> getExcludeAutoConfigurationsProperty() {
    if (getEnvironment() instanceof ConfigurableEnvironment) {
        Binder binder = Binder.get(getEnvironment());
        return binder.bind(PROPERTY_NAME_AUTOCONFIGURE_EXCLUDE, String[].class)
                .map(Arrays::asList).orElse(Collections.emptyList());
    }
    String[] excludes = getEnvironment()
            .getProperty(PROPERTY_NAME_AUTOCONFIGURE_EXCLUDE, String[].class);
    return (excludes != null) ? Arrays.asList(excludes) : Collections.emptyList();
}

5.checkExcludedClasses(configurations, exclusions),对参数exclusions进行验证,exclusion必须为自动装配的类,否则抛出异常。

private void checkExcludedClasses(List<String> configurations,
        Set<String> exclusions) {
    List<String> invalidExcludes = new ArrayList<>(exclusions.size());
    for (String exclusion : exclusions) {
        if (ClassUtils.isPresent(exclusion, getClass().getClassLoader())
                && !configurations.contains(exclusion)) {
            invalidExcludes.add(exclusion);
        }
    }
    if (!invalidExcludes.isEmpty()) {
        handleInvalidExcludes(invalidExcludes);
    }
}

/**
 * Handle any invalid excludes that have been specified.
 * @param invalidExcludes the list of invalid excludes (will always have at least one
 * element)
 */
protected void handleInvalidExcludes(List<String> invalidExcludes) {
    StringBuilder message = new StringBuilder();
    for (String exclude : invalidExcludes) {
        message.append("\\t- ").append(exclude).append(String.format("%n"));
    }
    throw new IllegalStateException(String
            .format("The following classes could not be excluded because they are"
                    + " not auto-configuration classes:%n%s", message));
}

6. filter(configurations, autoConfigurationMetadata),根据项目中的AutoConfigurationImportFilter类进行过滤
在项目中找到所有AutoConfigurationImportFilter类进行过滤,对于自动配置类,只要其不满足任意一个filter的match方法,就将其进行过滤,不再自动配置。

private List<String> filter(List<String> configurations,
            AutoConfigurationMetadata autoConfigurationMetadata) {
    long startTime = System.nanoTime();
    String[] candidates = StringUtils.toStringArray(configurations);
    boolean[] skip = new boolean[candidates.length];
    boolean skipped = false;
    for (AutoConfigurationImportFilter filter : getAutoConfigurationImportFilters()) {
        invokeAwareMethods(filter);
        boolean[] match = filter.match(candidates, autoConfigurationMetadata);
        for (int i = 0; i < match.length; i++) {
            if (!match[i]) {
                skip[i] = true;
                candidates[i] = null;
                skipped = true;
            }
        }
    }
    if (!skipped) {
        return configurations;
    }
    List<String> result = new ArrayList<>(candidates.length);
    for (int i = 0; i < candidates.length; i++) {
        if (!skip[i]) {
            result.add(candidates[i]);
        }
    }
    if (logger.isTraceEnabled()) {
        int numberFiltered = configurations.size() - result.size();
        logger.trace("Filtered " + numberFiltered + " auto configuration class in "
                + TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startTime)
                + " ms");
    }
    return new ArrayList<>(result);
}

至此,自动配置的逻辑实现。我们通过RedisAutoConfiguration为例,完整的展示一遍整个流程:

@Configuration //标识是一个配置类
@ConditionalOnClass(RedisOperations.class) //判断环境中是否有这个类
@EnableConfigurationProperties(RedisProperties.class) //启动指定类的配置功能,并且把配置文件中的属性和RedisProperties关联
@Import({ LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class }) 
public class RedisAutoConfiguration {

    @Bean
    @ConditionalOnMissingBean(name = "redisTemplate")
    public RedisTemplate<Object, Object> redisTemplate(
            RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {
        RedisTemplate<Object, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(redisConnectionFactory);
        return template;
    }

    @Bean
    @ConditionalOnMissingBean
    public StringRedisTemplate stringRedisTemplate(
            RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {
        StringRedisTemplate template = new StringRedisTemplate();
        template.setConnectionFactory(redisConnectionFactory);
        return template;

具体的实现流程如图:

 

# Auto Configure

以上是关于Springboot 自动装配置的主要内容,如果未能解决你的问题,请参考以下文章

VSCode 配置 用户自定义代码片段 自定义自动代码补充

SpringBoot自动配置/装配(SPI)

从零开始配置vim(27)——代码片段

从零开始配置vim(27)——代码片段

从零开始配置vim(27)——代码片段

[AndroidStudio]_[初级]_[配置自动完成的代码片段]