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