用于源码分析的代码:Github
接着上一篇继续debug,这次看run方法里的源码。上一篇已经将源码2.0的逻辑分析完了,这一篇就只分析3.0处的源代码:
public ConfigurableApplicationContext run(String... args) {
//StopWatch就是一个监控程序启动时间的类,start方法表示开始计时,stop方法表示计时结束
//用于日志输出启动时间
StopWatch stopWatch = new StopWatch();
stopWatch.start();
ConfigurableApplicationContext context = null;
Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
// 设置一个环境变量,该环节变量标识当前程序是在没有显示器、鼠标等显示设备上运行的
// 目的是为了能直接访问支持在无显示设备下的图形和文字处理对象的API,比如AWT的绘图API
configureHeadlessProperty();
// 1.0
SpringApplicationRunListeners listeners = getRunListeners(args);
// 2.0
listeners.starting();
try {
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
// 3.0
ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
configureIgnoreBeanInfo(environment);
Banner printedBanner = printBanner(environment);
context = createApplicationContext();
exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
new Class[] { ConfigurableApplicationContext.class }, context);
prepareContext(context, environment, listeners, applicationArguments, printedBanner);
refreshContext(context);
afterRefresh(context, applicationArguments);
stopWatch.stop();
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
}
listeners.started(context);
callRunners(context, applicationArguments);
}
catch (Throwable ex) {
handleRunFailure(context, ex, exceptionReporters, listeners);
throw new IllegalStateException(ex);
}
try {
listeners.running(context);
}
catch (Throwable ex) {
handleRunFailure(context, ex, exceptionReporters, null);
throw new IllegalStateException(ex);
}
return context;
}
还是首先列出自己的问题,带着问题看源码
待解答的问题
- 这段源码做了什么?
- 为什么这么做?
- 学到了哪些东西?
源码分析
因为这一节的源码会讲到Spring怎么解析环境变量、启动参数的一些内容,所以这一次需要使用远程debug的方式来启动,先将项目使用mvn package 命令打成jar包,然后cd到jar包所在目录,使用命令方式启动项目,我这里启动命令是:java -Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=5005 -Dlogging.level.root=info -jar spring-boot-learn-1.0-SNAPSHOT.jar --spring.profiles.active=profile --spring.main.lazy-initialization=false ,5005就是远程debug时的端口,然后通过idea来做远程debug,在启动过程一中有讲到远程debug的配置。
源码3.0
从方法命名和返回值上看是为了准备一个类似环境变量的对象,我们可以先看下ConfigurableEnvironment接口的定义,可以看出这个类提供了访问我们配置的springboot的配置文件、环境变量、jvm启动命令里的系统属性和运行参数的访问和设置功能:
private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,
ApplicationArguments applicationArguments) {
// Create and configure the environment
// 3.1
ConfigurableEnvironment environment = getOrCreateEnvironment();
// 3.2
configureEnvironment(environment, applicationArguments.getSourceArgs());
// 这里会将配置文件、环境变量、运行参数、系统属性封装到一个对象里,以便后续更方便的访问,没有这一步之前,每个对象都有自己单独的key来映射,这里是将这几个对象封装到一个key里
ConfigurationPropertySources.attach(environment);
// 3.3
listeners.environmentPrepared(environment);
// 3.4
bindToSpringApplication(environment);
if (!this.isCustomEnvironment) {
environment = new EnvironmentConverter(getClassLoader()).convertEnvironmentIfNecessary(environment,
deduceEnvironmentClass());
}
ConfigurationPropertySources.attach(environment);
return environment;
}
源码3.1
ConfigurableEnvironment environment = getOrCreateEnvironment();
这里会根据我们的应用的类型来返回具体的配置对象(这个参数在Springboot 启动过程二中讲到),可以看下StandardServletEnvironment对象的结构:
这里有一个需要关注的点是在初始化这个类的时候,会在这个对象里添加jvm启动时的参数和环境变量,因为java里new一个类的实例时,会先调用父类的类构造器,所以这个代码位置在期父类AbstractEnvironment类的构造器里,会调用StandardServletEnvironment类的customizePropertySources方法:
@Override
protected void customizePropertySources(MutablePropertySources propertySources) {
propertySources.addLast(
new PropertiesPropertySource(SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME, getSystemProperties()));
propertySources.addLast(
new SystemEnvironmentPropertySource(SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME, getSystemEnvironment()));
}
这里环境变量是通过System.getEnv()获取,而JVM启动参数是通过System.getProperties()获取,这里就包含我们经常在JVM启动参数里加的-Dfile.encoding、-Dlog.path这种以-D开头的参数,其中还包括我们设置的–spring.profiles.active、–server.port参数(会保存在sun.java.command这个key里,后续会解析这个参数),可以看debug截图:
源码3.2
这段代码主要有两个作用:
- 添加一个转换服务类ConversionService 到environment对象
- 将启动参数解析到environment对象,这里args就是我们启动参数里加载jar包后的参数,比如–spring.profiles.active、–server.port等参数
- 找到需要激活哪一个的yml配置文件
protected void configureEnvironment(ConfigurableEnvironment environment, String[] args) {
if (this.addConversionService) {
// 双重校验锁的单例模式
ConversionService conversionService = ApplicationConversionService.getSharedInstance();
environment.setConversionService((ConfigurableConversionService) conversionService);
}
// 将启动命令后的参数args(比如--spring.profiles.active=profile)封装成SimpleCommandLinePropertySource对象,并存到environment对象中,供后续访问
configurePropertySources(environment, args);
// 3.2.1 找到需要激活哪一个的yml配置文件
configureProfiles(environment, args);
}
这里的args就是在启动参数后面加的参数,我在启动命令后面使用了常用的–spring.profiles.active=profile,所以这里的args就是这个参数:
源码3.2.1
作用就是解析出要激活哪个配置文件,springboot可以配置多个yml配置文件,比如根据线上、线下、预发各配置一个配置文件,然后在启动参数上来制定不同的配置文件,这里从源码上看可以在3个位置来配置spring.profiles.active这个参数,根据解析顺序如下:- 以运行参数的方式指定,就是在jar后面加上–spring.profiles.active=online,比如我们这里的启动参数:java -jar spring-boot-learn-1.0-SNAPSHOT.jar --spring.profiles.active=online
- 以指定系统属性的方式,即:在启动参数里-Dspring.profiles.active开头的方式来制定,比如 java -Dspring.profiles.active=online -jar spring-boot-learn-1.0-SNAPSHOT.jar
- 在环境变量里指定spring.profiles.active
protected void configureProfiles(ConfigurableEnvironment environment, String[] args) {
Set<String> profiles = new LinkedHashSet<>(this.additionalProfiles);
profiles.addAll(Arrays.asList(environment.getActiveProfiles()));
environment.setActiveProfiles(StringUtils.toStringArray(profiles));
}
源码3.3
listeners.environmentPrepared(environment);
发布环境准备事件,这个发布过程在启动过程三中也有讲到,至于怎么通过事件的类型来匹配对应的Listener,这里不重复讲了,但是通过源码可以看到这里有个扩展点,可以扩展自己逻辑:
这里监听这个事件的有7个Listener,我们debug进ConfigFileApplicationListener里:
private void onApplicationEnvironmentPreparedEvent(ApplicationEnvironmentPreparedEvent event) {
// 3.3.1
List<EnvironmentPostProcessor> postProcessors = loadPostProcessors();
postProcessors.add(this);
AnnotationAwareOrderComparator.sort(postProcessors);
for (EnvironmentPostProcessor postProcessor : postProcessors) {
postProcessor.postProcessEnvironment(event.getEnvironment(), event.getSpringApplication());
}
}
List<EnvironmentPostProcessor> loadPostProcessors() {
return SpringFactoriesLoader.loadFactories(EnvironmentPostProcessor.class, getClass().getClassLoader());
}
源码3.3.1
找到所有依赖的jar里的META-INF/spring.factories里配置的EnvironmentPostProcessor类,我们看到
这里有个很重要的后置处理器类ConfigFileApplicationListener.class(实现了EnvironmentPostProcessor),这个监听器就是负责加载Spingboot的配置文件,比如我们的yml配置文件。
所以这里也是一个扩展点,我们可以在自己的spring.factories里配置上自己的EnvironmentPostProcessor做一些自定义逻辑,比如往ConfigurableEnvironment对象里添加一些自己的全局的信息,所以结合启动过程三有两个扩展点:
- 扩展ApplicationEnvironmentPreparedEvent事件的Listener,即:在spring.factories里添加ApplicationListener配置
- 扩展EnvironmentPostProcessor类,即:在spring.factories里添加EnvironmentPostProcessor配置
- 可以参考ConfigurableEnvironment,用SpringFactoriesLoader.loadFactories()从spring.factories里读取自己定义的类
源码3.4
bindToSpringApplication(environment);
这段代码作用主要是将enviroment中以spring.main开头的配置的属性都赋到这个SpringApplication对象里,而配置的地方与spring.active.profile的配置位置相同,比如我们的启动命令里加的–spring.main.lazy-initialization=false,当前支持的有(文档:https://docs.spring.io/spring-boot/docs/current/reference/html/appendix-application-properties.html#core-properties )
这些配置对应这SpringApplication的属性:
下面再通过源码来分析怎么实现这些的:
bindToSpringApplication(environment);
protected void bindToSpringApplication(ConfigurableEnvironment environment) {
try {
Binder.get(environment).bind("spring.main", Bindable.ofInstance(this));
}
catch (Exception ex) {
throw new IllegalStateException("Cannot bind to SpringApplication", ex);
}
}
3.4.1
这里先看Binder的定义:一个将需要用到的组件都组合在一起的容器
/**
* A container object which Binds objects from one or more
* {@link ConfigurationPropertySource ConfigurationPropertySources}.
*
* @author Phillip Webb
* @author Madhura Bhave
* @since 2.0.0
*/
我们通过Binder.get(environment) 看下这里到底组合了哪些对象:
/**
* Create a new {@link Binder} instance from the specified environment.
* @param environment the environment source (must have attached
* {@link ConfigurationPropertySources})
* @param defaultBindHandler the default bind handler to use if none is specified when
* binding
* @return a {@link Binder} instance
* @since 2.2.0
*/
public static Binder get(Environment environment, BindHandler defaultBindHandler) {
Iterable<ConfigurationPropertySource> sources = ConfigurationPropertySources.get(environment);
PropertySourcesPlaceholdersResolver placeholdersResolver = new PropertySourcesPlaceholdersResolver(environment);
// 代码3.4.1
return new Binder(sources, placeholdersResolver, null, null, defaultBindHandler);
}
这里的BindHandler是个接口,用户在binder绑定过程的各个时间点触发回调的,接口定义如下:
从代码3.4.1这个构造函数里的参数能看出这个Binder类所绑的对象,能知道这个对象就是为了解析环境变量、系统参数的
/**
* Create a new {@link Binder} instance for the specified sources.
* @param sources the sources used for binding
* @param placeholdersResolver strategy to resolve any property placeholders
* @param conversionService the conversion service to convert values (or {@code null}
* to use {@link ApplicationConversionService})
* @param propertyEditorInitializer initializer used to configure the property editors
* that can convert values (or {@code null} if no initialization is required). Often
* used to call {@link ConfigurableListableBeanFactory#copyRegisteredEditorsTo}.
* @param defaultBindHandler the default bind handler to use if none is specified when
* binding
* @since 2.2.0
*/
public Binder(Iterable<ConfigurationPropertySource> sources, PlaceholdersResolver placeholdersResolver,
ConversionService conversionService, Consumer<PropertyEditorRegistry> propertyEditorInitializer,
BindHandler defaultBindHandler) {
Assert.notNull(sources, "Sources must not be null");
this.sources = sources;
this.placeholdersResolver = (placeholdersResolver != null) ? placeholdersResolver : PlaceholdersResolver.NONE;
this.conversionService = (conversionService != null) ? conversionService
: ApplicationConversionService.getSharedInstance();
this.propertyEditorInitializer = propertyEditorInitializer;
this.defaultBindHandler = (defaultBindHandler != null) ? defaultBindHandler : BindHandler.DEFAULT;
}
Bindable.ofInstance(this),Bindable可以理解为是个包装类,是将当前SpringApplication对象包装成Bindable对象,这里有个小的知识点,就是怎么获取一个基本类型的装箱类型:
// 先获取一个该class的数组,然后用Array.get来获取装箱类型的class
// 原因就是Array.get这个API说明里已经明确说了,如果是基本类型的话,会返回装箱类型
Object array = Array.newInstance(class, 1);
Class<?> wrapperType = Array.get(array, 0).getClass();
3.4.2
现在主要分析bind方法,再说作用:
private <T> T bind(ConfigurationPropertyName name, Bindable<T> target, BindHandler handler, Context context,
boolean allowRecursiveBinding, boolean create) {
context.clearConfigurationProperty();
try {
Bindable<T> replacementTarget = handler.onStart(name, target, context);
if (replacementTarget == null) {
return handleBindResult(name, target, handler, context, null, create);
}
target = replacementTarget;
Object bound = bindObject(name, target, handler, context, allowRecursiveBinding);
return handleBindResult(name, target, handler, context, bound, create);
}
catch (Exception ex) {
return handleBindError(name, target, handler, context, ex);
}
}
主要是将spring.main的配置set到SpringApplication对象里的。主要实现步骤:
- 先查找evironment里以name或者name开头的配置,比如这里的name是spring.main(ConfigurationPropertyName对象实际就是个包装类,会将spring.main字符创包装成这个对象,并且提供一些额外的功能),那么会先查询是否有spring.main或者以spring.main开头的配置,比如spring.main.lazy-initialization
- 分析target这个对象的元数据信息(Method、Field等信息),以便后续来set属性,比如这里是SpringApplication对象,那么就会将SpringApplication对象里包装成一个Bean对象,这个Bean对象封装了SpringApplication所有对象的属性,以及属性的set、get、is开头的方法的Method对象
- 通过1和2两步,我们得到了spring.main开头的配置的值,又得到了SpringApplication对象的属性的set方法,那么根据SpringApplication的属性的类型,将这个值转成对应的对象,然后通过set方法赋值就可以了,比如SpringApplication的lazyInitialization对象是Boolean类型,那么就将spring.main.lazy-initialization的值转成一个Boolean对象,然后通过set方法赋值给SpringApplication对象
这里需要注意的一点是,如果SpringApplication对象的属性不是Boolean、String这些基本类型或者装箱类(即:不可变对象),是个JavaBean,并且这个JavaBean还包含其他属性的话,那么会递归赋值,这个特性在我们代码里也会经常用到,比如SpringApplication的Banner属性(这个就是用来自定义Springboot启动时控制台输出的那个banner图片),就可以通过spring.banner开头的配置将相应的配置给set到这个Banner属性里,默认的启动banner如下图:
当前支持的自定义配置如图:
下面来分析源码,先列举下参数及作用:
ConfigurationPropertyName name:就是配置的key的包装类,这里就是spring.main的包装类,封装了一些比较、拆分的方法
Bindable target:绑定的对象,这里就是SpringApplication的封装类,因为要将spring.main的配置绑到这个SpringApplication对象的属性里
BindHandler handler:主要是用于在bind的各个阶段做回调的,比如在开始前会调用onstart方法,成功后会调用onsuccess方法等
Context context:用于存储bind过程中涉及的一些上下文数据,比如一些缓存,或者递归赋值时,递归的深度等数据
boolean allowRecursiveBinding:是否允许递归调用
boolean create:当对象不存在时,是否创建一个新对象
private <T> Object bindObject(ConfigurationPropertyName name, Bindable<T> target, BindHandler handler,
Context context, boolean allowRecursiveBinding) {
// 会查询配置中有没有等于spring.main的配置
ConfigurationProperty property = findProperty(name, context);
// 如果没有完全匹配spring.main的配置,那么查询是否有配置以spring.main开头的配置,比如spring.main.lazy-initialization就是以spring.main开头
if (property == null && containsNoDescendantOf(context.getSources(), name) && context.depth != 0) {
return null;
}
// 会校验target的类型是否是Collection/Map/数组,如果是的话,就使用针对这三种类型的binder来完成绑定
AggregateBinder<?> aggregateBinder = getAggregateBinder(target, context);
if (aggregateBinder != null) {
return bindAggregate(name, target, handler, context, aggregateBinder);
}
// 如果有精准匹配上了spring.main的配置,则说明这个配置并不是target(这里就是SpringApplication对象)的属性(比如spring.main.lazy-initialization配置就是SpringApplication对象的lazyInitialization属性的值),则直接将该配置转成target的类型然后返回
if (property != null) {
try {
return bindProperty(target, context, property);
}
catch (ConverterNotFoundException ex) {
// We might still be able to bind it using the recursive binders
Object instance = bindDataObject(name, target, handler, context, allowRecursiveBinding);
if (instance != null) {
return instance;
}
throw ex;
}
}
// 3.4.2.1
return bindDataObject(name, target, handler, context, allowRecursiveBinding);
}
这里重点看上面代码里的3.4.2.1部分,源码如下:
private Object bindDataObject(ConfigurationPropertyName name, Bindable<?> target, BindHandler handler,
Context context, boolean allowRecursiveBinding) {
// 检查是否存在以name开头的配置属性,比如这里name是spring.main,则检查是否有以spring.mian开头的配置,由于我们启动参数里配置了spring.main.lazy-initialization=false,所以这里会跳过
if (isUnbindableBean(name, target, context)) {
return null;
}
// 获取target真正的类型,这里就是SpringApplication.class
Class<?> type = target.getType().resolve(Object.class);
// 判断当前类型是否允许重复绑定 以及 是否正在bind中,如果不允许重复绑定且正在绑定中的话,就跳过
if (!allowRecursiveBinding && context.isBindingDataObject(type)) {
return null;
}
// 这个函数又递归调用上面的bind方法,因为最后的目的是为了将SpringApplication的属性及其子属性的属性都能填充上
// 比如:SpringApplication对象有个名为ca并且类型为ClassA对象的属性,而ClassA类里包含了一个名为cb且类型为ClassB的属性,有个配置是spring.main.ca.cb=3
// 那么这里就会先从SpringApplication的属性里遍历出ca,这时候通过evironmen里找以spring.main.ca开头的配置,即:spring.main.ca.cb配置,然后递归遍历ca这个属性的元数据,发现有个属性叫cb,就会重复上面的步骤找到spring.main.ca.cb操作递归赋值
DataObjectPropertyBinder propertyBinder = (propertyName, propertyTarget) -> bind(name.append(propertyName),
propertyTarget, handler, context, false, false);
// 这个方法就是最后真正赋值的代码,后面着重分析这个地方
return context.withDataObject(type, () -> {
for (DataObjectBinder dataObjectBinder : DATA_OBJECT_BINDERS) {
Object instance = dataObjectBinder.bind(name, target, context, propertyBinder);
if (instance != null) {
return instance;
}
}
return null;
});
}
开始分析赋值部分的源码:
private static final DataObjectBinder[] DATA_OBJECT_BINDERS = { new ValueObjectBinder(), new JavaBeanBinder() };
for (DataObjectBinder dataObjectBinder : DATA_OBJECT_BINDERS) {
Object instance = dataObjectBinder.bind(name, target, context, propertyBinder);
if (instance != null) {
return instance;
}
}
DATA_OBJECT_BINDERS数组有两个对象,ValueObjectBinder和JavaBeanBinder对象,ValueObjectBinder对象时用来给不可变对象赋值的,即:这里的不可变对象就是定义了带参数的构造器的类。
ValueObjectBinder类的bind源码:
@Override
public <T> T bind(ConfigurationPropertyName name, Bindable<T> target, Binder.Context context,
DataObjectPropertyBinder propertyBinder) {
// 先判断target对象里是否有值,如果有值,说明这里需要给这个对象的属性赋值,而不需要创建一个新对象,所以直接跳过,使用后面的JavaBeanBinder来做赋值操作
ValueObject<T> valueObject = ValueObject.get(target);
if (valueObject == null) {
return null;
}
// 找到一个构造器的参数,如果一个类里定义了多个构造器,这里也只会返回第一个
List<ConstructorParameter> parameters = valueObject.getConstructorParameters();
List<Object> args = new ArrayList<>(parameters.size());
boolean bound = false;
for (ConstructorParameter parameter : parameters) {
// 这个地方就是从配置里解析出构造器里这个参数的值,就是调用3.4.2.1里的那个函数
Object arg = parameter.bind(propertyBinder);
bound = bound || arg != null;
arg = (arg != null) ? arg : parameter.getDefaultValue(context.getConverter());
args.add(arg);
}
// 3.4.2.2 这个就是通过反射 给属性赋值
return bound ? valueObject.instantiate(args) : null;
}
JavaBeanBinder对象的bind源码:
@Override
public <T> T bind(ConfigurationPropertyName name, Bindable<T> target, Context context,
DataObjectPropertyBinder propertyBinder) {
// 因为目的是为了给target所包装的那个对象的属性赋值,所以这里要检查配置是否存在,这里就是检查spring.main的配置
boolean hasKnownBindableProperties = target.getValue() != null && hasKnownBindableProperties(name, context);
// 这里就是在3.4.2那个步骤里的第二步,就是为了将target所包装的对象(这里就是SpringApplication对象)的所有的Method、Field对象都拆出来,封装到这个Bean里,可以看下下面贴出来的debug截图
Bean<T> bean = Bean.get(target, hasKnownBindableProperties);
if (bean == null) {
return null;
}
BeanSupplier<T> beanSupplier = bean.getSupplier(target);
// 通过类的反射给对应的属性赋值,我们单独贴出代码
boolean bound = bind(propertyBinder, bean, beanSupplier);
return (bound ? beanSupplier.get() : null);
}
boolean bound = bind(propertyBinder, bean, beanSupplier);源码分析:
private <T> boolean bind(DataObjectPropertyBinder propertyBinder, Bean<T> bean, BeanSupplier<T> beanSupplier) {
boolean bound = false;
// 遍历SpringApplication对象的所有属性,见后面bean对象的debug截图
for (BeanProperty beanProperty : bean.getProperties().values()) {
// 检查每个属性是否有对应的配置,如果存在,就进行赋值操作
bound |= bind(beanSupplier, propertyBinder, beanProperty);
}
return bound;
}
private <T> boolean bind(BeanSupplier<T> beanSupplier, DataObjectPropertyBinder propertyBinder,
BeanProperty property) {
String propertyName = property.getName();
ResolvableType type = property.getType();
Supplier<Object> value = property.getValue(beanSupplier);
Annotation[] annotations = property.getAnnotations();
// 这一步就是调用3.4.2.1里的那个函数来获取到该属性的配置的值,并生成对应的对象
Object bound = propertyBinder.bindProperty(propertyName,
Bindable.of(type).withSuppliedValue(value).withAnnotations(annotations));
if (bound == null) {
return false;
}
if (property.isSettable()) {
// 用反射给该属性赋值
property.setValue(beanSupplier, bound);
}
else if (value == null || !bound.equals(value.get())) {
throw new IllegalStateException("No setter found for property: " + property.getName());
}
return true;
}
Bean的内容的debug截图:
总结及思考
源码3.0已经分析完了,这里开始总结和回答下开头的问题,我这里先列出启动jar包时的命令:java -Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=5005 -Dlogging.level.root=info -jar spring-boot-learn-1.0-SNAPSHOT.jar --spring.profiles.active=profile --spring.main.lazy-initialization=false
这段源码做了什么?
可以看出这段代码主要做了三件事:
- 创建了一个enviroment对象,这个对象里包含了当前spring进程启动参数(比如:-D开头的-Dlog.root.level,)、环境变量、命令行参数(比如这里的–spring.profiles.active=profile --spring.main.lazy-initialization=false)等信息
- 将spring.main开头的配置的值赋值给SpringApplication对象里的属性上,SpringApplication对象就是整个Springboot启动时的主入口类
- 在environment对象创建时,发布environmentPrepared事件给所有的监听者,来启动对应的事件监听逻辑
为什么这么做?
知道做了什么,那么也就理解为什么了,主要就是将相关配置项都解析到这个environment对象里,供后续的逻辑使用
学到了哪些东西?
- 通过这段代码,了解到配置的生效原理,只要在对应的位置配置相应的配置,那么在spring容器里,就可以通过注入来使用这些属性
- 在这里我们可以利用Listener的扩展机制,监听自己需要监听事件
- SpringApplication对象支持一些初始的配置可供选择,比如这个启动命令里的spring.profiles.active、spring.main.lazy-initialization,其中比较常用的可能就是logging.file.path、logging.level.*、spring.config.location、spring.profiles.active等等,具体见文档:https://docs.spring.io/spring-boot/docs/current/reference/html/appendix-application-properties.html#core-properties