Property

简单记录一下,便于明天查阅。

为了测试方便,新建一个简单 Java 项目。目录结构如下:

  [plaintext]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
. ├── a.properties ├── out │   └── production │   └── property │   ├── Main.class │   └── com │   └── ryo │   └── property │   ├── ReadProperty.class │   ├── WriteProperty.class │   └── a.properties ├── property.iml └── src ├── Main.java └── com └── ryo └── property ├── ReadProperty.java ├── WriteProperty.java └── a.properties
  • src/…/a.properties
  [plaintext]
1
2
name=class age=10
  • /a.properties
  [plaintext]
1
2
name=root age=10

Read

固定路径

直接写出配置文件的全路径。

  • easyRead()
  [java]
1
2
3
4
private static InputStream easyRead() throws FileNotFoundException { InputStream inputStream = new FileInputStream("/Users/houbinbin/IT/fork/property/a.properties"); return inputStream; }
  • main()
  [java]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/** * @since 1.7 * @param args * @throws IOException */ public static void main(String[] args) throws IOException { try(InputStream inputStream = easyRead()) { Properties properties = new Properties(); properties.load(inputStream); String name = properties.getProperty("name"); System.out.println(name); commonRead(); } }
  • output
  [plaintext]
1
root

常用方式

上一种方式虽然简单明了,但是有一个缺陷,在不同的环境下(如换个文件夹),可能结果就不正确了。所以我们一般需要相对路径。

  • commonRead()

此方法读取在根目录下的配置文件。

  [java]
1
2
3
4
private static InputStream commonRead() throws IOException { InputStream inputStream = new FileInputStream("a.properties"); return inputStream; }
  • main()

easyRead() 替换为 commonRead()

  • output
  [plaintext]
1
root

class 读取

还有一种方式是根据 class 来读取。此时去读的路径在是根据当前类编译后的 class 文件路径决定。

对应上述目录结构中的这一段:

  [plaintext]
1
2
3
4
5
6
7
8
9
10
├── out │   └── production │   └── property │   ├── Main.class │   └── com │   └── ryo │   └── property │   ├── ReadProperty.class │   ├── WriteProperty.class │   └── a.properties
  • classRead()
  [java]
1
2
3
4
private static InputStream classRead() { InputStream inputStream = ReadProperty.class.getResourceAsStream("a.properties"); return inputStream; }
  • main()

easyRead() 替换为 classRead()

  • output
  [plaintext]
1
class

Write

上面介绍了对于配置文件的读取,我们继续看下对于配置文件的写入。

内存写

  [java]
1
2
3
4
5
6
7
8
9
10
public static void main(String[] args) throws IOException { final String filePath = "/Users/houbinbin/IT/fork/property/a.properties"; try(InputStream inputStream = new FileInputStream(filePath)) { Properties properties = new Properties(); properties.load(inputStream); properties.setProperty("name", "newName"); String name = properties.getProperty("name"); System.out.println(name); } }
  • output
  [plaintext]
1
newName

此时去查看配置文件内容,其实是没有变化的。我们只是修改了内存中对应的属性值。

如果想让修改的值持久保存在文件中,怎么办呢?

文件写

  [java]
1
2
3
4
5
6
7
8
final String filePath = "/Users/houbinbin/IT/fork/property/a.properties"; try(InputStream inputStream = new FileInputStream(filePath); OutputStream outputStream = new FileOutputStream(filePath)) { Properties properties = new Properties(); properties.load(inputStream); properties.setProperty("name", "newName"); //内存修改 properties.store(outputStream, "this is update comment"); //文件修改 }

然后去看下配置文件。

  • a.properties
  [properties]
1
2
3
#this is update comment #Tue Aug 29 20:32:13 CST 2017 name=newName

等等,我的 age 信息怎么不见了?

其实看下 JDK 源码就知道了。如下:

  [java]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
/** * Creates a file output stream to write to the file with the specified * name. If the second argument is <code>true</code>, then * bytes will be written to the end of the file rather than the beginning. * A new <code>FileDescriptor</code> object is created to represent this * file connection. * <p> * First, if there is a security manager, its <code>checkWrite</code> * method is called with <code>name</code> as its argument. * <p> * If the file exists but is a directory rather than a regular file, does * not exist but cannot be created, or cannot be opened for any other * reason then a <code>FileNotFoundException</code> is thrown. * * @param name the system-dependent file name * @param append if <code>true</code>, then bytes will be written */ public FileOutputStream(String name, boolean append) throws FileNotFoundException{ this(name != null ? new File(name) : null, append); } public FileOutputStream(String name) throws FileNotFoundException { this(name != null ? new File(name) : null, false); }
  1. 我们直接调用的话,是默认不添加的。(会导致直接文件清空)。但是不要想着直接设置 append=true 来解决问题,只会导致文件内容重复。(此处不再演示)

  2. 我们此时的输入流也没有关闭,所以才造成了上述的情况。正确的姿势如下:

  [java]
1
2
3
4
5
6
7
8
9
10
11
final String filePath = "/Users/houbinbin/IT/fork/property/a.properties"; InputStream inputStream = new FileInputStream(filePath); Properties properties = new Properties(); properties.load(inputStream); inputStream.close(); //提前关闭输入流对象 OutputStream outputStream = new FileOutputStream(filePath); properties.setProperty("name", "newName");//内存修改 properties.store(outputStream, "this is update comment"); outputStream.close();
  • a.properties
  [properties]
1
2
3
4
#this is update comment #Tue Aug 29 20:39:56 CST 2017 age=10 name=newName

当你看到这里,觉得终于结束了。别急,还差一点。

  [plaintext]
1
Properties extends Hashtable<Object,Object>{}

我们使用的 Properties 继承自 HashTable,这会导致保存后的文件属性是乱序的。(自行测试时,可多写几个属性值,效果更明显)

  • OrderedProperties.class
  [java]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
/** * JDK 自带的 properties 继承自 Hashtable<Object,Object>() 所以保存后的顺序是未知的。 * 1. 此类用于解决乱序问题 * * @author bbhou * Created by bbhou on 2017/8/30. */ public class OrderedProperties extends Properties { private final LinkedHashSet<Object> keys = new LinkedHashSet<>(); public Enumeration<Object> keys() { return Collections.enumeration(keys); } public Object put(Object key, Object value) { keys.add(key); return super.put(key, value); } public Set<Object> keySet() { return keys; } public Set<String> stringPropertyNames() { Set<String> set = new LinkedHashSet<>(); for (Object key : this.keys) { set.add((String) key); } return set; } }
  • PropertyUtil.class
  [java]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
/** * 属性文件工具类 * @author bbhou * Created by bbhou on 2017/6/9. */ public final class PropertyUtil { private static Logger LOGGER = Logger.getLogger(PropertyUtil.class); /** * 文件路径 */ private static final String FILE_PATH = "YOUR_PROPERTY_FILE_PATH"; private static Properties properties; static { try(InputStream inputStream = new FileInputStream(FILE_PATH); InputStreamReader inputStreamReader = new InputStreamReader(inputStream, "UTF-8")) { properties = new OrderedProperties(); properties.load(inputStreamReader); } catch (IOException e) { LOGGER.error("Init properties meet ex: "+e, e); } } /** * 获取对应的值 * @param key * @return */ public static String get(final String key) { return properties.getProperty(key); } /** * 获取对应的值 如果没有则使用 defaultValue; * @param key * @param defaultValue * @return */ public static String getOrDefault(final String key, final String defaultValue) { return properties.getProperty(key, defaultValue); } /** * 设置对应的值 * 1. 有则设置,无则添加 * 2. 会直接修改文件的内容 * @param key 属性的键 * @param value 属性的值 * @throws IOException 为了方便使用者对异常的处理,此处抛出异常 */ public static void set(final String key, final String value) throws IOException { try(OutputStream outputStream = new FileOutputStream(FILE_PATH)) { properties.setProperty(key, value); properties.store(outputStream, null); } } private PropertyUtil() { } }

Web & common

上述的方法在普通的 Java 项目中使用是没问题的,但是在 Web 项目中,会出现找不到文件路径的问题。

  • PropertyUtilAnyWhere.java

对上述方法进一步简单改进。

  [java]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
/** * 可以在 web 和普通 java 项目中使用。 * @author houbinbin * @version 1.1 * @since 1.7 */ public class PropertyUtilAnyWhere { /** * 文件路径 */ private static final String FILE_PATH = "/one.properties"; /** * 配置属性 */ private static Properties properties; /** * 实际路径 */ private static String path = ""; static { URL url = PropertyUtil.class.getResource(FILE_PATH); try { path = url.toURI().getPath(); } catch (URISyntaxException e) { e.printStackTrace(); //实际请使用 LOG 代替 } try (InputStream inputStream = new FileInputStream(path); InputStreamReader inputStreamReader = new InputStreamReader(inputStream, "UTF-8")) { properties = new OrderedProperties(); properties.load(inputStreamReader); } catch (IOException e) { e.printStackTrace(); //实际请使用 LOG 代替 } } /** * 获取对应的值 * * @param key * @return */ public static String get(final String key) { return properties.getProperty(key); } /** * 获取对应的值 如果没有则使用 defaultValue; * * @param key * @param defaultValue * @return */ public static String getOrDefault(final String key, final String defaultValue) { return properties.getProperty(key, defaultValue); } /** * 设置对应的值 * 1. 有则设置,无则添加 * 2. 会直接修改文件的内容 * * @param key 属性的键 * @param value 属性的值 * @throws IOException 为了方便使用者对异常的处理,此处抛出异常 */ public static void set(final String key, final String value) throws IOException { try (OutputStream outputStream = new FileOutputStream(path)) { properties.setProperty(key, value); properties.store(outputStream, null); } } /** * 获取配置属性 * * @return */ public static Properties getProperties() { return properties; } private PropertyUtilAnyWhere() { } }
  • 通用获取方式
  [java]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
/** * 获取文件对应输入流 * * @param filePath 文件路径 * @return */ private static InputStream getInputStream(final String filePath) throws Exception { InputStream inputStream = null; try { inputStream = new URL(filePath).openStream(); } catch (MalformedURLException localMalformedURLException) { try { inputStream = new FileInputStream(filePath); } catch (Exception localException2) { ClassLoader localClassLoader = Thread.currentThread().getContextClassLoader(); if (localClassLoader == null) { localClassLoader = PropertyUtil.class.getClassLoader(); } inputStream = localClassLoader.getResourceAsStream(filePath); if (inputStream == null) { throw new Exception("Could not find file: " + filePath); } } } catch (IOException localIOException1) { throw new Exception(localIOException1); } return inputStream; }