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);
}
-
我们直接调用的话,是默认不添加的。(会导致直接文件清空)。但是不要想着直接设置
append=true
来解决问题,只会导致文件内容重复。(此处不再演示) -
我们此时的输入流也没有关闭,所以才造成了上述的情况。正确的姿势如下:
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;
}