Spring Environment
最后更新于
最后更新于
先对 Environment
有一个大体的认识吧。Environment
是从 SpringFramework 3.1 开始引入的一个抽象模型,至于抽象模型,和具体的理解,我想小伙伴们可以先自行思索一下。
其实第一眼看到这个名词,我们就应该有一个模糊的猜想了,它应该是基于 SpringFramework 的工程的运行时环境。所以我们可以这样看待我们编写的基于 SpringFramework 的应用程序:
这个理解是否正确呢?我们可以去官方文档加以验证。
当我们去翻 SpringFramework 的官方文档时,会发现官方是这样概述 Environment
的:
The Environment
interface is an abstraction integrated in the container that models two key aspects of the application environment: profiles and properties. A profile is a named, logical group of bean definitions to be registered with the container only if the given profile is active. Beans may be assigned to a profile whether defined in XML or with annotations. The role of the Environment
object with relation to profiles is in determining which profiles (if any) are currently active, and which profiles (if any) should be active by default . Properties play an important role in almost all applications and may originate from a variety of sources: properties files, JVM system properties, system environment variables, JNDI, servlet context parameters, ad-hoc Properties
objects, Map
objects, and so on. The role of the Environment
object with relation to properties is to provide the user with a convenient service interface for configuring property sources and resolving properties from them.
Environment
接口是集成在容器中的抽象,可对应用程序环境的两个关键方面进行建模:Profile
和 properties
。 Profiles 是仅在指定 profile 处于活动状态( active )时才向容器注册 BeanDefinition
的命名逻辑组。它可以将 Bean 分配给不同的 profile (无论是以 XML 定义还是注解配置)。与配置文件相关的 Environment
作用是确定哪些配置文件当前处于活动状态,以及哪些配置文件在默认情况下应处于活动状态。 Properties 在几乎所有应用程序中都起着重要作用,并且可能源自多种来源:属性文件,JVM 系统属性,系统环境变量,JNDI,ServletContext
参数,临时属性对象,Map
对象等。Environment
与属性相关联的作用是为用户提供方便的接口,它可以用于配置属性源,并从 Environment
中解析属性。
讲道理这段话理解起来不是那么容易,不过第一句【Environment
是集成在容器中的抽象】,会让我们产生一种感觉:前面的理解是不是出现了一些偏差?如果按照官方文档的说法,Environment
与工程的结构应该是这样才对:
到底是不是这样呢,根据个人的理解不同,表达出来的也会不太一样。
作者个人是倾向于如下的结构理解,这样解释起来会相对更合理一些:
首先,Environment
中包含 profiles 和 properties ,这些配置信息会影响 IOC 容器中的 bean 的注册与创建;
其次,Environment
的创建是在 ApplicationContext
创建后才创建的( IOC 原理部分会解释),所以 Environment
应该是伴随着 ApplicationContext
的存在而存在;
第三,ApplicationContext
中同时包含 Environment
和组件 bean ,而且从 BeanFactory
的视角来看,Environment
也是一个 Bean ,只不过它的地位比较特殊。
基于这三点,Environment
与工程的结构应该是下图这样的:
理解了两者的结构及关系,再来回头看看 Environment
的组成部分:profiles 和 properties ,咱之前也都了解过了,所以 Environment
的整体理解也就相对没有那么难了吧!
上面并没有引用 Environment
的 javadoc 来阐述 Environment
的概念和定义,原因是 javadoc 并没有对 Application 和 Environment
之间的关系进行描述,所以小册选择在这里再贴出。
由于 javadoc 的篇幅太长,咱们拆解开来看。
nterface representing the environment in which the current application is running. Models two key aspects of the application environment: profiles and properties. Methods related to property access are exposed via the PropertyResolver
superinterface.
Environment
是表示当前应用程序正在其中运行的环境的接口。它为应用环境制定了两个关键的方面:profile 和 properties。与属性访问有关的方法通过 PropertyResolver
这个父接口公开。
这一段也是总体的概括 Environment
的基本设计和作用,不过它又提到了 PropertyResolver
这个接口,这个接口负责解析占位符( ${...} )对应的值,作用也比较容易理解,这里就不多展开解释啦。
A profile is a named, logical group of bean definitions to be registered with the container only if the given profile is active. Beans may be assigned to a profile whether defined in XML or via annotations; see the spring-beans 3.1 schema
or the @Profile
annotation for syntax details. The role of the Environment
object with relation to profiles is in determining which profiles (if any) are currently active, and which profiles (if any) should be active by default. profile 机制保证了仅在给定 profile 处于激活状态时,才向容器注册的 BeanDefinition
的命名逻辑组。无论是用 XML 定义还是通过注解定义,都可以将 Bean 分配给指定的 profile。有关语法的详细信息,请参见 spring-beans 3.1规范文档
或 @Profile
注解。Environment
的作用是决定当前哪些配置文件(如果有)处于活动状态,以及默认情况下哪些配置文件(如果有)应处于活动状态。
通过前面的学习,这段文档注释也就不难理解了吧。Environment
配合 profile 可以完成指定模式的环境的组件装配,以及不同的配置属性注入。
Properties play an important role in almost all applications, and may originate from a variety of sources: properties files, JVM system properties, system environment variables, JNDI, servlet context parameters, ad-hoc Properties objects, Maps, and so on. The role of the environment object with relation to properties is to provide the user with a convenient service interface for configuring property sources and resolving properties from them. Properties
在几乎所有应用程序中都起着重要作用,并且可能来源自多种途径:属性文件,JVM 系统属性,系统环境变量,JNDI,ServletContext
参数,临时属性对象,Map等。Environment
与 Properties
的关系是为用户提供方便的服务接口,以配置属性源,并从中解析属性值。
上一章的配置元信息中我们已经知道 properties 的最大作用之一是做外部化配置,Environment
中存放了很多 properties ,它们的来源有很多种,而最终的作用都是提供了属性配置,或者给组件注入属性值。
Beans managed within an ApplicationContext
may register to be EnvironmentAware
or @Inject
the Environment in order to query profile state or resolve properties directly. In most cases, however, application-level beans should not need to interact with the Environment directly but instead may have to have ${...} property values replaced by a property placeholder configurer such as PropertySourcesPlaceholderConfigurer
, which itself is EnvironmentAware and as of Spring 3.1 is registered by default when using <context:property-placeholder/>
.
在 ApplicationContext
中管理的 Bean 可以注册为 EnvironmentAware
或使用 @Inject
标注在 Environment
上,以便直接查询 profile 的状态或解析 Properties
。 但是,在大多数情况下,应用程序级 Bean 不必直接与 Environment
交互,而是通过将 ${...} 属性值替换为属性占位符配置器进行属性注入(例如 PropertySourcesPlaceholderConfigurer
),该属性本身是 EnvironmentAware
,当配置了 <context:property-placeholder/>
时,默认情况下会使用 Spring 3.1 的规范注册。
这一段的描述主要讲了两件事情:Environment
可以注入到组件中,用于获取当前环境激活的所有 profile 模式;但是又不推荐开发者直接使用它,而是通过占位符注入配置属性的值。为什么会这么说呢,其实这个又要说回 Environment
设计的原始意图。Environment
的设计本身就应该是一个不被应用程序接触到的 “环境” ,我们只能从环境中获取一些它已经有的信息,但不应该获取它本身。所以,在处理 properties 的获取时,直接使用占位符就可以获取了。
Configuration of the environment object must be done through the ConfigurableEnvironment
interface, returned from all AbstractApplicationContext
subclass getEnvironment()
methods. See ConfigurableEnvironment Javadoc
for usage examples demonstrating manipulation of property sources prior to application context refresh()
.
必须通过从所有 AbstractApplicationContext
子类的 getEnvironment()
方法返回的 ConfigurableEnvironment
接口完成环境对象的配置。请参阅 ConfigurableEnvironment
的 javadoc 以获取使用示例,这些示例演示在应用程序上下文 refresh()
方法被调用之前对属性源进行的操作。
注意这里,ApplicationContext
的根实现类 AbstractApplicationContext
获取到的是 ConfigurableEnvironment
,它具有 “可写” 的特征,换言之我们可以修改它内部的属性值 / 数据。不过话又说回来,通常情况下我们都不会直接改它,除非要对 SpringFramework 应用的启动流程或者运行中进行一些额外的扩展或者修改。
到这里,整个 javadoc 也就读完了,最后总结一下吧,这部分最终还是要理解,并且最好用自己的话概括出来。
**Environment**
是 SpringFramework 3.1 引入的抽象的概念,它包含 profiles 和 properties 的信息,可以实现统一的配置存储和注入、配置属性的解析等。其中 profiles 实现了一种基于模式的环境配置,properties 则应用于外部化配置。
了解了概念和设计的思想,下面咱来看看 Environment
在 SpringFramework 中设计的结构。
借助 IDEA ,可以看到 Environment
的上下级继承和派生关系:
这里面的几个重要的接口和类关注一下。
这个接口,只从接口名就知道它应该是处理占位符 ${} 的。观察接口的方法定义,直接实锤了它就是做配置属性值的获取和解析的:(下面是 PropertyResolver
的部分方法定义)
所以由此也就证明了:**Environment**
可以获取配置元信息,同时也可以解析占位符的信息。
老套路了,看到 Configurable 开头,立马应该想到它又扩展了什么 set 方法吧!果然,可以从接口的方法定义中看到这样的方法名:setActiveProfiles
、addActiveProfile
。。。好了不用说了,这个接口一定可以用编程式设置 profile !
除此之外,这个接口中还有一个方法比较值得注意:
看方法名可以知道它会返回所有的 PropertySource
对象,可是 MutablePropertySources
是个什么鬼呢?点开它的源码,发现它的内部就是一个 List :
由此可以总结出一个小小的结论:Mutable 开头的类名,通常可能是一个类型的 List 组合封装。
它是 SpringFramework 中默认使用的标准运行时环境的抽象实现,不过它里面的方法实现的非常少,基本都是由 AbstractEnvironment
负责实现。在后面的原理部分,我们还会再见到它的,这里先留意一下就好。
虽说 Environment
不建议直接在应用程序中使用,但是部分场景下还是需要直接接触它来操纵。本节小册不会直接介绍 Environment
的实际使用,而是先带小伙伴用最简单的方式用一用,体会一下 Environment
的作用即可。
既然 Environment
存在于 ApplicationContext
中,那么获取 Environment
的方式自然也就可以想到:@Autowired
就可以吧!下面咱来实际操作一下。
任意编写一个 Bean ,并声明注入 Environment
:
之后直接使用包扫描的方式,驱动 AnnotationConfigApplicationContext
,就可以取到刚写的这个 EnvironmentHolder
了:
运行 main
方法,控制台会打印出 Environment
的对象信息:
除此之外,联想到 BeanFactory
、ApplicationContext
的注入方式还有回调注入,作为 SpringFramework 的内置 API ,估计也会有一个 Aware 回调注入的接口吧!那自然是必须的,EnvironmentAware
就是回调注入的接口,小伙伴们可以自行实现操作一下,小册这里就不示范了(实在是简单的一批)。
注:使用 @Autowired
的方式在某些情况下会注入失败,所以对于小伙伴们而言,注入是否能成功需要亲手测试运行检验才能知道。在后面的后置处理器部分,会演示一种无法使用 @Autowired
注入 Environment
的方式,小伙伴们到时候可以留意一下。
既然上面已经获取到 Environment
了,那操作 Environment
的方法自然也就不是问题了。
为了方便获取 properties 的配置信息,这里编写一个配置类,把上一章 PropertySource
的 jdbc.properties
加载进去:
之后使用该配置类驱动 IOC 容器即可。
EnvironmentHolder
中,这次我们取一下默认的 profiles ,以及 jdbc.properties
中的配置属性值:
重新驱动 IOC 容器,并取出 EnvironmentHolder
执行 printEnvironment
方法,控制台打印如下信息:
至此,Environment
的功能已正常使用。
Environment
中其他的 API ,小伙伴都可以自己动手使用一下,小册就不展开举例了。下面我们来向更深层次研究几个问题。
先注意一下上面控制台打印的默认 profiles ,发现它有一个默认值是 default ,它是从哪来的呢?我们又没声明呀!
想知道 profiles 的默认配置,就要进入到 Environment
的抽象实现 AbstractEnvironment
中了:
看,这里有一个很有意思的操作:getDefaultProfiles
调用了 doGetDefaultProfiles
方法,这个设计在 SpringFramework 中大量出现和使用!
在 SpringFramework 的框架编码中,如果有出现一个方法是 do 开头,并且去掉 do 后能找到一个与剩余名称一样的方法,则代表如下含义:不带 do 开头的方法一般负责前置校验处理、返回结果封装,带 do 开头的方法是真正执行逻辑的方法(如 **getBean**
方法的底层会调用 **doGetBean**
来真正的寻找 IOC 容器的 bean ,**createBean**
会调用 **doCreateBean**
来真正的创建一个 bean )。
看这个方法的实现,整体逻辑也不算复杂,关键是看它取框架默认的 profiles ,它其实就是取的 AbstractEnvironment
中内置的常量:
由此,可知默认的 default 来源。
上面的源码中,也可以看到,我们可以通过声明 **spring.profiles.default**
的配置,来覆盖 SpringFramework 中原有的默认 profiles ,一个比较常用的方法是在 jvm 的启动参数上添加:
在 IDEA 的启动配置中,声明 VM options 就可以指定默认的 profiles 了。
同理,指定激活的 profiles 也可以像这样指定,只不过它的参数名称为 **spring.profiles.active**
。
前面看 Environment
的结构,我们已经知道 Environment
继承了父接口 PropertyResolver
,自然它拥有解析配置元信息的能力,它的底层是如何实现的呢?是 Environment
自己干活,还是...有其人?
借助 IDE ,翻看 PropertyResolver 的子接口和实现类,发现仅仅就这么几个而已:
自然地,我们要去找 Environment
的实现类,StandardEnvironment
,看它是如何解析配置属性值的。
翻看 StandardEnvironment
,发现 getProperty
方法并没有在此实现,而是父类 AbstractEnvironment
中,但是实现类中发现它是直接调用了自身组合的一个 ConfigurablePropertyResolver
来处理:
一般的,我们称这种方式叫做 “委派” ,它与代理、装饰者不同:委派仅仅是将方法的执行转移给另一个对象,而代理可能会在此做额外的处理,装饰者也会在方法执行前后做增强。
继续往里看,就要进入 PropertySourcesPropertyResolver
的底层来研究了,小册认为再往里研究的性价比相对就不高了:这里面的解析逻辑相对复杂,但搞明白后的收益并不大,综合来看不太适合再深入研究。小伙伴们只需要了解 Environment
的解析配置属性值的底层是交给 PropertySourcesPropertyResolver
来处理就好啦。