項目參數外部配置化

来源:http://www.cnblogs.com/codestory/archive/2016/05/25/5527383.html
-Advertisement-
Play Games

開發一個項目,參數是必不可少的,規模越大參數越多。在不同的測試環境中部署,或者是依賴項目的信息發生了變化,你有沒有想跳樓的感覺?如果有,恭喜你,你至少已經不是在開發玩具系統了。 本文試圖列舉一些配置參數的方法,希望對你的項目有所幫助。 一、可用性模式-外部配置 引用自圖書《Java應用架構設計:模塊 ...


開發一個項目,參數是必不可少的,規模越大參數越多。在不同的測試環境中部署,或者是依賴項目的信息發生了變化,你有沒有想跳樓的感覺?如果有,恭喜你,你至少已經不是在開發玩具系統了。

本文試圖列舉一些配置參數的方法,希望對你的項目有所幫助。

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

引用自圖書《Java應用架構設計:模塊化模式與OSGi》10.2

“模塊應該可以在外部進行配置”

當把模塊部署到運行時環境中時,在使用它之前通常要進行初始化。例如,為了讓模塊能夠訪問資料庫中的數據,要用必要的用戶ID和密碼來初始化模塊。但是,我們也希望避免將配置信息與模塊緊密耦合。如果這樣做,將會使模塊與單一的上下文環境耦合,這樣就限制了模塊在其他可選的上下文中進行重用。

外部配置使得模塊可以跨環境上下文配置。下圖展現了外部配置,在這裡Client類使用一個XML配置文件配置client.jar模塊。要註意的是,用來初始化client.jar的配置信息與表示模塊行為的Client類分開了。能夠配置模塊到環境上下文中會增強跨環境重用模塊的能力。

配置文件的位置,有三種處理方式:

1、配置信息包含在模塊中,優勢是在模塊的預設上下文中很易於使用,不足在於在其他的上下文中不能正常工作。

clip_image002

2、配置信息不在模塊中,但是在初始化的時候由外部提供給模塊。優勢是能跨環境重用,不足是每個環境都要配置所有參數。

clip_image004

3、更靈活的方案是在模塊中提供預設配置文件,但是允許模塊外部提供替代的配置文件。下圖是圖書中的一個例子。

clip_image006

這三種方案中,最後一種看起來最有誘惑,能夠實現比較靈活的配置方式。後續我們用這種方案進行設計。

二、預設+替代的配置方案

考慮一個企業開發中一個相對簡單的項目,同時提供WEB界面和API介面。為了方便其他系統調用API,同時提供一個 client jar供調用。

1、系統設計

clip_image008

各個模塊的簡單介紹:

  • 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對參數進行管理。

clip_image010

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進行啟用,方法類似,配置界面為:

clip_image012

使用maven進行項目打包,也是相同的方法, 在profile處選擇testa即可。

4、在Eclipse中使用Server啟動

在Eclipse中添加Server Runtime Environments後,將項目部署到Server中。在項目上右鍵,選擇“屬性”,在彈出的視窗中選擇“Maven”,即可輸入相應額Profile。

clip_image014

四、實現參數實時更新

之前的實現,已經很好的解決了多環境部署的問題。考慮到生產環境的特殊性,不能隨便重啟應用。如果某一個關鍵參數需要修改,按照之前的方案,需要重新打包並部署到生產環境,應用將會重新啟動。

如果項目是關鍵業務,客戶要求不能停機,必須實現參數的實時修改,怎麼辦?多點環境灰度發佈,是一種解決方案;osgi模塊化開發部署應該也是一種解決方案。只是這兩種方案,很難在已有的項目中實現,我們還是考慮簡單一點的處理方式。

1、提供參數管理功能(DB)

在系統中實現一個參數設置功能,由管理員將最新的參數值保存在資料庫中。系統首先讀取資料庫中的參數值,如果為空再從properties文件中讀取。當需要調整系統參數時,管理員進入管理界面修改並保存即可。

clip_image015

可以看出,系統要實現這個定製功能,需要完成:參數數據表、參數封裝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。項目結構變為

clip_image017

2.2 PropsUtil的修改

這是在前面PropsUtil的基礎之上進行修改,不詳述,概要介紹一下需要修改的內容。

1、增加一個Resource

讀取資源文件的定義為classpath*:conf-disconf.properties。這個配置文件需要記錄更新時間。

2、增加一個Configuration,用於載入新配置文件的內容。這個配置需要檢查資源文件的更新時間,如果發現時間有變化,則重新載入內容。

3、讀取配置參數時,首先讀取conf-disconf.properties中的內容,如果沒有再載入原順序載入的配置信息。

這樣,當disconf Server中的配置信息發生變化,由disconf-client自動同步到應用系統後,項目中讀取參數值時,就能載入到最新的參數值。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 01 關於本書 02 代碼約定 03 關於例子 04 如何聯繫我們 1 核心模塊 11 介紹 111 內建函數和異常 112 操作系統介面模塊 113 類型支持模塊 114 正則表達式 115 語言支持模塊 12 _ _builtin_ _ 模塊 121 使用元組或字典中的參數調用函數 1211 E ...
  • Write a function that takes a string as input and reverse only the vowels of a string. Example 1:Given s = "hello", return "holle". Example 2:Given s ...
  • 想要深入瞭解JVM,就必須瞭解其實現機制。瞭解JVM實現的最好方法便是自己動手編譯JDK。好了,讓我們開始吧! 1. 準備工作 獲取OpenJDK源碼 獲取OpenJDK源碼 本次編譯選擇的是OpenJDK7u,官方源碼包:https://jdk7.java.net/source.html 系統需求 ...
  • Given two arrays, write a function to compute their intersection. Example:Given nums1 = [1, 2, 2, 1], nums2 = [2, 2], return [2]. Note: Each element i ...
  • 嚴格來講是給在OSC上貢獻過內容的OSC用戶刷積分。 OSC很多操作都需要消耗積分,很多人給OSC貢獻了內容, 但是卻沒有人點贊,所以有些人在OSC混了很多年也沒有積分。 本文中使用到的工具有gifcam 、fiddler 、Wireshark。gifcam 是一個小巧的GIF錄屏軟體,可以把你電腦 ...
  • consul的具體安裝與操作查看博客的consul系列。 一、啟動consul (1個server+1個client,方便起見,client使用本機):查看:http://www.cnblogs.com/java-zhao/p/5375132.html 1、開啟虛擬機-->切換到vagrantFil ...
  • 文件上傳: 1、JSP中提交method="post" enctype="multipart/form-data" 的表單 2、Action中接收file,fileFileName, fileContentType文件參數 3、 savedFile = new File("E:\\File", fi ...
  • 正式啟動項目後的第一周 這一周有三天是在研討會中進行的,各種需求討論。。。至少 最終版還是確定了,不會再改核心 1、搭建基本項目框架 maven聚合工程 - 後臺管理系統,restful介面服務(待更新:訂單服務,RMQ服務,SSO單點登錄) 2、安裝mysql 遇到的問題:遠程不能訪問,人之常情, ...
一周排行
    -Advertisement-
    Play Games
  • 示例項目結構 在 Visual Studio 中創建一個 WinForms 應用程式後,項目結構如下所示: MyWinFormsApp/ │ ├───Properties/ │ └───Settings.settings │ ├───bin/ │ ├───Debug/ │ └───Release/ ...
  • [STAThread] 特性用於需要與 COM 組件交互的應用程式,尤其是依賴單線程模型(如 Windows Forms 應用程式)的組件。在 STA 模式下,線程擁有自己的消息迴圈,這對於處理用戶界面和某些 COM 組件是必要的。 [STAThread] static void Main(stri ...
  • 在WinForm中使用全局異常捕獲處理 在WinForm應用程式中,全局異常捕獲是確保程式穩定性的關鍵。通過在Program類的Main方法中設置全局異常處理,可以有效地捕獲並處理未預見的異常,從而避免程式崩潰。 註冊全局異常事件 [STAThread] static void Main() { / ...
  • 前言 給大家推薦一款開源的 Winform 控制項庫,可以幫助我們開發更加美觀、漂亮的 WinForm 界面。 項目介紹 SunnyUI.NET 是一個基於 .NET Framework 4.0+、.NET 6、.NET 7 和 .NET 8 的 WinForm 開源控制項庫,同時也提供了工具類庫、擴展 ...
  • 說明 該文章是屬於OverallAuth2.0系列文章,每周更新一篇該系列文章(從0到1完成系統開發)。 該系統文章,我會儘量說的非常詳細,做到不管新手、老手都能看懂。 說明:OverallAuth2.0 是一個簡單、易懂、功能強大的許可權+可視化流程管理系統。 有興趣的朋友,請關註我吧(*^▽^*) ...
  • 一、下載安裝 1.下載git 必須先下載並安裝git,再TortoiseGit下載安裝 git安裝參考教程:https://blog.csdn.net/mukes/article/details/115693833 2.TortoiseGit下載與安裝 TortoiseGit,Git客戶端,32/6 ...
  • 前言 在項目開發過程中,理解數據結構和演算法如同掌握蓋房子的秘訣。演算法不僅能幫助我們編寫高效、優質的代碼,還能解決項目中遇到的各種難題。 給大家推薦一個支持C#的開源免費、新手友好的數據結構與演算法入門教程:Hello演算法。 項目介紹 《Hello Algo》是一本開源免費、新手友好的數據結構與演算法入門 ...
  • 1.生成單個Proto.bat內容 @rem Copyright 2016, Google Inc. @rem All rights reserved. @rem @rem Redistribution and use in source and binary forms, with or with ...
  • 一:背景 1. 講故事 前段時間有位朋友找到我,說他的窗體程式在客戶這邊出現了卡死,讓我幫忙看下怎麼回事?dump也生成了,既然有dump了那就上 windbg 分析吧。 二:WinDbg 分析 1. 為什麼會卡死 窗體程式的卡死,入口門檻很低,後續往下分析就不一定了,不管怎麼說先用 !clrsta ...
  • 前言 人工智慧時代,人臉識別技術已成為安全驗證、身份識別和用戶交互的關鍵工具。 給大家推薦一款.NET 開源提供了強大的人臉識別 API,工具不僅易於集成,還具備高效處理能力。 本文將介紹一款如何利用這些API,為我們的項目添加智能識別的亮點。 項目介紹 GitHub 上擁有 1.2k 星標的 C# ...