Preface
Nowadays, almost most Java applications, such as tomcat, struts2, netty, etc., which we are familiar with, can't count them,
To meet universality, configuration files are provided for users to customize functions.
There are even some network frameworks such as Netty that are almost completely configured-driven, and we usually call such software "microker architecture".
Whatever you configure it, it is what it is.
It is what you configure it to be.
The most common configuration file formats are XML, Properties and other files.
This article discusses the most common and common scenario in loading configurations, which is to map a configuration file into a POJO object in Java.
And discuss how to implement different ways of loading. For example, some configurations are loaded from local XML files, while some configurations need to be loaded from local Properties files, and more so, some configurations need to be loaded through the network.
How to implement such a configuration loading mechanism so that after we have this mechanism, the code loading configuration will not be spread everywhere, and it is scalable and manageable.
Configure the loader
First, we need a configuration loader, and this configuration loader can have many different loading methods. Therefore, we use an interface to describe it as follows:
/** * * * @author Bean * @date January 21, 2016 at 11:47:12 am * @version 1.0 * */public interface IConfigLoader<T> { /** * load the config typed by T * * @return * @throws ConfigException */ public T load() throws ConfigException;} But why do we need to declare generics <T> on this interface?
Obviously, when we want to use a configuration loader, you have to tell this configuration loader what results you need to get after loading.
For example, if you want to load the configuration and get an AppleConfig object, you can use the interface defined above:
IConfigLoader<AppleConfig> loader = new AppleConfigLoader<AppleConfig>(); AppleConfig config = loader.load();
So you convert the information in the configuration file into an AppleConfig object, and you can get this AppleConfig object instance.
So far, it seems that as long as our AppleConfigLoader implements the specific labor of how to load configuration files, we can easily load configurations.
It can be said that, but it is not that the configuration may be loaded in different ways, such as loading through Properties, dom, sax, or loading through some third-party open source libraries.
Therefore, in addition to configuring the loader, we also need another role, the provider of configuring the loading method. Let's call it IConfigProvider.
Configure the provider of loading methods
The provider of configuration loading methods can provide a loading method to the configuration loader, in other words, provide an object to the configuration loader.
If loaded through dom, the provider provides a Document object to the loader.
If loaded through Properties, the provider provides a Properties object to the loader. If loaded through the method provided by a third-party class library, such as apache-commons-digester3 (tomcat configuration loading), the provider provides a Digester object to the loader provider is to provide it, that's all, only providing the objects required to configure the loader, but it itself does not participate in the labor of configuring the loading.
We use an interface IConfigProvider to define this provider
/** * * * @author Bean * @date January 21, 2016 at 11:54:28 am * @version 1.0 * */public interface IConfigProvider<T> { /** * provides a config source used for loading config * * @return * @throws ConfigException */ public T provides() throws ConfigException;} Why are there <T> here to declare generics?
If you need a provider, you must at least tell the provider what it should provide.
Therefore, what a provider will provide is determined by this.
At the same time, we can first build a factory and let it produce specific providers:
/** * * * @author Bean * @date January 21, 2016 at 11:56:28 am * @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); }}Can you start implementing the specific configuration loader?
Not OK yet!
At this point, suppose we have a configuration file called apple.xml. And we need to load this apple.xml into an AppleConfig object through DOM.
So, first of all, I want to create a provider that can provide Document through the provider factory. Then I get this provider and I can call its provide method to get the Document object.
With the document object, I can start loading the configuration.
However, if you want to load BananaConfig, PearConfig......, the steps are the same. Therefore, we also need an abstract class to implement some default common behaviors.
/** * * * @author Bean * @date January 21, 2016 at 11:59:19 am * @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; }}Each configuration loader has a parameter constructor that receives a Provider.
The generic indicates whether I want to load AppleConfig or BananConfig. The generic <U> indicates which load method to load, is it a Document, Properties, or something else.
Practical application examples
There is a vegetable market configuration file market.xml, which configures the products of the vegetable market, including two products, namely apples and eggs.
<market> <apple> <color>red</color> <price>100</price> </apple> <egg> <weight>200</weight> </egg></market>
There is also a configuration file for the names of bosses in each stall, owner.properties
port1=Steve Jobsport2=Bill Gatesport3=Kobe Bryant
Let's define the following classes first:
MarketConfig.java
/** * * * @author Bean * @date January 21, 2016 at 11:03:37 pm * @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 January 21, 2016 at 11:03:45 pm * @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 January 21, 2016 at 11:03:58 pm * @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 January 21, 2016 at 11:04:06 pm * @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); }} There are two configuration loading methods for this example, namely Dom and Properties loading methods.
So our providers need to build factories with two providers.
And two configuration loaders need to be defined, namely:
OwnerConfigLoader
/** * * * @author Bean * @date January 21, 2016 at 11:24:50 pm * @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(); /** * Use props to set the property value of ownerConfig* * The code here is omitted */ return ownerConfig; }}Then there is MarketConfigLoader
import org.w3c.dom.Document;/** * * @author Bean * @date January 21, 2016 at 11:18:56 pm * @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(); /** * Process the document here, and then you can get * AppleConfig and EggConfg * * The code here is omitted */ marketConfig.setAppleConfig(appleConfig); marketConfig.setEggConfig(eggConfig); /** * Since OwnerConfig requires properties to load, it is not xml * So here we need to create a new OwnerConfigLoader and delegate it to load OwnerConfig */ OwnerConfigLoader ownerConfigLoader = new OwnerConfigLoader(ConfigProviderFactory.createPropertiesProvider(YOUR_FILE_PATH)); OwnerConfig ownerConfig = ownerConfigLoader.load(); marketConfig.setOwnerConfig(ownerConfig); return marketConfig; }}Then, how do we get MarketConfig at the application level?
MarketConfigLoader marketConfigLoader = new MarketConfigLoader(ConfigProviderFactory.createDocumentProvider(YOUR_FILE_PATH));
MarketConfig marketConfig = marketConfigLoader.load();
Maybe there is a place where people may be surprised. There are obviously four configuration classes, so why are there only two configuration loaders?
Because MarketConfig, EggConfig and AppleConfig are all loaded from the same xml configuration file, as long as a Document object is used, it can be loaded through the MarketConfigLoader.
OwnerConfig is a different loading method, so another loader is needed.
end
The configuration loading mechanism proposed in this article cannot actually help load the configuration. This should be left to DOM, SAX, and some other open source libraries such as dom4j and Digester.
However, the configuration loading mechanism proposed in this article can make the configuration loading mechanism more flexible and easy to expand, and can integrate multiple configuration loading methods, integrate them into one mechanism, and play their own points.
In fact, some software often need to load configurations from configuration files in multiple different formats at the same time, such as struts2, and a domestic open source database middleware software that I have been researching and vomiting blood recently.
If there is no complete configuration loading mechanism, the code will be more scattered and less maintainable. It can easily cause people to vomit blood.