简介
我们在做一些Java应用开发的时候,会将很多环境特定的变量定义到一个配置文件中。比较常见的定义文件有xml, properties,甚至txt等格式的。在Java里有一个类似于标准配置格式的文件,就是.properties类型的文件。它主要用来存储一些名值对的内容,里面的内容都存成如propertyname=propertyvalue这种样式。读取properties文件的方法有很多种,网上也有大量的说明,这里主要针对两种典型的方式做一个对比。
问题场景
现在假定我们有一个config.properties的文件,里面存有如下的内容信息:
dbpassword=password database=localhost dbuser=user
我们希望通过程序来读取它的内容。
粗粗一看,这问题实在是太简单了,且看我们的第一个方法:
File IO
我们可以直接通过File Stream的经典方式来读文件,然后解析一把出来不就可以了么?这种实现的代码如下::
import java.io.FileInputStream; import java.io.IOException; import java.util.Properties; public class App { public static void main(String[] args) { Properties prop = new Properties(); try { prop.load(new FileInputStream("config/config.properties")); System.out.println(prop.getProperty("database")); System.out.println(prop.getProperty("dbuser")); System.out.println(prop.getProperty("dbpassword")); } catch(IOException e) { e.printStackTrace(); } } }
这部分代码几乎没什么可说的,就是通过创建一个java.util.Properties的对象,然后在指定properties文件之后通过FileInputStream读取它,将这个InputStream作为参数传给properties对象。properties对象的load方法就把结果解析出来了。我们执行它之后就会看到一个如下的结果:
localhost user password
还有一个要注意的地方就是,我们在传递给FileInputStream的路径是当前的相对路径,也就是说我们必须把config.properties文件放到我class文件所在目录的一个config字目录下。
现在,在考虑其他方式之前,我们看看FileInputStream这种方式的特点。我们读取properties文件的时候必须要知道它所在的路径。采用FileInputStream的时候我们必须将文件的路径传递给程序。否则程序找不到文件后会出现异常。考虑到我们的程序很多时候是编译后要部署到不同的生产环境的,如果每个环境不一样,难道我们每次都要来修改这个程序吗?或者说我们将配置文件所在的路径还要作为一个参数传给程序?这两种方式很明显不太合适。那么有没有什么法子比如说我不需要修改程序或者刻意传参数给程序就搞定了呢?另外一种方法可以达到这个理想的结果。
classloader
采用classloader的方式主要是通过它的getResourceAsStream方法来实现加载properties文件。具体细节我们可以看如下的示例:
import java.io.FileInputStream; import java.io.IOException; import java.util.Properties; public class NewApp { public static void main(String[] args) { Properties prop = new Properties(); try { prop.load(NewApp.class.getClassLoader().getResourceAsStream("config.properties")); System.out.println(prop.getProperty("database")); System.out.println(prop.getProperty("dbuser")); System.out.println(prop.getProperty("dbpassword")); } catch(IOException e) { e.printStackTrace(); } } }
这里我们并没有指定一个特定的路径给classloader,我们只是将文件名直接传给了它。如果我们将config.properties文件和class文件放在同一个目录下,结果运行正常。如果我们将properties放到其他地方则会碰到如下的错误:
Exception in thread "main" java.lang.NullPointerException at java.util.Properties$LineReader.readLine(Properties.java:434) at java.util.Properties.load0(Properties.java:353) at java.util.Properties.load(Properties.java:341) at NewApp.main(NewApp.java:10)
这是怎么回事呢?这是因为classloader每次会去classpath这些路径搜索需要装载的文件。如果不在这些路径下,则会报找不到文件的错误信息。所以说,只要我们将配置文件放在classpath的路径下,我们就可以保证它会被装载进来,而且程序就不会被改动了。这样就带来了一定的灵活性。比如说前面这个示例,如果我们将properties放到当前目录的config子目录下,该怎么装载它呢?
我们需要指定这个路径为classpath就可以了,所以我们运行如下的命令:
java -cp c:\samplecode\config; NewApp
我们将看到正常运行的结果。这里我们是将代码放在c:\samplecode这个路径下。仔细看看这种classloader装载的方法,其实它只是在一个指定的范围内来查找目标文件。我们在不同的环境下部署安装的时候则不需要去修改程序或给特定程序部分传参数来。我们只需要将需要加载的配置项文件配置到classpath中就可以。
当然,除了classloader这种装载properties的方式,还有其他几种具体的方法,比如class.getResourceAsStream(), ResourceBundle.getBundle()等几种方法,他们的本质上和classloader的方式都差不多。
classpath的作用
前面几个地方也一直提到classpath,那么这个classpath有什么作用呢?在哪些地方我们要用到它?这里也一并讨论一下。
我们通常写的一些简单的程序,比如说只是通过console显示结果的,很可能没有留意到classpath的作用。它是指定class所在路径的。在一些需要引用到其他类库的情况下,我们需要这些类库所在的具体位置,比如说我们把一个jar包放在一个lib的文件夹下面了,我们希望程序编译或者执行的时候要能够引用到它。可是java怎么知道引用它呢?这就是classpath的作用了。java虚拟机通过搜索指定的classpath路径变量,它会在这些路径里搜索和尝试装载这些需要的类库。如果能够装载成功则可以保证程序的正常运行,否则会产生错误。
所以说,classpath说白了就是一个指定类所在位置的路径以使得jvm可以识别和装载它们。
总结
操作properties文件的方式有好几种,考虑到properties文件主要是用于针对不同环境进行特定配置的,所以我们必须要保证用一种灵活的方式来加载它。使用classloader等加载的话不需要修改程序,只是需要在部署的时候针对特定环境配置好classpath就可以了。
参考材料
http://www.javaworld.com/javaqa/2003-08/01-qa-0808-property.html?page=1