本文包含两个部分: 一个是配置文件加载顺序, 一个是SpringBoot的自动装配过程.

一. 我们先来看看"配置文件加载顺序"

  1. 配置文件扫描规则

springboot配置文件的扫描顺序:
工程根目录:./config/
工程根目录:./
classpath:/config/
classpath:/
加载的优先级顺序是从上向下加载,并且所有的文件都会被加载,高优先级的内容会覆盖底优先级的内容,形成互补配置

也可以通过指定配置spring.config.location来改变默认配置,我们可以通过指令:

java -jar xxxx.jar --spring.config.location=D:/kawa/application.yml
# 该命令指定的配置文件,会使默认互补覆盖配置失效,建议用additional-location 
java -jar xxxx.jar  --spring.config.additional-location=D:/kawa/
# –spring.config.additional-location后面接的必须是一个目录

2. 先加载指定profile配置, 再加载默认配置

jar包外部的application-{profile}.propertie或application.yml(带spring.profile)配置文件           
jar包内部的application-{profile}.propertie或application.yml(带spring.profile)配置文件
jar包外部的application.propertie或application.yml(不带spring.profile)配置文件
jar包内部的application.propertie或application.yml(不带spring.profile)配置文件

3. 命令行参数

java -jar xxxx.jar --server.port=8087 --server.context-path=/show

这些配置的加载顺序: 命令行参数 > profile文件 > 默认配置文件

1. 先加载命令行参数
java -jar xxxx.jar --server.port=8087 --server.context-path=/show
2. 加载带profile的配置文件
2.1 jar包外部./config/下的application-{profile}.propertie或application-{profile}.yml
2.2 jar包外部./下的application-{profile}.propertie或application-{profile}.yml
2.3 jar包内部./config/下的application-{profile}.propertie或application-{profile}.yml
2.4 jar包内部./下的application-{profile}.propertie或application-{profile}.yml
3. 再来加载不带profile
3.1 jar包外部./config/下的application.propertie或application.yml
3.2 jar包外部./下的application.propertie或application.yml
3.3 jar包内部./config/下的application.propertie或application.yml
3.4 jar包内部./下的application.propertie或application.yml

二. Spring Boot给我们带来最大的便捷就是很多组件都是默认装配好的, 那么SpringBoot的自动装配过程是啥样子?

看了<<SpringBoot实战>>, 了解到大概过程如下:

 1. springboot项目入口类的注解就是@SpringBootApplication, 这个注解主要包含了其他三个注解:@SpringBootConfiguration, @ComponentScan,@EnableAutoConfiguration;

其中自动装配的核心注解是@EnableAutoConfiguration

@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 {
  //TODO:
}

2. @EnableAutoConfiguration引入了一个装配选择器: AutoConfigurationImportSelector(实现了ImportSelector接口), 通过 selectImports方法加载其他的初始化入口.

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

AutoConfigurationImportSelector.java

@Override
	public String[] selectImports(AnnotationMetadata annotationMetadata) {
		if (!isEnabled(annotationMetadata)) {
			return NO_IMPORTS;
		}
                //获取自动装配的注解, 扫描路径:META-INF/spring-autoconfigure-metadata.properties(用于下一步的排除操作)
		AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
				.loadMetadata(this.beanClassLoader);
                //获取待装配的类, 扫描路径:META-INF/spring-factories(会排除掉autoConfigurationMetadata中的类)
		AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(
				autoConfigurationMetadata, annotationMetadata);
		return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
	}
-- META-INF/spring-autoconfigure-metadata.properties
org.springframework.boot.autoconfigure.security.servlet.SecurityFilterAutoConfiguration.AutoConfigureAfter=org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration
org.springframework.boot.autoconfigure.data.cassandra.CassandraDataAutoConfiguration.Configuration=

-- META-INF/spring-factories
# PropertySource Loaders
org.springframework.boot.env.PropertySourceLoader=\
org.springframework.boot.env.PropertiesPropertySourceLoader,\
org.springframework.boot.env.YamlPropertySourceLoader

# Run Listeners
org.springframework.boot.SpringApplicationRunListener=\
org.springframework.boot.context.event.EventPublishingRunListener

# Error Reporters
org.springframework.boot.SpringBootExceptionReporter=\
org.springframework.boot.diagnostics.FailureAnalyzers

3. 配置文件中的每一项都是模块的初始化入口, 这里举CacheAutoConfiguration为例:

@Configuration
@ConditionalOnClass(CacheManager.class)
@ConditionalOnBean(CacheAspectSupport.class)
@ConditionalOnMissingBean(value = CacheManager.class, name = "cacheResolver")
@EnableConfigurationProperties(CacheProperties.class)
@AutoConfigureBefore(HibernateJpaAutoConfiguration.class)
@AutoConfigureAfter({ CouchbaseAutoConfiguration.class, HazelcastAutoConfiguration.class,
		RedisAutoConfiguration.class })
@Import(CacheConfigurationImportSelector.class)
public class CacheAutoConfiguration {
  //TODO:
}

同样的, 它也引入了一个装配选择器:CacheConfigurationImportSelector, 通过 selectImports方法, 加载不同cache框架的初始化配置.

final class CacheConfigurations {

	private static final Map<CacheType, Class<?>> MAPPINGS;

	static {
		Map<CacheType, Class<?>> mappings = new EnumMap<>(CacheType.class);
		mappings.put(CacheType.GENERIC, GenericCacheConfiguration.class);
		mappings.put(CacheType.EHCACHE, EhCacheCacheConfiguration.class);
		mappings.put(CacheType.HAZELCAST, HazelcastCacheConfiguration.class);
		mappings.put(CacheType.INFINISPAN, InfinispanCacheConfiguration.class);
		mappings.put(CacheType.JCACHE, JCacheCacheConfiguration.class);
		mappings.put(CacheType.COUCHBASE, CouchbaseCacheConfiguration.class);
		mappings.put(CacheType.REDIS, RedisCacheConfiguration.class);
		mappings.put(CacheType.CAFFEINE, CaffeineCacheConfiguration.class);
		mappings.put(CacheType.SIMPLE, SimpleCacheConfiguration.class);
		mappings.put(CacheType.NONE, NoOpCacheConfiguration.class);
		MAPPINGS = Collections.unmodifiableMap(mappings);
	}
  ......
}

这里以redis为例, 加载了RedisCacheConfiguration配置

@Configuration
@AutoConfigureAfter(RedisAutoConfiguration.class)
@ConditionalOnBean(RedisConnectionFactory.class)
@ConditionalOnMissingBean(CacheManager.class)
@Conditional(CacheCondition.class)
class RedisCacheConfiguration {
  //TODO:
}

4. 初始化配置一般都含有@Conditional 注解(ConditionalOnBean, ConditionalOnMissingBean都是基于Conditional), 用于判断当前类是否需要初始化到spring容器中.

这里举CacheCondition为例:

class CacheCondition extends SpringBootCondition {

	@Override
	public ConditionOutcome getMatchOutcome(ConditionContext context,
			AnnotatedTypeMetadata metadata) {
		String sourceClass = "";
		if (metadata instanceof ClassMetadata) {
			sourceClass = ((ClassMetadata) metadata).getClassName();
		}
		ConditionMessage.Builder message = ConditionMessage.forCondition("Cache",
				sourceClass);
		Environment environment = context.getEnvironment();
       .........
}

CacheCondition继承自SpringBootCondition(实现了Condition接口), 实现getMatchOutcome方法, 返回ConditionOutcome, 用于判断是否初始化.

public class ConditionOutcome {

  private final boolean match;

  private final ConditionMessage message;
  ...........
}

match为true, 则初始化; 否则, springboot不会初始化RedisCacheConfiguration.

5. @Conditional由ConditionEvaluator处理, 而ConditionEvaluator和ImportSelector, 最终被ConfigurationClassParser调用

(processConfigurationClass->doProcessConfigurationClass->processImports)

  不对之处, 敬请指点.

发表评论

您的电子邮箱地址不会被公开。 必填项已用*标注