Credit Suisse和Oracle曾试图为Java EE的配置创建一个宏伟的JSR标准,现在距离这个计划的破产已经过去了两年的时间。导致这个计划破产的原因很多,我们在这里的关注点也不是讨论它的细节。需要说明的是,尽管官方的JSR从未被JCP执行委员会所批准,但是标准化Java配置的努力却从未停止过。在本文中,我将会关注后续的工作以及这个初始项目的当前状态。
配置是一个通用的横切性关注点,跨所有的应用类型。属性通常会以key = value的形式进行指定,这些属性会以文件的形式来提供并且会加载到一个Java Properties对象中。令人遗憾的是,OSGi、Spring、Java EE、SE以及其他在Java中运行的框架和解决方案都提供了自己的配置API和格式。其中有很多会使用专有的XML格式,另外一些则可能使用更为现代化的格式,比如Yaml。Java EE甚至不支持大多数场景下的动态和远程配置。在应用中,组合使用不同的框架通常都是非常繁琐的,这要归因于不同的配置格式、存放位置以及冗余性。这都会增加不必要的复杂性并且易于出现错误。它会影响到某个应用内部的代码编写,同时还会影响它与周边系统的集成。在过去的二十年间,Java在很多领域都做出了巨大的贡献,为各种类型的应用开发构建了无与伦比的生态系统。这不免令人觉得有些怪异,在配置管理这样一个通用关注点上居然缺乏一个标准API,如果能有一个这样的标准的话,应用程序就不用构建自己的配置方案了,同时也可以简化与不同利益相关者所提供的模块进行集成。
在如何进行配置以及配置到底该是什么样子方面,所涉及到的意见差别很大。因此,配置标准不应该关注于配置什么内容或何时进行配置。以此作为驱动力,我们将已有的知识和实验性代码转移到了一个新的孵化项目中,这个项目的名称叫做Apache Tamaya。我们早期的讨论集中在已有的想法和需求上,但最终,我们后退了一步,从头开始重新定义使用场景,打造了一个崭新的实现。鉴于配置管理是使用最广泛的横切性关注点之一,我们希望和期待这项工作能够成为某种形式的标准,让整个Java生态系统都能从中受益。
Tamaya的一些特性包括:
-
定义了一组配置注解(tamaya-inject-api),它们可以添加到客户端代码中,从而注入配置的值。注解会按照一种统一的方式来运行,不管你的代码是作为简单老式的Java SE方式运行,还是运行在CDI容器或Spring环境之中。它们甚至还支持OSGi服务。
-
Tamaya所支持的并不局限于
String
值,可以是任意的Java类型,只要我们所注册的
PropertyConverters
能够从原始的配置值(
String
类型)衍生出类型化的值就可以,例如将其作为Date或URL。
-
此外,Apache Tamaya还提供了无数的扩展和功能集成,这样的话就能根据用户的需求自定义运行时的配置(这样的话,允许用户为他们的系统选择最合适功能,从而解决了配置复杂性所面临的挑战)。这里很棒的一点在于所有的扩展都不会依赖于核心模块,除非运行在测试作用域(test scope)中,这个作用域提供了一个功能性的实现,用来执行我们的测试用例。
简而言之,借助Apache Tamaya当前发布的0.2-incubating版本,我们有了一个功能完整的配置解决方案,它提供了稳定且经过验证的API/SPI。众多的扩展业已成功证明,它能够以一种可重用和可扩展的方式对配置进行建模。
除此之外,我们还基于注入API定义了一个完整的注解,并提供了对Java EE/CDI的支持(以及多个其他的运行时平台)。所以,现在也许应该重新讨论一下是否要将我们的想法转化为一个JSR。这样的话,所有的Java爱好者和专家(包括本文的读者)都可以了解一下Tamaya并提供反馈。毫无疑问,我们目前并没有上百万美元的营销预算,所以对于您的帮助,我们将会感激之至。
现在,我们更加深入一些,看一下所谓的“一次配置,到处运行”对于开发人员的日常工作到底意味着什么。
Apache Tamaya涵盖了各种各样的使用场景,借助我们的模块化结构,你可以很容易地添加任何目前尚不存在的功能。大致看来,Tamaya最为重要的特性包括:
-
一套完整统一的API,可用于基于编程或注解方式的配置访问,适用于Java SE和EE环境;
-
支持像Spring和OSGi这样的框架;
-
支持动态增强(允许我们引入额外的属性源);
-
过滤可访问的key/value,这种过滤可以按照单个属性来进行,也可以按照完整的配置来进行。这样的话,就允许key和value进行变更(例如,密码要进行掩码处理)、省略或添加。或者,我们也可以基于访问控制列表来进行访问的限制;
-
默认情况下,配置会组织成PropertySource的有序列表,每个PropertySource都会有一个顺序值。PropertySource可以提供任意类型的属性,比如系统属性、环境属性、基于文件的属性,以及任意种类的来源和格式。具有较高顺序值的PropertySource将会覆盖(默认情况下)具有较低顺序值的PropertySource所提供的属性条目(每个PropertySource会实现针对某种资源的映射);
-
正如前面所介绍的,针对相同的key,重要性较高的值(由具有较高顺序值的PropertySource所提供的值)会覆盖掉重要性较低的值。但在有些场景下,由用户来自定义行为会更加合适,为了支持这些场景,我们允许用户自定义联合策略,从而实现更为灵活的覆盖机制。例如,在配置
List
值的时候,我们可以定义一种行为来联合两个值
“a, b”
和
“c, d, e”
,从而形成
“a, b, c, d, e”
;
-
在配置文件中,支持占位符,例如
${sys:a.sys.property}、${url:config.server:8090/v1/a.b.c}、${conf:cross.ref}
;
-
支持多种配置格式,比如Yaml、Json、属性文件等;
-
能够与各种新的、特定的配置后端集成,比如etcd和Consul;
-
支持动态和灵活的资源定位,例如在数据库中、在Consul或etcd中、在文件中等;
-
支持动态的配置处理、事件以及可变配置。
尚未发布的特性包括(计划在0.3-incubating版本会涵盖):
接下来,让我们看一下“配置一次,到处运行”的一些样例。使用Tamaya来实现可配置的组件通常会包含如下步骤:
-
组件的实现;
-
决定可配置的方面;
-
定义key以及合理的默认值,从而能够支持“约定优于配置”;
-
将提供集成功能的Tamaya库添加到你的目标运行时环境中,这种环境可能是Java EE servlet容器或Spring Boot应用;
-
通过硬编码的默认值来使用组件,随后可以通过正常的实现类来运行,不需要任何额外的配置。借助Tamaya,可以文档化和观察我们的配置,通过添加某项简单的依赖,我们就可以指定生效的文件格式或配置后端。
我们来考虑一个简单的样例:假设我们正在构建一个组件,这个组件能够提供某个应用的售后支持通讯录信息。
SupportContact
组件的定义如下所示:
为了配置这个类,我们可以实现一个构造器来执行配置逻辑:
这种声明式的访问方式确实可以运行,但是大多数开发人员都用过像Spring这样的依赖注入框架,这样的话,他们可能会想要使用tamaya-injection模块来配置该实例:
配置代码也可以位于一个外部的配置类中,这样的话,原始的类就不会受到什么影响了。所有内置的Java类型,比如
String、boolean
和
int
以及
java.math.BigDecimal
和
BigInteger
类型,默认都是支持的。如果以依赖的方式将tamaya-collections 模块添加进来的话,也能够支持集合类型。Tamaya的ConfigurationInjector是一个将配置注入到POJO中的接口,如果没有配置注解的话,它会按照一种最优的猜测方案来配置所有能够找到属性。它会将包名、类名以及域名组合为候选key的有序列表,并试图查找对应的配置值。所遇到的第一个非null值将会被注入。未定义的属性(所有的候选key均没有匹配到值)将会以警告的形式进行日志记录,但是不会抛出异常。这样的话,就允许我们在代码中,通过标准Java的形式提供默认值,如果需要的话,这些默认值会被属性重写:
更深入地介绍一下,Tamaya的属性映射机制会将这些条目映射为如下的候选key列表:
我们还可以借助tamaya-injection-api扩展模块所提供的注解,为代码配置更为精确的key。如果我们采用这种方式的话,那么可以为类和属性添加如下所示的注解:
这样的话,我们就可以完全控制Tamaya的属性映射机制如何映射这些条目,也就是:
所以,我们可以将这些属性定义到一个简单的
.properties
文件中:
或者在定义一个yaml文件之中: