关于将键/值对从流中加载到java.util.Properties对象中,我遇到了一个我自己无法回答的问题。我正在研究的Foo servlet类中有一个调用loadProperties()方法的方法。在情况1)中,检索所选键的值有效,但在情况2)中,loadProperties()。getProperty(“ bar”)抛出NullPointerException。我不确定为什么会抛出NPE。我忘记添加,但是在相同的Foo实例中多次调用了loadProperties()。

情况1)

public class Foo extends HttpServlet {
    private InputStream is = null;

    private Properties loadProperties() {
        Properties p = new Properties();
        is = Foo.class.getClassLoader().getResourceStream("/com/test/bar.properties");
        p.load(is);
        return p;
    }
}


情况2)

public class Foo extends HttpServlet {
    private final InputStream is = Foo.class.getClassLoader().getResourceStream("/com/test/bar.properties);

    private Properties loadProperties() {
        Properties p = new Properties();
        p.load(is);
        return p;
    }
}


调用loadProperties()

public class Foo extends HttpServlet {
    private Properties loadProperties() { .... }

    private void doSomething() {
        PrintStream ps = new PrintStream(new FileOutputStream(loadProperties().getProperty("bar"))); // NPE was thrown in the case 2)
        is.close();
    }

    private void doSomething2() {
        PrintStream ps = new PrintStream(new FileOutputStream(loadProperties().getProperty("xyz")));
        is.close();
    }
}


[更新]

安迪回答了我的问题。当他问我loadProperties()是否调用了不止一次时,我检查了Foo类的长行和a!我发现它在doPost方法中被无意间调用了一次。

public class Foo extends HttpServlet {
    protected void doPost(...) {
        loadProperties();

        callDoSomething();
        callDoSomething2();
    }

    private Properties loadPropeties() {
        ....
    }

    private void doSomething() {
        ....
    }

    private void doSomething2() {
        ....
    }
}

最佳答案

在情况2中,您每次调用loadProperties方法时都尝试重用相同的流。

这可能在第一次调用时正确运行:Properties.load将消耗流中的所有数据,直到到达末尾,然后将返回的所有Property都退还给您。

(“可能”是由于下面提到的线程安全性问题)。

但是,在随后的loadProperties()调用(情况2)上,没有更多要读取的内容-流中的所有数据都已消耗。除非您明确地倒带流(根据返回的InputStream的特定子类,您甚至可能无法倒带),否则将没有更多的数据可读取。

但是,在情况2中还有另一个问题,这意味着您不应尝试倒回该流:它不是线程安全的。如果两个线程尝试同时调用loadProperties(),我不想猜测会发生什么。您可能会胡说八道。

Properties.load(InputStream)的Javadoc没有说明在传递的InputStream上进行同步的方法。因此,应避免遇到线程不安全代码的情况-在第一种情况下,您需要为每个调用创建一个新的InputStream

我假设您正在尝试避免多次重新读取属性。我建议在类外加载Properties并将其作为构造函数参数注入:

class Foo extends HttpServlet {
  private final Properties properties;

  Foo(Properties properties) {
    this.properties = checkNotNull(properties);
  }

  private void doSomething() {
    PrintStream ps = new PrintStream(new FileOutputStream(properties.getProperty("bar")));
    // ...
}


这样,如果您有Foo实例,则它具有有效的Properties;您并不是在等待执行特定的代码路径,这将触发属性的加载,并导致加载失败。

这也使代码更易于测试-您不再依赖于从文件加载的属性-它可以来自任何地方。

10-08 16:12