springboot源码分析7-环境属性构造过程(上)

简介: 使用springboot的目的就是在项目开发中,快速出东西,因此springboot对于配置文件的格式支持是非常丰富的,最常见的配置文件后缀有如下四种:properties、xml、yml、yaml,比如我们在springboot项目根目录中配置了一个application.properties文件,则springboot项目启动的时候就会自动将该文件的内容解析并设置到环境中,这样后续需要使用该文件中配置的属性的时候,只需要使用@value即可。

使用springboot的目的就是在项目开发中,快速出东西,因此springboot对于配置文件的格式支持是非常丰富的,最常见的配置文件后缀有如下四种:properties、xml、yml、yaml,比如我们在springboot项目根目录中配置了一个application.properties文件,则springboot项目启动的时候就会自动将该文件的内容解析并设置到环境中,这样后续需要使用该文件中配置的属性的时候,只需要使用@value即可。同理application.xml、application.yml、application.yaml文件也会自动被加载并最终设置到环境中。

上面我们提到了环境,那么环境到底是个什么玩意呢?在这里提前说一下:我们这里关注的是源码层面的事情。并非讲解api如何使用。

大家首先思考一下,springboot项目如何启动,这个到很简单,无外乎引入springboot依赖包,设置项目启动的main方法如下所示:

@EnableAutoConfiguration

public class Application {

    private static Logger logger = LoggerFactory.getLogger(Application.class);

    public static void main(String[] args) {

        long startTime = System.currentTimeMillis();

        SpringApplication.run(Application.class,args);

        logger.info("程序启动花费时间为:" + (System.currentTimeMillis() - startTime) / 1000 + "秒");

    }

}

上述的代码非常的简单,但是springboot做了非常多的事情,因为springboot代码体系非常庞大,所以后续的文章是我们讲解那些源码就直接看那些源码,把不需要了解的暂时放到一边。因此在这里暂时先关注环境的创建源码,我们快速定位到SpringApplication类中的public ConfigurableApplicationContext run(String... args)方法,该方法关于环境的准备代码如下所示:

...

ApplicationArguments applicationArguments = new DefaultApplicationArguments(

args);

ConfigurableEnvironment environment = prepareEnvironment(listeners,applicationArguments);

...

prepareEnvironment方法从名字就可以看出来是准备环境(Environment),prepareEnvironment代码如下:

private ConfigurableEnvironment prepareEnvironment(

SpringApplicationRunListeners listeners,

    ApplicationArguments applicationArguments) {

    //获取或者创建环境

    ConfigurableEnvironment environment = getOrCreateEnvironment();

    //配置环境的信息

    configureEnvironment(environment, applicationArguments.getSourceArgs());

    //通知所有的观察者,环境已经准备好了。

    listeners.environmentPrepared(environment);

    bindToSpringApplication(environment);

if (this.webApplicationType == WebApplicationType.NONE) {

environment = new EnvironmentConverter(getClassLoader())

.convertToStandardEnvironmentIfNecessary(environment);

}

ConfigurationPropertySources.attach(environment);

    return environment;

}

接下来,我们一步步的分析。

1.1.  1.获取或者创建环境

getOrCreateEnvironment()方法如下所示:

 if (this.environment != null) {

    return this.environment;

}

if (this.webApplicationType == WebApplicationType.SERVLET) {

    return new StandardServletEnvironment();

}

return new StandardEnvironment();

上述代码逻辑如下:

1.如果environment不为空则直接返回。

2.如果是web环境则直接实例化StandardServletEnvironment类。

3.如果不是web环境则直接实例化StandardEnvironment类。

1.2.  WebApplicationType类型

Springboot2版本开始增加了WebApplicationType的类型,其定义如下:

public enum WebApplicationType {

/**

 * 不需要再web容器的环境下运行,也就是普通的工程

 */

NONE,

/**

    基于servletWeb项目

 */

SERVLET,

/**

响应式web应用==reactive web Spring5版本的新特性

 */

REACTIVE

}

1.3.  ConfigurableEnvironment

environment 为ConfigurableEnvironment类型。我们不妨看一下该类的层次图如下所示:

 

    Environment接口是Spring对当前程序运行期间的环境的封装(spring)。主要提供了两大功能:profile和property(顶级接口PropertyResolver提供)。目前主要有StandardEnvironment、StandardServletEnvironment和MockEnvironmentStandardReactiveWebEnvironment4种实现,分别代表普通程序、Web程序测试程序的环境、响应式web环境。通过上述的getOrCreateEnvironment方法处理逻辑也是可以总结出来的。

StandardReactiveWebEnvironmentSpringboot2新引入的,之前的版本没有这个类。关于这一个后续的章节会单独的详细讲解。

2.环境的装载

在上面的代码中实例化了StandardServletEnvironment类(我自己的环境是web),实例化该类的时候肯定会实例化其父类AbstractEnvironment,AbstractEnvironment类的构造函数如下:

public AbstractEnvironment() {

    customizePropertySources(this.propertySources);

 }

需要注意一点,因为实例化的是StandardServletEnvironment类,jvm会自动触发其父类中的构造函数,但是当前程序的this指针依然是StandardServletEnvironment。

this.propertySources属性如下所示:

AbstractEnvironment.java

private final MutablePropertySources propertySources = new MutablePropertySources(this.logger);

我们继续跟踪customizePropertySources方法,如下所示:

AbstractEnvironment.java

protected void customizePropertySources(MutablePropertySources propertySources) {

 }

好吧,customizePropertySources方法竟然是个空的实现,但是注意一点,当前程序this是StandardServletEnvironment实例,我们不妨看一下StandardServletEnvironment类中是否重写了该方法。果不其然,StandardServletEnvironment类重写了customizePropertySources方法,详细代码如下所示:

StandardServletEnvironment.java

protected void customizePropertySources(MutablePropertySources propertySources) {

 //servletConfigInitParams

propertySources.addLast(new StubPropertySource(SERVLET_CONFIG_PROPERTY_SOURCE_NAME));

//servletContextInitParams

propertySources.addLast(new StubPropertySource(SERVLET_CONTEXT_PROPERTY_SOURCE_NAME));

 //jndiProperties

 if (JndiLocatorDelegate.isDefaultJndiEnvironmentAvailable()) {

     propertySources.addLast(new JndiPropertySource(JNDI_PROPERTY_SOURCE_NAME));

    }

    super.customizePropertySources(propertySources);

    }

上述的代码中,propertySources为AbstractEnvironment.java中的propertySources字段,因为他是个引用类型,所以可以拿到指针即可修改其值。

1.4.  propertySources

虽然我们暂时还不知道propertySources要干啥,但是我们还是先看明白PropertySources到底要干啥。PropertySources类可以参考springboot源码分析6-springboot之PropertySource类初探一文。

我们再次看一下customizePropertySources方法的实现:

首先添加servletConfigInitParams,然后添加servletContextInitParams,其次判断是否是jndi环境,如果是则添加jndiProperties,最后调用父类的customizePropertySources(propertySources)。

在跟进父类的customizePropertySources(propertySources)方法之前,我们总结一下MutablePropertySources类中propertySourceList已经存在的属性为servletConfigInitParams、servletContextInitParams、jndiProperties(如果存在)。

StandardEnvironment类为StandardServletEnvironment类的父类,该类的customizePropertySources方法如下:

protected void customizePropertySources(MutablePropertySources propertySources) {

    propertySources.addLast(new MapPropertySource(SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME, getSystemProperties()));

    propertySources.addLast(new SystemEnvironmentPropertySource(SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME, getSystemEnvironment()));

  }

1、添加systemProperties

2、添加systemEnvironment。

上述的方法逻辑执行完毕之后,MutablePropertySources类中propertySourceList已经存在的属性为servletConfigInitParams、servletContextInitParams、jndiProperties(如果存在)、systemProperties、systemEnvironment。

经过一系列的跟踪getOrCreateEnvironment方法所做的事情已经分析完毕了。我们不妨继往下看。

3.配置环境信息

configureEnvironment(environment, applicationArguments.getSourceArgs())方法详细实现如下所示:

protected void configureEnvironment(ConfigurableEnvironment environment,

    String[] args) {

    configurePropertySources(environment, args);

    configureProfiles(environment, args);

  }

3.1配置属性源

configurePropertySources(environment, args)方法的核心实现如下:

protected void configurePropertySources(ConfigurableEnvironment environment,

    String[] args) {

    MutablePropertySources sources = environment.getPropertySources();

    if (this.defaultProperties != null && !this.defaultProperties.isEmpty()) {

       sources.addLast(

       new MapPropertySource("defaultProperties", this.defaultProperties));

    }

    if (this.addCommandLineProperties && args.length > 0) {

       String name = CommandLinePropertySource.COMMAND_LINE_PROPERTY_SOURCE_NAME;

       if (sources.contains(name)) {

           PropertySource<?> source = sources.get(name);

           CompositePropertySource composite = new CompositePropertySource(name);

               composite.addPropertySource(new SimpleCommandLinePropertySource(

           name + "-" + args.hashCode(), args));

           composite.addPropertySource(source);

       sources.replace(name, composite);

       }

    else {

           sources.addFirst(new SimpleCommandLinePropertySource(args));

    }

    }

     }

1、如果defaultProperties不为空,则继续添加defaultProperties。思考一个问题defaultProperties怎么设置?

2、如果addCommandLineProperties为true并且有命令参数,分两步骤走:第一步存在commandLineArgs则继续设置属性;第二步commandLineArgs不存在则在头部添加commandLineArgs。

上述的代码执行完毕之后,MutablePropertySources类中propertySourceList已经存在的属性为commandLineArgs、servletConfigInitParams、servletContextInitParams、jndiProperties(如果存在)、systemProperties、systemEnvironment、defaultProperties(如果存在)。

3.2配置Profiles

这个后续我们用到了再来讲解。

本文我们暂时讲解到这里,后续的文章中,我们继续跟踪属性文件的加载规则以及加载过程。提前曝光一点:

commandLineArgs、servletConfigInitParams、servletContextInitParams、jndiProperties(如果存在)、systemProperties、systemEnvironment、defaultProperties(如果存在)中的属性优先级从前到后依次降低。在最前面的使用优先级最高。

比如commandLineArgs中存在一个属性a=1; systemProperties中存在一个属性a=2,则我们程序使用的时候a=1,因为越靠前的优先级越高。通过上述的优先级我们可以发现一个规律,命令行的优先级最高、其次是程序中的、然后是系统的环境变量以及属性、最后是默认的。

propertySources接口我们下一节课重点进行分析。


欢迎关注我的微信公众号,第一时间获得博客更新提醒,以及更多成体系的Java相关原创技术干货。 
扫一扫下方二维码或者长按识别二维码,即可关注。
 


相关文章
|
3月前
|
安全 Java Spring
SpringBoot2 | SpringBoot监听器源码分析 | 自定义ApplicationListener(六)
SpringBoot2 | SpringBoot监听器源码分析 | 自定义ApplicationListener(六)
49 0
|
3月前
|
设计模式 Java 容器
SpringBoot2 | SpringBoot启动流程源码分析(二)
SpringBoot2 | SpringBoot启动流程源码分析(二)
44 0
|
27天前
|
JSON Java 数据库连接
属性注入掌握:Spring Boot配置属性的高级技巧与最佳实践
属性注入掌握:Spring Boot配置属性的高级技巧与最佳实践
28 1
|
2月前
|
XML 设计模式 Java
springboot创建并配置环境3 - 配置扩展属性(下)
springboot创建并配置环境3 - 配置扩展属性(下)
springboot创建并配置环境3 - 配置扩展属性(下)
|
2月前
|
XML JSON Java
springboot如何创建并配置环境3 - 配置扩展属性(上)
springboot如何创建并配置环境3 - 配置扩展属性(上)
springboot如何创建并配置环境3 - 配置扩展属性(上)
|
2月前
|
Java
springboot创建并配置环境2 - 配置基础环境
springboot创建并配置环境2 - 配置基础环境
springboot创建并配置环境2 - 配置基础环境
|
2月前
|
Java API uml
springboot创建并配置环境1 - 创建环境
springboot创建并配置环境1 - 创建环境
springboot创建并配置环境1 - 创建环境
|
2月前
|
XML 设计模式 Java
springboot如何创建并配置环境
springboot如何创建并配置环境
springboot如何创建并配置环境
|
3月前
|
Java Docker 容器
docker-compose部署一个springboot项目(包含环境)
docker-compose部署一个springboot项目(包含环境)
59 0
|
3月前
|
前端开发 Java Spring
SpringBoot2 | SpringBoot Environment源码分析(四)
SpringBoot2 | SpringBoot Environment源码分析(四)
23 0