application.properties与Spring Environment详解

目录

概述

从application.properties配置文件说起

Spring的Environment

Profile-specific Properties – 特定profile的属性

如何激活某一个profile

PropertySource

@Value注解的使用

spring boot 中的实现以及 bootstrap.properties用法介绍


概述

spring boot提供了非常多的方便的所谓拆箱即用的功能,也提供了丰富的配置支持,java的开发人员在日常学习和工作中几乎都在和spring boot提供的这些配置打交道,我们实现逻辑,进行配置,当不熟悉一个东西的时候,配置往往是我们需要去学习的第一步,配置从某种程度来说就是我们的app的一个运行环境,在spring的官方文档当中,相关的内容也被放到了Environment,即环境这个部分之下。那么这些配置的全貌到底是怎样的,又是怎样实现的呢?

application.properties配置文件用法

为什么先从application.properties文件说起,是因为我们在学习以及使用spring boot的一些示例程序时,都会有这样一个文件,我们对这个文件已经不陌生了,它的功能我们也是大概知道的。

在spring boot的项目当中,我们可以将配置信息以key-value,即键值对的形式写在application.properties文件里面,例如:

test.spring.configuration = testValue

其中test.spring.configuration就是key,testValue就是这个key的value。
不要小看写在这里的这个字符串。将信息写在properties文件里的意味着它已经和我们的应用程序隔离开了,是解耦合的,文件里面的内容是在程序启动之后动态读取的,这意味着我们可以通过更改配置文件而改变程序执行的逻辑,但是却并不需要改动任何逻辑代码。为什么写在这些properties文件里的值可以被我们在代码中使用呢,原因是,spring boot在启动时已经帮我们把这些值都放在了Spring的Environment里面了。

SpringApplication从以下位置的application.properties文件加载属性,并将其添加到Spring的Environment中:

  1. 当前目录的/config子目录
  2. 当前目录
  3. classpath下的/config目录
  4. classpath路径根目录

上面的目录将按优先级排序,若在不同目录下定义了相同的属性怎么办呢,spring采取的方式是让在这些目录中较高位置定义的属性覆盖在较低位置定义的属性。我们也可以自己定制propertie文件的名字,以及它加载属性的位置,spring支持这样的用法,但一般我们使用标准的用法就能满足需求。

Spring的Environment概念

Environment即环境,也可以叫做上下文,相当于是是我们的应用程序运行时的一个背景信息。在spring中,Environment这个概念是与spring的容器container集成在一起的一个抽象概念。它主要为我们的应用程序环境的两个方面的支持:profiles and properties。在spring库中,Environment也有其接口的代码定义,如下所示:

public interface Environment extends PropertyResolver {
    String[] getActiveProfiles();

    String[] getDefaultProfiles();

    boolean acceptsProfiles(String... var1);
}

properties我们在上文已经有所涉及了,我们知道它是与配置文件有关的东西,那么profiles是什么呢?

spring是这么定义profile这个概念的:profile的作用就是符合这个给定profile配置的bean的集合。我们已经很熟悉spring中的bean了,而bean在定义好之后,是可以分配到一个指定的profile的。注意,这与bean定义的方式没有关系,无论是采用xml的方式还是java注解的方式定义bean,我们都可以配置其属于哪一个profile。并且这一点是在运行时确立的。

同时,当有多个profile的时候,我们可以指定哪一个Profile是生效的,如果不指定的话spring则会根据默认的profile去运行。若使用java配置的话,我们可以使用注解@Profile来指定某个bean属于哪一个profile,只有当这个指定的profile被激活处于active状态时,这个bean才会被创建。例如下面这个datasource bean的例子:

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
import org.springframework.jdbc.datasource.DriverManagerDataSource;

import javax.sql.DataSource;

@Configuration
public class TestConfiguration {

    @Bean
    @Profile("dev")
    public DataSource devDatasource() {
        DriverManagerDataSource dataSource = new DriverManagerDataSource();
        dataSource.setDriverClassName("org.hsqldb.jdbcDriver");
        dataSource.setUrl("jdbc:hsqldb:hsql://localhost:");
        dataSource.setUsername("");
        dataSource.setPassword("");
        return dataSource;

    }
}

此处的@Profile注解是修饰在方法上的,目前的spring技术支持@Profile在类级别以及方法级别修饰,但是在spring3之前是只支持类级别的。若放在类上的话,则整个类中的bean都只有在对应的profile被激活时才会被创建。我们也可以用xml配置,在这里不做展开。

再回过头来看看properties,properties文件顾名思义就是拿来存放属性的。它能帮我们管理各种信息,如:应用程序中的一些属性变量、JVM系统属性、系统的环境变量、servlet上下文参数、等等。properties配置文件在几乎所有的应用程序中都有着重要的应用。

另外,在上面贴出的Environment接口代码当中,我们看但这个接口继承了另一个叫PropertyResolver的接口,这个接口是与property相关的,而Environment当中除了定义了profile的相关方法之后并没有其他和profile相关的类或接口的抽象。这一点其实不难理解,Environment和property是最基本的概念与抽象,而profile是指的生效的properties,因此它只是提供了方法的抽象,从涉及角度出发我们就能准确的理解这两个接口的定义。PropertyResolver接口的定义如下:

public interface PropertyResolver {
    boolean containsProperty(String var1);

    @Nullable
    String getProperty(String var1);

    String getProperty(String var1, String var2);

    @Nullable
    <T> T getProperty(String var1, Class<T> var2);

    <T> T getProperty(String var1, Class<T> var2, T var3);

    String getRequiredProperty(String var1) throws IllegalStateException;

    <T> T getRequiredProperty(String var1, Class<T> var2) throws IllegalStateException;

    String resolvePlaceholders(String var1);

    String resolveRequiredPlaceholders(String var1) throws IllegalArgumentException;
}

 接口里面定义的大部分方法都是获取property的,这很好理解,同时还有两个方法与PlaceHolder占位符有关,我们可以联想到这与spring中property的用法必然有关系,这一点稍后再谈。

Profile-specific Properties – 特定profile的属性

上文我们已经介绍了profile和property这两个概念,以及更大的Environment概念,现在我们来看一下前两者结合的一个重要用法。

我们在上文提到properties文件时,都是默认其为application.properties文件,事实上除了application.properties文件外,还可以使用以下命名约定定义特定的属性:

application-profile.properties

spring的配置环境有一组默认配置文件,如果未在启动时指定active的配置文件,则使用默认配置文件。换句话说,如果没有显式激活某一个配置文件,那么应用程序就将加载application-default.properties中的属性。

应用这个功能,在不同的环境下我们只需要切换配置文件即可。这个功能的一个典型使用场景是:对于每一个产品,我们都必然有开发dev环境,测试test环境,以及生产production环境,这几套环境之间的代码逻辑往往是保持相对同步的,但是他们的环境却是截然不同,如每个环境的一些权限的验证往往是不一样的,那么对于每一个环境特有的的配置信息,我们就可以放在这些配置文件当中。

这意味着我们的项目里会有多个properties文件。例如applicatio.properties存放通用的配置信息,application-test.properties用于存放测试环境的配置,application-prod.properties用于存放生产环境的一些信息。然后在启动我们的应用的时候,只需要指定生效的配置文件即可。而指定可以通过java程序启动的参数传禁区,我们不用改任何逻辑,甚至不用改任何配置文件。在云开发大势所趋的今天,这样的功能是必须的。

下图是项目中的一个配置文件结构示例:

 

如何激活某一个profile

上面的内容讲到了我们可以定义多个profile,并根据实际需求进行配置和切换。那么spring是如何确定到底哪个profile是active的呢。spring主要是通过下面两个属性来实现判断逻辑的

  • spring.profiles.active
  • spring.profiles.default

我们可以通过设置spring.profiles.active来告诉spring哪个profile应该被激活。若不设置,则spring就查找spring.profiles.default的值激活默认的profile,如果两者都没设置,则spring只创建没有依赖于profile的bean。值得注意的是,spring boot的应用当中,spring为我们提前已经设置了默认的profile。

告诉spring激活哪个profile的方式也是很多种的,我们可以在代码里面指定如下:

AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.getEnvironment().setActiveProfiles("development");
ctx.refresh();

注意这里的方法定义是叫setActiveProfiles,这个方法值得注意的点是它是个复数,这意味着我们可以指定多个生效的profile,事实也确实如此,如下的方式也是支持的:

ctx.getEnvironment().setActiveProfiles("developement", "test");

也可以用项目的启动参数指定生效的profile,如下

在springboot的应用当中,哪个具体的配置文件会被加载,需要在application.properties文件中通过spring.profiles.active属性来设置,例如:

spring.profiles.active=dev

就会加载application-dev.properties配置文件内容,若要指定多个profile,则以逗号分隔即可。

PropertySource介绍

上文中我们提到了property,并且知道spring boot当中我们可以在application.properties文件里以及同一目录下的其他文件配置property,这样我们就能在系统当中使用它们。事实上这是因为spring boot帮我们做了一些事情,application.properties文件里的内容才能被检测到。由于是学习知识,我们考虑没有spring boot的项目,一个普通的spring项目是如何告诉spring哪些property是应该加载的呢?答案是通过一个叫PropertySource的东西。

PropertySource是一个可以存放任何一个键值对(key value)抽象概念,这和我们上面提到的属性值就联系起来了。Spring的中的标准的环境(standardProperty)里面环境变量是通过两个PropertySource对象来确定的:

  • 一个是表示JVM系统属性的(通过System.GetProperties()方法获取)
  • 另一个则是表示系统环境变量的(通过System.GetEnv()方法获取)。

对于spring用户来说必须关注的一点是,PropertySource中的内容是可配置的,任何时候,我们想传入一个换进参数,只需将它以一个键值对的形式告诉spring就可以了。PropertySource的路径也是可以自己指定的,这意味着,我们可以指定某一个路径下的某一个文件,作为PropertySource的环境变量输入文件。使用java注解的代码示例如下:

@Configuration
@PropertySource("classpath:/com/myco/app.properties")
public class AppConfig {

    @Autowired
    Environment env;

    @Bean
    public TestBean testBean() {
        TestBean testBean = new TestBean();
        testBean.setName(env.getProperty("testbean.name"));
        return testBean;
    }
}

我们假设这个app.properties的文件已被创建,且里面有一个键值对属性:

testbean.name=myTestBean

那么上述代码的TestBean对象就能够拿到这个myTestBean的String字符串。不难想到,spring boot的加载功能,也必然是像这样实现的。

@Value注解的使用

上文我们提到了我们可以在properties文件里定义我们需要的属性,也可以通过spring的Environment 提供的getProperty方法获取对应的值,除此之外,spring还提供了非常方便的注解@Value供我们使用。

我们可以使用@value注释将properties中的值直接注入到spring的任意一个bean中。下面是一个使用@Value注解的代码示例

import org.springframework.stereotype.*;
import org.springframework.beans.factory.annotation.*;

@Component
public class MyBean {

    @Value("${name}")
    private String name;

    // ...

}

@Value后面可以跟一个参数,也就是上面代码中的name。Spring将根据这个name作为key值去我们对应的properties文件当中找它对应的value值并赋给这个String类型的变量name,例如将上述代码改成:

@Component
public class MyBean {

    @Value("${test.spring.configuration}")
    public String name;

}
 

那么在我们启动应用程序之后,这个name的值就被初始化了为test.@Value的功能就是从配置中获取属性的值,然后赋给对应的变量,这个注解可以算作一个property变量的使用方法。

spring cloud bootstrap.properties介绍

所谓bootstrap,就是指的引导程序的意思,它比我们上面提到的配置文件优先级更加高,或者说更先被加载。它的作用是负责加载外部资源的配置属性,并对加密属性进行解密。bootstrap properties指的就是外部资源,工具的一些属性。

这里需要注意的是外部这个词语,这里的外部是相对于应用程序而言的,如应用程序所部署的平台,对应用程序而言就是一个外部的环境,一个第三方的管理权限的工具,也是一个外部的环境。但是数据库的信息,我们看作是应用程序的一部分。

spring怎么处理这些外部平台的bootstrap properties呢,是通过另外为它创建一套抽象概念吗,这显然不合理,外部的环境依然属于环境这个大概念的一部分。bootstrap properties和application应用程序是共享的一个环境。并且默认情况下,这些bootstrap properties具有更高的加载优先级,并且它们是不能被覆盖的。这意外着如果我们在bootstrap.properties和application.properties中定义两个key一样的属性,那么bootstrap.properties中的属性优先级更高,且application.properties中的定义不会覆盖。并且,从这两种文件的职责划分来看,它们不应该出现定义相同key属性的情况。

spring中读取property的代码底层实现

在上文中,我们以及提到了关于property的一个重要抽象:PropertyResolver。并且贴出了其方法定义,不难猜测spring实现读取property无非就是对这些方法的一个实现。先抛开spring 的实现不看,我们不妨自己想一想,我们来实现这个功能应该怎么做。大致的思路我想应该是如下图所示:

  1. 属性定义很简单,没什么好说的
  2. 加载到环境中的功能无非是去根据指定路径读文件,若没有指定路径则可设定一个默认路径。然后按照spring的规则进行属性切分,存在某一个地方备用,为考虑提高性能,节约内存,这里很可能会设计延迟加载以及缓存这些功能在里面。加载时,同时切分到各个profile里面,如果没有指定profile,则都放在默认的profile里面。
  3. 在使用property时,根据指定的key值到缓存中进行查找,同时考虑profile是不是匹配

我们来看一下spring 的实现是否和想的类似.spring 中获取某个property的方法的定义位于类PropertySource当中,其方法定义如下:

	/**
	 * Return the value associated with the given name,
	 * or {@code null} if not found.
	 * @param name the property to find
	 * @see PropertyResolver#getRequiredProperty(String)
	 */
	@Nullable
	public abstract Object getProperty(String name);

结合注释一看就懂,这个方法是一个抽象方法,spring根据实际的情况对他进行了实现,其实现类非常之多,如下图所示:

我们以AnnotationsPropertySource的实现为例来看一下。AnnotationsPropertySource类中的方法实现如下:

  public Object getProperty(String name) {
        return this.properties.get(name);
    }

简单到不敢相信,代码中的properties属性定义如下:

    private final Map<String, Object> properties;

还是简单到不敢相信,这不就是一个我们天天都会用到的map吗。和我们推测的基本是一致的。获取一个属性是十分简单的,至于这个属性是怎么加载的,无非就是我们上面已经描述了的流程。简单的也是最主要的思路就是去指定的目录下的文件去读取然后加载到我们这里的map即可。

  • 15
    点赞
  • 41
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值