在哪里初始化java Properties对象?

时间:2022-09-10 17:57:28

I inherited an application which uses a java properties file to define configuration parameters such as database name.

我继承了一个应用程序,它使用java属性文件来定义配置参数,例如数据库名称。

There is a class called MyAppProps that looks like this:

有一个名为MyAppProps的类看起来像这样:

public class MyAppProps {

   protected static final String PROP_FILENAME = "myapp.properties";
   protected static Properties myAppProps = null;

   public static final String DATABASE_NAME = "database_name";
   public static final String DATABASE_USER = "database_user";
   // etc...

   protected static void init() throws MyAppException {
     try {
       Classloader loader = MyAppException.class.getClassLoader();
       InputStream is = loader.getResourceAsStream(PROP_FILENAME);
       myAppProps = new Properties();
       myAppProps.load(is);
     } catch (Exception e) {
       threw new MyAppException(e.getMessage());
     }
    }

    protected static String getProperty(String name) throws MyAppException {
      if (props==null) {
        throw new MyAppException("Properties was not initialized properly.");
      }
      return props.getProperty(name);
    }
  }

Other classes which need to get property values contain code such as:

其他需要获取属性值的类包含以下代码:

String dbname = MyAppProps.getProperty(MyAppProps.DATABASE_NAME);

Of course, before the first call to MyAppProps.getProperty, MyAppProps needs to be initialized like this:

当然,在第一次调用MyAppProps.getProperty之前,MyAppProps需要像这样初始化:

MyAppProps.init();

I don't like the fact that init() needs to be called. Shouldn't the initialization take place in a static initialization block or in a private constructor?

我不喜欢需要调用init()的事实。初始化不应该发生在静态初始化块或私有构造函数中吗?

Besides for that, something else seems wrong with the code, and I can't quite put my finger on it. Are properties instances typically wrapped in a customized class? Is there anything else here that is wrong?

除此之外,代码似乎还有其他问题,我无法完全理解它。属性实例通常包含在自定义类中吗?这里有什么别的错吗?

4 个解决方案

#1


If I make my own wrapper class like this; I always prefer to make strongly typed getters for the values, instead of exposing all the inner workings through the static final variables.

如果我像这样创建自己的包装类;我总是喜欢为值创建强类型的getter,而不是通过静态final变量公开所有内部工作。

private static final String DATABASE_NAME = "database_name"
private static final String DATABASE_USER = "database_user"
public String getDatabaseName(){
   return getProperty(MyAppProps.DATABASE_NAME);
}
public String getDatabaseUser(){
   return getProperty(MyAppProps.DATABASE_USER);
}

A static initializer looks like this;

静态初始化器看起来像这样;

static {
   init();
}

This being said, I will readily say that I am no big fan of static initializers.

话虽如此,我很乐意说我不是静态初始化器的忠实粉丝。

You may consider looking into dependency injection (DI) frameworks like spring or guice, these will let you inject the appropriate value directly into the places you need to use them, instead of going through the indirection of the additional class. A lot of people find that using these frameworks reduces focus on this kind of plumbing code - but only after you've finished the learning curve of the framework. (DI frameworks are quick to learn but take quite some time to master, so this may be a bigger hammer than you really want)

您可以考虑查看依赖注入(DI)框架(如spring或guice),这些将允许您将适当的值直接注入需要使用它们的位置,而不是通过附加类的间接方式。很多人发现使用这些框架可以减少对这种管道代码的关注 - 但只有在你完成框架的学习曲线之后。 (DI框架很快就可以学习,但需要花费很长时间才能掌握,所以这可能比你真正想要的更重要)

#2


Reasons to use static initializer:

使用静态初始化程序的原因:

  • Can't forget to call it
  • 不能忘记叫它

Reasons to use an init() function:

使用init()函数的原因:

  • You can pass parameters to it
  • 您可以将参数传递给它

  • Easier to handle errors
  • 更容易处理错误

I've created property wrappers in the past to good effect. For a class like the example, the important thing to ensure is that the properties are truly global, i.e. a singleton really makes sense. With that in mind a custom property class can have type-safe getters. You can also do cool things like variable expansion in your custom getters, e.g.:

我在过去创建了属性包装器,效果很好。对于类似示例的类,要确保的重要一点是属性是真正全局的,即单例确实有意义。考虑到这一点,自定义属性类可以具有类型安全的getter。您还可以在自定义getter中执行变量扩展等很酷的操作,例如:

myapp.data.path=${myapp.home}/data

Furthermore, in your initializer, you can take advantage of property file overloading:

此外,在初始化程序中,您可以利用属性文件重载:

  • Load in "myapp.properties" from the classpath
  • 从类路径加载“myapp.properties”

  • Load in "myapp.user.properties" from the current directory using the Properties override constructor
  • 使用Properties覆盖构造函数从当前目录加载“myapp.user.properties”

  • Finally, load System.getProperties() as a final override
  • 最后,加载System.getProperties()作为最终覆盖

The "user" properties file doesn't go in version control, which is nice. It avoids the problem of people customizing the properties file and accidentally checking it in with hard-coded paths, etc.

“用户”属性文件不在版本控制中,这很好。它避免了人们自定义属性文件并使用硬编码路径意外检查它的问题。

Good times.

#3


You can use either, a static block or a constructor. The only advice I have is to use ResourceBundle, instead. That might better suit your requirement. For more please follow the link below.

您可以使用静态块或构造函数。我唯一的建议是使用ResourceBundle。这可能更适合您的要求。有关更多信息,请点击以下链接。

Edit: ResourceBundles vs Properties

编辑:ResourceBundles与属性

#4


The problem with static methods and classes is that you can't override them for test doubles. That makes unit testing much harder. I have all variables declared final and initialized in the constructor. Whatever is needed is passed in as parameters to the constructor (dependency injection). That way you can substitute test doubles for some of the parameters during unit tests.

静态方法和类的问题是您不能为测试双精度覆盖它们。这使得单元测试更加困难。我将所有变量声明为final并在构造函数中初始化。无论需要什么,都将作为参数传递给构造函数(依赖注入)。这样,您可以在单元测试期间将测试双精度替换为某些参数。

For example:

public class MyAppProps {

   protected static final String PROP_FILENAME = "myapp.properties";
   protected Properties props = null;

   public String DATABASE_NAME = "database_name";
   public String DATABASE_USER = "database_user";
   // etc...

   public MyAppProps(InputStream is) throws MyAppException {
     try {
       props = new Properties();
       props.load(is);
     } catch (Exception e) {
       threw new MyAppException(e.getMessage());
     }
    }

    public String getProperty(String name) {
      return props.getProperty(name);
    }
    // Need this function static so
    // client objects can load the
    // file before an instance of this class is created.
    public static String getFileName() {
      return PROP_FILENAME;
    }
}

Now, call it from production code like this:

现在,从生产代码中调用它,如下所示:

String fileName = MyAppProps.getFileName();
Classloader loader = MyAppException.class.getClassLoader();
InputStream is = loader.getResourceAsStream(fileName);
MyAppProps p = new MyAppProps(is);

The dependency injection is when you include the input stream in the constructor parameters. While this is slightly more of a pain than just using the static class / Singleton, things go from impossible to simple when doing unit tests.

依赖注入是指在构造函数参数中包含输入流。虽然这比使用静态类/ Singleton稍微困难一些,但在进行单元测试时,事情从不可能变为简单。

For unit testing, it might go something like:

对于单元测试,它可能类似于:

@Test
public void testStuff() {
    // Setup
    InputStringTestDouble isTD = new InputStreamTestDouble();
    MyAppProps instance = new MyAppProps(isTD);

    // Exercise
    int actualNum = instance.getProperty("foo");

    // Verify
    int expectedNum = 42;
    assertEquals("MyAppProps didn't get the right number!", expectedNum, actualNum);
}

The dependency injection made it really easy to substitute a test double for the input stream. Now, just load whatever stuff you want into the test double before giving it to the MyAppProps constructor. This way you can test how the properties are loaded very easily.

依赖注入使得将测试double替换为输入流非常容易。现在,在将它提供给MyAppProps构造函数之前,只需将您想要的任何内容加载到test double中。这样,您可以非常轻松地测试属性的加载方式。

#1


If I make my own wrapper class like this; I always prefer to make strongly typed getters for the values, instead of exposing all the inner workings through the static final variables.

如果我像这样创建自己的包装类;我总是喜欢为值创建强类型的getter,而不是通过静态final变量公开所有内部工作。

private static final String DATABASE_NAME = "database_name"
private static final String DATABASE_USER = "database_user"
public String getDatabaseName(){
   return getProperty(MyAppProps.DATABASE_NAME);
}
public String getDatabaseUser(){
   return getProperty(MyAppProps.DATABASE_USER);
}

A static initializer looks like this;

静态初始化器看起来像这样;

static {
   init();
}

This being said, I will readily say that I am no big fan of static initializers.

话虽如此,我很乐意说我不是静态初始化器的忠实粉丝。

You may consider looking into dependency injection (DI) frameworks like spring or guice, these will let you inject the appropriate value directly into the places you need to use them, instead of going through the indirection of the additional class. A lot of people find that using these frameworks reduces focus on this kind of plumbing code - but only after you've finished the learning curve of the framework. (DI frameworks are quick to learn but take quite some time to master, so this may be a bigger hammer than you really want)

您可以考虑查看依赖注入(DI)框架(如spring或guice),这些将允许您将适当的值直接注入需要使用它们的位置,而不是通过附加类的间接方式。很多人发现使用这些框架可以减少对这种管道代码的关注 - 但只有在你完成框架的学习曲线之后。 (DI框架很快就可以学习,但需要花费很长时间才能掌握,所以这可能比你真正想要的更重要)

#2


Reasons to use static initializer:

使用静态初始化程序的原因:

  • Can't forget to call it
  • 不能忘记叫它

Reasons to use an init() function:

使用init()函数的原因:

  • You can pass parameters to it
  • 您可以将参数传递给它

  • Easier to handle errors
  • 更容易处理错误

I've created property wrappers in the past to good effect. For a class like the example, the important thing to ensure is that the properties are truly global, i.e. a singleton really makes sense. With that in mind a custom property class can have type-safe getters. You can also do cool things like variable expansion in your custom getters, e.g.:

我在过去创建了属性包装器,效果很好。对于类似示例的类,要确保的重要一点是属性是真正全局的,即单例确实有意义。考虑到这一点,自定义属性类可以具有类型安全的getter。您还可以在自定义getter中执行变量扩展等很酷的操作,例如:

myapp.data.path=${myapp.home}/data

Furthermore, in your initializer, you can take advantage of property file overloading:

此外,在初始化程序中,您可以利用属性文件重载:

  • Load in "myapp.properties" from the classpath
  • 从类路径加载“myapp.properties”

  • Load in "myapp.user.properties" from the current directory using the Properties override constructor
  • 使用Properties覆盖构造函数从当前目录加载“myapp.user.properties”

  • Finally, load System.getProperties() as a final override
  • 最后,加载System.getProperties()作为最终覆盖

The "user" properties file doesn't go in version control, which is nice. It avoids the problem of people customizing the properties file and accidentally checking it in with hard-coded paths, etc.

“用户”属性文件不在版本控制中,这很好。它避免了人们自定义属性文件并使用硬编码路径意外检查它的问题。

Good times.

#3


You can use either, a static block or a constructor. The only advice I have is to use ResourceBundle, instead. That might better suit your requirement. For more please follow the link below.

您可以使用静态块或构造函数。我唯一的建议是使用ResourceBundle。这可能更适合您的要求。有关更多信息,请点击以下链接。

Edit: ResourceBundles vs Properties

编辑:ResourceBundles与属性

#4


The problem with static methods and classes is that you can't override them for test doubles. That makes unit testing much harder. I have all variables declared final and initialized in the constructor. Whatever is needed is passed in as parameters to the constructor (dependency injection). That way you can substitute test doubles for some of the parameters during unit tests.

静态方法和类的问题是您不能为测试双精度覆盖它们。这使得单元测试更加困难。我将所有变量声明为final并在构造函数中初始化。无论需要什么,都将作为参数传递给构造函数(依赖注入)。这样,您可以在单元测试期间将测试双精度替换为某些参数。

For example:

public class MyAppProps {

   protected static final String PROP_FILENAME = "myapp.properties";
   protected Properties props = null;

   public String DATABASE_NAME = "database_name";
   public String DATABASE_USER = "database_user";
   // etc...

   public MyAppProps(InputStream is) throws MyAppException {
     try {
       props = new Properties();
       props.load(is);
     } catch (Exception e) {
       threw new MyAppException(e.getMessage());
     }
    }

    public String getProperty(String name) {
      return props.getProperty(name);
    }
    // Need this function static so
    // client objects can load the
    // file before an instance of this class is created.
    public static String getFileName() {
      return PROP_FILENAME;
    }
}

Now, call it from production code like this:

现在,从生产代码中调用它,如下所示:

String fileName = MyAppProps.getFileName();
Classloader loader = MyAppException.class.getClassLoader();
InputStream is = loader.getResourceAsStream(fileName);
MyAppProps p = new MyAppProps(is);

The dependency injection is when you include the input stream in the constructor parameters. While this is slightly more of a pain than just using the static class / Singleton, things go from impossible to simple when doing unit tests.

依赖注入是指在构造函数参数中包含输入流。虽然这比使用静态类/ Singleton稍微困难一些,但在进行单元测试时,事情从不可能变为简单。

For unit testing, it might go something like:

对于单元测试,它可能类似于:

@Test
public void testStuff() {
    // Setup
    InputStringTestDouble isTD = new InputStreamTestDouble();
    MyAppProps instance = new MyAppProps(isTD);

    // Exercise
    int actualNum = instance.getProperty("foo");

    // Verify
    int expectedNum = 42;
    assertEquals("MyAppProps didn't get the right number!", expectedNum, actualNum);
}

The dependency injection made it really easy to substitute a test double for the input stream. Now, just load whatever stuff you want into the test double before giving it to the MyAppProps constructor. This way you can test how the properties are loaded very easily.

依赖注入使得将测试double替换为输入流非常容易。现在,在将它提供给MyAppProps构造函数之前,只需将您想要的任何内容加载到test double中。这样,您可以非常轻松地测试属性的加载方式。