从Java中的配置文件中读取配置参数的最佳方法是什么?

时间:2014-11-13 12:25:53

标签: java oop configuration initialization runtime

让我们假设运行时我们不知道配置的细节是什么(用户可能需要在运行应用程序之前在config文件中配置这些参数。

我想阅读这些配置细节,并且需要在我的应用程序中的任何需要的地方重用它们。为此,我想将它们作为全局常量(public static final)。

所以,我怀疑的是,如果我直接从所需的类中读取config文件,是否有任何性能影响?因为,运行时值我不能直接放在单独的Interface中。

我认为它会影响性能。请建议我做任何更好的方法。

更新:我可以使用单独的最终类来获取配置详细信息吗? 将所有配置详细信息作为常量放在单独的public final class中 (要从配置文件中一次读取所有配置详细信息并将其存储为全局常量,以便以后在应用程序中使用)

11 个答案:

答案 0 :(得分:21)

  

我认为这会影响效果。

我怀疑这是真的。

假设应用程序在启动时只读取一次配置文件,则读取文件所花费的时间可能与应用程序的整体性能无关。实际上,应用程序运行的时间越长,启动时间就越不重要。

标准建议是,只有当您有具体证据(即测量值)表明性能 是一个重要问题时,才会优化应用程序性能。然后,只优化代码中那些分析告诉您确实性能瓶颈的部分。


  

我可以使用单独的最终类来获取配置详细信息吗

是的,可以这样做。没有人会阻止你 1 。 但是,这是一个坏主意。任何意味着您需要重新编译代码以更改配置参数的事情都是一个坏主意。 IMO。


  

从配置文件中一次读取所有配置详细信息,并将它们存储为全局常量,以便以后在应用程序中使用。

啊......所以想要读取“常量”的值而不是硬连接它们。

是的,这是可能的。并且它比将配置参数硬连接到代码中更有意义。但它仍然不是一个好主意(IMO)。

为什么呢?那么让我们来看看代码的样子:

public final class Config { 
    public static final int CONST_1;
    public static final String CONST_2;

    static {
        int c1;
        String c2;
        try (Scanner s = new Scanner(new File("config.txt"))) {
            c1 = s.nextInt();
            c2 = s.next();
        } catch (IOException ex) {
            throw RuntimeException("Cannot load config properties", ex);
        }
        CONST_1 = c1;
        CONST_2 = c2; 
    }
}

首先观察的是,该类是final没有区别。它将字段声明为final,使它们保持不变。 (将类声明为final会阻止子类化,但这对static字段没有影响。静态字段不受继承影响。)

接下来的观察是这段代码在很多方面都很脆弱:

  • 如果静态初始化程序块出错。块抛出的未经检查的异常将被包装为ExceptionInInitializerError(是的......它是Error !!),Config类将被标记为错误。

  • 如果发生这种情况,就没有恢复的现实希望,尝试诊断Error甚至可能是个坏主意。

  • 上面的代码在初始化Config类时执行,但确定何时发生可能会很棘手。

  • 如果配置文件名是一个参数,那么在触发静态初始化之前你就有了获取参数值的问题。

接下来,与将状态加载到实例变量相比,代码非常混乱。而这种混乱很大程度上是因为必须在静态初始化器的约束下工作。如果您使用final实例变量,则代码如下所示。

public final class Config { 
    public final int CONST_1;
    public final String CONST_2;

    public Config(File file) throws IOException {
        try (Scanner s = new Scanner(file)) {
            CONST_1 = s.nextInt();
            CONST_2 = s.next();
        } 
    }
}

最后,static final字段对final字段的性能优势很小:

  • 每次访问其中一个常量时,可能只有一两个机器指令,

  • 如果JIT编译器很聪明,你可能没有任何东西,你可以适当地处理单例Config引用。

在任何一种情况下,绝大多数情况下的好处都是微不足道的。


1 - 好的......如果你的代码经过代码审查,那么有人可能会阻止你。

答案 1 :(得分:20)

您是否听说过apache commons配置http://commons.apache.org/proper/commons-configuration/? 它是我见过的最好的配置阅读器,甚至可以在我的应用程序中使用它,该应用程序从1年开始在生产中运行。从未发现任何问题,非常容易理解和使用,性能卓越。我知道它对你的应用程序有点依赖,但相信我你会喜欢它。

您需要做的就是

Configuration config = new ConfigSelector().getPropertiesConfiguration(configFilePath);
String value = config.getString("key");
int value1 = config.getInt("key1");
String[] value2 = config.getStringArray("key2");
List<Object> value3 = config.getList("key3");

就是这样。您的配置对象将保存所有配置值,您只需将该对象传递给任意数量的类即可。有了这么多有用的方法,你可以提取你想要的任何类型的密钥。

答案 2 :(得分:5)

如果您将它们放在属性文件中并在应用程序启动时读取文件并将所有参数初始化为系统参数( System.setProperty ),则只需一次性成本然后在代码中定义常量,如

public static final String MY_CONST = System.getProperty("my.const");

但是在加载任何其他类之前确保应用程序启动时的初始化。

答案 3 :(得分:5)

有不同类型的配置。

通常需要某种引导配置(例如连接到数据库或服务)才能启动应用程序。指定数据库连接参数的J2EE方法是通过容器的JNDI注册表(Glassfish,JBoss,Websphere,...)中指定的“数据源”。然后在persistence.xml中按名称查找此数据源。在非J2EE应用程序中,更常见的是在Spring上下文或甚至.properties文件中指定它们。在任何情况下,您通常都需要一些东西来将您的应用程序连接到某种数据存储。

引导到数据存储后,一个选项是管理此数据存储区中的配置值。例如,如果您有数据库,则可以使用单独的表(在应用程序中由JPA实体表示)来获取配置值。如果您不想/需要这种灵活性,可以使用简单的.properties文件。 Java(ResourceBundle)和Spring等框架中的.properties文件得到了很好的支持。 vanilla ResourceBundle只加载属性一次,Spring助手提供可配置的缓存和重新加载(这有助于你提到的性能方面)。注意:您还可以使用数据存储支持的属性而不是文件。

两种方法通常在应用程序中共存。可以从属性文件中读取在已部署的应用程序中永不更改的值(如应用程序名称)。应用程序维护者在运行时可能需要更改的值而不进行重新部署(例如会话超时间隔)可能最好保存在可重新加载的.properties文件或数据库中。应用程序用户可以更改的值应保存在应用程序的数据存储中,并且通常具有应用程序内屏幕以进行编辑。

所以我的建议是将配置设置分为几类(例如引导程序,部署,运行时和应用程序),并选择适当的机制来管理它们。这还取决于您的应用程序的范围,即它是J2EE Web应用程序,桌面应用程序,命令行实用程序,批处理过程吗?

答案 4 :(得分:4)

您有什么样的配置文件?如果它是属性文件,这可能适合您:

public class Configuration {

    // the configuration file is stored in the root of the class path as a .properties file
    private static final String CONFIGURATION_FILE = "/configuration.properties";

    private static final Properties properties;

    // use static initializer to read the configuration file when the class is loaded
    static {
        properties = new Properties();
        try (InputStream inputStream = Configuration.class.getResourceAsStream(CONFIGURATION_FILE)) {
            properties.load(inputStream);
        } catch (IOException e) {
            throw new RuntimeException("Failed to read file " + CONFIGURATION_FILE, e);
        }
    }

    public static Map<String, String> getConfiguration() {
        // ugly workaround to get String as generics
        Map temp = properties;
        Map<String, String> map = new HashMap<String, String>(temp);
        // prevent the returned configuration from being modified 
        return Collections.unmodifiableMap(map);
    }


    public static String getConfigurationValue(String key) {
        return properties.getProperty(key);
    }

    // private constructor to prevent initialization
    private Configuration() {
    }

}

您也可以立即从Properties方法返回getConfiguration()对象,但随后可能会被访问​​它的代码修改。 Collections.unmodifiableMap()不会使配置不变(因为Properties实例在创建后通过load()方法获取其值),但是因为它被包装在 unmodifiable 映射中,其他类不能更改配置。

答案 5 :(得分:4)

这是一个遗嘱中每个人生活中都面临的一个大问题。现在遇到这个问题,可以通过创建一个单例类来解决这个问题,该单例类的实例变量与配置文件中的默认值相同。其次,这个类应该有一个类似getInstance()的方法,它只读取一次属性,每次返回同一个对象(如果存在)。对于读取文件,我们可以使用环境变量来获取路径或类似System.getenv("Config_path");的内容。读取属性(readProperties()方法)应从配置文件中读取每个项目,并将值设置为singleton对象的实例变量。所以现在单个对象包含所有配置参数的值,如果参数为空,则考虑默认值。

答案 6 :(得分:2)

另一种方法是定义一个类并读取该类中的属性文件。 此类需要处于应用程序级别,并且可以标记为Singleton。 将类标记为Singleton将避免创建多个实例。

答案 7 :(得分:1)

我建议使用JAXB或与基于文本的文件一起使用的类似绑定框架。由于JAXB实现是JRE的一部分,因此它非常易于使用。作为Denis,我建议不要使用配置密钥。

这是一个简单的示例,它提供了一个易于使用且仍然相当强大的方法来使用XML和JAXB配置应用程序。当您使用DI框架时,您只需在DI上下文中添加类似的配置对象。

@XmlRootElement
@XmlAccessorType(XmlAccessType.FIELD)
public class ApplicationConfig {

    private static final JAXBContext CONTEXT;
    public static final ApplicationConfig INSTANCE;

    // configuration properties with defaults
    private int number = 0;
    private String text = "default";
    @XmlElementWrapper
    @XmlElement(name = "text")
    private List<String> texts = new ArrayList<>(Arrays.asList("default1", "default2"));

    ApplicationConfig() {
    }

    static {
        try {
            CONTEXT = JAXBContext.newInstance(ApplicationConfig.class);
        } catch (JAXBException ex) {
            throw new IllegalStateException("JAXB context for " + ApplicationConfig.class + " unavailable.", ex);
        }
        File applicationConfigFile = new File(System.getProperty("config", new File(System.getProperty("user.dir"), "config.xml").toString()));
        if (applicationConfigFile.exists()) {
            INSTANCE = loadConfig(applicationConfigFile);
        } else {
            INSTANCE = new ApplicationConfig();
        }
    }

    public int getNumber() {
        return number;
    }

    public String getText() {
        return text;
    }

    public List<String> getTexts() {
        return Collections.unmodifiableList(texts);
    }

    public static ApplicationConfig loadConfig(File file) {
        try {
            return (ApplicationConfig) CONTEXT.createUnmarshaller().unmarshal(file);
        } catch (JAXBException ex) {
            throw new IllegalArgumentException("Could not load configuration from " + file + ".", ex);
        }
    }

    // usage
    public static void main(String[] args) {
        System.out.println(ApplicationConfig.INSTANCE.getNumber());
        System.out.println(ApplicationConfig.INSTANCE.getText());
        System.out.println(ApplicationConfig.INSTANCE.getTexts());
    }
}

配置文件如下所示:

<?xml version="1.0" encoding="UTF-8"?>
<applicationConfig>
    <number>12</number>
    <text>Test</text>
    <texts>
        <text>Test 1</text>
        <text>Test 2</text>
    </texts>
</applicationConfig>

答案 8 :(得分:0)

将配置键直接放到类上是不好的:配置键将分散在代码上。最佳实践是分离应用程序代码和配置代码。通常使用像弹簧一样的依赖注入框架。它加载配置文件并使用配置值构造对象。如果您需要在类中使用某些配置值,则应为此值创建一个setter。 Spring将在上下文初始化期间设置此值。

答案 9 :(得分:0)

  protected java.util.Properties loadParams() throws IOException {
  // Loads a ResourceBundle and creates Properties from it
  Properties prop = new Properties();
  URL propertiesFileURL = this.getClass().getResource("/conf/config.properties");
  prop.load(new FileInputStream(new File(propertiesFileURL.getPath())));
  return prop;
  }


Properties prop = loadParams();
String prop1=(String) prop.get("x.y.z");

答案 10 :(得分:0)

鉴于 YML 表达配置的普遍性,我建议创建一个包含配置的 YML 文件,然后在启动时将其加载到 POJO 中,然后访问该 POJO 的字段以获取配置:

user: someuser
password: somepassword
url: jdbc://mysql:3306/MyDatabase

使用 Java 类

public class Config {
   private String user;
   private String password;
   private String url;
  
   // getters/setters

Jackson 可以像 SnakeYml 一样直接加载 YML。

最重要的是,您可以使用我一直在开发的操作系统项目 - https://github.com/webcompere/lightweight-config - 它允许您将其包装起来,甚至在您的文件中表达占位符以插入环境变量:

user: ${USER}
password: ${PASSWORD}
url: jdbc://${DB_HOST}:3306/MyDatabase

然后

Config config = ConfigLoader.loadYmlConfigFromResource("config.yml", Config.class);