项目参数外部配置化

开发一个项目,参数是必不可少的,规模越大参数越多。在不同的测试环境中部署,或者是依赖项目的信息发生了变化,你有没有想跳楼的感觉?如果有,恭喜你,你至少已经不是在开发玩具系统了。

本文试图列举一些配置参数的方法,希望对你的项目有所帮助。

一、可用性模式-外部配置

引用自图书《Java应用架构设计:模块化模式与OSGi》10.2

“模块应该可以在外部进行配置”

当把模块部署到运行时环境中时,在使用它之前通常要进行初始化。例如,为了让模块能够访问数据库中的数据,要用必要的用户ID和密码来初始化模块。但是,我们也希望避免将配置信息与模块紧密耦合。如果这样做,将会使模块与单一的上下文环境耦合,这样就限制了模块在其他可选的上下文中进行重用。

外部配置使得模块可以跨环境上下文配置。下图展现了外部配置,在这里Client类使用一个XML配置文件配置client.jar模块。要注意的是,用来初始化client.jar的配置信息与表示模块行为的Client类分开了。能够配置模块到环境上下文中会增强跨环境重用模块的能力。

配置文件的位置,有三种处理方式:

1、配置信息包含在模块中,优势是在模块的默认上下文中很易于使用,不足在于在其他的上下文中不能正常工作。

2、配置信息不在模块中,但是在初始化的时候由外部提供给模块。优势是能跨环境重用,不足是每个环境都要配置所有参数。

3、更灵活的方案是在模块中提供默认配置文件,但是允许模块外部提供替代的配置文件。下图是图书中的一个例子。

这三种方案中,最后一种看起来最有诱惑,能够实现比较灵活的配置方式。后续我们用这种方案进行设计。

二、默认+替代的配置方案

考虑一个企业开发中一个相对简单的项目,同时提供WEB界面和API接口。为了方便其他系统调用API,同时提供一个 client jar供调用。

1、系统设计

各个模块的简单介绍:

  • base-util.jar : 通用的基础包,实现基本工具类。我们自定义的读取配置文件工具类(PropsUtil)就在这个包中。
  • business-core.jar : 业务系统的基础包,如model定义等
  • business-web.war : 业务系统的WEB项目,实现基本的业务逻辑,并提供API实现。
  • business-client.jar : 业务系统的client包,供其它系统调用。

图中的箭头代表依赖关系。题外话,在设计module时,尤其要注意的是不能出现循环依赖。

2、配置参数的约定

本文不考虑数据库连接信息等特殊需求的配置,重点放在能够通过配置工具类PropsUtil读取的那一类参数。如线程池的大小、client调用api的是服务器地址和uri等。

  • 在每个module中都放置一个配置文件conf.properties,将配置信息写在这个配置文件中。
  • 相同名称的参数加载,module中的参数会覆盖所依赖module中的参数。
  • 读取配置参数,必须使用PropsUtil.getString()/getInt()/getBoolean()的函数来读取。

3、PropsUtil的实现

工具类的实现,核心是需要解决两个问题:

  • 如何将各个jar中的conf.properties都加载
  • 如何处理各个conf.properties的加载顺序

使用SpringFrameworks的ResourcePatternResolver,可以将多个jar包、war包中的特定文件读取成Resource对象,然后加载到apache的commons configuration Configuration中。下面用代码解释一下实现。

3.1 加载Resource List

String filePattern = "classpath*:conf.properties";

// 根据文件名读取Resource列表,并做必要的排序

public static List<Resource> getResources(String filePattern) {

List<Resource> resultResources = new ArrayList<Resource>();

try {

ResourcePatternResolver resolver =

new PathMatchingResourcePatternResolver();

Resource[] resources = (Resource[]) resolver.getResources(filePattern);

List<Resource> jarResources = new ArrayList<Resource>();

List<Resource> webResources = new ArrayList<Resource>();

// 将各个jar包中发现的conf.properties文件按顺序放到jarResources

// 将war包中发现的conf.properties文件按顺序放到webResources

// 这部分代码自行脑补

// 最终合并到 resultResources

for (Resource oneResource : jarResources) {

resultResources.add(oneResource);

}

for (Resource oneResource : webResources) {

resultResources.add(oneResource);

}

} catch (IOException e1) {

logger.error("getResources", e1);

}

return resultResources;

}

3.2 将内容加载到Configuration

private volatile static Configuration[] configs = null;

private static void initConfigArray() {

configs = new Configuration[] {};

try {

int index = 0;

List<Resource> resourceList = ResourceFileUtil.getResources(propFile);

for (Resource resource : resourceList) {

InputStream inputStream = resource.getInputStream();

if (inputStream != null) {

FileConfiguration oneFileConfig = new PropertiesConfiguration();

oneFileConfig.setEncoding(StringPool.UTF8);

oneFileConfig.load(inputStream);

index++;

configs = ArrayUtil.append(configs, oneFileConfig);

}

inputStream.close();

}

} catch (IOException e1) {

}

}

3.3 读取配置参数

public static String getString(String key, String defaultValue) {

String stringValue = null;

for (Configuration oneConfig : configs) {

if (oneConfig.containsKey(key)) {

String tempValue = oneConfig.getString(key);

if (Validator.isNotNull(tempValue)) {

stringValue = tempValue;

}

}

}

if (Validator.isNull(stringValue) && Validator.isNotNull(defaultValue)) {

stringValue = defaultValue;

} else if (stringValue == null) {

stringValue = StringPool.BLANK;

}

return stringValue;

}

这儿只写了读取字符串类型的配置,如果是其他数据格式,自行从String做必要的转换即可。

至此,在需要读取配置参数的时候,只需要调用 PropsUtil.getString(),就可以取到相应的参数值。这种方法已经实现了“默认+替代”的方案,在基础模块的conf.properties中提供缺省设置,在依赖模块的conf.properties中使用新的参数值替换。

当不同的WEB项目调用同一个基础模块时,因参数不同,只需要在web的conf.properties中重新设置新的参数值即可。

三、利用Maven Profile解决多环境部署问题

conf.properties是项目的源码。如果一套系统需要在多个环境中进行部署,并且在不同的环境中参数值还不同。如果直接修改conf.properties文件,那会给打包部署带来繁琐的手工工作量。

如果项目使用Maven进行管理,则可以方便的利用maven profile对参数进行管理。

1、修改conf.properties中的参数值

以下用两个参数为例,

# 数据处理线程数

disrupter.handler.threads=2

# 向门户推送消息的尝试次数

notify.portal.try.times=5

修改后的参数值为

# 数据处理线程数

disrupter.handler.threads=${param.disrupter.handler.threads}

# 向门户推送消息的尝试次数

notify.portal.try.times=${param.notify.portal.try.times}

注意,参数值中的变量名称,不能跟前面的参数名相同,否则maven会抛异常。最简单的处理方式,就是在变量名前面加上param.

2、pom.xml中增加profiles

假设系统的部署有四套环,分别是

  • dev: 开发环境
  • testa: 第一轮测试
  • testb: 第二轮测试
  • product: 生产环境

那么,修改pom.xml文件,相关部分代码为:

<profiles>

<profile>

<id>dev</id>

<activation>

<activeByDefault>true</activeByDefault>

</activation>

<properties>

<param.disrupter.handler.threads>1</param.disrupter.handler.threads>

<param.notify.portal.try.times>1</param.notify.portal.try.times>

</properties>

<build>

<filters>

<filter>src/main/resources/conf.properties</filter>

</filters>

</build>

</profile>

<profile>

<id>testa</id>

<activation>

<activeByDefault>false</activeByDefault>

</activation>

<properties>

<param.disrupter.handler.threads>1</param.disrupter.handler.threads>

<param.notify.portal.try.times>2</param.notify.portal.try.times>

</properties>

<build>

<filters>

<filter>src/main/resources/conf.properties</filter>

</filters>

</build>

</profile>

<profile>

<id>testb</id>

<activation>

<activeByDefault>false</activeByDefault>

</activation>

<properties>

<param.disrupter.handler.threads>1</param.disrupter.handler.threads>

<param.notify.portal.try.times>2</param.notify.portal.try.times>

</properties>

<build>

<filters>

<filter>src/main/resources/conf.properties</filter>

</filters>

</build>

</profile>

<profile>

<id>product</id>

<activation>

<activeByDefault>false</activeByDefault>

</activation>

<properties>

<param.disrupter.handler.threads>2</param.disrupter.handler.threads>

<param.notify.portal.try.times>5</param.notify.portal.try.times>

</properties>

<build>

<filters>

<filter>src/main/resources/conf.properties</filter>

</filters>

</build>

</profile>

</profiles>

其中,activeByDefault表示是否为缺省profile。设置完参数后,就是在不同的环境中应用不同profile的方法问题。

3、Maven启动WEB项目时应用profile

这种方式,需要在pom.xml中增加tomcat7-maven-plugin这个plugin。

如果是在命令行使用Maven启动Tomcat,可使用如下命令:

mvn tomcat7:run -P testa

其中,-P testa , 代表的是使用testa这个profile。

如果使用Eclipse中的Run进行启用,方法类似,配置界面为:

使用maven进行项目打包,也是相同的方法, 在profile处选择testa即可。

4、在Eclipse中使用Server启动

在Eclipse中添加Server Runtime Environments后,将项目部署到Server中。在项目上右键,选择“属性”,在弹出的窗口中选择“Maven”,即可输入相应额Profile。

四、实现参数实时更新

之前的实现,已经很好的解决了多环境部署的问题。考虑到生产环境的特殊性,不能随便重启应用。如果某一个关键参数需要修改,按照之前的方案,需要重新打包并部署到生产环境,应用将会重新启动。

如果项目是关键业务,客户要求不能停机,必须实现参数的实时修改,怎么办?多点环境灰度发布,是一种解决方案;osgi模块化开发部署应该也是一种解决方案。只是这两种方案,很难在已有的项目中实现,我们还是考虑简单一点的处理方式。

1、提供参数管理功能(DB)

在系统中实现一个参数设置功能,由管理员将最新的参数值保存在数据库中。系统首先读取数据库中的参数值,如果为空再从properties文件中读取。当需要调整系统参数时,管理员进入管理界面修改并保存即可。

可以看出,系统要实现这个定制功能,需要完成:参数数据表、参数封装Service和维护界面。这种方案,比较适合产品化销售的独立运行系统,能够适应不同客户的需求。

2、利用disconf实现

如果一个运营性系统中有多个Project,则每个Project都需要开发管理功能,比较繁琐。Disconf就是针对这种情况的解决方案,在此不仔细介绍它,请自行前往网站学习 https://github.com/knightliao/disconf

Disconf的应用有两种方案:注解式分布式配置使用方式和XML配置式分布式配置方式。使用注解式,需要为配置信息定义一个专门的Java类,增减参数都需要修改这个Java类,不太适合于我们之前的配置解决方案。所以,建议采用“XML配置式分布式配置方式”。

2.1 Disconf分发配置文件

为了简化实现,项目中在原有的conf.properties文件之外,设计一个专门用于disconf更新的文件conf-disconf.properties。项目结构变为

2.2 PropsUtil的修改

这是在前面PropsUtil的基础之上进行修改,不详述,概要介绍一下需要修改的内容。

1、增加一个Resource

读取资源文件的定义为classpath*:conf-disconf.properties。这个配置文件需要记录更新时间。

2、增加一个Configuration,用于加载新配置文件的内容。这个配置需要检查资源文件的更新时间,如果发现时间有变化,则重新加载内容。

3、读取配置参数时,首先读取conf-disconf.properties中的内容,如果没有再加载原顺序加载的配置信息。

这样,当disconf Server中的配置信息发生变化,由disconf-client自动同步到应用系统后,项目中读取参数值时,就能加载到最新的参数值。

时间: 2024-10-04 08:05:55

项目参数外部配置化的相关文章

elasticsearch-query-builder, 一款可以基于配置化以及参数绑定的ES语句构造神器

前言 在这里,我想向大家推荐一个我自己开发的项目,也就是elasticsearch-query-builder,这个项目目前在github上已经开源,有兴趣的朋友可以去fork或者star,你的star就是对我最大的鼓励.同时,本项目长期维护和更新,我也接受并且很高兴有小伙伴向本项目pull request,或者协同开发,有兴趣的同学可以给我发邮件. elasticsearch-query-builder是一个非常方便构造elasticsearch(后面简称ES) DSL 查询语句的工具包,在e

External Configuration Store Pattern 外部配置存储模式

Move configuration information out of the application deployment package to a centralized location. This pattern can provide opportunities for easier management and control of configuration data, and for sharing configuration data across applications

php配置php-fpm启动参数及配置详解

约定几个目录 /usr/local/php/sbin/php-fpm/usr/local/php/etc/php-fpm.conf/usr/local/php/etc/php.ini一,php-fpm的启动参数 #测试php-fpm配置 /usr/local/php/sbin/php-fpm -t /usr/local/php/sbin/php-fpm -c /usr/local/php/etc/php.ini -y /usr/local/php/etc/php-fpm.conf -t #启动p

云计算设计模式(八)——外部配置存储模式

云计算设计模式(八)--外部配置存储模式 移动配置信息从应用部署包到一个集中位置.这个模式可以提供机会,以便管理和配置数据的控制,以及用于跨应用程序和应用程序实例共享的配置数据. 背景和问题 大多数应用程序运行时环境包括位于应用程序文件夹内的在部署应用程序文件保持配置信息.在某些情况下也能够编辑这些文件来改变该应用程序的行为,它已经被部署之后.然而,在许多情况下,改变配置所需要的应用程序被重新部署,从而导致不可接受的停机时间和额外的管理开销. 本地配置文件还配置限制为单个应用程序,而在某些情况下

3springboot:springboot配置文件(外部配置加载顺序、自动配置原理,@Conditional)

1.外部配置加载顺序 SpringBoot也可以从以下位置加载配置: 优先级从高到低 高优先级的配置覆盖低优先级的配置,所有的配置会形成互补配置  1.命令行参数 所有的配置都可以在命令行上进行指定 先打包在进行测试 java -jar spring-boot-02-config-02-0.0.1-SNAPSHOT.jar --server.port=8087 --server.context-path=/abc 指定访问的路径 多个配置用空格分开: --配置项=值 -- 由jar包外向jar包

springboot学习总结(四)外部配置

Springboot允许使用properties文件.yaml文件或者命令行参数作为外部配置 (一)常规属性配置(基于properties.yaml文件) 请求http://localhost:8090/yml-value-test,返回结果如下: (二)命令行参数配置 当Springboot是基于jar包运行时,可以使用命令行来修改yaml或者properties里面的配置文件 一般按上面的逻辑,请求http://localhost:8090/yml-value-test.返回结果应该是vin

Tokyo Tyrant(TTServer)系列(二)-启动参数和配置

启动参数介绍 ttserver命令可以启动一个数据库实例.因为数据库已经实现了Tokyo Cabinet的抽象API,所以可以在启动的时候指定数据库的配置类型. 支持的数据库类型有: 内存hash数据库 内存tree数据库 hash数据库 B+ tree数据库, 命令通过下面的格式来使用,'dbname'制定数据库名,如果省略,则被视作内存hash数据库. ttserver [-host name] [-port num] [-thnum num] [-tout num] [-dmn] [-pi

struts文件上传拦截器中参数的配置(maximumSize,allowedTypes ,allowedExtensions)问题

<interceptor-ref name="fileUpload"> <param name="allowedTypes">image/bmp,image/png,image/gif,image/jpeg,image/pjpeg</param> <!-- 图片不能大于5M --> <param name="maximumSize">5242880</param> </

springboot 外部配置&lt;上篇&gt;

SpringBoot允许在外部进行配置,让你在不同的环境中运行相同的代码.你可以通过属性文件.YAML文件.环境变量和命令行来进行外部配置.属性值可以直接通过@Value注入,并可以通过Spring的Environment抽象类 或者 绑定了@ConfigurationProperties的实体类访问. 1.配置随机值. RandomValuePropertySource在注入随机值时候非常有用(例如测试程序.加密程序中),它可以生成int.long.uuids和string等等. my.sec