Java實現配置加載機制

前言

現現在幾乎大多數Java應用,例如咱們耳熟能詳的tomcat, struts2, netty...等等數都數不過來的軟件,
要知足通用性,都會提供配置文件供使用者定製功能。java

甚至有一些例如Netty這樣的網絡框架,幾乎徹底就是由配置驅動,這樣的軟件咱們也一般稱之爲"微內核架構"的軟件。
你把它配置成什麼,它就是什麼。數據庫

It is what you configure it to be.

最多見的配置文件格式是XML, Properties等等文件。apache

本文探討加載配置中最通用也是最多見的場景,那就是把一個配置文件映射成Java裏的POJO對象.
並探討如何實現不一樣方式的加載,例如,有一些配置是從本地XML文件裏面加載的,而有一些配置須要從本地Properties文件加載,
更有甚者,有一些配置須要經過網絡加載配置。tomcat

如何實現這樣一個配置加載機制,讓咱們擁有這個機制後,不會讓加載配置的代碼散佈獲得處都是,而且可擴展,可管理。微信

配置加載器

首先,咱們須要一個配置加載器,而這個配置加載器是能夠有多種不一樣的加載方式的,所以,咱們用一個接口來描述它,以下所示:網絡

/**
 * 
 *
 * @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> ?
很明顯,當咱們要使用一個配置加載器時,你得告訴這個配置加載器你須要加載後獲得什麼結果。
例如,你但願加載配置後獲得一個AppleConfig對象,那麼你就能夠這麼去使用上述定義的接口:架構

IConfigLoader<AppleConfig> loader = new AppleConfigLoader<AppleConfig>();
    AppleConfig config = loader.load();

因而你將配置文件裏的信息轉化成了一個AppleConfig對象,而且你能獲得這個AppleConfig對象實例。app

到目前,貌似只要咱們的AppleConfigLoader裏面實現了怎麼加載配置文件的具體勞動,咱們就能夠輕易加載配置了。框架

能夠這麼說,可是不是尚未考慮到,配置可能經過不一樣的方式加載呢,好比經過Properties加載,經過dom方式加載,經過sax方式加載,或者經過某些第三方的開源庫來加載。dom

所以,除了配置加載器,咱們還須要另一種角色,配置加載方式的提供者。暫且,咱們就叫它IConfigProvider。

配置加載方式的提供者

配置加載方式的提供者能夠提供一種加載方式給配置加載器,換言之,提供一個對象給配置加載器。

  • 若是經過dom方式加載,那麼提供者提供一個Document對象給加載器
  • 若是經過Properties方式加載,那麼提供者提供一個Properties對象給加載器
  • 若是經過第三方類庫提供的方式加載,好比apache-commons-digester3(tomcat的配置加載),那麼提供者提供一個Digester對象給加載器

提供者的職責就是提供,僅此而已,只提供配置加載器所須要的對象,但它自己並不參與配置加載的勞動。

咱們用一個接口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>來聲明泛型呢?
若是須要一個提供者,那麼至少得告訴這個提供者它該提供什麼吧。

所以,一個提供者會提供什麼,由這個<T>來決定。

同時,到這裏,咱們能夠先建造一個工廠,讓它來生產特定的提供者:

/**
 *
 *
 * @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);
    }
}

能夠開始實現具體配置加載器了?

還不行!

到這裏,假設咱們有一個配置文件,叫apple.xml。並且咱們要經過DOM方式把這一份apple.xml加載後變成AppleConfig對象。

那麼,首先我要經過提供者工廠給我製造一個能提供Document的提供者。而後拿到這個提供者,我就能夠調用它的provide方法來得到Document對象,
有了document對象,那麼我就能夠開始來加載配置了。

但是,若是要加載BananaConfig、PearConfig.......呢,其步驟都是同樣的。所以咱們還要有一個抽象類,來實現一些默認的共同行爲。

/**
 *
 *
 * @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;
    }
}

每一個配置加載器都有一個帶參數構造器,接收一個Provider。

泛型<T>指明瞭我要加載的是AppleConfig仍是BananConfig,泛型<U>指明瞭要用什麼加載方式加載,是Document呢,仍是Properties,或者其餘。

實戰運用實例

有一份菜市場配置文件market.xml,配置了菜市場的商品,裏面有兩種商品,分別是蘋果和雞蛋。

<market>
    <apple>
        <color>red</color>
        <price>100</price>
    </apple>
    <egg>
        <weight>200</weight>
    </egg>
</market>

另外還有一份關於各個檔口老闆名字的配置文件,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,設置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;
    }
}

而後,咱們在應用層面如何獲取到MarketConfig呢

MarketConfigLoader marketConfigLoader = new MarketConfigLoader(ConfigProviderFactory.createDocumentProvider(YOUR_FILE_PATH));
MarketConfig marketConfig = marketConfigLoader.load();

也許有個地方會人奇怪,明明有四個配置類,爲何只有2個配置加載器呢。
由於MarketConfig、EggConfig和AppleConfig,都是從同一個xml配置文件裏面加載,因此只要一個Document對象,經過MarketConfigLoader就能夠所有加載。

而OwnerConfig是不一樣的加載方式,因此須要另一個加載器。

尾聲

本文提出的配置加載機制,並不可以實際幫忙加載配置,這事應該留給DOM,SAX,以及其餘一些開源庫如dom4j,Digester去作。
但本文提出的配置加載機制可以讓配置加載機制更靈活,容易擴展,而且可以集成多種配置加載方式,融合到一個機制進來,發揮各自有點。

實際上,有些軟件常常須要同時從多種不一樣格式的配置文件裏面加載配置,例如struts2,以及我最近在研究並被氣到吐血的某國產開源數據庫中間件軟件,
若是沒有一套完整的配置加載機制,那麼代碼會比較散亂,可維護性不高。容易令人吐血。

掃一掃關注個人微信公衆號

相關文章
相關標籤/搜索