【问题标题】:Where to initialize a java Properties object?在哪里初始化 java Properties 对象?
【发布时间】:2008-12-18 03:03:12
【问题描述】:

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

有一个名为 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);
    }
  }

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

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

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

MyAppProps.init();

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

除此之外,代码似乎还有其他问题,我无法完全确定。属性实例通常包装在自定义类中吗?这里还有什么不对的地方吗?

【问题讨论】:

    标签: java properties


    【解决方案1】:

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

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

    静态初始化器如下所示;

    static {
       init();
    }
    

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

    您可以考虑研究依赖注入 (DI) 框架,例如 spring 或 guice,它们可以让您将适当的值直接注入到您需要使用它们的地方,而不是通过附加类的间接。很多人发现使用这些框架可以减少对这种管道代码的关注——但只有在你完成了框架的学习曲线之后。 (DI 框架学起来很快,但需要相当长的时间才能掌握,所以这可能是一个比你真正想要的更大的锤子)

    【讨论】:

      【解决方案2】:

      使用静态初始化器的原因:

      • 不能忘记调用它

      使用 init() 函数的原因:

      • 你可以给它传递参数
      • 更容易处理错误

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

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

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

      • 从类路径加载“myapp.properties”
      • 使用属性覆盖构造函数从当前目录加载“myapp.user.properties”
      • 最后,加载 System.getProperties() 作为最终覆盖

      “用户”属性文件没有进入版本控制,这很好。它避免了人们自定义属性文件并意外使用硬编码路径等签入的问题。

      美好时光。

      【讨论】:

        【解决方案3】:

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

        编辑: ResourceBundles vs Properties

        【讨论】:

          【解决方案4】:

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

          例如:

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

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

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

          依赖注入是指在构造函数参数中包含输入流。虽然这比只使用静态类/单例更痛苦,但在进行单元测试时,事情变得从不可能到简单。

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

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

          依赖注入使得用测试替身替换输入流变得非常容易。现在,只需将您想要的任何东西加载到测试替身中,然后再将其提供给 MyAppProps 构造函数。通过这种方式,您可以非常轻松地测试属性是如何加载的。

          【讨论】:

            猜你喜欢
            • 2013-05-23
            • 1970-01-01
            • 2016-12-03
            • 1970-01-01
            • 2014-04-27
            • 2015-06-27
            • 1970-01-01
            • 1970-01-01
            • 2023-02-04
            相关资源
            最近更新 更多