springboot启动源码补充和配置优先级
上一篇分析了springboot的启动流程,其中还有一些部分没有分析完全。接下来通过一下方面进行分析
- @SpringBootApplication注解
- 启动流程中的第五步,准备环境变量部分 这部分和springboot配置优先级有关
- callRunners(context, applicationArguments); 方法解释
- 对启动流程的总结
@SpringBootApplication注解分析
先来看下@SpringBootApplication注解有哪些内容 @SpringBootApplication包含了以上几个注解和属性。
先来分析几个常用的属性 exclude()和excludeName()都是用来排除自动配置类的。注意这里排除的是自动配置类,不是我们定义的bean。这部分源码在springboot自动配置源码分析中有分析过 scanBasePackages()和scanBasePackageClasses()配置对应的扫描路径,这部分的内容是spring的配置扫描的内容Bean的生命周期源码解析中分析过扫描流程 nameGenerator() 指定使用BeanNameGenerator类型,BeanNameGenerator类型用来创建bean的名字的,一般都不会配置 proxyBeanMethods() 这个是spring的内容,在spring配置类解析源码解析中提到过
在来看看SpringBootApplication的注解 @SpringBootConfiguration 实际上就是 @Configuration spring的内容 标注它的类被spring当成配置类来解析 @EnableAutoConfiguration 开启自动配置的注解 其中@Import(AutoConfigurationImportSelector.class)在自动配置底层源码分析中分析了 @AutoConfigurationPackage 的两个属性的值 就是@SpringBootApplication对应的属性配置的值 然后来看下@Import(AutoConfigurationPackages.Registrar.class)做了什么?
static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
register(registry, new PackageImports(metadata).getPackageNames().toArray(new String[0]));
}
@Override
public Set<Object> determineImports(AnnotationMetadata metadata) {
return Collections.singleton(new PackageImports(metadata));
}
}
其中new PackageImports(metadata).getPackageNames().toArray(new String[0])
PackageImports(AnnotationMetadata metadata) {
AnnotationAttributes attributes = AnnotationAttributes
.fromMap(metadata.getAnnotationAttributes(AutoConfigurationPackage.class.getName(), false));
List<String> packageNames = new ArrayList<>(Arrays.asList(attributes.getStringArray("basePackages")));
for (Class<?> basePackageClass : attributes.getClassArray("basePackageClasses")) {
packageNames.add(basePackageClass.getPackage().getName());
}
if (packageNames.isEmpty()) {
packageNames.add(ClassUtils.getPackageName(metadata.getClassName()));
}
this.packageNames = Collections.unmodifiableList(packageNames);
}
获取主启动类的包路径和配置了basePackages和basePackageClasses的包路径。 register(registry, new PackageImports(metadata).getPackageNames().toArray(new String[0]));
public static void register(BeanDefinitionRegistry registry, String... packageNames) {
if (registry.containsBeanDefinition(BEAN)) {
BasePackagesBeanDefinition beanDefinition = (BasePackagesBeanDefinition) registry.getBeanDefinition(BEAN);
beanDefinition.addBasePackages(packageNames);
}
else {
registry.registerBeanDefinition(BEAN, new BasePackagesBeanDefinition(packageNames));
}
}
把获取到的扫描路径生成BeanDefinition保存起来,一方面给后续执行扫描逻辑时使用,一方面给第三方框架获取扫描路径使用 总结起来就是@EnableAutoConfiguration注解,一方面开启自动配置功能,一方面获取主启动类所在的包作为扫描路径
@ComponentScan spring的扫描注解,在springboot中
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
主要是配置了两个过滤器AutoConfigurationExcludeFilter和TypeExcludeFilter 以AutoConfigurationExcludeFilter为例 这个过滤器会通过match方法判断当前加载bean是否有@Configuration注解,并且是否是META-INF/spring.factories下的自动配置类当中的。如果是返回true,表示springboot不处理这个类,相当于过滤掉,如果不是返回false,springboot回去解析这个类。 总结下来就是:AutoConfigurationExcludeFilter的作用是扫描到的配置类名字如果在自动配置类名集合中,就不解析 TypeExcludeFilter 同理
@Override
public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory)
throws IOException {
if (this.beanFactory instanceof ListableBeanFactory && getClass() == TypeExcludeFilter.class) {
for (TypeExcludeFilter delegate : getDelegates()) {
if (delegate.match(metadataReader, metadataReaderFactory)) {
return true;
}
}
}
return false;
}
到此整个@SpringBootApplication就分析完毕
callRunners(context, applicationArguments); 方法解释
这是在springboot启动路程中的第十步,过程大概是,获取Spring容器中的ApplicationRunner类型和CommandLineRunner类型的Bean,执行它们的run()。
private void callRunners(ApplicationContext context, ApplicationArguments args) {
List<Object> runners = new ArrayList<>();
runners.addAll(context.getBeansOfType(ApplicationRunner.class).values());
runners.addAll(context.getBeansOfType(CommandLineRunner.class).values());
AnnotationAwareOrderComparator.sort(runners);
for (Object runner : new LinkedHashSet<>(runners)) {
if (runner instanceof ApplicationRunner) {
callRunner((ApplicationRunner) runner, args);
}
if (runner instanceof CommandLineRunner) {
callRunner((CommandLineRunner) runner, args);
}
}
}
那么ApplicationRunner和CommandLineRunner这两个接口是用来干嘛的呢,实际上就是对命令行参数的处理。举个例子
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
@Bean
public ApplicationRunner applicationRunner(){
return new ApplicationRunner() {
@Override
public void run(ApplicationArguments args) throws Exception {
System.out.println(args.getNonOptionArgs());
System.out.println(args.getOptionNames());
System.out.println(args.getOptionValues("aa"));
}
};
}
}
springboot启动类中定义了一个bean ,之前说过args的值就是命令行参数。所以在idea中模拟命令行参数 启动springboot
启动流程中的第五步,准备环境变量部分和SpringBoot配置优先级
先来看下这部分源码
private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,
DefaultBootstrapContext bootstrapContext, ApplicationArguments applicationArguments) {
ConfigurableEnvironment environment = getOrCreateEnvironment();
configureEnvironment(environment, applicationArguments.getSourceArgs());
ConfigurationPropertySources.attach(environment);
listeners.environmentPrepared(bootstrapContext, environment);
DefaultPropertiesPropertySource.moveToEnd(environment);
Assert.state(!environment.containsProperty("spring.main.environment-prefix"),
"Environment prefix cannot be set via properties.");
bindToSpringApplication(environment);
if (!this.isCustomEnvironment) {
environment = convertEnvironment(environment);
}
ConfigurationPropertySources.attach(environment);
return environment;
}
这里面比较复杂,具体的步骤源码不再展开,具体说说大致流程。 ConfigurableEnvironment environment = getOrCreateEnvironment(); 首先创建一个ConfigurableEnvironment 环境变量的对象。这个过程中往ConfigurableEnvironment 条件四个PropertySource。每个PropertySource相当于properties文件解析后或者理解成一个map。一开始这四个PropertySource分别是
- StubPropertySource {name=‘servletConfigInitParams’} servlet的初始化参数的键值对
- StubPropertySource {name=‘servletContextInitParams’} web容器的初始化参数
- PropertiesPropertySource {name=‘systemProperties’} jvm参数和-D配置的参数
- SystemEnvironmentPropertySource {name=‘systemEnvironment’} 操作系统的参数
当我们需要用到配置参数的时候就会从这些PropertySource里面拿,那么是怎么拿的呢?,springboot会从第一个开始,拿到了就直接使用,这就是优先级了。
configureEnvironment(environment, applicationArguments.getSourceArgs()); 这个方法中,先判断有没有设置DefaultProperties,例如
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication springApplication = new SpringApplication(Application.class);
Map<String,Object> map = new HashMap<>();
map.put("server.port",8009);
springApplication.setDefaultProperties(map);
springApplication.run(args);
}
如果设置了,在最后一个PropertySource后面添加一个名字叫defaultProperties的PropertySource。此时就有五个PropertySource
servletConfigInitParams servletContextInitParams systemProperties systemEnvironment defaultProperties
然后把命令行参数放到名字叫commandLineArgs的PropertySource中,放到最前面。命令行参数也就是java -jar xxx.jar --server.port=8002 此时就有6个PropertySource
commandLineArgs :命令行参数 servletConfigInitParams :servlet的初始化参数的键值对 servletContextInitParams :web容器的初始化参数 systemProperties:jvm参数和-D配置的参数 systemEnvironment :操作系统的参数 defaultProperties: SpringApplication 配置的参数
listeners.environmentPrepared(bootstrapContext, environment); 发布一个环境变量准备好的事件,有一个叫EnvironmentPostProcessorApplicationListener的监听器监听到这个事件后会进行后续处理。具体的处理代码
private void onApplicationEnvironmentPreparedEvent(ApplicationEnvironmentPreparedEvent event) {
ConfigurableEnvironment environment = event.getEnvironment();
SpringApplication application = event.getSpringApplication();
for (EnvironmentPostProcessor postProcessor : getEnvironmentPostProcessors(application.getResourceLoader(),
event.getBootstrapContext())) {
postProcessor.postProcessEnvironment(environment, application);
}
}
这个方法会从spring.factories拿到一系列的EnvironmentPostProcessor。对environment进行处理,例如第一个RandomValuePropertySourceEnvironmentPostProcessor,就会在systemEnvironment 这个PropertySource后面在增加加一个叫random的PropertySource。此时就有7个PropertySource
commandLineArgs :命令行参数 servletConfigInitParams :servlet的初始化参数的键值对 servletContextInitParams :web容器的初始化参数 systemProperties:jvm参数和-D配置的参数 systemEnvironment :操作系统的参数 random :存储一些随机数 通过random.int 可以获取到int的随机数 同理还有random.long 等等 defaultProperties: SpringApplication 配置的参数
其他的EnvironmentPostProcessor就不在分析,核心来分析下ConfigDataEnvironmentPostProcessor ConfigDataEnvironmentPostProcessor 首先ConfigDataEnvironmentPostProcessor有一些默认路径来找application.properties 等这些配置文件 optional:前缀表示允许在该路径下找不到配置文件 也可以通过配置以下参数指定找配置文件的路径
spring.config.location 指定了默认路径就不生效 spring.config.import 在默认路径下新增 spring.config.additional-location 在默认路径下新增
当然这些参数配置在配置文件中是不生效,因为现在就是要去找寻配置文件。 那么这些路径的顺序是怎样的呢,先从那个路劲下开始找呢? 如果通过spring.config.import 或者 spring.config.additional-location配置了新的路径 那么该路径排最前面
新配置的路径 file./ file./config file./config/*/ classpath:/ classpath:/config/
从上到下的顺序去找配置文件。那么找那些配置文件呢,可以通过spring.config.name 进行配置,默认值为application。 所以会从上面几个路径依次去找application.yaml,application.yml,application.properties 按照上述文件顺序找到后,有一个倒序的操作 所以配置文件的优先级变成properties>yml。如果多个路径下有配置文件,那么先按照路径的优先级,再到文件的优先级。 此时找到的配置文件并没有解析所有配置文件放入到环境变量中,而是解析成PropertySource按顺序放到一个集合当中,判断有没有配置spring.profiles.active 。比如说配置了spring.profiles.active=dve ,那么就会去找application-dve的配置文件。所以spring.profiles.active=dve 的配置需要放在application配置文件下。 找到以后再解析application-dve的配置文件,解析完成后放入到环境变量中,此时application-dve的优先级是高于application的。 此时环境变量的PropertySource 顺序如下
commandLineArgs :命令行参数 servletConfigInitParams :servlet的初始化参数的键值对 servletContextInitParams :web容器的初始化参数 systemProperties:jvm参数和-D配置的参数 systemEnvironment :操作系统的参数 random :存储一些随机数 通过random.int 可以获取到int的随机数 同理还有random.long 等等 application-dve.properties application-dve.yml application.properties application.yml defaultProperties: SpringApplication 配置的参数
优先级自然从上到下。 到此整个环境配置就完成了
|