Java 配置加載機(jī)制詳解及實例
前言
現(xiàn)如今幾乎大多數(shù)Java應(yīng)用,例如我們耳熟能詳?shù)膖omcat, struts2, netty…等等數(shù)都數(shù)不過來的軟件,要滿足通用性,都會提供配置文件供使用者定制功能。
甚至有一些例如Netty這樣的網(wǎng)絡(luò)框架,幾乎完全就是由配置驅(qū)動,這樣的軟件我們也通常稱之為”微內(nèi)核架構(gòu)”的軟件。你把它配置成什么,它就是什么。
It is what you configure it to be.
最常見的配置文件格式是XML, Properties等等文件。
本文探討加載配置中最通用也是最常見的場景,那就是把一個配置文件映射成Java里的POJO對象.
并探討如何實現(xiàn)不同方式的加載,例如,有一些配置是從本地XML文件里面加載的,而有一些配置需要從本地Properties文件加載,
更有甚者,有一些配置需要通過網(wǎng)絡(luò)加載配置。
如何實現(xiàn)這樣一個配置加載機(jī)制,讓我們擁有這個機(jī)制后,不會讓加載配置的代碼散布得到處都是,并且可擴(kuò)展,可管理。
配置加載器
首先,我們需要一個配置加載器,而這個配置加載器是可以有多種不同的加載方式的,因此,我們用一個接口來描述它,如下所示:
/**
*
*
* @author Bean
* @date 2016年1月21日 上午11:47:12
* @version 1.0
*
*/
public interface IConfigLoader<T> {
/**
* load the config typed by T
*
* @return
* @throws ConfigException
*/
public T load() throws ConfigException;
}
可是,為什么我們需要在這個接口上聲明泛型 <T> ?
很明顯,當(dāng)我們要使用一個配置加載器時,你得告訴這個配置加載器你需要加載后得到什么結(jié)果。
例如,你希望加載配置后得到一個 AppleConfig 對象,那么你就可以這么去使用上述定義的接口:
IConfigLoader<AppleConfig> loader = new AppleConfigLoader<AppleConfig>();
AppleConfig config = loader.load();
于是你將配置文件里的信息轉(zhuǎn)化成了一個AppleConfig對象,并且你能得到這個AppleConfig對象實例。
到目前,貌似只要我們的 AppleConfigLoader 里面實現(xiàn)了怎么加載配置文件的具體勞動,我們就可以輕易加載配置了。
可以這么說,但是不是還沒有考慮到,配置可能通過不同的方式加載呢,比如通過Properties加載,通過dom方式加載,通過sax方式加載,或者通過某些第三方的開源庫來加載。
因此,除了 配置加載器 ,我們還需要另外一種角色,配置加載方式的提供者。暫且,我們就叫它IConfigProvider。
配置加載方式的提供者
配置加載方式的提供者可以提供一種加載方式給配置加載器,換言之,提供一個 對象 給配置加載器。
- 如果通過dom方式加載,那么 提供者 提供一個 Document 對象給 加載器 。
- 如果通過Properties方式加載,那么 提供者 提供一個 Properties 對象給 加載器
- 如果通過第三方類庫提供的方式加載,比如apache-commons-digester3(tomcat的配置加載),那么 提供者 提供一個 Digester 對象給 加載器
提供者的職責(zé)就是 提供 ,僅此而已,只提供配置加載器所需要的對象,但它本身并不參與配置加載的勞動。
我們用一個接口 IConfigProvider 來定義這個 提供者
/**
*
*
* @author Bean
* @date 2016年1月21日 上午11:54:28
* @version 1.0
*
*/
public interface IConfigProvider<T> {
/**
* provide a config source used for loading config
*
* @return
* @throws ConfigException
*/
public T provide() throws ConfigException;
}
這里為什么又會有 <T> 來聲明泛型呢?
如果需要一個提供者,那么至少得告訴這個提供者它該提供什么吧。
因此,一個提供者會提供什么,由這個來決定。
同時,到這里,我們可以先建造一個工廠,讓它來生產(chǎn)特定的提供者:
/**
*
*
* @author Bean
* @date 2016年1月21日 上午11:56:28
* @version 1.0
*
*/
public class ConfigProviderFactory {
private ConfigProviderFactory() {
throw new UnsupportedOperationException("Unable to initialize a factory class : "
+ getClass().getSimpleName());
}
public static IConfigProvider<Document> createDocumentProvider(String filePath) {
return new DocumentProvider(filePath);
}
public static IConfigProvider<Properties> createPropertiesProvider(String filePath) {
return new PropertiesProvider(filePath);
}
public static IConfigProvider<Digester> createDigesterProvider(String filePath) {
return new DigesterProvider(filePath);
}
}
可以開始實現(xiàn)具體配置加載器了?
還不行!
到這里,假設(shè)我們有一個配置文件,叫apple.xml。而且我們要通過DOM方式把這一份apple.xml加載后變成AppleConfig對象。
那么,首先我要通過提供者工廠給我制造一個能提供Document的提供者。然后拿到這個提供者,我就可以調(diào)用它的provide方法來獲得Document對象,有了document對象,那么我就可以開始來加載配置了。
可是,如果要加載BananaConfig、PearConfig…….呢,其步驟都是一樣的。因此我們還要有一個抽象類,來實現(xiàn)一些默認(rèn)的共同行為。
/**
*
*
* @author Bean
* @date 2016年1月21日 上午11:59:19
* @version 1.0
*
*/
public abstract class AbstractConfigLoader <T, U> implements IConfigLoader<T>{
protected IConfigProvider<U> provider;
protected AbstractConfigLoader(IConfigProvider<U> provider) {
this.provider = provider;
}
/*
* @see IConfigLoader#load()
*/
@Override
public T load() throws ConfigException {
return load(getProvider().provide());
}
public abstract T load(U loaderSource) throws ConfigException;
protected IConfigProvider<U> getProvider() {
return this.provider;
}
}
每個配置加載器都有一個帶參數(shù)構(gòu)造器,接收一個Provider。
泛型指明了我要加載的是AppleConfig還是BananConfig,泛型 <U> 指明了要用什么加載方式加載,是Document呢,還是Properties,或者其他。
實戰(zhàn)運(yùn)用實例
有一份菜市場配置文件market.xml,配置了菜市場的商品,里面有兩種商品,分別是蘋果和雞蛋。
<market>
<apple>
<color>red</color>
<price>100</price>
</apple>
<egg>
<weight>200</weight>
</egg>
</market>
另外還有一份關(guān)于各個檔口老板名字的配置文件,owner.properties
port1=Steve Jobs
port2=Bill Gates
port3=Kobe Bryant
我們先定義好如下類:MarketConfig.java
/**
*
*
* @author Bean
* @date 2016年1月21日 下午11:03:37
* @version 1.0
*
*/
public class MarketConfig {
private AppleConfig appleConfig;
private EggConfig eggConfig;
private OwnerConfig ownerConfig;
public AppleConfig getAppleConfig() {
return appleConfig;
}
public void setAppleConfig(AppleConfig appleConfig) {
this.appleConfig = appleConfig;
}
public EggConfig getEggConfig() {
return eggConfig;
}
public void setEggConfig(EggConfig eggConfig) {
this.eggConfig = eggConfig;
}
public OwnerConfig getOwnerConfig() {
return ownerConfig;
}
public void setOwnerConfig(OwnerConfig ownerConfig) {
this.ownerConfig = ownerConfig;
}
}
AppleConfig.java
/**
*
*
* @author Bean
* @date 2016年1月21日 下午11:03:45
* @version 1.0
*
*/
public class AppleConfig {
private int price;
private String color;
public void setPrice(int price) {
this.price = price;
}
public int getPrice() {
return this.price;
}
public void setColor(String color) {
this.color = color;
}
public String getColor() {
return this.color;
}
}
EggConfig.java
/**
*
*
* @author Bean
* @date 2016年1月21日 下午11:03:58
* @version 1.0
*
*/
public class EggConfig {
private int weight;
public void setWeight(int weight) {
this.weight = weight;
}
public int getWeight() {
return this.weight;
}
}
OwnerConfig.java
/**
*
*
* @author Bean
* @date 2016年1月21日 下午11:04:06
* @version 1.0
*
*/
public class OwnerConfig {
private Map<String, String> owner = new HashMap<String, String>();
public void addOwner(String portName, String owner) {
this.owner.put(portName, owner);
}
public String getOwnerByPortName(String portName) {
return this.owner.get(portName);
}
public Map<String, String> getOwners() {
return Collections.unmodifiableMap(this.owner);
}
}
這個例子有兩種配置加載方式,分別是Dom和Properties加載方式。
所以我們的提供者建造工廠需要制造兩種提供者provider.
而且需要定義2個配置加載器,分別是:
OwnerConfigLoader
/**
*
*
* @author Bean
* @date 2016年1月21日 下午11:24:50
* @version 1.0
*
*/
public class OwnerConfigLoader extends AbstractConfigLoader<OwnerConfig, Properties>{
/**
* @param provider
*/
protected OwnerConfigLoader(IConfigProvider<Properties> provider) {
super(provider);
}
/*
* @see AbstractConfigLoader#load(java.lang.Object)
*/
@Override
public OwnerConfig load(Properties props) throws ConfigException {
OwnerConfig ownerConfig = new OwnerConfig();
/**
* 利用props,設(shè)置ownerConfig的屬性值
*
* 此處代碼省略
*/
return ownerConfig;
}
}
然后是MarketConfigLoader
import org.w3c.dom.Document;
/**
*
*
* @author Bean
* @date 2016年1月21日 下午11:18:56
* @version 1.0
*
*/
public class MarketConfigLoader extends AbstractConfigLoader<MarketConfig, Document> {
/**
* @param provider
*/
protected MarketConfigLoader(IConfigProvider<Document> provider) {
super(provider);
}
/*
* AbstractConfigLoader#load(java.lang.Object)
*/
@Override
public MarketConfig load(Document document) throws ConfigException {
MarketConfig marketConfig = new MarketConfig();
AppleConfig appleConfig = new AppleConfig();
EggConfig eggConfig = new EggConfig();
/**
* 在這里處理document,然后就能得到
* AppleConfig和EggConfg
*
* 此處代碼省略
*/
marketConfig.setAppleConfig(appleConfig);
marketConfig.setEggConfig(eggConfig);
/**
* 由于OwnerConfig是需要properties方式來加載,不是xml
* 所以這里要新建一個OwnerConfigLoader,委托它來加載OwnerConfig
*/
OwnerConfigLoader ownerConfigLoader = new OwnerConfigLoader(ConfigProviderFactory.createPropertiesProvider(YOUR_FILE_PATH));
OwnerConfig ownerConfig = ownerConfigLoader.load();
marketConfig.setOwnerConfig(ownerConfig);
return marketConfig;
}
}
然后,我們在應(yīng)用層面如何獲取到MarketConfig呢
MarketConfigLoader marketConfigLoader = new MarketConfigLoader(ConfigProviderFactory.createDocumentProvider(YOUR_FILE_PATH)); MarketConfig marketConfig = marketConfigLoader.load();
也許有個地方會人奇怪,明明有四個配置類,為什么只有2個配置加載器呢。因為MarketConfig、EggConfig和AppleConfig,都是從同一個xml配置文件里面加載,所以只要一個Document對象,通過MarketConfigLoader就可以全部加載。
而OwnerConfig是不同的加載方式,所以需要另外一個加載器。
總結(jié)
本文提出的配置加載機(jī)制,并不能夠?qū)嶋H幫忙加載配置,這事應(yīng)該留給DOM,SAX,以及其他一些開源庫如dom4j,Digester去做。但本文提出的配置加載機(jī)制能夠讓配置加載機(jī)制更靈活,容易擴(kuò)展,并且能夠集成多種配置加載方式,融合到一個機(jī)制進(jìn)來,發(fā)揮各自有點。
實際上,有些軟件經(jīng)常需要同時從多種不同格式的配置文件里面加載配置,例如struts2,以及我最近在研究并被氣到吐血的某國產(chǎn)開源數(shù)據(jù)庫中間件軟件,如果沒有一套完整的配置加載機(jī)制,那么代碼會比較散亂,可維護(hù)性不高。容易使人吐血。
通過此文希望大家理解并掌握 Java 配置加載機(jī)制的知識,謝謝大家對本站的支持!
- java使double保留兩位小數(shù)的多方法 java保留兩位小數(shù)
- javascript 手機(jī)號碼正則表達(dá)式驗證函數(shù)
- Javascript 的addEventListener()及attachEvent()區(qū)別分析
- JAVA8 十大新特性詳解
- Java環(huán)境變量的設(shè)置方法(圖文教程)
- JAVA正則表達(dá)式 Pattern和Matcher
- java.net.SocketException: Connection reset 解決方法
- javascript的console.log()用法小結(jié)
- java寫入文件的幾種方法分享
- JavaScript window.setTimeout() 的詳細(xì)用法
相關(guān)文章
Java IO中字節(jié)流復(fù)制圖片實現(xiàn)代碼
這篇文章主要介紹了Java IO中字節(jié)流復(fù)制圖片實現(xiàn)代碼的相關(guān)資料,需要的朋友可以參考下2017-04-04
Apache POI將PPT轉(zhuǎn)換成圖片實例代碼
這篇文章主要介紹了Apache POI將PPT轉(zhuǎn)換成圖片實例代碼,具有一定借鑒價值,需要的朋友可以參考下2018-01-01

