我继承了一个应用程序,该应用程序使用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()
的事实。初始化不应该发生在静态初始化块或私有构造函数中吗?
除此之外,代码似乎还有其他问题,我无法完全理解它。属性实例通常包含在自定义类中吗?这里还有什么不对的吗?
答案 0 :(得分:5)
如果我像这样制作自己的包装类;我总是喜欢为值创建强类型的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);
}
静态初始化器看起来像这样;
static {
init();
}
话虽如此,我很乐意说我不是静态初始化器的忠实粉丝。
您可以考虑查看依赖注入(DI)框架(如spring或guice),这些将允许您将适当的值直接注入需要使用它们的位置,而不是通过附加类的间接方式。很多人发现使用这些框架可以减少对这种管道代码的关注 - 但只有在你完成了框架的学习曲线之后。 (DI框架很快就可以学习,但需要花费很长时间才能掌握,所以这可能比你真正想要的更重要。)
答案 1 :(得分:3)
使用静态初始化程序的原因:
使用init()函数的原因:
我在过去创建了属性包装器,效果很好。对于像示例这样的类,要确保的重要一点是属性是真正的全局属性,即单例确实有意义。考虑到这一点,自定义属性类可以具有类型安全的getter。您还可以在自定义getter中执行变量扩展等很酷的操作,例如:
myapp.data.path=${myapp.home}/data
此外,在初始化程序中,您可以利用属性文件重载:
“用户”属性文件不在版本控制中,这很好。它避免了人们自定义属性文件并使用硬编码路径意外检入它的问题。
美好时光。
答案 2 :(得分:1)
您可以使用静态块或构造函数。我唯一的建议是使用ResourceBundle。这可能更适合您的要求。欲了解更多信息,请点击以下链接。
答案 3 :(得分:1)
静态方法和类的问题在于您不能为测试双精度覆盖它们。这使得单元测试更加困难。我将所有变量声明为final并在构造函数中初始化。无论需要什么,都将作为参数传递给构造函数(依赖注入)。这样,您可以在单元测试期间将测试双精度替换为某些参数。
例如:
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);
依赖注入是指在构造函数参数中包含输入流。虽然这比使用静态类/ Singleton稍微痛苦一些,但在进行单元测试时,事情从不可能变为简单。
对于单元测试,它可能类似于:
@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);
}
依赖注入使得将测试double替换为输入流非常容易。现在,在将它提供给MyAppProps构造函数之前,只需将您想要的任何内容加载到测试double中。这样,您可以非常轻松地测试属性的加载方式。