# ZmlTools
**Repository Path**: zml2015/ZmlTools
## Basic Information
- **Project Name**: ZmlTools
- **Description**: 集成了各种工具类,包含数据类型转换、系统参数获取、JSON实体转换、文件操作、文件编码、字符串常用工具、反射常用方法等
- **Primary Language**: Java
- **License**: Apache-2.0
- **Default Branch**: master
- **Homepage**: None
- **GVP Project**: No
## Statistics
- **Stars**: 6
- **Forks**: 4
- **Created**: 2017-11-08
- **Last Updated**: 2026-09-08
## Categories & Tags
**Categories**: Uncategorized
**Tags**: None
## README
# ZmlTools
ZmlTools 是一个面向 Java 应用的通用工具库,集中提供数据转换、字符串处理、日期时间、文件与流、集合、JSON、HTTP、加密、随机数据、反射、系统信息、线程池和文件监听等常用能力。
> 当前项目版本:`1.4.6`
> Maven 坐标:`top.wuyongshi:ZmlTools`
> License:Apache License 2.0
[](https://search.maven.org/artifact/top.wuyongshi/ZmlTools)
[](https://www.apache.org/licenses/LICENSE-2.0.html)
## 目录
- [1. 环境要求](#1-环境要求)
- [2. 引入项目](#2-引入项目)
- [3. 快速开始](#3-快速开始)
- [4. 功能总览](#4-功能总览)
- [5. 数据类型转换](#5-数据类型转换)
- [6. 字符串、数组和集合](#6-字符串数组和集合)
- [7. 数字和日期时间](#7-数字和日期时间)
- [8. 文件、目录和 IO](#8-文件目录和-io)
- [9. JSON 转换](#9-json-转换)
- [10. HTTP、Cookie 和资源](#10-httpcookie-和资源)
- [11. CSV 文件](#11-csv-文件)
- [12. 加密、摘要和 Base64](#12-加密摘要和-base64)
- [13. 随机数据和 ID](#13-随机数据和-id)
- [14. 反射、校验和系统信息](#14-反射校验和系统信息)
- [15. 线程池](#15-线程池)
- [16. 文件监听](#16-文件监听)
- [17. 其他工具](#17-其他工具)
- [18. 构建与测试](#18-构建与测试)
- [19. 使用注意事项](#19-使用注意事项)
- [20. 源码和示例](#20-源码和示例)
---
## 1. 环境要求
- JDK 8 及以上。
- 项目源码以 Java 8 为编译目标。
- HTTP、JSON、CSV 等能力由传递依赖 `com.alianga:jkit` 提供。
- 运行测试使用 JUnit 4。
## 2. 引入项目
### 2.1 Maven
```xml
top.wuyongshi
ZmlTools
1.4.6
```
如果使用尚未发布的源码版本,可以在项目根目录执行:
```bash
mvn install
```
### 2.2 Gradle
```groovy
implementation 'top.wuyongshi:ZmlTools:1.4.6'
```
### 2.3 主要依赖
`1.4.6` 起 HTTP / JSON / CSV 等能力改由 jkit 提供,不再传递 OkHttp、Fastjson、Guava、Commons CSV、javaee-api。
| 能力 | 依赖 |
| --- | --- |
| 通用工具(HTTP、JSON、CSV 等) | jkit |
| JSON(Gson 封装) | Gson |
| HTML 解析 | Jsoup |
| 日志 | SLF4J、Logback |
| 编译期代码生成 | Lombok |
## 3. 快速开始
工具类大多是静态方法,不需要创建实例:
```java
import top.wys.utils.DateUtils;
import top.wys.utils.FileUtils;
import top.wys.utils.StringUtils;
import top.wys.utils.convert.ConvertUtils;
import java.io.File;
public class QuickStart {
public static void main(String[] args) throws Exception {
boolean enabled = ConvertUtils.toBoolean("yes");
int count = ConvertUtils.toInt("12.8");
String text = ConvertUtils.toNoneNullString(null);
String now = DateUtils.getNowDateTime();
boolean blank = StringUtils.isBlank(" ");
File file = new File("target/example.txt");
FileUtils.createFile(file);
FileUtils.writeFile(file.getPath(), "hello", false);
System.out.println(enabled); // true
System.out.println(count); // 12
System.out.println(text); // ""
System.out.println(now); // yyyy-MM-dd HH:mm:ss
System.out.println(blank); // true
}
}
```
## 4. 功能总览
| 包 | 主要内容 |
| --- | --- |
| `top.wys.utils` | 字符串、日期、文件、HTTP、JSON、加密、系统、随机数据等基础工具 |
| `top.wys.utils.convert` | 对象、数字、布尔、日期和二进制转换 |
| `top.wys.utils.collection` | 数组、集合、Map、布尔值处理 |
| `top.wys.utils.math` | 数字运算和数字字符串处理 |
| `top.wys.utils.io` | 资源、文件类型、属性文件处理 |
| `top.wys.utils.io.monitor` | 基于 JDK WatchService 的文件监听 |
| `top.wys.utils.http` | Cookie、下载、HTTP 回调和 SSL 辅助类 |
| `top.wys.utils.crypto` | AES、DES 等底层加密实现 |
| `top.wys.utils.image` | 验证码、图片和 GIF 相关工具 |
| `top.wys.utils.thread` | 线程池创建、执行和关闭 |
| `top.wys.utils.reflect` / `jdk` | 字段访问、Unsafe 和 JDK 相关辅助类 |
| `top.wys.utils.valid` | 参数和状态校验 |
## 5. 数据类型转换
核心类:`top.wys.utils.convert.ConvertUtils`。
### 5.1 字符串和空值处理
```java
import top.wys.utils.convert.ConvertUtils;
String a = ConvertUtils.toString(null); // null
String b = ConvertUtils.toString(null, "default"); // default
String c = ConvertUtils.toNoneNullString(null); // ""
String d = ConvertUtils.toNoneNullString("NULL", "N/A"); // N/A
String e = ConvertUtils.toNoneEmptyString("", "N/A"); // N/A
String f = ConvertUtils.toNoneEmptyString("value", "N/A");// value
```
`toNoneNullString` 和 `toNoneEmptyString` 都会把大小写不敏感的字符串 `"null"` 当作无效值处理。
### 5.2 布尔和数字转换
```java
boolean b1 = ConvertUtils.toBoolean("true");
boolean b2 = ConvertUtils.toBoolean("YES");
boolean b3 = ConvertUtils.toBoolean(1, 1, "yes");
Integer i1 = ConvertUtils.toInteger("12.9", 0); // 12
int i2 = ConvertUtils.toInt("yes"); // 1
long l1 = ConvertUtils.toLongValue("true"); // 1
Double d1 = ConvertUtils.toDouble("3.14", 0D); // 3.14
```
默认情况下,以下文本会被识别为真值:`ok`、`y`、`yes`、`t`、`true`、`1`,且忽略大小写。
转换方法的失败行为不同:
- 带 `defaultValue` 的包装类型方法通常返回默认值。
- 不带默认值的部分方法可能返回 `null`、`0`,或抛出 `NumberFormatException`。
- 调用外部输入转换前,建议使用带默认值的重载并对业务范围进行校验。
### 5.3 字节和二进制
```java
byte[] bytes = {1, 2, 3, 4};
int value = ConvertUtils.toInt(bytes); // 0x01020304
int unsigned = ConvertUtils.toInt((byte) -1); // 255
String binaryByte = ConvertUtils.toBinaryString((byte) 5); // 00000101
String binaryInt = ConvertUtils.toBinaryString(5); // 32 位
String binaryLong = ConvertUtils.toBinaryString(5L); // 64 位
```
`toInt(byte[])` 按 4 个字节、无符号、大端序拼接;传入数组应至少包含 4 个元素。
### 5.4 日期对象转换
```java
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.Date;
Date date1 = ConvertUtils.toDate("2024-01-02");
Date date2 = ConvertUtils.toDate("2024-01-02 12:30:45");
Date date3 = ConvertUtils.toDate(LocalDate.of(2024, 1, 2));
Date date4 = ConvertUtils.toDate(LocalDateTime.now());
Date date5 = ConvertUtils.toDate(null, new Date());
```
同时支持 `Date`、`Calendar`、`LocalDate`、`LocalDateTime`、10 位秒级时间戳、13 位毫秒级时间戳和若干常见日期字符串格式。
## 6. 字符串、数组和集合
### 6.1 `StringUtils`
`top.wys.utils.StringUtils` 提供空值判断、大小写、截取、路径和模板处理等方法:
```java
import top.wys.utils.StringUtils;
StringUtils.isEmpty(null); // true
StringUtils.isEmpty("null"); // true
StringUtils.isBlank(" \t\n"); // true
StringUtils.isNotBlank(" text "); // true
StringUtils.substring("abcdef", -2); // "ef"
StringUtils.substring("abcdef", 1, 4); // "bcd"
StringUtils.substringBefore("a/b/c", "/");// "a"
StringUtils.substringAfterLast("a/b/c", "/");// "c"
StringUtils.substringBetween("a[body]b", "[", "]");// "body"
StringUtils.upperFirstLetter("hello"); // Hello
StringUtils.lowerFirstLetter("Hello"); // hello
StringUtils.reverse("abc"); // cba
StringUtils.cleanValue("\"value\""); // value
StringUtils.lenientFormat("name=%s, age=%s", "Tom", 18);
```
常用路径和集合字符串方法:
```java
String normalized = StringUtils.cleanPath("a/../b/./c");
boolean same = StringUtils.pathEquals("a/./b", "a/b");
String csv = StringUtils.collectionToDelimitedString(
java.util.Arrays.asList("a", "b", "c"), ",");
String[] parts = StringUtils.delimitedListToStringArray("a,b,c", ",");
```
> `StringUtils.isEmpty(Object)` 会把 `null`、空字符串以及字符串 `"null"` 判定为空;如果只需要判断空字符串和 `null`,请使用 `hasLength` 或标准 Java 判断。
### 6.2 数组、集合和 Map
```java
import top.wys.utils.Objects;
import top.wys.utils.collection.ArrayUtils;
import top.wys.utils.collection.Collections;
import top.wys.utils.collection.Maps;
ArrayUtils.isEmpty((String[]) null); // true
ArrayUtils.getLength(new int[]{1, 2, 3}); // 3
ArrayUtils.deepEquals0(
new int[]{1, 2}, new int[]{1, 2}); // true
Collections.isEmpty(java.util.Collections.emptyList());
Collections.getFirst(java.util.Arrays.asList("a", "b"));
Collections.getLast(java.util.Arrays.asList("a", "b"));
Collections.findFirst(list, item -> item.startsWith("a"));
java.util.HashMap map = Maps.newHashMapWithExpectedSize(100);
java.util.LinkedHashMap ordered = Maps.newLinkedHashMap();
java.util.concurrent.ConcurrentMap concurrent = Maps.newConcurrentMap();
Objects.isEmpty(java.util.Optional.empty());
Objects.unwrapOptional(java.util.Optional.of("value"));
Objects.deepEquals(new int[]{1, 2}, new int[]{1, 2});
```
`Maps` 还提供 `TreeMap`、`EnumMap`、`IdentityHashMap`、`ConcurrentMap` 等工厂方法;`Collections` 提供集合删除、查找、转换、计数和类型查找方法。
## 7. 数字和日期时间
### 7.1 `NumberUtils`
```java
import top.wys.utils.NumberUtils;
NumberUtils.isPositiveInteger("123");
NumberUtils.isPositiveDecimal("12.30");
NumberUtils.isCoinAmount("12.30");
String truncated = NumberUtils.cutByPoint("12.3456", 2); // 12.34
String roundedUp = NumberUtils.cutByPointUp("12.341", 2); // 12.35
String sum = NumberUtils.add("1.20", "2.30", 2); // 3.5
String total = NumberUtils.add(2, "1.20", "2.30", "3"); // 6.5
String difference = NumberUtils.subtract("10", "3.2", 2); // 6.8
String rmb = NumberUtils.amount2rmb("1234.56");
```
`NumberUtils` 还包含十六进制转换、大小比较、平均值、精度获取、四舍五入和中文金额转换等方法。金额计算优先使用 `BigDecimal` 语义,不建议使用 `double` 直接进行金融计算。
### 7.2 `DateUtils`
```java
import top.wys.utils.DateUtils;
String now = DateUtils.getNowDateTime();
String today = DateUtils.getDateString(new java.util.Date());
String formatted = DateUtils.getStringByPattern(
new java.util.Date(), "yyyy/MM/dd HH:mm:ss");
java.util.Date tomorrow = DateUtils.getNextDay();
java.util.Date nextWeek = DateUtils.getNextDate(new java.util.Date(), 7);
java.util.Date dayBegin = DateUtils.getBeginOfDay();
java.util.Date dayEnd = DateUtils.getEndOfDay();
long hours = DateUtils.getTimeGapsInHours(start, end);
long minutes = DateUtils.getTimeGapsInMinutes(start, end);
String elapsed = DateUtils.pastTimes(90_500L); // 1分钟500毫秒的中文描述
java.time.LocalDateTime local = DateUtils.toLocalDateTime(new java.util.Date());
java.util.Date date = DateUtils.toDate(local);
```
常用常量:
- `DateUtils.DATE_PATTERN`:`yyyy-MM-dd`
- `DateUtils.DATE_TIME_PATTERN`:`yyyy-MM-dd HH:mm:ss`
- `DateUtils.ONE_SECOND`、`ONE_MINUTE`、`ONE_HOUR`、`ONE_DAY`
注意:`ONE_MONTH` 按 30 天计算,`ONE_YEAR` 按 365 天计算;涉及自然月、闰年或夏令时的业务应使用 `Calendar` 或 `java.time` 的日历运算方法。
## 8. 文件、目录和 IO
### 8.1 文件读写
```java
import top.wys.utils.FileUtils;
java.io.File file = new java.io.File("target/data/example.txt");
FileUtils.createFile(file);
FileUtils.writeFile(file.getPath(), "第一行\n", false);
FileUtils.writeFile(file.getPath(), "第二行\n", true);
String content = FileUtils.readTxtFile(file, "UTF-8");
long size = FileUtils.getFileSize(file);
String readableSize = FileUtils.getSize(size);
String contentType = FileUtils.getContentType(file);
```
常用能力包括:
- `createFile`、`makeDir`:创建文件和父目录。
- `writeFile`、`readTxtFile`:文本写入和读取。
- `nioTransferCopyFile`:使用 NIO 通道复制文件。
- `deleteDir`:递归删除文件或目录。
- `fileToZip`:将目录内容压缩成 ZIP。
- `cutFile`、`cutFileByLine`:按字节或行切割大文件。
- `getFileNameFromUrl`、`getFileNameFromHttp`:获取下载文件名。
- `getFilename`、`getFilenameExtension`、`stripFilenameExtension`:处理路径文件名。
- `cleanPath`、`applyRelativePath`:规范化和拼接资源路径。
读取流时建议使用 try-with-resources:
```java
try (java.io.InputStream input = FileUtils.class
.getResourceAsStream("/example.txt")) {
String text = top.wys.utils.IOUtils.is2String(input);
}
```
### 8.2 `IOUtils`
```java
import top.wys.utils.IOUtils;
byte[] bytes = IOUtils.isToBytes(inputStream);
String text = IOUtils.is2String(inputStream); // 默认 UTF-8
String text2 = IOUtils.is2String(inputStream, "GBK");
IOUtils.flush(writer);
IOUtils.close(inputStream, reader, writer);
```
`IOUtils.close` 会捕获关闭过程中的异常并记录日志;如果业务需要感知关闭失败,请直接使用 Java 流 API。
### 8.3 资源和文件类型
```java
import top.wys.utils.io.FileType;
import top.wys.utils.io.ResourceUtils;
java.net.URL url = ResourceUtils.getURL("classpath:sysconfig.properties");
java.io.File file = ResourceUtils.getFile("file:/tmp/example.txt");
boolean isJar = ResourceUtils.isJarURL(url);
String suffix = FileType.getSuffixByMimeType("image/png");
String mime = FileType.getMimeTypeBySuffix("png");
```
`ResourceUtils` 支持普通文件路径、`file:`、`classpath:` 以及 JAR/WAR 资源判断。JAR 内资源不一定能转换为可写的本地 `File`,需要按 URL 或流读取。
## 9. JSON 转换
项目同时提供 Gson 和 Fastjson 两套封装。
### 9.1 Gson:`GsonTools`
```java
import top.wys.utils.GsonTools;
public class User {
private String name;
private int age;
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public int getAge() { return age; }
public void setAge(int age) { this.age = age; }
}
User user = new User();
user.setName("Tom");
user.setAge(18);
String json = GsonTools.createJsonString(user);
User parsed = GsonTools.getBeanFromJson(json, User.class);
java.util.List users = GsonTools.getList("[{\"name\":\"Tom\",\"age\":18}]", User.class);
java.util.Map map = GsonTools.getMapFromJson(json);
```
常用方法:
- `createJsonString`:对象转 JSON。
- `getBeanFromJson`:JSON 转对象,支持 `Class` 和 `TypeToken`。
- `getList`、`getListFromJson`:JSON 转对象列表。
- `getStrings`:JSON 转字符串列表。
- `getMaps`、`getMapFromJson`:JSON 转 Map 集合。
### 9.2 Fastjson:`FastJsonTools`
```java
import com.alibaba.fastjson.TypeReference;
import top.wys.utils.FastJsonTools;
String json = FastJsonTools.createJsonString(user);
User parsed = FastJsonTools.createJsonBean(json, User.class);
java.util.List list = FastJsonTools.createJsonToListBean(jsonArray, User.class);
java.util.Map map = FastJsonTools.createJsonToMap(json);
java.util.Map beanMap = FastJsonTools.createBeanToMap(user);
java.util.List complex = FastJsonTools.createJsonBean(
jsonArray,
new TypeReference>() {}
);
```
`createJsonToMap(json, true)` 和 `createBeanToMap(bean, true)` 会尝试移除空值字段。解析不可信 JSON 前应根据业务限制输入大小和对象结构。
## 10. HTTP、Cookie 和资源
### 10.1 GET 请求
```java
import top.wys.utils.HttpUtils;
String body = HttpUtils.get("https://example.com/api");
java.util.Map params = new java.util.HashMap<>();
params.put("keyword", "Java 工具");
params.put("page", 1);
String result = HttpUtils.get("https://example.com/search", params);
```
获取完整响应时应关闭响应对象:
```java
try (okhttp3.Response response = HttpUtils.getResponse("https://example.com")) {
if (!response.isSuccessful()) {
throw new java.io.IOException("HTTP status: " + response.code());
}
String body = response.body() == null ? "" : response.body().string();
}
```
GET 参数使用 `HttpUtils.getRequestParamString` 生成,参数值会进行 URL 编码;数组和 `List` 会展开为多个同名参数。
### 10.2 HTTP 客户端、代理和 Cookie
```java
HttpUtils.setHttpProxy("127.0.0.1", 7890);
HttpUtils.setCookieJar(new top.wys.utils.http.CookieJarImpl());
okhttp3.OkHttpClient client = HttpUtils.getOkHttpClient();
```
相关类:
- `CookieJarImpl`:OkHttp CookieJar 实现。
- `InMemoryCookieStore`:内存 Cookie 存储。
- `Cookies`:从 Servlet 请求中读取 Cookie。
- `UploadInfo`:描述 multipart 上传文件。
- `HttpCallBack`:异步 HTTP 回调接口。
- `SSLSocketClient`:SSL 客户端辅助类。
### 10.3 HTTPS 安全说明
当前 `HttpUtils` 的默认配置会启用忽略 SSL 证书校验的逻辑,适合测试环境,不适合生产环境。生产环境请使用严格证书校验的 `OkHttpClient`,不要因为请求失败而全局关闭 hostname 或证书验证。
`ignoreSNI()` 会修改 JVM 全局系统属性 `jsse.enableSNIExtension`,应谨慎使用;`fakeIp` 只是设置请求头,不会改变真实网络来源。
## 11. CSV 文件
核心类:`top.wys.utils.CSVUtils`。
### 11.1 写入 CSV
```java
import top.wys.utils.CSVUtils;
java.io.File output = new java.io.File("target/users.csv");
java.util.List> rows = java.util.Arrays.asList(
java.util.Arrays.asList("Tom", "18"),
java.util.Arrays.asList("Lucy", "20")
);
CSVUtils.write(output, rows, "name", "age");
```
### 11.2 读取 CSV
```java
try (org.apache.commons.csv.CSVParser parser = CSVUtils.getCSVParser(output)) {
for (org.apache.commons.csv.CSVRecord record : parser) {
String name = record.get("name");
String age = record.get("age");
System.out.println(name + ": " + age);
}
}
```
默认格式 `CSVUtils.DEFAULT_CSVFORMAT`:
- 使用 Oracle 风格 CSV 格式。
- 第一行作为表头。
- 忽略表头大小写。
- 允许缺失列名。
- 忽略空行并自动 trim。
如果输入文件不是 UTF-8,可以使用带 `encoding` 的 `getCSVParser` 或 `getCSVPrinter` 重载。
## 12. 加密、摘要和 Base64
### 12.1 摘要和 Base64
```java
import top.wys.utils.EncryptUtils;
String md5 = EncryptUtils.md5("hello");
String sha256 = EncryptUtils.sha256("hello");
String sha512 = EncryptUtils.sha512("hello");
String encoded = EncryptUtils.base64Encode("hello");
String decoded = EncryptUtils.base64Decode(encoded);
```
摘要是不可逆的完整性校验,不是加密。密码存储不要直接使用单次 MD5,应使用专门的密码哈希算法和随机盐。
文件 Base64:
```java
java.io.File image = new java.io.File("image.png");
String dataUrl = EncryptUtils.base64EncodeWithPrefix(image);
java.io.File restored = EncryptUtils.base64DecodeFile(dataUrl, "target/restored");
```
### 12.2 AES / DES
```java
import top.wys.utils.EncryptUtils;
EncryptUtils.AES aes = EncryptUtils.AES.newInstance(
"strong-password", EncryptUtils.AES.BIT_256);
String encrypted = aes.encrypt("hello");
String decrypted = aes.decrypt(encrypted);
EncryptUtils.DES des = EncryptUtils.DES.newInstance("password");
String desEncrypted = des.encrypt("hello");
String desDecrypted = des.decrypt(desEncrypted);
```
AES 支持 128、192、256 位密钥;不要把密码硬编码到源码或日志中。`zmlEncode` / `zmlDecode` 是项目自定义的可逆编码方案,解码时必须使用相同的时间参数,不应替代标准密码学算法。
### 12.3 RSA
```java
java.security.KeyPair pair = EncryptUtils.RSA.buildKeyPair();
byte[] encrypted = EncryptUtils.RSA.encrypt(
"hello".getBytes(java.nio.charset.StandardCharsets.UTF_8),
pair.getPublic());
byte[] plain = EncryptUtils.RSA.decrypt(encrypted, pair.getPrivate());
```
RSA 适合加密短数据或加密对称密钥;大文件应使用混合加密。项目中的 RSA 示例使用 1024 位密钥,生产系统建议根据安全策略使用更高强度密钥和明确的填充方案。
## 13. 随机数据和 ID
### 13.1 随机测试数据
```java
import top.wys.utils.RandomUtils;
String ip = RandomUtils.getRandomIp();
String phone = RandomUtils.getRandomTel();
String email = RandomUtils.getRandomEmail(6, 10);
String code = RandomUtils.getRandomCode(6);
String numericCode = RandomUtils.getRandomNumCode(6);
String uuid = RandomUtils.getUUID();
String userAgent = RandomUtils.getRandomUserAgent();
String idCard = RandomUtils.getRandomIdCard(18, 60);
```
这些方法适合测试数据、演示数据和模拟请求,不适合生成密码、Token、支付随机数或其他安全敏感随机值。安全随机数请使用 `SecureRandom`。
### 13.2 Snowflake ID
```java
import top.wys.utils.SnowFlakeIdWorker;
long id1 = SnowFlakeIdWorker.INSTANCE.nextId();
String id2 = SnowFlakeIdWorker.INSTANCE.nextStringId();
SnowFlakeIdWorker worker = new SnowFlakeIdWorker(
1, // workerId,0~31
2, // datacenterId,0~31
0 // 初始序列
);
long id3 = worker.nextId();
```
ID 结构为:
- 1 位符号位。
- 41 位毫秒时间戳差值。
- 5 位数据中心 ID。
- 5 位工作机器 ID。
- 12 位毫秒内序列号。
部署多个实例时,应确保 `workerId` 和 `datacenterId` 的组合不会重复;系统时钟回拨时,`nextId()` 会拒绝生成 ID 并抛出异常。
## 14. 反射、校验和系统信息
### 14.1 反射
```java
import top.wys.utils.ReflectionUtils;
java.lang.reflect.Field field = ReflectionUtils.getField(User.class, "name");
java.util.List fields =
ReflectionUtils.getFields(User.class, true);
Object value = ReflectionUtils.methodInvoke(user, "getName");
```
`ReflectionUtils` 还提供字段名、父类字段、Getter/Setter、方法查找和 Bean 属性处理方法。反射调用可能抛出 `IllegalAccessException`、`InvocationTargetException` 或 `NoSuchMethodException`,应在边界层处理。
`UnsafeUtils` 直接封装 `sun.misc.Unsafe`,属于 JDK 内部 API,除非明确了解内存偏移、对象布局和模块限制,否则不要在业务代码中使用。
### 14.2 参数校验
```java
import top.wys.utils.Assert;
import top.wys.utils.valid.Preconditions;
Assert.notNull(user, "user 不能为空");
Assert.hasText(user.getName(), "name 不能为空");
Assert.isTrue(user.getAge() >= 0, "age 不能小于 0");
Preconditions.checkArgument(user.getAge() < 150, "age=%s 非法", user.getAge());
```
`Assert` 失败时通常抛出 `IllegalArgumentException`;`Preconditions` 提供更接近 Guava 风格的参数、状态和非空检查。
### 14.3 系统信息
```java
import top.wys.utils.Systems;
String os = Systems.osName();
String host = Systems.HOST_NAME;
String arch = Systems.OS_ARCH;
long pid = Systems.getPid();
String tmp = Systems.tmpDirName();
String dump = Systems.threadDump();
String value = Systems.getProperty("app.name", "default-app");
long size = Systems.parseSize("cache.size", "128m");
long timeout = Systems.parseDuration("request.timeout", "2s");
```
`Systems` 还提供 JVM 参数、调试器检测、属性文件加载、线程转储、操作系统判断和系统尺寸/时长解析。
### 14.4 端口检测
```java
import top.wys.utils.PortsUtils;
boolean open = PortsUtils.isOpen("127.0.0.1", 8080);
java.util.List ports = PortsUtils.scanPorts(
"127.0.0.1", "8080,8443,9000-9010");
```
端口扫描只应对自己拥有或明确获准测试的主机执行,并控制扫描范围、超时时间和并发数。
## 15. 线程池
```java
import top.wys.utils.thread.ExecutorServiceUtil;
java.util.concurrent.ExecutorService pool =
ExecutorServiceUtil.newExecutorService();
try {
pool.submit(() -> System.out.println("run task"));
} finally {
ExecutorServiceUtil.shutdown(pool);
}
java.util.concurrent.ScheduledExecutorService scheduler =
ExecutorServiceUtil.newSingleScheduledExecutorService();
scheduler.schedule(() -> System.out.println("delayed"),
1, java.util.concurrent.TimeUnit.SECONDS);
ExecutorServiceUtil.execute(() -> System.out.println("default pool"));
boolean slept = ExecutorServiceUtil.sleep(100);
```
线程工具默认创建守护线程。长期运行的服务仍应显式管理线程池生命周期;临时线程池使用完毕后调用 `shutdown` 或直接调用标准 Executor API 关闭。
## 16. 文件监听
文件监听位于 `top.wys.utils.io.monitor`,基于 JDK `WatchService`,支持创建、修改、删除和溢出事件。
### 16.1 基础监听
```java
import top.wys.utils.io.monitor.BaseMonitor;
import top.wys.utils.io.monitor.FileMonitor;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.WatchEvent;
Path directory = Paths.get("/tmp/config");
BaseMonitor watcher = new BaseMonitor() {
@Override
public void onCreate(WatchEvent> event, Path currentPath) {
System.out.println("创建:" + resolve(event, currentPath));
}
@Override
public void onModify(WatchEvent> event, Path currentPath) {
System.out.println("修改:" + resolve(event, currentPath));
}
@Override
public void onDelete(WatchEvent> event, Path currentPath) {
System.out.println("删除:" + resolve(event, currentPath));
}
@Override
public void onOverflow(WatchEvent> event, Path currentPath) {
System.out.println("事件溢出:" + currentPath);
}
private Path resolve(WatchEvent> event, Path currentPath) {
return currentPath.resolve((Path) event.context());
}
};
FileMonitor monitor = FileMonitor.createAll(directory, watcher);
monitor.start(); // 后台线程监听
// 应用退出或不再使用时关闭
Runtime.getRuntime().addShutdownHook(new Thread(monitor::close));
```
`currentPath` 是事件所在的目录,`event.context()` 通常是相对于该目录的 `Path`。处理 `OVERFLOW` 时,context 不保证是 `Path`,不要直接强制转换。
### 16.2 监听单个文件
```java
Path file = Paths.get("/tmp/config/application.yml");
FileMonitor monitor = FileMonitor.create(
file,
FileMonitor.ENTRY_CREATE,
FileMonitor.ENTRY_MODIFY,
FileMonitor.ENTRY_DELETE
).setWatcher(watcher);
monitor.start();
```
监听单个文件时,底层实际监听的是文件所在目录,再根据目标文件过滤事件;目标文件删除后重新创建仍可收到事件。
### 16.3 递归监听目录
```java
FileMonitor monitor = FileMonitor.create(
Paths.get("/tmp/project"),
3,
FileMonitor.EVENTS_ALL
).setWatcher(watcher);
monitor.start();
```
`maxDepth` 含义:
- `maxDepth <= 1`:只监听当前目录。
- `maxDepth = 2`:监听当前目录和直接子目录。
- `maxDepth = 3`:监听当前目录和下两层目录。
监听过程中创建的子目录会按剩余深度自动注册。
### 16.4 合并重复修改事件
一次保存操作可能产生多次 `ENTRY_MODIFY`。可以使用延迟合并:
```java
FileMonitor monitor = FileMonitor.createAll(
Paths.get("/tmp/config/application.yml"),
watcher,
200L
);
monitor.start();
```
上例表示同一个文件连续修改时重新计时,文件停止修改 200 毫秒后只回调一次 `onModify`。创建、删除和溢出事件会立即转发。
也可以显式组合递归监听和 `DelayMonitor`:
```java
import top.wys.utils.io.monitor.impl.DelayMonitor;
FileMonitor monitor = FileMonitor.create(
Paths.get("/tmp/project"),
3,
FileMonitor.EVENTS_ALL
).setWatcher(new DelayMonitor(watcher, 200L));
monitor.start();
```
### 16.5 生命周期
1. `FileMonitor.create(...)` 创建并初始化监听服务。
2. `start()` 启动后台监听线程,不阻塞当前线程。
3. `watch()` 在当前线程中阻塞监听。
4. 使用结束后调用 `close()`,释放 `WatchService`、线程和延迟任务。
5. `MonitorException` 用于包装监听初始化、路径注册和监听过程异常。
## 17. 其他工具
### 17.1 HTML 和 XPath
`JsoupUtils` 在 Jsoup 基础上提供 CSS 内容查找、父节点查找和 XPath 选择:
```java
org.jsoup.nodes.Document document =
org.jsoup.Jsoup.parse("Hello
");
org.jsoup.nodes.Element element =
top.wys.utils.JsoupUtils.findElement(document, "span", "Hello");
org.w3c.dom.NodeList nodes =
top.wys.utils.JsoupUtils.selectXpath(element, ".//text()");
```
### 17.2 图片和验证码
`top.wys.utils.image` 包包含:
- `Captcha`、`SpecCaptcha`、`GifCaptcha`:验证码生成。
- `Encoder`、`GifEncoder`:图片/GIF 编码辅助。
- `Quant`:图片颜色量化。
- `Randoms`:图片模块使用的随机数据。
具体验证码样式和输出方法请以对应类的 Javadoc 为准。
### 17.3 编码检测
```java
String encoding = top.wys.utils.EncodingDetect
.getJavaEncode("target/example.txt");
```
编码检测是启发式判断,对内容过短、混合编码或二进制文件可能不准确;读取重要文件时应优先明确编码。
### 17.4 异常和堆栈
```java
String stack = top.wys.utils.ExceptionUtils.getStackTrace(exception);
Throwable root = top.wys.utils.ExceptionUtils.getRootCause(exception);
```
## 18. 构建与测试
### 18.1 编译和测试
```bash
# 编译并执行 Maven 测试生命周期
mvn test -DskipTests=false
# 默认项目属性 skipTests=true,以下命令只编译测试代码但不运行测试
mvn test
# 打包并安装到本地 Maven 仓库
mvn install -Dmaven.javadoc.skip=true
```
项目的 `pom.xml` 当前默认配置了 `skipTests=true`。CI 或本地验证时建议显式传入 `-DskipTests=false`。
### 18.2 运行指定测试
```bash
mvn test -DskipTests=false \
-Dtest=ConvertUtilsTest,SnowFlakeIdWorkerTest
```
### 18.3 Javadoc
发布配置包含源码包和 Javadoc 包生成。若本机 Maven 无法找到 `javadoc` 命令,请先正确设置 `JAVA_HOME`,或在仅验证编译时使用:
```bash
mvn install -Dmaven.javadoc.skip=true
```
## 19. 使用注意事项
### 19.1 空值与异常
- 不同工具类对无效输入的处理方式不同:返回 `null`、默认值、空集合,或抛出异常。
- 对外部输入优先使用带默认值的转换方法,并在转换后进行范围校验。
- `FileUtils` 的部分历史方法会记录日志或打印堆栈,不要把它们当作事务性文件操作使用。
- `ResourceUtils.getFile` 只能把文件系统资源转换为 `File`;JAR 内资源通常应通过 `URL` 或流读取。
### 19.2 资源释放
- `InputStream`、`Reader`、`CSVParser`、`CSVPrinter`、OkHttp `Response` 使用后必须关闭。
- `FileMonitor`、`DelayMonitor`、`ExecutorService` 使用结束后必须关闭。
- 网络请求不要长时间持有 response body;读取完成后及时释放连接。
### 19.3 安全
- 生产环境不要默认信任所有 HTTPS 证书。
- 不要使用 MD5、SHA-1 作为密码存储算法。
- 不要使用 `RandomUtils` 生成安全 Token 或密码。
- `UnsafeUtils` 只能用于明确了解 JDK 内部实现和内存模型的场景。
- `PortsUtils` 仅用于获得授权的资产检测。
- 解析外部 JSON、HTML、文件时应限制输入大小,必要时设置超时和资源上限。
### 19.4 线程安全
- 大部分工具方法是无状态静态方法,可以直接调用。
- `GsonTools` 使用全局 Gson 实例,替换全局实例会影响整个进程。
- `HttpUtils` 的 Cookie、代理和部分配置是静态共享状态,多个业务模块使用时应统一管理。
- `SnowFlakeIdWorker#nextId` 已同步,但不同实例的机器 ID 仍需由部署系统保证唯一。
## 20. 源码和示例
- 源码目录:`src/main/java`
- 测试目录:`src/test/java`
- 测试资源:`src/test/resources`
- Maven 配置:`pom.xml`
- 文件监听测试:`src/test/java/top/wys/utils/io/monitor/FileMonitorTest.java`
如果某个方法的 README 示例与当前版本 Javadoc 不一致,应以当前源码签名和测试用例为准,并欢迎提交修正文档或示例。