Java implementations configure the loading mechanism

  • 2020-04-01 04:39:45
  • OfStack

preface

Today, almost most Java applications, such as tomcat, struts2, netty... And so on and so forth and so on and so forth,
For versatility, configuration files are provided for users to customize.

There are even some network frameworks, such as Netty, that are driven almost entirely by configuration, which we call "microkernel architecture".
It is what you configure it to be.

It is what you configure It to be.
The most common configuration file formats are XML, Properties, and so on.

This article explores the most common and common scenario in load configuration, which is mapping a configuration file to POJO objects in Java.
For example, some configurations are loaded from a local XML file, some from a local Properties file, and even some from a network.

How to implement a configuration loading mechanism so that we don't have the code to load the configuration scattered, extensible, and manageable.

Configure loader

First, we need a configuration loader, and this configuration loader can be loaded in a variety of different ways, so we use an interface to describe it, as follows:



public interface IConfigLoader<T> {
  
  
  public T load() throws ConfigException;
}

However, why do we need to declare generics on this interface? T> The & # 63;
Obviously, when we're going to use a configuration loader, you have to tell the configuration loader what you're going to get when you load it.
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 turn the information in the configuration file into an AppleConfig object, and you get an instance of that AppleConfig object.

So far, it seems that once our AppleConfigLoader implements the specific labor of how to load the configuration file, we can easily load the configuration.

Suffice it to say, but it's not lost on the fact that configurations can be loaded in different ways, such as by Properties, by dom, by sax, or by some third-party open source library.

Therefore, in addition to configuring the loader, we need another role to configure the load mode provider. For the moment, let's call it IConfigProvider.

Configure the load mode provider

A provider configuring the load mode can provide a load mode to the configuration loader, in other words, an object to the configuration loader.

If it is loaded dom style, the provider provides a Document object to the loader.
If it is loaded by Properties, the provider provides a Properties object to the loader
If the load is provided by a third-party class library, such as apache-commons-digester3(configuration loading of tomcat), the provider provides a Digester object to the loader
The responsibility of the provider is to provide, and that is all, only the objects needed to configure the loader, but it does not participate in the configuration load itself.

We use an interface IConfigProvider to define the provider



public interface IConfigProvider<T> {

  
  public T provide() throws ConfigException;
}

Why are there again < T> To declare generics?
If you need a provider, at least tell the provider what it has to offer.

Therefore, what a provider will provide is determined by this.

Meanwhile, at this point, we can first build a factory to produce a specific supplier:



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);
  }
}

You can start to implement the specific configuration loader.

Also not line!

So here, let's say we have a configuration file called apple.xml. And we're going to turn this copy of apple.xml into an AppleConfig object by DOM loading.

So, first I'm going to go through the provider factory and make me a provider that's going to provide the Document. And then I get this provider, and I can call its provide method to get the Document object,
Now that I have the document object, I can start loading the configuration.

However, if you want to load BananaConfig, PearConfig... Well, the steps are the same. So we also have an abstract class that implements some default common behavior.



public abstract class AbstractConfigLoader <T, U> implements IConfigLoader<T>{

  protected IConfigProvider<U> provider;
  
  protected AbstractConfigLoader(IConfigProvider<U> provider) {
    this.provider = provider;
  }

  
  @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 constructor with parameters and receives a Provider.

Generics indicate whether I'm loading AppleConfig or BananConfig, generics < U> Specifies how to load, whether it's a Document, Properties, or something else.

Practical application example

There is a dish market configuration file, market.xml, which connotes the goods in the vegetable market. There are two kinds of goods in it, 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, owner.properties, for the names of the owners of each file


port1=Steve Jobs
port2=Bill Gates
port3=Kobe Bryant

Let's first define the following class:
MarketConfig. Java



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



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



public class EggConfig {

  private int weight;
  
  public void setWeight(int weight) {
    this.weight = weight;
  }
  
  public int getWeight() {
    return this.weight;
  }
}

OwnerConfig. Java



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 loads for this example, Dom and Properties.
So our provider build factory needs to make two provider providers.
And two configuration loaders need to be defined, which are:

OwnerConfigLoader



public class OwnerConfigLoader extends AbstractConfigLoader<OwnerConfig, Properties>{

  
  protected OwnerConfigLoader(IConfigProvider<Properties> provider) {
    super(provider);
  }

  
  @Override
  public OwnerConfig load(Properties props) throws ConfigException {
    OwnerConfig ownerConfig = new OwnerConfig();
    
    
    return ownerConfig;
  }
}

And then the MarketConfigLoader


import org.w3c.dom.Document;


public class MarketConfigLoader extends AbstractConfigLoader<MarketConfig, Document> {

  
  protected MarketConfigLoader(IConfigProvider<Document> provider) {
    super(provider);
  }

  
  @Override
  public MarketConfig load(Document document) throws ConfigException {
    
    MarketConfig marketConfig = new MarketConfig();
    AppleConfig appleConfig = new AppleConfig();
    EggConfig eggConfig = new EggConfig();
    
    marketConfig.setAppleConfig(appleConfig);
    marketConfig.setEggConfig(eggConfig);
    
    
    
    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. The load ();
One might wonder why there are only two configuration loaders when there are four configuration classes.
Since MarketConfig, EggConfig, and AppleConfig are all loaded from the same XML configuration file, as long as there is one Document object, all of them can be loaded through MarketConfigLoader.

OwnerConfig is a different type of load, so you need another loader.

The end of the

The configuration loading mechanism proposed in this article does not actually help to load the configuration, which should be left to DOM, SAX, and other open source libraries such as dom4j and Digester.
However, the configuration loading mechanism proposed in this paper can make the configuration loading mechanism more flexible, easy to extend, and can integrate multiple configuration loading methods into one mechanism to play their respective advantages.

In fact, some software often needs to load configuration from multiple configuration files at the same time, such as struts2, and some domestic open source database middleware software that I recently worked on and was angry at.
If you don't have a complete configuration load mechanism, your code will be messy and less maintainable. Easy to make people vomit blood.


Related articles: