Property

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

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

.
├── 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
name=class
age=10
  • /a.properties
name=root
age=10

Read

固定路径

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

  • easyRead()
private static InputStream easyRead() throws FileNotFoundException {
    InputStream inputStream = new FileInputStream("/Users/houbinbin/IT/fork/property/a.properties");
    return inputStream;
}
  • main()
/**
 * @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
root

常用方式

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

  • commonRead()

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

private static InputStream commonRead() throws IOException {
    InputStream inputStream = new FileInputStream("a.properties");
    return inputStream;
}
  • main()

easyRead() 替换为 commonRead()

  • output
root

class 读取

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

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

├── out
│   └── production
│       └── property
│           ├── Main.class
│           └── com
│               └── ryo
│                   └── property
│                       ├── ReadProperty.class
│                       ├── WriteProperty.class
│                       └── a.properties
  • classRead()
private static InputStream classRead() {
    InputStream inputStream = ReadProperty.class.getResourceAsStream("a.properties");
    return inputStream;
}
  • main()

easyRead() 替换为 classRead()

  • output
class

Write

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

内存写

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
newName

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

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

文件写

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
#this is update comment
#Tue Aug 29 20:32:13 CST 2017
name=newName

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

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

/**
 * 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. 我们此时的输入流也没有关闭,所以才造成了上述的情况。正确的姿势如下:

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
#this is update comment
#Tue Aug 29 20:39:56 CST 2017
age=10
name=newName

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

Properties extends Hashtable<Object,Object>{}

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

  • OrderedProperties.class
/** 
 * 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
/**
 * 属性文件工具类
 * @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

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

/**
 * 可以在 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() {
    }

}
  • 通用获取方式
/**
 * 获取文件对应输入流
 *
 * @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;
}