springboot源码分析之环境属性构造过程1

165 篇文章 5 订阅
57 篇文章 1 订阅

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

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

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

@EnableAutoConfiguration
@ComponentScan(value = "cn.bainuoyoupin.web")
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);
    //将第一步创建的环境进行转换
    if (isWebEnvironment(environment) && !this.webEnvironment) {
        environment = convertToStandardEnvironment(environment);
    }
    return environment;
}

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

1.获取或者创建环境。

getOrCreateEnvironment()方法如下所示:

private ConfigurableEnvironment getOrCreateEnvironment() {
    if (this.environment != null) {
        return this.environment;
    }
    if (this.webEnvironment) {
        return new StandardServletEnvironment();
    }
    return new StandardEnvironment();
}

上述代码逻辑如下:

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

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

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

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

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

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字段,因为他是个引用类型,所以可以拿到指针即可修改其值。

虽然我们暂时还不知道propertySources要干啥,但是我们还是先看明白PropertySources到底要干啥。PropertySources类图如下所示:

MutablePropertySources类内部持有一个propertySourceList集合,该集合的详细定义如下:

private final List<PropertySource<?>> propertySourceList = new CopyOnWriteArrayList<PropertySource<?>>();

注意:propertySourceList为List<PropertySource<?>>类型。

MutablePropertySources类提供了一系列对propertySourceList操作的方法:

addFirst:在propertySourceList 头部添加元素。

addLast:在propertySourceList 尾部添加元素。

addAtIndex:在propertySourceList 指定的位置添加元素。

等等。

不管是哪个api均是对propertySourceList集合进行操作的,这一点我们了解就可以了。

2.1PropertySource类

PropertySource类是一个抽象类,代码如下:

public abstract class PropertySource<T> {
   	protected final String name;
   	protected final T source;
...省略其他的方法
}

getSource()方法:,这个方法会返回得到属性源的源头。比如MapPropertySource的源头就是一个Map,PropertiesPropertySource的源头就是一个Properties。

name:我们可以理解为一个map中的key。

PropertySource类的子类结构如下:

RandomValuePropertySource:source是random。

ServletConfigPropertySource:source是ServletConfig。

ServletContextPropertySource:source是ServletContext。

JndiPropertySource:source是JndiLocatorDelegate。

StubPropertySource:source是Object。

MapPropertySource:source是Map<String, Object>。

了解了这些内容之后,我们再次看一下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。

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,因为越靠前的优先级越高。通过上述的优先级我们可以发现一个规律,命令行的优先级最高、其次是程序中的、然后是系统的环境变量以及属性、最后是默认的。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值