How to switch from a hardcoded static config file

2020-03-24 03:15发布

问题:

I have some code that uses a class holding a huge lot of hardcoded constans. This is what it looks like:

class Constants{
    public static final String name1 = "value1";
    public static final String name2 = "value2";
    public static final Integer value3 = 3;
    ... and so on
}

These constants are used everywhere in the code like Constants.name1.

What I need to do now is to make it possible to specify values for these constants in a configuration file, probably a *.properties file.

My question is: what is the best way to do it, having to rewrite as little code as possible?

I thought of using a single configuration class which reads properties from file when instantiated, but then I'll have to replace all the static calls of values to calls to an instance of that class and I'll have to alter existing methods to pass this config instance into them. Is there a better way?

回答1:

Here is a piece of a code I have used in the past - that can be adapted to your example:

public enum Configuration {

    PROPERTY1("property1.name", "default_value_1"),
    PROPERTY2("property2.name", "default_value_2");

    private final String key;
    private String defaultValue;

    Configuration(String key) {
        this(key, NA);
    }

    Configuration(String key, String defaultValue) {
        this.key = key;
        this.defaultValue = defaultValue;
    }
    private final static Logger logger = LoggerFactory.getLogger(Configuration.class);
    private final static String NA = "n.a.";
    private final static String CONFIG_FILE = "properties/config.properties";
    private final static String NOT_A_VALID_KEY = "Not a valid property key";
    private final static Map<Configuration, String> configuration = new EnumMap<>(Configuration.class);

    static {
        readConfigurationFrom(CONFIG_FILE);
    }

    private static void readConfigurationFrom(String fileName) {
        logger.info("Reading resource: {}", fileName);
        try (InputStream resource = Configuration.class.getClassLoader().getResourceAsStream(fileName);) {
            Properties properties = new Properties();
            properties.load(resource); //throws a NPE if resource not founds
            for (String key : properties.stringPropertyNames()) {
                configuration.put(getConfigurationKey(key), properties.getProperty(key));
            }
        } catch (IllegalArgumentException | IOException | NullPointerException e) {
            logger.error("Error while reading the properties file {}", fileName, e);
            populateDefaultValues();
        }
    }

    private static Configuration getConfigurationKey(String key) {
        for (Configuration c : values()) {
            if (c.key.equals(key)) {
                return c;
            }
        }
        throw new IllegalArgumentException(NOT_A_VALID_KEY + ": " + key);
    }

    private static void populateDefaultValues() {
        for (Configuration c : values()) {
            configuration.put(c, c.defaultValue);
        }
    }

    /**
     * @return the property corresponding to the key or null if not found
     */
    public String get() {
        return configuration.get(this);
    }
}


回答2:

Load the properties from the file using Properties.load(...) and assign the constants from those properties.

class Constants{
  public static final String name1; 
  public static final String name2;
  public static final Integer value3;

  static{
    Properties p = new Properties();
    try ( FileInputStream stream = new FileInputStream( new File("path/to/file.properties") )) {          
      p.load( stream );
    }catch( Exception e ){
      //handle exceptions
    }

    name1 = p.getProperty( "name1" );
    name2 = p.getProperty( "name2" );
    value3 = Integer.valueOf( p.getProperty( "value3" ) );
} 

Note that this is just a quick and dirty solution and makes a lot of assumptions. It would be better to handle the individual exceptions and you'd also have to handle the NumberFormatException that might be thrown by Integer.valueOf(...) if the configuration is either empty or not a number.

Another note: you might also try and use some non-static or at least non-final configuration in order to be able to change properties at runtime.

Edit: I added autoclose for the the stream, but note that prior to Java 7 you'd have to handle that yourself.



回答3:

As a quick hack, you can read the properties file in the static initializer of the class, then you don't have to change the static nature of the class fields immediately, but I suggest you do that anyway over time.

Create a new class that holds all of your old constant values. From now on, inject this configuration object into your new code.

class NewConstants {
    public final String name1;
    public final String name2;
    public final Integer value3;
    ... and so on

   public NewConstants ( Properties props )
   {
       name1 = props.getProperty( "name1" );
       ...
   }
}

Now refactor your old Constants class

class Constants (
    public static final String name1;
    public static final String name2;
    public static final Integer value3;

    static {
        Properties props = new Poperties( );
        props.load( ... );

        NewConstants newConstants = new NewConstants( props );

        name1 = newConstants.getName1( );
        name2 = newConstants.getName2( );

        ...
    }
}