PropUtils 是一個用於操作 Properties 檔案的工具類別,提供讀取、更新及查詢 properties 檔案內容的相關方法。此類別設計為靜態工具類別,所有方法皆可直接呼叫使用。
// 檢查檔案是否為有效的 properties 格式
boolean isValid = PropUtils.isPropertiesFile("config.properties"); // true
boolean notValid = PropUtils.isPropertiesFile("invalid.txt"); // false
// 更新預設 properties 檔案中的變數值
PropUtils.updateProperty("database.url", "jdbc:mysql://localhost:3306/mydb");
// 更新指定 properties 檔案中的變數值
PropUtils.updateProperty("custom.properties", "server.port", "8080");
// 讀取整個 properties 檔案內容
Map<String, String> allProps = PropUtils.loadProps("config.properties");
// 從預設 properties 檔案讀取變數值
String value = PropUtils.getProps("app.name");
// 從指定 properties 檔案讀取變數值
String value = PropUtils.getProps("custom.properties", "app.version");
檔案處理
格式驗證
更新操作
錯誤處理
以下測試範例示範 PropUtils 的常見測試場景:
@Test
void testLoadAndUpdateProps(@TempDir Path tempDir) throws Exception {
Path p = tempDir.resolve("app.properties");
Files.write(p, Arrays.asList("app.name=demo"), StandardCharsets.UTF_8);
Map<String,String> props = PropUtils.loadProps(p.toString());
assertEquals("demo", props.get("app.name"));
PropUtils.updateProperty(p.toString(), "app.name", "newdemo");
assertEquals("newdemo", PropUtils.getProps(p.toString(), "app.name"));
}
@Test
void testIsPropertiesFileAndError(@TempDir Path tempDir) throws Exception {
Path p = tempDir.resolve("notprop.txt");
Files.write(p, Arrays.asList("just text"), StandardCharsets.UTF_8);
assertFalse(PropUtils.isPropertiesFile(p.toString()));
// 讀取不存在的檔案應建立並回傳空 map
Map<String,String> created = PropUtils.loadProps(tempDir.resolve("new.properties").toString());
assertNotNull(created);
}
配置管理
動態設定
系統整合
package tw.lewishome.webapp.base.utility.common;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import lombok.extern.slf4j.Slf4j;
import tw.lewishome.webapp.GlobalConstants;
/**
*
* PropUtils 是一個用於操作 properties 檔案的工具類別,提供讀取、更新及查詢 properties 檔案內容的相關方法。
* 主要功能包含:
* <ul>
* <li>更新指定 properties 檔案中的某個變數值</li>
* <li>載入 properties 檔案所有變數與內容至 Map</li>
* <li>查詢指定變數的內容值</li>
* </ul>
* 所有方法皆為靜態方法,方便直接呼叫使用。
*
*
*
* 注意事項:
* <ul>
* <li>更新或讀取檔案時,若檔案不存在會自動建立新檔案。</li>
* <li>載入 properties 時,會忽略註解行(以 # 開頭)及格式不正確的行。</li>
* <li>更新變數時,若該變數不存在則會新增該變數。</li>
* <li>所有方法皆可能拋出例外,請妥善處理例外狀況。</li>
* </ul>
*
*
*
* 適用場景:
* <ul>
* <li>需要動態修改或查詢 Java properties 檔案內容時</li>
* <li>批次處理或設定檔管理</li>
* </ul>
*
*
* @author Lewis
* @version 1.0
*/
@Slf4j
public class PropUtils {
/** Private constructor to prevent instantiation */
private PropUtils() {
throw new IllegalStateException("This is a utility class and cannot be instantiated");
}
/**
* 判斷指定路徑是否為 properties 檔案。
*
* 此方法會嘗試以 {@link FileInputStream} 開啟並以 {@link Properties#load(InputStream)}
* 載入檔案。若載入成功(即能被解析為 properties 格式),則回傳 {@code true}。
* 若檔案不存在、不可讀或在載入時發生 IO/解析錯誤,則回傳 {@code false}。
* 空檔案或僅有註解的檔案仍會視為可解析並回傳 {@code true}(因為 load() 成功)。
*
*
* @param filePath 屬性檔案的路徑
* @return 若能成功以 Properties 載入則回傳 {@code true},否則回傳 {@code false}
*/
public static boolean isPropertiesFile(String filePath) {
List<String> listFilelines = new ArrayList<>();
try {
listFilelines = FileUtils.fileToStringArrayList(filePath);
} catch (IOException e) {
log.debug("無法讀取檔案 [{}]: {}", filePath, e.getMessage());
return false;
}
if (listFilelines.size() == 0) {
log.debug("Properties 檔案 [{}] 為空檔案或不存在", filePath);
return true; // 空檔案視為有效的 properties 檔案
}
for (int i = 0; i < listFilelines.size(); i++) {
String oneLine = listFilelines.get(i).trim();
if (oneLine.isEmpty() || oneLine.startsWith("#")) {
continue; // 忽略空行和註解行
}
if (Boolean.FALSE.equals(oneLine.contains("="))) {
log.debug("檔案 [{}] 第 {} 行格式錯誤: {}", filePath, i + 1, oneLine);
return false; // 非 properties 格式
}
}
return true; // 成功載入且格式正確
}
/**
* 更新檔案 propertyFileName 的變數 propName 內容 value
*
* @param propNameKey a String object
* @param value 對應 內容
* @throws java.lang.Exception java.lang.Exception
*
*/
public static void updateProperty(String propNameKey, String value) throws Exception {
updateProperty(GlobalConstants.PROPERTIES_FILE, propNameKey, value);
}
/**
* 更新檔案 propertyFileName 的變數 propName 內容 value
*
* @param propertyFileName properties File name
* @param propertyValue 對應 內容
* @param propertyKey a String object
* @throws IOException IOException
*/
public static void updateProperty(String propertyFileName, String propertyKey, String propertyValue) throws IOException {
// 1. Load existing properties
List<String> listFilelines = new ArrayList<>();
try {
listFilelines = FileUtils.fileToStringArrayList(propertyFileName);
} catch (IOException e) {
log.debug("無法讀取檔案 [{}]: {}", propertyFileName, e.getMessage());
return;
}
List<String> listNewFilelines = new ArrayList<>();
// 2. update the property
boolean hasKey = false;
for (int i = 0; i < listFilelines.size(); i++) {
String oneLine = listFilelines.get(i).trim();
if (oneLine.isEmpty() || oneLine.startsWith("#")) {
listNewFilelines.add(listFilelines.get(i));
continue; // 忽略空行和註解行
}
List<String> splitLine = CommUtils.splitDelimiter(oneLine, "=");
if (splitLine.size() == 2) {
String key = splitLine.get(0).trim();
String value = splitLine.get(1).trim();
if (key.equals(propertyKey)) {
value = propertyValue;
listNewFilelines.add(key + "=" + value);
hasKey = true;
continue;
}
listNewFilelines.add(oneLine);
} else {
listNewFilelines.add(oneLine);
}
}
if (Boolean.FALSE.equals(hasKey)) {
// Append new property if key not found
listNewFilelines.add(propertyKey + "=" + propertyValue);
}
// 3. Save the updated properties
FileUtils.stringArrayListToFile(listNewFilelines, propertyFileName);
}
/**
* 取的檔案 propertyFileName 的所有變數與內容 (以File)
*
* @param propertyFile 對應 properties File
* @return Map 對應 properties 變數
* @throws IOException IOException
*/
public static Map<String, String> loadProps(String propertyFile) throws IOException {
Map<String, String> mapProperties = new HashMap<>();
// 1. Load existing properties
List<String> listFilelines = new ArrayList<>();
try {
listFilelines = FileUtils.fileToStringArrayList(propertyFile);
} catch (IOException e) {
log.debug("無法讀取檔案 [{}]: {}", propertyFile, e.getMessage());
return mapProperties ;
}
for (int i = 0; i < listFilelines.size(); i++) {
String oneLine = listFilelines.get(i).trim();
if (oneLine.isEmpty() || oneLine.startsWith("#")) {
continue; // 忽略空行和註解行
}
List<String> splitLine = CommUtils.splitDelimiter(oneLine, "=");
if (splitLine.size() == 2) {
String key = splitLine.get(0).trim();
String value = splitLine.get(1).trim();
mapProperties.put(key, value);
} else {
log.debug("檔案 [{}] 第 {} 行格式錯誤: {}", propertyFile, i + 1, oneLine);
}
}
return mapProperties;
}
/**
* 取的檔案 propertyFileName 的所有變數與內容 (以File Name String)
*
* @param propertyFileName 對應 properties File Name
* @return Map 對應 properties 變數
* @throws IOException IOException
*/
public static Map<String, String> loadProps(File propertyFileName) throws IOException {
String propertyFile = propertyFileName.getAbsolutePath();
return loadProps(propertyFile);
}
/**
* 取的檔案 propertyFileName 的 propName 變數與內容 (以File Name String)
*
* @param propertyFileName 對應 properties File Name
* @param propName property 變數
* @return String properties 變數內容
*/
public static String getProps(String propertyFileName, String propName) {
try {
File propertyFile = new File(propertyFileName);
Map<String, String> allProperties = loadProps(propertyFile);
return allProperties.get(propName);
} catch (Exception e) {
return null;
}
}
/**
* 取的檔案 default PROPERTIES_FILE 的 propName 變數與內容
*
* @param propName property 變數
* @return String properties 變數內容
*/
public static String getProps(String propName) {
return getProps(GlobalConstants.PROPERTIES_FILE, propName);
}
}
package tw.lewishome.webapp.base.utility.common;
import static org.junit.jupiter.api.Assertions.*;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import java.io.File;
import java.io.IOException;
import java.nio.file.Path;
import java.util.Map;
/**
* PropUtils 的單元測試類別
*/
class PropUtilsTest {
private static final String TEST_PROP_FILE = "test.properties";
private File testFile;
@TempDir
Path tempDir;
@BeforeEach
void setUp() throws IOException {
// 建立測試用的 properties 檔案
testFile = tempDir.resolve(TEST_PROP_FILE).toFile();
FileUtils.stringArrayListToFile(java.util.Arrays.asList(
"test.key1=value1",
"test.key2=value2",
"#This is a comment",
"test.key3=value3"), testFile.getAbsolutePath());
}
@AfterEach
void tearDown() {
// 清理測試檔案
FileUtils.deleteFile(testFile.getAbsolutePath());
}
@Test
void testIsPropertiesFile() {
// 測試有效的 properties 檔案
assertTrue(PropUtils.isPropertiesFile(testFile.getAbsolutePath()));
// 測試不存在的檔案
assertFalse(PropUtils.isPropertiesFile("nonexistent.properties"));
// 測試無效的 properties 檔案
try {
File invalidFile = tempDir.resolve("invalid.properties").toFile();
FileUtils.stringArrayListToFile(java.util.Arrays.asList(
"invalid content",
"not a properties format"), invalidFile.getAbsolutePath());
assertFalse(PropUtils.isPropertiesFile(invalidFile.getAbsolutePath()));
} catch (IOException e) {
fail("Should not throw exception: " + e.getMessage());
}
}
@Test
void testUpdateProperty() throws Exception {
// 測試更新現有屬性
PropUtils.updateProperty(testFile.getAbsolutePath(), "test.key1", "newValue1");
assertEquals("newValue1", PropUtils.getProps(testFile.getAbsolutePath(), "test.key1"));
// 測試新增屬性
PropUtils.updateProperty(testFile.getAbsolutePath(), "test.key4", "value4");
assertEquals("value4", PropUtils.getProps(testFile.getAbsolutePath(), "test.key4"));
// 測試更新空值
PropUtils.updateProperty(testFile.getAbsolutePath(), "test.key5", "");
assertNull(PropUtils.getProps(testFile.getAbsolutePath(), "test.key5"));
}
@Test
void testLoadProps() throws IOException {
// 測試載入所有屬性
Map<String, String> props = PropUtils.loadProps(testFile);
assertNotNull(props);
assertEquals(3, props.size());
assertEquals("value1", props.get("test.key1"));
assertEquals("value2", props.get("test.key2"));
assertEquals("value3", props.get("test.key3"));
// 測試載入不存在的檔案
File nonExistentFile = new File("nonexistent.properties");
Map<String, String> emptyProps = PropUtils.loadProps(nonExistentFile);
assertTrue(emptyProps.isEmpty());
}
@Test
void testGetProps() {
// 測試取得存在的屬性值
assertEquals("value1", PropUtils.getProps(testFile.getAbsolutePath(), "test.key1"));
assertEquals("value2", PropUtils.getProps(testFile.getAbsolutePath(), "test.key2"));
// 測試取得不存在的屬性值
assertNull(PropUtils.getProps(testFile.getAbsolutePath(), "nonexistent.key"));
// 測試取得不存在檔案中的屬性值
assertNull(PropUtils.getProps("nonexistent.properties", "test.key1"));
}
@Test
void testLoadPropsByString() throws IOException {
// 測試使用檔案路徑字串載入屬性
Map<String, String> props = PropUtils.loadProps(testFile.getAbsolutePath());
assertNotNull(props);
assertEquals(3, props.size());
assertEquals("value1", props.get("test.key1"));
// 測試載入不存在的檔案
Map<String, String> emptyProps = PropUtils.loadProps("nonexistent.properties");
assertTrue(emptyProps.isEmpty());
}
}