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