Java實(shí)現(xiàn)配置加載機(jī)制
前言
現(xiàn)如今幾乎大多數(shù)Java應(yīng)用,例如我們耳熟能詳?shù)膖omcat, struts2, netty...等等數(shù)都數(shù)不過(guò)來(lái)的軟件,
要滿(mǎn)足通用性,都會(huì)提供配置文件供使用者定制功能。
甚至有一些例如Netty這樣的網(wǎng)絡(luò)框架,幾乎完全就是由配置驅(qū)動(dòng),這樣的軟件我們也通常稱(chēng)之為"微內(nèi)核架構(gòu)"的軟件。
你把它配置成什么,它就是什么。
It is what you configure it to be.
最常見(jiàn)的配置文件格式是XML, Properties等等文件。
本文探討加載配置中最通用也是最常見(jiàn)的場(chǎng)景,那就是把一個(gè)配置文件映射成Java里的POJO對(duì)象.
并探討如何實(shí)現(xiàn)不同方式的加載,例如,有一些配置是從本地XML文件里面加載的,而有一些配置需要從本地Properties文件加載,更有甚者,有一些配置需要通過(guò)網(wǎng)絡(luò)加載配置。
如何實(shí)現(xiàn)這樣一個(gè)配置加載機(jī)制,讓我們擁有這個(gè)機(jī)制后,不會(huì)讓加載配置的代碼散布得到處都是,并且可擴(kuò)展,可管理。
配置加載器
首先,我們需要一個(gè)配置加載器,而這個(gè)配置加載器是可以有多種不同的加載方式的,因此,我們用一個(gè)接口來(lái)描述它,如下所示:
/** * * * @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; }
可是,為什么我們需要在這個(gè)接口上聲明泛型<T> ?
很明顯,當(dāng)我們要使用一個(gè)配置加載器時(shí),你得告訴這個(gè)配置加載器你需要加載后得到什么結(jié)果。
例如,你希望加載配置后得到一個(gè)AppleConfig對(duì)象,那么你就可以這么去使用上述定義的接口:
IConfigLoader<AppleConfig> loader = new AppleConfigLoader<AppleConfig>(); AppleConfig config = loader.load();
于是你將配置文件里的信息轉(zhuǎn)化成了一個(gè)AppleConfig對(duì)象,并且你能得到這個(gè)AppleConfig對(duì)象實(shí)例。
到目前,貌似只要我們的AppleConfigLoader里面實(shí)現(xiàn)了怎么加載配置文件的具體勞動(dòng),我們就可以輕易加載配置了。
可以這么說(shuō),但是不是還沒(méi)有考慮到,配置可能通過(guò)不同的方式加載呢,比如通過(guò)Properties加載,通過(guò)dom方式加載,通過(guò)sax方式加載,或者通過(guò)某些第三方的開(kāi)源庫(kù)來(lái)加載。
因此,除了配置加載器,我們還需要另外一種角色,配置加載方式的提供者。暫且,我們就叫它IConfigProvider。
配置加載方式的提供者
配置加載方式的提供者可以提供一種加載方式給配置加載器,換言之,提供一個(gè)對(duì)象給配置加載器。
如果通過(guò)dom方式加載,那么提供者提供一個(gè)Document對(duì)象給加載器。
如果通過(guò)Properties方式加載,那么提供者提供一個(gè)Properties對(duì)象給加載器
如果通過(guò)第三方類(lèi)庫(kù)提供的方式加載,比如apache-commons-digester3(tomcat的配置加載),那么提供者提供一個(gè)Digester對(duì)象給加載器
提供者的職責(zé)就是提供,僅此而已,只提供配置加載器所需要的對(duì)象,但它本身并不參與配置加載的勞動(dòng)。
我們用一個(gè)接口IConfigProvider來(lái)定義這個(gè)提供者
/** * * * @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; }
這里為什么又會(huì)有<T>來(lái)聲明泛型呢?
如果需要一個(gè)提供者,那么至少得告訴這個(gè)提供者它該提供什么吧。
因此,一個(gè)提供者會(huì)提供什么,由這個(gè)來(lái)決定。
同時(shí),到這里,我們可以先建造一個(gè)工廠,讓它來(lái)生產(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); } }
可以開(kāi)始實(shí)現(xiàn)具體配置加載器了?
還不行!
到這里,假設(shè)我們有一個(gè)配置文件,叫apple.xml。而且我們要通過(guò)DOM方式把這一份apple.xml加載后變成AppleConfig對(duì)象。
那么,首先我要通過(guò)提供者工廠給我制造一個(gè)能提供Document的提供者。然后拿到這個(gè)提供者,我就可以調(diào)用它的provide方法來(lái)獲得Document對(duì)象,
有了document對(duì)象,那么我就可以開(kāi)始來(lái)加載配置了。
可是,如果要加載BananaConfig、PearConfig.......呢,其步驟都是一樣的。因此我們還要有一個(gè)抽象類(lèi),來(lái)實(shí)現(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; } }
每個(gè)配置加載器都有一個(gè)帶參數(shù)構(gòu)造器,接收一個(gè)Provider。
泛型指明了我要加載的是AppleConfig還是BananConfig,泛型<U>指明了要用什么加載方式加載,是Document呢,還是Properties,或者其他。
實(shí)戰(zhàn)運(yùn)用實(shí)例
有一份菜市場(chǎng)配置文件market.xml,配置了菜市場(chǎng)的商品,里面有兩種商品,分別是蘋(píng)果和雞蛋。
<market> <apple> <color>red</color> <price>100</price> </apple> <egg> <weight>200</weight> </egg> </market>
另外還有一份關(guān)于各個(gè)檔口老板名字的配置文件,owner.properties
port1=Steve Jobs port2=Bill Gates port3=Kobe Bryant
我們先定義好如下類(lèi):
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); } }
這個(gè)例子有兩種配置加載方式,分別是Dom和Properties加載方式。
所以我們的提供者建造工廠需要制造兩種提供者provider.
而且需要定義2個(gè)配置加載器,分別是:
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方式來(lái)加載,不是xml * 所以這里要新建一個(gè)OwnerConfigLoader,委托它來(lái)加載OwnerConfig */ OwnerConfigLoader ownerConfigLoader = new OwnerConfigLoader(ConfigProviderFactory.createPropertiesProvider(YOUR_FILE_PATH)); OwnerConfig ownerConfig = ownerConfigLoader.load(); marketConfig.setOwnerConfig(ownerConfig); return marketConfig; } }
然后,我們?cè)趹?yīng)用層面如何獲取到MarketConfig呢
MarketConfigLoader marketConfigLoader = new MarketConfigLoader(ConfigProviderFactory.createDocumentProvider(YOUR_FILE_PATH));
MarketConfig marketConfig = marketConfigLoader.load();
也許有個(gè)地方會(huì)人奇怪,明明有四個(gè)配置類(lèi),為什么只有2個(gè)配置加載器呢。
因?yàn)镸arketConfig、EggConfig和AppleConfig,都是從同一個(gè)xml配置文件里面加載,所以只要一個(gè)Document對(duì)象,通過(guò)MarketConfigLoader就可以全部加載。
而OwnerConfig是不同的加載方式,所以需要另外一個(gè)加載器。
尾聲
本文提出的配置加載機(jī)制,并不能夠?qū)嶋H幫忙加載配置,這事應(yīng)該留給DOM,SAX,以及其他一些開(kāi)源庫(kù)如dom4j,Digester去做。
但本文提出的配置加載機(jī)制能夠讓配置加載機(jī)制更靈活,容易擴(kuò)展,并且能夠集成多種配置加載方式,融合到一個(gè)機(jī)制進(jìn)來(lái),發(fā)揮各自有點(diǎn)。
實(shí)際上,有些軟件經(jīng)常需要同時(shí)從多種不同格式的配置文件里面加載配置,例如struts2,以及我最近在研究并被氣到吐血的某國(guó)產(chǎn)開(kāi)源數(shù)據(jù)庫(kù)中間件軟件,
如果沒(méi)有一套完整的配置加載機(jī)制,那么代碼會(huì)比較散亂,可維護(hù)性不高。容易使人吐血。
相關(guān)文章
springboot實(shí)現(xiàn)全局異常捕獲的使用示例
任何系統(tǒng),我們不會(huì)傻傻的在每一個(gè)地方進(jìn)行異常捕獲和處理,整個(gè)系統(tǒng)一般我們會(huì)在一個(gè)的地方統(tǒng)一進(jìn)行異常處理,本文主要介紹了springboot實(shí)現(xiàn)全局異常捕獲的使用示例,感興趣的可以了解一下2023-11-11淺析從同步原語(yǔ)看非阻塞同步以及Java中的應(yīng)用
非阻塞同步是基于沖突檢測(cè)的樂(lè)觀并發(fā)策略,這種樂(lè)觀的并發(fā)策略使得很多線(xiàn)程不需要因?yàn)楦?jìng)爭(zhēng)失敗直接掛起,這種同步措施稱(chēng)為非阻塞同步。下面我們就從硬件原語(yǔ)開(kāi)始了解非阻塞同步,并看一看在Java中非阻塞同步的一些應(yīng)用2021-06-06SpringBoot登錄驗(yàn)證碼實(shí)現(xiàn)過(guò)程詳解
這篇文章主要介紹了SpringBoot登錄驗(yàn)證碼實(shí)現(xiàn)過(guò)程詳解,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-04-04JAVA集成Freemarker生成靜態(tài)html過(guò)程解析
這篇文章主要介紹了JAVA集成Freemarker生成靜態(tài)html過(guò)程解析,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-06-06Spring Boot實(shí)現(xiàn)簡(jiǎn)單的定時(shí)任務(wù)
這篇文章主要給大家介紹了關(guān)于利用Spring Boot實(shí)現(xiàn)簡(jiǎn)單的定時(shí)任務(wù)的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者使用Spring Boot具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-07-07Javaweb mybatis接口開(kāi)發(fā)實(shí)現(xiàn)過(guò)程詳解
這篇文章主要介紹了Javaweb mybatis接口開(kāi)發(fā)實(shí)現(xiàn)過(guò)程詳解,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-07-07一文了解Java?線(xiàn)程池的正確使用姿勢(shì)
線(xiàn)程池在平時(shí)的工作中出場(chǎng)率非常高,基本大家多多少少都要了解過(guò),可能不是很全面,本文和大家基于jdk8學(xué)習(xí)下線(xiàn)程池的全面使用,以及分享下使用過(guò)程中遇到的一些坑,希望對(duì)大家有所幫助2022-10-10springboot項(xiàng)目中添加自定義日志及配置過(guò)程
這篇文章主要介紹了springboot項(xiàng)目中添加自定義日志,本文通過(guò)示例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2023-07-07