通用工具包(Commons)
常用的工具类库封装,是在开发 YMP 框架过程中积累下来的一些非常实用的辅助工具,其中主要涉及 HttpClient 请求包装器、JSON 包装器、文件及资源管理、数据导入与导出、视频图片处理、二维码、序列化、类、日期时间、数学、经纬度、字符串加解密、运行时环境、网络、线程操作等。
Maven包依赖
<dependency>
<groupId>net.ymate.platform</groupId>
<artifactId>ymate-platform-commons</artifactId>
<version>2.1.4-dev</version>
</dependency>
基础类型(Lang)
提供了针对任意对象之间的类型转换、组合和无限层级树型结构的支持。
模糊对象(BlurObject)
BlurObject 是一个用于任意类型对象之间转换的工具类,基本涵盖日常使用的数据类型。通 过 IConverter 接口可以实现自定义转换器,并支持手动注册或 SPI 机制自动注册。
核心特性
- 类型安全转换:支持各种基本数据类型、包装类型、集合类型之间的自动转换
- 智能类型推断:自动识别源类型并进行相应的转换处理
- 可扩展性:通过
IConverter接口支持自定义类型转换 - SPI 自动注册:支持通过 SPI 机制自动发现和注册转换器
创建 BlurObject 实例
// 使用静态 bind 方法创建
BlurObject blurObject = BlurObject.bind("123.4");
// 使用构造方法创建
BlurObject blurObject = new BlurObject(123);
// 绑定 null 值
BlurObject blurObject = BlurObject.bind(null);
基本数据类型转换
BlurObject 提供了丰富的方法用于基本数据类型转换:
| 方法 | 返回类型 | 说明 |
|---|---|---|
toBoolean() / toBooleanValue() | Boolean / boolean | 转换为布尔值 |
toInteger() / toIntValue() | Integer / int | 转换为整数 |
toLong() / toLongValue() | Long / long | 转换为长整型 |
toFloat() / toFloatValue() | Float / float | 转换为浮点数 |
toDouble() / toDoubleValue() | Double / double | 转换为双精度浮点数 |
toShort() / toShortValue() | Short / short | 转换为短整型 |
toByte() / toByteValue() | Byte / byte | 转换为字节 |
toStringValue() | String | 转换为字符串 |
toCharValue() | char | 转换为字符 |
示例: 基本数据类型转换
Object targetObj = "123.4";
BlurObject blurObject = BlurObject.bind(targetObj);
// 转换为整数(自动解析字符串)
int intValue = blurObject.toIntValue(); // 123
// 转换为双精度浮点数
double doubleValue = blurObject.toDoubleValue(); // 123.4
// 转换为浮点数
float floatValue = blurObject.toFloatValue(); // 123.4f
// 转换为字符串
String stringValue = blurObject.toStringValue(); // "123.4"
// 转换为布尔值
BlurObject boolObj = BlurObject.bind("true");
boolean boolValue = boolObj.toBooleanValue(); // true
集合类型转换
BlurObject 支持将对象转换为各种集合类型:
| 方法 | 返回类型 | 说明 |
|---|---|---|
toMapValue() | Map<?, ?> | 转换为 Map |
toListValue() | List<?> | 转换为 List |
toSetValue() | Set<?> | 转换为 Set |
示例: 集合类型转换
// Map 转换
Map<String, Object> map = new HashMap<>();
map.put("key1", "value1");
map.put("key2", 123);
BlurObject blurObject = BlurObject.bind(map);
Map<?, ?> resultMap = blurObject.toMapValue();
// List 转换
List<String> list = Arrays.asList("a", "b", "c");
BlurObject blurObject = BlurObject.bind(list);
List<?> resultList = blurObject.toListValue();
// 非集合类型会自动包装为单元素集合
BlurObject singleObj = BlurObject.bind("single");
List<?> singleList = singleObj.toListValue(); // ["single"]
字节数组转换
BlurObject 支持字节数组和包装类型的转换:
| 方法 | 返回类型 | 说明 |
|---|---|---|
toBytes() | Byte[] | 转换为 Byte 数组(包装类型) |
toBytesValue() | byte[] | 转换为 byte 数组(基本类型) |
示例: 字节数组转换
// byte[] 转换
byte[] bytes = {1, 2, 3, 4, 5};
BlurObject blurObject = BlurObject.bind(bytes);
byte[] result = blurObject.toBytesValue();
// Byte[] 转换
Byte[] byteObjects = {1, 2, 3, 4, 5};
BlurObject blurObject = BlurObject.bind(byteObjects);
byte[] result = blurObject.toBytesValue();
指定类型转换
使用 toObjectValue(Class<?> clazz) 方法可以将对象转换为指定类型:
BlurObject blurObject = BlurObject.bind("123");
// 转换为指定类型
Integer intObj = (Integer) blurObject.toObjectValue(Integer.class);
Double doubleObj = (Double) blurObject.toObjectValue(Double.class);
String strObj = (String) blurObject.toObjectValue(String.class);
自定义类型转换器
通过实现 IConverter 接口并配合 @Converter 注解,可以创建自定义类型转换器。
示例: 自定义类型转换器的注册与使用
// 自定义类型转换器,通过注解明确从...到...类型之间的转换
// 此类可以通过 SPI 方式被自动注册
@Converter(from = {java.util.Date.class}, to = java.sql.Date.class)
public class DateConverter implements IConverter<java.sql.Date> {
@Override
public java.sql.Date convert(Object target) {
return new java.sql.Date(((java.util.Date) target).getTime());
}
}
// 手动注册转换器
BlurObject.registerConverter(java.util.Date.class, java.sql.Date.class, new DateConverter());
// 执行转换
java.util.Date date = new java.util.Date();
java.sql.Date sqlDate = BlurObject.bind(date).toObjectValue(java.sql.Date.class);
SPI 自动注册
转换器可以通过 SPI 机制自动注册,只需在 META-INF/services/net.ymate.platform.commons.lang.IConverter 文件中添加转换器类的全限定名:
com.example.converter.DateConverter
com.example.converter.TimestampConverter
特殊类型处理
BlurObject 对一些特殊类型提供了专门的处理:
| 源类型 | 处理方式 |
|---|---|
Clob | 读取字符流并转换为字符串 |
Blob | 读取二进制流并转换为字节数组 |
Date / Calendar | 转换为时间戳(Long) |
LocalDate / LocalDateTime / ZonedDateTime | 转换为时间戳(Long) |
| 枚举类型 | 通过名称匹配进行转换 |
Object[] | 使用 | 连接为字符串 |
示例: 日期时间转换
// Date 转换为 Long(时间戳)
java.util.Date date = new java.util.Date();
BlurObject blurObject = BlurObject.bind(date);
long timestamp = blurObject.toLongValue();
// LocalDateTime 转换为 Long
LocalDateTime localDateTime = LocalDateTime.now();
BlurObject blurObject = BlurObject.bind(localDateTime);
long timestamp = blurObject.toLongValue();
最佳实践
-
优先使用带默认值的方法:如
toIntValue()、toBooleanValue()等,避免处理 null 值时的空指针异常 -
合理使用类型转换:了解各种类型之间的转换规则,避免精度丢失或数据截断
-
自定义转换器:对于复杂的类型转换需求,建议实现
IConverter接口进行封装 -
SPI 自动注册:对于通用的转换器,使用 SPI 机制自动注册,减少手动配置
结对对象(PairObject)
PairObject<K, V> 是一个通用的键值对容器类,用于将任意两种类型的对象以 <K, V> 的形式组合在一起。它实现了 Serializable 接口,支持序列化操作。
核心特性
- 泛型支持:支持任意类型的键和值组合
- 链式调用:setter 方法支持链式调用,方便构建对象
- 空值检查:提供便捷的空值检查方法
- 序列化支持:实现
Serializable接口,支持对象序列化
创建 PairObject 实例
PairObject 提供了多种创建实例的方式:
| 方法 | 说明 |
|---|---|
PairObject() | 默认构造方法,创建空的结对对象 |
PairObject(K key) | 使用键创建结对对象,值为 null |
PairObject(K key, V value) | 使用键和值创建结对对象 |
PairObject.bind(K key) | 静态方法,使用键创建结对对象 |
PairObject.bind(K key, V value) | 静态方法,使用键和值创建结对对象 |
示例: 创建结对对象
// 使用静态 bind 方法创建(推荐)
PairObject<String, Integer> pairObject1 = PairObject.bind("suninformation", 18);
// 使用构造方法创建
PairObject<String, Integer> pairObject2 = new PairObject<>("suninformation", 18);
// 只设置键,值为 null
PairObject<String, Integer> pairObject3 = PairObject.bind("key");
// 使用默认构造方法,然后通过 setter 设置
PairObject<String, Integer> pairObject4 = new PairObject<>();
pairObject4.setKey("suninformation").setValue(18);
常用方法
| 方法 | 返回类型 | 说明 |
|---|---|---|
getKey() | K | 获取键 |
setKey(K key) | PairObject<K, V> | 设置键,支持链式调用 |
getValue() | V | 获取值 |
setValue(V value) | PairObject<K, V> | 设置值,支持链式调用 |
isEmpty() | boolean | 检查键和值是否都为 null |
isAnyEmpty() | boolean | 检查键 或值是否为 null |
使用示例
示例一: 基本类型结对
String name = "suninformation";
int age = 18;
PairObject<String, Integer> pairObject = PairObject.bind(name, age);
// 获取键和值
String key = pairObject.getKey(); // "suninformation"
Integer value = pairObject.getValue(); // 18
// 检查是否为空
boolean empty = pairObject.isEmpty(); // false
boolean anyEmpty = pairObject.isAnyEmpty(); // false
示例二: 复杂类型结对
// 使用集合类型作为键和值
List<String> keyList = new ArrayList<>();
keyList.add("item1");
keyList.add("item2");
Map<String, String> valueMap = new HashMap<>();
valueMap.put("key1", "value1");
valueMap.put("key2", "value2");
PairObject<List<String>, Map<String, String>> pairObject =
new PairObject<>(keyList, valueMap);
// 获取键和值
List<String> keys = pairObject.getKey();
Map<String, String> values = pairObject.getValue();
示例三: 链式调用
PairObject<String, Integer> pairObject = new PairObject<>()
.setKey("age")
.setValue(25);
// 修改值
pairObject.setValue(26);
示例四: 空值检查
// 创建空的结对对象
PairObject<String, Integer> emptyPair = new PairObject<>();
// 检查是否为空
boolean isEmpty = emptyPair.isEmpty(); // true
boolean isAnyEmpty = emptyPair.isAnyEmpty(); // true
// 创建只有键的结对对象
PairObject<String, Integer> keyOnlyPair = PairObject.bind("key");
boolean isEmpty2 = keyOnlyPair.isEmpty(); // false(键不为null)
boolean isAnyEmpty2 = keyOnlyPair.isAnyEmpty(); // true(值为null)
示例五: 在集合中使用
// 创建结对对象列表
List<PairObject<String, Integer>> pairList = new ArrayList<>();
pairList.add(PairObject.bind("Alice", 25));
pairList.add(PairObject.bind("Bob", 30));
pairList.add(PairObject.bind("Charlie", 35));
// 遍历并输出
for (PairObject<String, Integer> pair : pairList) {
System.out.println(pair.getKey() + ": " + pair.getValue());
}
// 使用 Map 存储结对对象
Map<String, PairObject<String, Integer>> pairMap = new HashMap<>();
pairMap.put("user1", PairObject.bind("Alice", 25));
pairMap.put("user2", PairObject.bind("Bob", 30));
最佳实践
-
优先使用静态 bind 方法:代码更简洁,意图更明确
-
使用泛型确保类型安全:在创建
PairObject时明确指定键和值的类型 -
合理使用空值检查:在获取值之前使用
isEmpty()或isAnyEmpty()进行检查 -
链式调用简化代码:在需要连续设置多个属性时使用链式调用
-
作为方法返回值:当方法需要返回两个相关联的值时,可以使用
PairObject作为返回类型
// 作为方法返回值
public PairObject<String, Integer> getUserInfo() {
String name = getUserName();
int age = getUserAge();
return PairObject.bind(name, age);
}
树型对象(TreeObject)
使用级联方式存储各种数据类型,不限层级深度。
TreeObject 是一个功能强大的树形数据结构,支持以下特性:
- 支持三种存储模式:值模式、映射模式和数组集合模式
- 支持多种数据类型,包括基本类型、字符串、数组、集合等
- 支持 JSON 和 XML 的序列化与反序列化
- 提供丰富的类型转换和取值方法
- 支持通过默认值避免空指针异常
存储模式
TreeObject 支持三种存储模式:
| 常量 | 值 | 说明 |
|---|---|---|
| MODE_VALUE | 1 | 值模式,用于存储单个值 |
| MODE_MAP | 2 | 映射模式,用于存储键值对 |
| MODE_ARRAY | 3 | 数组集合模式,用于存储有序列表 |
数据类型常量
TreeObject 支持以下数据类型常量:
| 常量 | 值 | 说明 |
|---|---|---|
| TYPE_NULL | 0 | NULL 类型 |
| TYPE_INTEGER | 1 | Integer 类型 |
| TYPE_MIX_STRING | 2 | 混合 String 类型(通过 Base64 编码的字符串) |
| TYPE_STRING | 3 | String 类型 |
| TYPE_LONG | 4 | Long 类型 |
| TYPE_TIME | 5 | Time 类型(UTC 时间) |
| TYPE_BOOLEAN | 6 | Boolean 类型 |
| TYPE_FLOAT | 7 | Float 类型 |
| TYPE_DOUBLE | 8 | Double 类型 |
| TYPE_MAP | 9 | Map<String, ? extends Object> 类型 |
| TYPE_COLLECTION | 10 | Collection<? extends Object> 类型 |
| TYPE_BYTE | 11 | Byte 类型 |
| TYPE_CHAR | 12 | Character 类型 |
| TYPE_SHORT | 13 | Short 类型 |
| TYPE_BYTES | 14 | byte[] 类型 |
| TYPE_OBJECT | 15 | Object 类型 |
| TYPE_UNKNOWN | 99 | 未知类型 |
| TYPE_TREE_OBJECT | 100 | 树对象类型 |
示例: 基本使用
public class TreeObjectTest {
public static void main(String[] args) {
// 创建一个映射模式的 TreeObject
TreeObject treeObject = new TreeObject()
.put("id", UUIDUtils.UUID())
.put("category", new Byte[]{1, 2, 3, 4})
.put("create_time", System.currentTimeMillis(), true)
.put("is_locked", true)
.put("detail", new TreeObject()
.put("real_name", "汉字将被混淆", true)
.put("age", 32));
// 创建一个数组集合模式的 TreeObject
TreeObject list = new TreeObject()
.add("item1")
.add("item2");
// 创建另一个映射模式的 TreeObject
TreeObject map = new TreeObject()
.put("key1", "value1")
.put("key2", "value2");
// 组合多个 TreeObject
TreeObject group = new TreeObject()
.put("ids", list)
.put("maps", map);
treeObject.put("group", group);
// 操作集合
TreeObject ids = group.get("ids");
if (ids.isList()) {
System.out.println("ids: " + ids.getList());
}
// 操作映射
TreeObject maps = group.get("maps");
if (maps.isMap()) {
System.out.println("maps: " + maps.getMap());
}
// 提取被混淆的汉字内容
System.out.println("real_name: " + treeObject.get("detail").getMixString("real_name"));
}
}
执行结果:
ids: [item1, item2]
maps: {key1=value1, key2=value2}
real_name: 汉字将被混淆
示例: 使用类型转换方法
TreeObject treeObject = new TreeObject()
.put("count", 100)
.put("price", 99.99)
.put("enabled", true)
.put("timestamp", System.currentTimeMillis(), true);
int count = treeObject.getInt("count");
double price = treeObject.getDouble("price");
boolean enabled = treeObject.getBoolean("enabled");
long timestamp = treeObject.getTime("timestamp");
System.out.println("Count: " + count);
System.out.println("Price: " + price);
System.out.println("Enabled: " + enabled);
System.out.println("Timestamp: " + timestamp);
示例: 使用默认值避免空指针异常
TreeObject treeObject = new TreeObject()
.put("name", "test");
String name = treeObject.getString("name", "default_name");
int age = treeObject.getInt("age", 18);
boolean active = treeObject.getBoolean("active", false);
System.out.println("Name: " + name); // 输出: test
System.out.println("Age: " + age); // 输出: 18 (默认值)
System.out.println("Active: " + active); // 输出: false (默认值)
示例: 判断数据类型和模式
TreeObject treeObject = new TreeObject()
.put("id", 123)
.put("list", new TreeObject().add("a").add("b"));
TreeObject idNode = treeObject.get("id");
if (idNode.isValue()) {
System.out.println("id is value mode, type: " + idNode.getType());
}
TreeObject listNode = treeObject.get("list");
if (listNode.isList()) {
System.out.println("list is array mode");
}
if (treeObject.isMap()) {
System.out.println("treeObject is map mode");
}
示例: JSON 序列化与反序列化
public class TreeObjectJsonTest {
public static void main(String[] args) {
TreeObject treeObject = new TreeObject()
.put("id", UUIDUtils.UUID())
.put("category", new Byte[]{1, 2, 3, 4})
.put("create_time", System.currentTimeMillis(), true)
.put("is_locked", true)
.put("detail", new TreeObject()
.put("real_name", "汉字将被混淆", true)
.put("age", 32))
.put("group", new TreeObject()
.put("ids", new TreeObject().add("item1").add("item2"))
.put("maps", new TreeObject().put("key1", "value1").put("key2", "value2")));
// 转换为 JSON 字符串
String jsonStr = treeObject.toJson().toString(true, true);
System.out.println("JSON: " + jsonStr);
// 从 JSON 字符串解析为 TreeObject
TreeObject fromJson = TreeObject.fromJson(jsonStr);
System.out.println("ID: " + fromJson.getString("id"));
System.out.println("Real Name: " + fromJson.get("detail").getMixString("real_name"));
}
}
执行结果:
JSON: {
"_c": 9,
"_v": {
"is_locked": {
"_c": 6,
"_v": true
},
"create_time": {
"_c": 5,
"_v": 1634100753548
},
"id": {
"_c": 3,
"_v": "4e6b780192da4f04af9b6b826ea7ead6"
},
"detail": {
"_c": 9,
"_v": {
"real_name": {
"_c": 2,
"_v": "5rGJ5a2X5bCG6KKr5re35reG"
},
"age": {
"_c": 1,
"_v": 32
}
}
},
"category": {
"_c": 14,
"_v": "AQIDBA=="
},
"group": {
"_c": 9,
"_v": {
"maps": {
"_c": 9,
"_v": {
"key1": {
"_c": 3,
"_v": "value1"
},
"key2": {
"_c": 3,
"_v": "value2"
}
}
},
"ids": {
"_c": 10,
"_v": [
{
"_c": 3,
"_v": "item1"
},
{
"_c": 3,
"_v": "item2"
}
]
}
}
}
}
}
ID: 4e6b780192da4f04af9b6b826ea7ead6
Real Name: 汉字将被混淆
示例: XML 序列化与反序列化
public class TreeObjectXmlTest {
public static void main(String[] args) {
TreeObject treeObject = new TreeObject()
.put("id", "12345")
.put("name", "测试用户", true)
.put("age", 25)
.put("active", true)
.put("tags", new TreeObject()
.add("java")
.add("spring")
.add("ymp"));
// 转换为 XML 字符串
String xmlStr = treeObject.toXml();
System.out.println("XML: " + xmlStr);
// 从 XML 字符串解析为 TreeObject
TreeObject fromXml = TreeObject.fromXml(xmlStr);
System.out.println("ID: " + fromXml.getString("id"));
System.out.println("Name: " + fromXml.getMixString("name"));
System.out.println("Age: " + fromXml.getInt("age"));
System.out.println("Active: " + fromXml.getBoolean("active"));
TreeObject tags = fromXml.get("tags");
if (tags.isList()) {
System.out.println("Tags: " + tags.getList());
}
}
}
执行结果:
XML: <?xml version="1.0" encoding="UTF-8"?>
<tree _c="9">
<_v>
<id _c="3">
<_v>12345</_v>
</id>
<name _c="2">
<_v>5rWL6K+V5a6J</_v>
</name>
<age _c="1">
<_v>25</_v>
</age>
<active _c="6">
<_v>true</_v>
</active>
<tags _c="10">
<_v>
<item index="0">
<_v>java</_v>
</item>
<item index="1">
<_v>spring</_v>
</item>
<item index="2">
<_v>ymp</_v>
</item>
</_v>
</tags>
</_v>
</tree>
ID: 12345
Name: 测试用户
Age: 25
Active: true
Tags: [java, spring, ymp]
HttpClient
基于 Apache HttpComponents 组件封装的 HttpClient 请求与处理工具,使用时需在工程中引入以下依赖包:
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpmime</artifactId>
<version>4.5.14</version>
<exclusions>
<!-- YMP 框架已引入更高版本,排除为了避免在产生不必要的问题 -->
<exclusion>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
</exclusion>
<exclusion>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
CloseableHttpClientHelper
HttpClientHelper 类主要应用于早期 YMP 框架版本及扩展模块中,支持自定义安全连接方式,支持 GET、POST 请求方法,简化文件上传与下载的处理逻辑等。
从 2.1.3 开始 HttpClientHelper 标记为不被推荐,请使用 CloseableHttpClientHelper 替换。
示例: 通过 GET 方式发送请求
public void sendGetRequest(String url, String charset) throws Exception {
Header[] headers = {
new BasicHeader("Accept", "text/html"),
new BasicHeader("Accept-Encoding", "gzip, deflate, br"),
new BasicHeader("Accept-Language", "zh-Hans-CN,zh-CN;"),
new BasicHeader("Cache-Control", "no-cache"),
new BasicHeader("Pragma", "no-cache"),
new BasicHeader("Connection", "keep-alive"),
new BasicHeader("User-Agent", "Mozilla/5.0......")
};
try (CloseableHttpClientHelper httpClientHelper = CloseableHttpClientHelper.create();
IHttpResponse response = httpClientHelper.get(url, headers, charset)) {
System.out.println("StatusCode: " + response.getStatusCode());
System.out.println("ContentType: " + response.getContentType());
System.out.println("ContentLength: " + response.getContentLength());
System.out.println("Content: " + response.getContent());
System.out.println("Headers: " + response.getHeaders());
System.out.println("Locale: " + response.getLocale());
System.out.println("ReasonPhrase: " + response.getReasonPhrase());
}
}
示例: 通过 POST 方式发送 application/x-www-form-urlencoded 请求
public void sendPostRequest(String url, String charset, Map<String, String> requestParams) throws Exception {
ContentType contentType = ContentType.create(CloseableHttpClientHelper.CONTENT_TYPE_FORM_URL_ENCODED, charset);
try (CloseableHttpClientHelper httpClientHelper = CloseableHttpClientHelper.create();
IHttpResponse response = httpClientHelper.post(url, contentType, ParamUtils.buildQueryParamStr(requestParams, true, charset))) {
// 判断响应状态码是否为 200
if (response.isSuccess()) {
System.out.printf("Content: %s%n", response.getContent());
} else {
System.out.printf("ReasonPhrase: %s%n", response.getReasonPhrase());
}
}
}
示例: 文件上传
public void uploadFile(String url, File distFile) throws Exception {
try (CloseableHttpClientHelper httpClientHelper = CloseableHttpClientHelper.create();
IHttpResponse response = httpClientHelper.upload(url, "file", distFile)) {
// 判断响应状态码是否为 200
if (response.isSuccess()) {
// ......
} else {
System.out.printf("ReasonPhrase: %s%n", response.getReasonPhrase());
}
}
}
示例: 文件下载
public void downloadFile(String url, File distFile) throws Exception {
try (CloseableHttpClientHelper httpClientHelper = CloseableHttpClientHelper.create()) {
httpClientHelper.download(url, new IFileHandler() {
@Override
public void handle(HttpResponse response, IFileWrapper fileWrapper) throws IOException {
if (fileWrapper != null) {
System.out.println("FileName: " + fileWrapper.getFileName());
System.out.println("ContentType: " + fileWrapper.getContentType());
System.out.println("ContentLength: " + fileWrapper.getContentLength());
// 获取被下载文件对象
fileWrapper.getFile();
// 获取被下载文件输入流
fileWrapper.getInputStream();
// 将被下载文件转移到目标文件
fileWrapper.transferTo(distFile);
// 将被下载文件写入目标文件
fileWrapper.writeTo(distFile);
} else {
System.out.printf("ReasonPhrase: %s%n", response.getStatusLine().getReasonPhrase());
}
}
});
}
}
示例: 自定义安全连接工厂并设置超时时间等配置参数
public void custom(URL certFilePath, String passwordChars) throws Exception {
// 通过证书文件构建安全套接字工厂
SSLConnectionSocketFactory socketFactory = CloseableHttpClientHelper.createConnectionSocketFactory("PKCS12", certFilePath, passwordChars.toCharArray());
try (CloseableHttpClientHelper httpClientHelper = CloseableHttpClientHelper.create(new ICloseableHttpClientConfigurable.Default() {
@Override
protected void doRequestConfig(RequestConfig.Builder builder) {
// 设置自定义代理
HttpHost proxy = new HttpHost("localhost", 8889);
builder.setProxy(proxy);
}
})
.customSSL(socketFactory)
.connectionTimeout(30000)
.requestTimeout(30000)
.socketTimeout(30000)) {
// ......
}
}
CloseableHttpRequestBuilder
此类是在 CloseableHttpClientHelper 类的基础上进行了优化、调整请求构建方式及响应的处理逻辑,除了 GET 和 POST 请求方法之外,还增加了对 PUT、 OPTIONS、 DELETE、 HEAD、 PATCH、 TRACE 等的支持。
从 2.1.3 开始 HttpClientRequestBuilder 标记为不被推荐,请使用 CloseableHttpRequestBuilder 替换。
示例: 构建并发送请求
public void newSendRequest(String url, String charset, Map<String, String> requestParams, URL certFilePath, String passwordChars) throws Exception {
// 此种方式的每次请求都要重新构建HttpClinet实例
try (IHttpResponse httpResponse = CloseableHttpRequestBuilder.create(url)
.socketFactory(CloseableHttpClientHelper.createConnectionSocketFactory("PKCS12", certFilePath, passwordChars.toCharArray()))
.contentType(ContentType.create(CloseableHttpClientHelper.CONTENT_TYPE_FORM_URL_ENCODED, charset))
.connectionTimeout(30000)
.requestTimeout(30000)
.socketTimeout(30000)
.charset(charset)
.addHeaders(new Header[]{
new BasicHeader("Accept", "text/html"),
new BasicHeader("Accept-Encoding", "gzip, deflate, br"),
new BasicHeader("Accept-Language", "zh-Hans-CN,zh-CN;"),
new BasicHeader("Cache-Control", "no-cache"),
new BasicHeader("Pragma", "no-cache"),
new BasicHeader("Connection", "keep-alive"),
new BasicHeader("User-Agent", "Mozilla/5.0......")
}).addParams(requestParams).build().post()) {
if (httpResponse.isSuccess()) {
// ......
} else {
System.out.printf("ResponseBody: %s%n", httpResponse);
}
}
}
示例: 通过 CloseableHttpClientHelper 实例构建并发送请求
public void newSendRequest(CloseableHttpClientHelper httpClientHelper, String url, String charset, Map<String, String> requestParams) throws Exception {
// 此种方式的每次请求都会复用HttpClient实例
try (IHttpResponse response = httpClientHelper.newRequestBuilder(url)
.charset(charset)
.contentType(ContentType.create(CloseableHttpClientHelper.CONTENT_TYPE_FORM_URL_ENCODED, charset))
.addHeaders(headers)
.addParams(requestParams)
.build()
.post()) {
if (response.isSuccess()) {
// ......
} else {
System.out.printf("ReasonPhrase: %s%n", response.getReasonPhrase());
}
}
}
示例: 新文件上传
public void newUploadFile(String url, File distFile) throws Exception {
try (IHttpResponse response = CloseableHttpRequestBuilder.create(url)
.addContent("file", distFile).build().post()) {
if (response.isSuccess()) {
// ......
} else {
System.out.printf("ResponseBody: %s%n", response);
}
}
}
示例: 新文件下载
public void newDownloadFile(String url, File distFile) throws Exception {
try (CloseableHttpClientHelper newHttpClientHelper = CloseableHttpClientHelper.create()
.customSSL(CloseableHttpClientHelper.createConnectionSocketFactory(SSLContexts.custom()
.setProtocol("TLSv1.3")
.build()));
IHttpResponse response = newHttpClientHelper.newRequestBuilder(url)
.download(true)
.build()
.get()) {
if (response.isSuccess()) {
IFileWrapper fileWrapper = response.getFileWrapper();
//
System.out.println("FileName: " + fileWrapper.getFileName());
System.out.println("ContentType: " + fileWrapper.getContentType());
System.out.println("ContentLength: " + fileWrapper.getContentLength());
// 获取被下载文件对象
fileWrapper.getFile();
// 获取被下载文件输入流
fileWrapper.getInputStream();
// 将被下载文件写入目标文件
fileWrapper.writeTo(distFile);
}
}
}
JsonWrapper
JSON 包装器,为了让不同的第三方 JSON 解析器拥有统一的 API 接口调用方式并能够做到灵活切换而不影响业务系统的正常运行而提供的一套完整的包装层实现。
设计目的
- 实现 JSON 操作的统一抽象,降低对具体 JSON 库的依赖
- 提高代码的可移植性和扩展性
- 支持在不同 JSON 库间无缝切换
- 提供丰富的 JSON 操作功能,满足各种业务场景需求
支持的 JSON 库
JsonWrapper 现已对当前比较流行且使用非常广泛的 FastJson、Gson 和 Jackson 等进行了封装与适配,同时也支持通过 SPI 或 JVM 启动参数的形式配置基于 IJsonAdapter 接口的自定义实现类。
依赖配置
-
FastJson
- 适配器类:net.ymate.platform.commons.json.impl.FastJsonAdapter
- 依赖配置:
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>2.0.60</version>
</dependency> -
Gson
- 适配器类:net.ymate.platform.commons.json.impl.GsonAdapter
- 依赖配置:
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.13.1</version>
</dependency> -
Jackson
- 适配器类:net.ymate.platform.commons.json.impl.JacksonAdapter
- 依赖配置:
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jdk8</artifactId>
<version>2.18.2</version>
</dependency>
加载逻辑
JSON 包装器是由 JsonWrapper 类进行统一维护和管理,当其被首次加载时,会按照以下顺序尝试实例化对应的包装器类:
- 首先检查 JVM 启动参数
ymp.jsonAdapterClass是否指定了自定义适配器 - 若未指定,则通过 SPI 机制查找
IJsonAdapterFactory实现类 - 若 SPI 未找到,则使用默认的
DefaultJsonAdapterFactory - 工厂类会按照 FastJson → Gson → Jackson 的顺序尝试加载适配器
- 加载成功则停止加载流程并返回,否则继续尝试直至未加载到任何结果为止
示例: 通过 JVM 启动参数指定 Gson 做为默认 JSON 包装器
java -jar xxxx.jar -Dymp.jsonAdapterClass=net.ymate.platform.commons.json.impl.GsonAdapter
核心功能
创建 JSON 对象和数组
// 创建空的 JSON 对象
IJsonObjectWrapper jsonObject = JsonWrapper.createJsonObject();
// 创建有序的 JSON 对象
IJsonObjectWrapper orderedJsonObject = JsonWrapper.createJsonObject(true);
// 创建指定初始容量的 JSON 对象
IJsonObjectWrapper jsonObjectWithCapacity = JsonWrapper.createJsonObject(16);
// 从 Map 创建 JSON 对象
Map<String, Object> map = new HashMap<>();
map.put("name", "test");
IJsonObjectWrapper jsonObjectFromMap = JsonWrapper.createJsonObject(map);
// 创建空的 JSON 数组
IJsonArrayWrapper jsonArray = JsonWrapper.createJsonArray();
// 创建指定初始容量的 JSON 数组
IJsonArrayWrapper jsonArrayWithCapacity = JsonWrapper.createJsonArray(10);
// 从数组创建 JSON 数组
Object[] array = new Object[]{1, "test", true};
IJsonArrayWrapper jsonArrayFromArray = JsonWrapper.createJsonArray(array);
// 从集合创建 JSON 数组
List<Object> list = Arrays.asList(1, "test", true);
IJsonArrayWrapper jsonArrayFromList = JsonWrapper.createJsonArray(list);
JSON 字符串与 Java 对象转换
// 将 JSON 字符串转换为 JsonWrapper
String jsonStr = "{\"name\":\"test\",\"age\":20}";
JsonWrapper jsonWrapper = JsonWrapper.fromJson(jsonStr);
// 将 Java 对象转换为 JsonWrapper
User user = new User();
user.setName("test");
user.setAge(20);
JsonWrapper userJson = JsonWrapper.toJson(user);
// 将 Java 对象转换为 JSON 字符串(默认紧凑输出)
String userJsonStr = JsonWrapper.toJsonString(user);
// 将 Java 对象转换为格式化的 JSON 字符串
String formattedJson = JsonWrapper.toJsonString(user, true);
// 转换时保留空值
String jsonWithNulls = JsonWrapper.toJsonString(user, true, true);
// 使用蛇形命名转换
String snakeCaseJson = JsonWrapper.toJsonString(user, true, true, true);
序列化和反序列化
// 序列化对象为 JSON 字节数组
byte[] serialized = JsonWrapper.serialize(user);
// 反序列化 JSON 字符串为对象
User deserializedUser = JsonWrapper.deserialize(jsonStr, User.class);
// 反序列化时处理蛇形命名
User userWithSnakeCase = JsonWrapper.deserialize(jsonStr, true, User.class);
// 反序列化泛型类型
String arrayJson = "[\"a\",\"b\",\"c\"]";
List<String> stringList = JsonWrapper.deserialize(arrayJson, new TypeReferenceWrapper<List<String>>() {});
属性过滤
// 创建属性过滤器
IJsonPropertyFilter filter = new IJsonPropertyFilter() {
@Override
public boolean apply(Object object, String name, Object value) {
// 过滤掉密码字段
return !"password".equals(name);
}
};
// 序列化时应用过滤器
String filteredJson = JsonWrapper.toJsonString(user, true, true, filter);
包装器类型
JsonWrapper 提供了三种类型的包装器,用于不同场景的 JSON 操作:
对象包装器(IJsonObjectWrapper)
用于创建和维护 JsonObject 数据结构,支持链式调用和各种类型的取值方法。
示例:
// 创建 JsonObject 对象实例并设置 ordered 为有序的
IJsonObjectWrapper jsonObj = JsonWrapper.createJsonObject(true);
jsonObj.put("name", "suninformation")
.put("realName", "有理想的鱼")
.put("age", 20)
.put("gender", (String) null)
.put("attrs", JsonWrapper.createJsonObject()
.put("key1", "value1")
.put("key2", "value2"));
// 采用格式化输出并保留值为空的属性
System.out.println(jsonObj.toString(true, true));
// 取值:
System.out.println("Name: " + jsonObj.getString("name"));
System.out.println("Age: " + jsonObj.getInt("age"));
IJsonObjectWrapper attrs = jsonObj.getJsonObject("attrs");
System.out.println("Key1: " + attrs.getString("key1"));
System.out.println("Key2: " + attrs.getString("key2"));
// 检查属性是否存在
if (jsonObj.has("name")) {
System.out.println("Name exists");
}
// 移除属性
jsonObj.remove("gender");
// 转换为 Map
Map<String, Object> map = jsonObj.toMap();
执行结果:
{
"name":"suninformation",
"realName":"有理想的鱼",
"age":20,
"gender":null,
"attrs":{
"key1":"value1",
"key2":"value2"
}
}
Name: suninformation
Age: 20
Key1: value1
Key2: value2
Name exists
数组包装器(IJsonArrayWrapper)
用于创建和维护 JsonArray 数据结构,支持链式调用和各种类型的取值方法。
示例:
// 创建 JsonArray 对象实例
IJsonArrayWrapper jsonArray = JsonWrapper.createJsonArray(new Object[]{1, null, 2, 3, false, true})
.add(JsonWrapper.createJsonArray().add(new String[]{"a", "b"}))
.add(JsonWrapper.createJsonObject(true)
.put("name", "suninformation")
.put("realName", "有理想的鱼")
.put("age", 20)
.put("gender", (String) null))
.add(11);
// 采用格式化输出并保留值为空的属性
System.out.println(jsonArray.toString(true, false));
// 取值:
System.out.println("Index3: " + jsonArray.getInt(3));
System.out.println("Index4: " + jsonArray.getBoolean(4));
IJsonObjectWrapper jsonObj = jsonArray.getJsonObject(7);
System.out.println("Name: " + jsonObj.getString("name"));
System.out.println("Age: " + jsonObj.getInt("age"));
// 获取数组长度
System.out.println("Array length: " + jsonArray.size());
// 转换为 List
List<Object> list = jsonArray.toList();
执行结果:
[
1,
null,
2,
3,
false,
true,
[
["a","b"]
],
{
"name":"suninformation",
"realName":"有理想的鱼",
"age":20
},
11
]
Index3: 3
Index4: false
Name: suninformation
Age: 20
Array length: 9
节点包装器(IJsonNodeWrapper)
在通过对象包装器或数组包装器提供的 get 方法获取对应的属性或索引下标值对象时,此值对象将被节点包装器重新包装,其作用是对被包装值对象的数据类型提供判断能力 。
示例:
// 创建复杂的 JsonObject 对象
IJsonObjectWrapper jsonObj = JsonWrapper.createJsonObject(true)
.put("name", "suninformation")
.put("realName", "有理想的鱼")
.put("age", 20)
.put("array", JsonWrapper.createJsonArray(new String[]{"a", "b"}))
.put("attrs", JsonWrapper.createJsonObject()
.put("key1", "value1")
.put("key2", "value2"));
// 采用格式化输出并保留值为空的属性
System.out.println(jsonObj.toString(true, true));
// 遍历:
for (String key : jsonObj.keySet()) {
IJsonNodeWrapper nodeWrapper = jsonObj.get(key);
if (nodeWrapper.isJsonArray()) {
// 判断当前元素是否为 JsonArray 对象
System.out.println(nodeWrapper.getJsonArray().getString(0));
} else if (nodeWrapper.isJsonObject()) {
// 判断当前元素是否为 JsonObject 对象
System.out.println(nodeWrapper.getJsonObject().getString("key1"));
} else {
// 否则为值对象,直接取值
System.out.println(nodeWrapper.getString());
}
}
执行结果:
{
"name":"suninformation",
"realName":"有理想的鱼",
"age":20,
"array":[
"a",
"b"
],
"attrs":{
"key1":"value1",
"key2":"value2"
}
}
suninformation
有理想的鱼
20
a
value1
高级用法
类型引用(TypeReferenceWrapper)
用于处理泛型类型的反序列化,解决 Java 泛型擦除的问题。
示例:
// 反序列化复杂泛型类型
String complexJson = "{\"users\":[{\"name\":\"test1\",\"age\":20},{\"name\":\"test2\",\"age\":21}]}";
// 定义类型引用
TypeReferenceWrapper<Map<String, List<User>>> typeRef = new TypeReferenceWrapper<Map<String, List<User>>>() {};
// 反序列化
Map<String, List<User>> result = JsonWrapper.deserialize(complexJson, typeRef);
System.out.println(result.get("users"));
蛇形命名转换
支持在序列化和反序列化时自动处理驼峰命名和蛇形命名之间的转换。
示例:
// 序列化时使用蛇形命名
String snakeCaseJson = JsonWrapper.toJsonString(user, true, true, true);
System.out.println(snakeCaseJson);
// 反序列化时处理蛇形命名
User userFromSnakeCase = JsonWrapper.deserialize(snakeCaseJson, true, User.class);
System.out.println(userFromSnakeCase);
属性过滤
通过 IJsonPropertyFilter 接口可以在序列化时过滤掉不需要的属性。
示例:
// 创建用户对象
User user = new User();
user.setName("test");
user.setAge(20);
user.setPassword("secret");
// 创建属性过滤器
IJsonPropertyFilter filter = (object, name, value) -> !"password".equals(name);
// 序列化时应用过滤器
String filteredJson = JsonWrapper.toJsonString(user, true, true, filter);
System.out.println(filteredJson);
执行结果:
{
"name":"test",
"age":20
}
最佳实践
- 选择合适的 JSON 库:根据项目需求和性能要求选择合适的 JSON 库
- 使用类型引用处理泛型:对于复杂泛型类型,使用 TypeReferenceWrapper 进行反序列化
- 合理使用属性过滤:在序列化敏感数据时,使用属性过滤器保护隐私信息
- 统一配置:通过 JVM 启动参数或 SPI 机制统一配置 JSON 适配器
- 异常处理:在处理 JSON 操作时,注意捕获和处理可能的异常
JsonWrapper 为 YMP 框架提供了统一、灵活的 JSON 处理能力,使得开发者可以更加专注于业务逻辑,而不必关心底层 JSON 库的实现细节。
Markdown
对 Markdown 语法中常用到的 格式,如:标题、文本、引用、表格、代码片段、图片、链接等进行对象封装,避免以往采用字符串拼接形式中经常出现的问题。
核心组件
Markdown 模块提供了以下核心组件,每个组件都实现了 IMarkdown 接口:
| 组件 | 说明 | 主要功能 |
|---|---|---|
MarkdownBuilder | Markdown 文档构建器 | 链式构建完整 Markdown 文档 |
Title | 标题组件 | 支持 1-6 级标题 |
Text | 文本组件 | 支持普通、粗体、斜体、下划线、删除线样式 |
Code | 代码组件 | 支持行内代码和代码块,可指定语言 |
Quote | 引用组件 | 生成引用块内容 |
Link | 链接组件 | 生成超链接 |
Image | 图片组件 | 生成图片,支持缩放 |
Table | 表格组件 | 生成表格,支持对齐方式 |
ParagraphList | 列表组件 | 支持有序/无序列表、嵌套列表 |
MarkdownBuilder
MarkdownBuilder 是 Markdown 文档构建器,用于链式构建完整的 Markdown 文档。
基础方法
// 创建实例
MarkdownBuilder builder = MarkdownBuilder.create();
// 换行和段落
builder.br(); // 添加换行符
builder.p(); // 添加段落分隔(两个换行符)
builder.p(3); // 添加指定数量的换行符
// 空格和缩进
builder.space(); // 添加一个空格
builder.space(3); // 添加指定数量的空格
builder.tab(); // 添加制表符(4个空格)
// 水平分隔线
builder.hr(); // 添加水平分隔线
// 追加内容
builder.append("字符串内容");
builder.append(markdownObject); // 追加 IMarkdown 对象
// 获取内容长度
int len = builder.length();
标题
// 一级标题
builder.title("一级标题");
builder.title(markdownObject); // 使用 IMarkdown 对象
// 指定级别标题(1-6级)
builder.title("二级标题", 2);
builder.title(markdownObject, 3);
文本
// 普通文本
builder.text("普通文本");
builder.text(markdownObject);
// 带样式的文本
builder.text("粗体文本", Text.Style.BOLD); // **粗体**
builder.text("斜体文本", Text.Style.ITALIC); // *斜体*
builder.text("下划线文本", Text.Style.UNDERLINE); // <u>下划线</u>
builder.text("删除线文本", Text.Style.STRIKEOUT); // ~~删除线~~
引用
builder.quote("引用文本内容");
builder.quote(markdownObject);
链接
// 带显示文本的链接
builder.link("YMP", "https://ymate.net/"); // [YMP](https://ymate.net/)
builder.link(markdownObject, "https://example.com");
// 仅 URL(显示文本与 URL 相同)
Link link = Link.create("https://example.com"); // [https://example.com](https://example.com)
图片
// 仅 URL
builder.image("https://ymate.net/img/logo.png"); // 
// 带替代文本
builder.image("Logo image.", "https://ymate.net/img/logo.png"); // 
// 带缩放比例(0-200,使用 HTML img 标签)
builder.image("Logo", "https://ymate.net/img/logo.png", 50); // <img style="zoom:50%;" />
代码
// 行内代码
builder.code("code"); // `code`
builder.code(markdownObject);
// 指定语言的代码块
builder.code("public class Test {}", "java"); // ```java\ncode\n```
builder.code(markdownObject, "python");
Text 组件
Text 组件用于生成不同样式的文本内容。
// 创建普通文本
Text text = Text.create("Hello World");
// 创建带样式的文本
Text boldText = Text.create("粗体", Text.Style.BOLD);
Text italicText = Text.create("斜体", Text.Style.ITALIC);
Text underlineText = Text.create("下划线", Text.Style.UNDERLINE);
Text strikeoutText = Text.create("删除线", Text.Style.STRIKEOUT);
// 使用 IMarkdown 对象创建文本
Text textFromMarkdown = Text.create(Title.create("标题"));
Text styledMarkdown = Text.create(Title.create("标题"), Text.Style.ITALIC);
// 追加内容(链式调用)
Text text = Text.create("Hello")
.append(" World")
.append(Title.create("!"));
支持的样式:
| 样式 | 枚举值 | Markdown 输出 |
|---|---|---|
| 普通 | Style.NORMAL | 普通文本 |
| 粗体 | Style.BOLD | **文本** |
| 斜体 | Style.ITALIC | *文本* |
| 下划线 | Style.UNDERLINE | <u>文本</u> |
| 删除线 | Style.STRIKEOUT | ~~文本~~ |
Title 组件
Title 组件用于生成不同级别的标题。
// 创建一级标题
Title title1 = Title.create("一级标题"); // # 一级标题
// 创建指定级别标题(1-6级)
Title title2 = Title.create("二级标题", 2); // ## 二级标题
Title title6 = Title.create("六级标题", 6); // ###### 六级标题
// 使用 IMarkdown 对象创建标题
Title title = Title.create(Text.create("粗体标题", Text.Style.BOLD), 2);
// 追加内容(链式调用)
Title title = Title.create("Hello")
.append(" World")
.append(Text.create("!"));
说明:
- 标题级别范围:1-6,超出范围会自动调整(小于1视为1,大于6视为6)
- 标题内容中的换行符会被替换为空格
Code 组件
Code 组件用于生成行内代码或代码块。
// 行内代码
Code inlineCode = Code.create("inline code"); // `inline code`
// 代码块(自动检测换行符)
Code codeBlock = Code.create("line1\nline2"); // ```\nline1\nline2\n```
// 指定语言的代码块
Code javaCode = Code.create("public class Test {}", "java");
// 输出:```java
// public class Test {}
// ```
// 使用 IMarkdown 对象
Code code = Code.create(Text.create("code"));
Code styledCode = Code.create(Text.create("code"), "java");
// 追加代码内容(链式调用)
Code code = Code.create("line1")
.append("\nline2")
.append("\nline3");
说明:
- 当明确指定了非空白语言,或内容包含换行符时,使用代码块格式(```)
- 否则使用行内代码格式(`)
- 空内容或仅空白字符的内容返回空字符串
Quote 组件
Quote 组件用于生成引用块。
// 创建引用
Quote quote = Quote.create("引用文本内容"); // > 引用文本内容\n> \n
// 使用 IMarkdown 对象
Quote quote = Quote.create(Text.create("引用文本"));
// 追加内容(链式调用)
Quote quote = Quote.create("第一行")
.append("\n第二行")
.append(Text.create("\n第三行"));
说明:
- 每行内容以
>开头 - 引用末尾会自动添加一个空引用行
> - 空内容或仅空白字符的内容返回空字符串
Link 组件
Link 组件用于生成超链接。
// 仅 URL(显示文本与 URL 相同)
Link link1 = Link.create("https://example.com"); // [https://example.com](https://example.com)
// 带显示文本的链接
Link link2 = Link.create("示例", "https://example.com"); // [示例](https://example.com)
// 使用 IMarkdown 对象作为显示文本
Link link3 = Link.create(Text.create("粗体链接", Text.Style.BOLD), "https://example.com");
说明:
- URL 为空或仅空白字符时返回空字符串
- 显示文本为空时会使用 URL 作为显示文本
- 首尾空白字符会被自动去除
Image 组件
Image 组件用于生成图片。
// 仅 URL
Image image1 = Image.create("https://example.com/img.jpg"); // 
// 带替代文本
Image image2 = Image.create("图片描述", "https://example.com/img.jpg"); // 
// 带缩放比例(0-200)
Image image3 = Image.create("Logo", "https://example.com/logo.png", 50);
// 输出:<img src="url" alt="Logo" style="zoom:50%;" />
说明:
- URL 为空或仅空白字符时返回空字符串
- 缩放比例为 0 时使用标准 Markdown 图片语法
- 缩放比例范围:0-200,超出范围会自动调整
- 首尾空白字符会被自动去除
Table 组件
Table 组件用于生成表格。
// 创建表格
Table table = Table.create()
// 添加表头
.addHeader("序号", Table.Align.CENTER) // 居中对齐
.addHeader("命令") // 默认左对齐
.addHeader("描述", Table.Align.RIGHT) // 右对齐
// 添加数据行
.addRow()
.addColumn("1")
.addColumn(Code.create("mvn clean"))
.addColumn("执行工程清理")
.build() // 结束当前行
.addRow()
.addColumn("2")
.addColumn(Code.create("mvn install"))
.addColumn("执行安装")
.build();
对齐方式:
| 对齐方式 | 枚举值 | Markdown 输出 |
|---|---|---|
| 默认 | Align.NORMAL | --- |
| 左对齐 | Align.LEFT | :--- |
| 居中 | Align.CENTER | :---: |
| 右对齐 | Align.RIGHT | ---: |
说明:
- 表头和列内容支持
IMarkdown对象 - 特殊字符(如
|、换行符)会自动转义
ParagraphList 组件
ParagraphList 组件用于生成有序列表或无序列表,支持嵌套。
// 无序列表
ParagraphList unorderedList = ParagraphList.create()
.addItem("Item 1")
.addItem("Item 2")
.addItem("Item 3");
// 有序列 表
ParagraphList orderedList = ParagraphList.create(true)
.addItem("第一步")
.addItem("第二步")
.addItem("第三步");
// 添加多个列表项
ParagraphList list = ParagraphList.create()
.addItems("Item 1", "Item 2", "Item 3");
// 嵌套子列表
ParagraphList nestedList = ParagraphList.create()
.addItem("父项 1")
.addSubItem("子项 1.1")
.addSubItem("子项 1.2")
.addItem("父项 2")
.addSubItems("子项 2.1", "子项 2.2");
// 添加指定类型的子列表
ParagraphList list = ParagraphList.create()
.addItem("父项")
.addSubItem("有序子项", true) // 有序子列表
.addSubItems(false, "无序子项1", "无序子项2"); // 无序子列表
// 添加正文内容
ParagraphList list = ParagraphList.create()
.addItem("列表项")
.addBody("这是列表项的正文内容,会显示在列表项下方。");
说明:
create()创建无序列表,使用-作为前缀create(true)创建有序列表,使用数字.作为前缀- 子列表会自动添加缩进(4个空格)
- 空内容或仅空白字符的列表项会被忽略
完整示例
MarkdownBuilder markdownBuilder = MarkdownBuilder.create()
.title("一级标题").p()
.title("二级标题", 2).p()
.text("普通文本")
.tab().text("斜体文本", Text.Style.ITALIC)
.space().text("粗体文本", Text.Style.BOLD).p()
.hr()
.quote(MarkdownBuilder.create()
.text("引用文本内容...").p()
.append(ParagraphList.create()
.addItem("Item")
.addSubItem("SubItem")
.addBody("Item body."))).p()
.append(Table.create()
.addHeader("序号", Table.Align.CENTER)
.addHeader("命令")
.addHeader("描述", Table.Align.RIGHT).addRow()
.addColumn("1")
.addColumn(Code.create("mvn clean"))
.addColumn("执行工程清理").build()).p()
.image("Logo image.", "https://ymate.net/img/logo.png").p()
.code("mvn clean source:jar install", "shell").br()
.link("YMP", "https://ymate.net/");
System.out.println(markdownBuilder);
输出内容:
# 一级标题
## 二级标题
普通文本 *斜体文本* **粗体文本**
------
> 引用文本内容...
>
> - Item
>
> - SubItem
>
> Item body.
|序号|命令|描述|
|:---:|:---:|---:|
|1|`mvn clean`|执行工程清理|

```shell
mvn clean source:jar install
```
[YMP](https://ymate.net/)
序列化(Serialize)
基于 ISerializer 接口实现对象序列化与反序列化操作,由 SerializerManager 对象序列化管理器维护管理,支持通过 SPI 机制和自动扫描 @Serializer 注解方式加载并注册。
核心组件
序列化模块提供了以下核心组件:
| 组件 | 说明 | 主要功能 |
|---|---|---|
ISerializer | 序列化器接口 | 定义序列化和反序列化的标准行为 |
SerializerManager | 序列化器管理器 | 管理和提供各种序列化器实例 |
@Serializer | 序列化器注解 | 标记和命名序列化器实现类 |
SerializationException | 序列化异常 | 序列化过程中的异常处理 |
内置序列化器
模块默认提供了以下序列化器实现:
| 序列化器 | 名称 | 内容类型 | 说明 | 依赖 |
|---|---|---|---|---|
DefaultSerializer | default | application/x-java-serialized-object | Java原生序列化 | 无 |
JSONSerializer | json | application/json | JSON格式序列化 | 无 |
FstSerializer | fst | application/x-java-serialized-fst | FST高性能序列化 | 可选 |
HessianSerializer | hessian | application/x-java-serialized-hessian | Hessian二进制序列化 | 可选 |
KryoSerializer | kryo | application/x-kryo | Kryo高性能序列化 | 可选 |
ProtobufSerializer | protobuf | application/x-protobuf | Protobuf序列化 | 可选 |
SerializerManager
SerializerManager 是序列化器管理器,负责管理和提供各种序列化器实例。
获取序列化器
// 获取默认序列化器(DefaultSerializer)
ISerializer serializer = SerializerManager.getDefaultSerializer();
// 获取JSON序列化器
ISerializer jsonSerializer = SerializerManager.getJsonSerializer();
// 获取FST序列化器(可选依赖)
ISerializer fstSerializer = SerializerManager.getFstSerializer();
// 获取Hessian序列化器(可选依赖)
ISerializer hessianSerializer = SerializerManager.getHessianSerializer();
// 获取Protobuf序列化器(可选依赖)
ISerializer protobufSerializer = SerializerManager.getProtobufSerializer();
// 获取Kryo序列化器(可选依赖)
ISerializer kryoSerializer = SerializerManager.getKryoSerializer();
// 根据名称获取序列化器(不区分大小写)
ISerializer customSerializer = SerializerManager.getSerializer("custom");
注册和注销序列化器
// 注册序列化器(通过Class)
SerializerManager.registerSerializer(CustomSerializer.class);
// 注册序列化器(通过名称和Class)
SerializerManager.registerSerializer("my-serializer", CustomSerializer.class);
// 注销指定名称的序列化器
SerializerManager.unregisterSerializer("custom");
// 注销指定类型的序列化器
SerializerManager.unregisterSerializer(CustomSerializer.class);
// 注销所有序列化器
SerializerManager.unregisterAll();
查询序列化器
// 检查是否存在指定名称的序列化器
boolean exists = SerializerManager.containsSerializer("json");
// 检查是否存在指定类型的序列化器
boolean exists = SerializerManager.containsSerializer(JSONSerializer.class);
// 获取所有已注册的序列化器名称
Set<String> names = SerializerManager.getRegisteredNames();
// 获取所有已注册的序列化器实例
Collection<ISerializer> serializers = SerializerManager.getRegisteredSerializers();
// 获取已注册的序列化器数量
int count = SerializerManager.getSerializerCount();
ISerializer 接口
ISerializer 是序列化器接口,定义了序列化和反序列化的标准行为。
序列化对象
// 序列化对象为字节数组
byte[] bytes = serializer.serialize(object);
反序列化对象
// 反序列化为指定类型
TestObject obj = serializer.deserialize(bytes, TestObject.class);
// 反序列化为复杂类型(支持泛型)
List<TestObject> list = serializer.deserialize(bytes, new TypeReferenceWrapper<List<TestObject>>() {});
获取内容类型
// 获取序列化器的内容类型
String contentType = serializer.getContentType();
@Serializer 注解
@Serializer 注解用于标记和命名序列化器实现类。
// 使用注解标记序列化器
@Serializer("custom")
public class CustomSerializer implements ISerializer {
// 实现序列化和反序列化方法
}
说明:
- 注解的
value用于指定序列化器名称 - 如果未指定名称,则使用类的全限定名
- 序列化器名称不区分大小写
- 通过 SPI 机制加载时,会使用该注解的值作为名称
序列化器说明
DefaultSerializer
Java原生序列化器,基于Java标准库的 ObjectOutputStream 和 ObjectInputStream。
特点:
- 支持所有实现了
Serializable接口的对象 - 序列化格式为Java专有的二进制格式
- 具有较好的兼容性和稳定性
注意事项:
- 序列化效率相对较低,不适合高性能场景
- 序列化结果较大,占用较多存储空间
JSONSerializer
JSON序列化器,基于 IJsonAdapter 接口提供JSON格式的序列化和反序列化功能。
特点:
- 支持跨平台、可读性强的JSON格式
- 支持复杂类型的反序列化,包括泛型集合、嵌套对象等
注意事项:
- JSON序列化结果通常比二进制格式大
- 序列化和反序列化性能相对较低
- 不支持循环引用
FstSerializer
FST(Fast Serialization)序列化器,基于FST库提供高性能的序列化和反序列化功能。
特点:
- 高性能、低内存占用
- 比Java原生序列化快10倍以上
- 支持复杂的对象图序列化
依赖配置:
<dependency>
<groupId>de.ruedigermoeller</groupId>
<artifactId>fst</artifactId>
<version>2.57</version>
</dependency>
注意事项:
- 需要添加FST库依赖
- 该序列化器通过SPI机制动态加载,仅在FST库可用时注册
- 序列化结果为二进制格式,不可读
- 跨语言支持有限
HessianSerializer
Hessian序列化器,基于Hessian二进制协议提供高效的序列化和反序列化功能。
特点:
- 跨语言、高性能的二进制协议
- 支持多种编程语言
- 适用于RPC远程调用和分布式系统
依赖配置:
<dependency>
<groupId>com.caucho</groupId>
<artifactId>hessian</artifactId>
<version>4.0.66</version>
</dependency>
注意事项:
- 需要添加Hessian库依赖
- 该序列化器通过SPI机制动态加载,仅在Hessian库可用时注册
- 序列化结果为二进制格式,不可读
- 某些Java特性可能不完全支持
KryoSerializer
Kryo序列化器,基于Kryo库提供高性能的序列化和反序列化功能。
特点:
- 极致的序列化性能
- 比Java原生序列化快数倍
- 生成的序列化结果更小
- 支持序列化任何Java对象,包括未实现Serializable接口的对象
依赖配置:
<dependency>
<groupId>com.esotericsoftware</groupId>
<artifactId>kryo</artifactId>
<version>5.6.2</version>
</dependency>
注意事项:
- 需要添加Kryo库依赖
- 该序列化器通过SPI机制动态加载,仅在Kryo库可用时注册
- 序列化结果为二进制格式,不可读
- 跨语言支持有限
- Kryo实例不是线程安全的,需要为每个线程创建独立实例
ProtobufSerializer
Protobuf(Protocol Buffers)序列化器,基于Google Protobuf库提供高效的序列化和反序列化功能。
特点:
- 高性能、跨语言支持
- 良好的向前/向后兼容性
- 比XML和JSON更小、更快、更简单
依赖配置:
<dependency>
<groupId>com.google.protobuf</groupId>
<artifactId>protobuf-java</artifactId>
<version>4.34.0</version>
</dependency>
注意事项:
- 需要添加Protobuf库依赖
- 该序列化器通过SPI机制动态加载,仅在Protobuf库可用时注册
- 序列化结果为二进制格式,不可读
- 仅支持实现了
Message或MessageLite接口的对象序列化
示例一:基本序列化与反序列化操作
public class SerialDemoBean implements Serializable {
private String name;
private String remark;
// Getter和Setter方法
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getRemark() {
return remark;
}
public void setRemark(String remark) {
this.remark = remark;
}
@Override
public String toString() {
return String.format("SerialDemoBean{name='%s', remark='%s'}", name, remark);
}
public static void main(String[] args) throws Exception {
// 创建待序列化对象
SerialDemoBean demoBean = new SerialDemoBean();
demoBean.setName("YMP");
demoBean.setRemark("A lightweight modular simple and powerful Java framework.");
// 通过对象序列化管理器获取默认序列化器
ISerializer serializer = SerializerManager.getDefaultSerializer();
// 执行对象序列化操作
byte[] bytes = serializer.serialize(demoBean);
// 执行对象反序列化操作
SerialDemoBean deserializeBean = serializer.deserialize(bytes, SerialDemoBean.class);
// 输出对象值
System.out.println(deserializeBean.toString());
}
}
示例二:使用JSON序列化器
public class JsonSerializeDemo {
public static void main(String[] args) throws Exception {
// 创建待序列化对象
SerialDemoBean demoBean = new SerialDemoBean();
demoBean.setName("YMP");
demoBean.setRemark("A lightweight modular simple and powerful Java framework.");
// 获 取JSON序列化器
ISerializer jsonSerializer = SerializerManager.getJsonSerializer();
// 执行序列化操作
byte[] bytes = jsonSerializer.serialize(demoBean);
System.out.println("JSON: " + new String(bytes, StandardCharsets.UTF_8));
// 执行反序列化操作
SerialDemoBean deserializeBean = jsonSerializer.deserialize(bytes, SerialDemoBean.class);
System.out.println(deserializeBean.toString());
}
}
示例三:使用FST序列化器(高性能)
public class FstSerializeDemo {
public static void main(String[] args) throws Exception {
// 检查FST序列化器是否可用
ISerializer fstSerializer = SerializerManager.getFstSerializer();
if (fstSerializer == null) {
System.out.println("FST serializer is not available. Please add FST dependency.");
return;
}
// 创建待序列化对象
SerialDemoBean demoBean = new SerialDemoBean();
demoBean.setName("YMP");
demoBean.setRemark("High performance serialization with FST.");
// 执行序列化操作
byte[] bytes = fstSerializer.serialize(demoBean);
System.out.println("FST bytes length: " + bytes.length);
// 执行反序列化操作
SerialDemoBean deserializeBean = fstSerializer.deserialize(bytes, SerialDemoBean.class);
System.out.println(deserializeBean.toString());
}
}
示例四:自定义序列化器实现
本例通过自动扫描 @Serializer 注解方式加载并注册一个名称为 custom 的自定义对象序列化器实现类。
@Serializer("custom")
public class CustomSerializer implements ISerializer {
@Override
public String getContentType() {
return "application/json";
}
@Override
public byte[] serialize(Object object) throws SerializationException {
com.alibaba.fastjson.serializer.JSONSerializer serializer = new com.alibaba.fastjson.serializer.JSONSerializer();
serializer.config(SerializerFeature.WriteEnumUsingToString, true);
serializer.write(object);
return serializer.getWriter().toBytes(StandardCharsets.UTF_8);
}
@Override
public <T> T deserialize(byte[] bytes, Class<T> clazz) throws SerializationException {
return JSON.parseObject(new String(bytes, StandardCharsets.UTF_8), clazz);
}
}
示例五:在YMP应用中使用自定义序列化器
@EnableAutoScan
@EnableBeanProxy
public class Starter {
static {
System.setProperty(IApplication.SYSTEM_MAIN_CLASS, Starter.class.getName());
}
private static final Log LOG = LogFactory.getLog(Starter.class);
public static void main(String[] args) throws Exception {
try (IApplication application = YMP.run(args)) {
// 创建待序列化对象
SerialDemoBean demoBean = new SerialDemoBean();
demoBean.setName("YMP");
demoBean.setRemark("A lightweight modular simple and powerful Java framework.");
// 获取自定义序列化器
ISerializer serializer = SerializerManager.getSerializer("custom");
// 执行序列化操作
byte[] bytes = serializer.serialize(demoBean);
// 执行反序列化操作
SerialDemoBean deserializeBean = serializer.deserialize(bytes, SerialDemoBean.class);
// 输出对象值
System.out.println(deserializeBean.toString());
}
}
}
最佳实践
-
选择合适的序列化器:
- 默认场景使用
DefaultSerializer或JSONSerializer - 高性能场景使用
FstSerializer或KryoSerializer - 跨语言通信使用
HessianSerializer或ProtobufSerializer
- 默认场景使用
-
处理可选依赖:
- 使用可选序列化器前,先检查是否可用
- 提供降级方案,当可选序列化器不可用时使用默认序列化器
-
异常处理:
- 捕获
SerializationException异常 - 记录序列化和反序列化过程中的错误信息
- 捕获
-
性能优化:
- 大数据量场景优先使用高性能序列化器
- 缓存序列化器实例,避免重复创建
-
自定义序列化器:
- 使用
@Serializer注解标记自定义序列化器 - 通过 SPI 机制或自动扫描注册序列化器
- 确保序列化和反序列化的对称性
- 使用
重试机制(Retry)
重试机制提供了一套完整的重试策略实现,用于处理网络请求、服务调用等可能失败的操作。该模块支持自定义重试次数、延迟策略、超时控制以及异常类型过滤等功能。
核心组件
重试机制包含以下核心组件:
| 组件 | 说明 |
|---|---|
IRetryable<T> | 可重试操作接口,定义需要执行重试逻辑的操作 |
IRetryDelayStrategy | 延迟策略接口,定义重试之间的延迟计算逻辑 |
RetryConfig | 重试配置类,用于定义重试行为的相关参数 |
RetryUtils | 重试工具类,提供便捷的重试操作执行方法 |
快速开始
最简单的使用方式
使用默认配置(3次重试、1秒初始延迟、指数退避策略)执行重试操作:
import net.ymate.platform.commons.retry.RetryUtils;
public class RetryDemo {
public static void main(String[] args) throws Exception {
String result = RetryUtils.executeWithRetry(() -> {
// 执行可能失败的操作
return doSomething();
});
System.out.println("Result: " + result);
}
private static String doSomething() throws Exception {
// 模拟网络请求或其他可能失败的 操作
return "success";
}
}
自定义重试次数和延迟
import net.ymate.platform.commons.retry.RetryUtils;
public class RetryDemo {
public static void main(String[] args) throws Exception {
// 设置最大重试次数为5次,初始延迟为2秒
String result = RetryUtils.executeWithRetry(() -> {
return doSomething();
}, 5, 2000);
}
}
指定可重试的异常类型
只有抛出指定类型的异常时才会进行重试:
import net.ymate.platform.commons.retry.RetryUtils;
import java.io.IOException;
import java.net.SocketTimeoutException;
public class RetryDemo {
public static void main(String[] args) throws Exception {
// 只对 IOException 和 SocketTimeoutException 进行重试
String result = RetryUtils.executeWithRetry(
() -> doSomething(),
3, 1000,
IOException.class, SocketTimeoutException.class
);
}
}
延迟策略
重试机制提供了三种内置的延迟策略,可以根据不同场景选择合适的策略。
固定延迟策略(FixedDelayStrategy)
每次重试使用相同的延迟时间,适用于对延迟时间要求固定的场景。
import net.ymate.platform.commons.retry.RetryConfig;
import net.ymate.platform.commons.retry.RetryUtils;
public class RetryDemo {
public static void main(String[] args) throws Exception {
RetryConfig config = RetryConfig.custom()
.maxRetries(3)
.fixedDelay(500) // 每次重试固定延迟500毫秒
.build();
String result = RetryUtils.executeWithRetry(() -> doSomething(), config);
}
}
延迟时间示例:
| 尝试次数 | 延迟时间 |
|---|---|
| 第1次失败后 | 500ms |
| 第2次失败后 | 500ms |
| 第3次失败后 | 500ms |
指数延迟策略(ExponentialDelayStrategy)
延迟时间随重试次数指数增长,公式为:delay = initialDelay * 2^(attempt-1)。适用于需要逐步增加重试间隔的场景,如网络请求、服务调用等。
import net.ymate.platform.commons.retry.RetryConfig;
import net.ymate.platform.commons.retry.RetryUtils;
public class RetryDemo {
public static void main(String[] args) throws Exception {
// 使用默认最大延迟(1小时)
RetryConfig config = RetryConfig.custom()
.maxRetries(5)
.exponentialDelay(1000) // 初始延迟1秒
.build();
// 或指定最大延迟时间
RetryConfig config2 = RetryConfig.custom()
.maxRetries(5)
.exponentialDelay(1000, 60000) // 初始延迟1秒,最大延迟1分钟
.build();
String result = RetryUtils.executeWithRetry(() -> doSomething(), config);
}
}
延迟时间示例(初始延迟1000ms):
| 尝试次数 | 计算公式 | 延迟时间 |
|---|---|---|
| 第1次失败后 | 1000 * 2^0 | 1000ms |
| 第2次失败后 | 1000 * 2^1 | 2000ms |
| 第3次失败后 | 1000 * 2^2 | 4000ms |
| 第4次失败后 | 1000 * 2^3 | 8000ms |
| 第5次失败后 | 1000 * 2^4 | 16000ms |
随机延迟策略(RandomDelayStrategy)
在指定的最小和最大延迟时间之间随机选择延迟时间,适用于需要避免重试请求同时发生 的场景(如分布式系统中的惊群效应)。
import net.ymate.platform.commons.retry.RetryConfig;
import net.ymate.platform.commons.retry.RetryUtils;
public class RetryDemo {
public static void main(String[] args) throws Exception {
RetryConfig config = RetryConfig.custom()
.maxRetries(3)
.randomDelay(100, 500) // 延迟时间在100ms到500ms之间随机
.build();
String result = RetryUtils.executeWithRetry(() -> doSomething(), config);
}
}
完整配置示例
使用 RetryConfig 进行完整的重试配置:
import net.ymate.platform.commons.retry.RetryConfig;
import net.ymate.platform.commons.retry.RetryUtils;
import java.io.IOException;
import java.net.SocketTimeoutException;
public class RetryDemo {
public static void main(String[] args) throws Exception {
RetryConfig config = RetryConfig.custom()
// 设置最大重试次数
.maxRetries(5)
// 使用指数延迟策略,初始延迟1秒,最大延迟1分钟
.exponentialDelay(1000, 60000)
// 设置总超时时间为5分钟
.totalTimeoutMs(5 * 60 * 1000L)
// 设置可重试的异常类型
.retryableExceptions(IOException.class, SocketTimeoutException.class)
.build();
String result = RetryUtils.executeWithRetry(() -> {
// 执行可能失败的操作
return callRemoteService();
}, config);
System.out.println("Result: " + result);
}
private static String callRemoteService() throws IOException {
// 模拟远程服务调用
return "success";
}
}
配置参数说明
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
maxRetries | int | 3 | 最大重试次数,必须大于0 |
initialDelayMs | long | 1000 | 初始延迟时间(毫秒) |
totalTimeoutMs | Long | 300000 (5分钟) | 总超时时间(毫秒),null表示不限制 |
delayStrategy | IRetryDelayStrategy | ExponentialDelayStrategy | 延迟策略 |
retryableExceptions | Class<? extends Exception>[] | 空数组 | 可重试的异常类型,为空表示所有异常都可重试 |
自定义延迟策略
通过实现 IRetryDelayStrategy 接口可以创建自定义的延迟策略:
import net.ymate.platform.commons.retry.IRetryDelayStrategy;
public class CustomDelayStrategy implements IRetryDelayStrategy {
@Override
public long getDelay(int attempt) {
// 自定义延迟逻辑
// 例如:线性增长延迟
return attempt * 1000L; // 第1次1秒,第2次2秒,第3次3秒...
}
}
// 使用自定义延迟策略
RetryConfig config = RetryConfig.custom()
.maxRetries(3)
.delayStrategy(new CustomDelayStrategy())
.build();
异常处理
异常类型过滤
通过 retryableExceptions 参数可以指定哪些异常类型需要重试:
import net.ymate.platform.commons.retry.RetryConfig;
import net.ymate.platform.commons.retry.RetryUtils;
import java.io.IOException;
import java.net.ConnectException;
import java.net.SocketTimeoutException;
public class RetryDemo {
public static void main(String[] args) {
RetryConfig config = RetryConfig.custom()
.maxRetries(3)
.exponentialDelay(1000)
// 只对网络相关异常进行重试
.retryableExceptions(
IOException.class,
ConnectException.class,
SocketTimeoutException.class
)
.build();
try {
String result = RetryUtils.executeWithRetry(() -> doSomething(), config);
} catch (IOException e) {
// 重试失败后的处理
System.err.println("All retries failed: " + e.getMessage());
} catch (Exception e) {
// 其他异常(非可重试异常)直接抛出
System.err.println("Non-retryable exception: " + e.getMessage());
}
}
}
异常处理规则:
- 如果未指定
retryableExceptions,所有异常都会触发重试 - 如果指定了
retryableExceptions,只有匹配的异常类型才会触发重试 - 非可重试异常会直接抛出,不会触发重试
超时处理
当总执行时间超过 totalTimeoutMs 时,会抛出 RuntimeException:
import net.ymate.platform.commons.retry.RetryConfig;
import net.ymate.platform.commons.retry.RetryUtils;
public class RetryDemo {
public static void main(String[] args) {
RetryConfig config = RetryConfig.custom()
.maxRetries(10)
.fixedDelay(1000)
.totalTimeoutMs(5000L) // 总超时5秒
.build();
try {
String result = RetryUtils.executeWithRetry(() -> doSomething(), config);
} catch (RuntimeException e) {
if (e.getMessage().contains("timeout")) {
System.err.println("Retry timeout: " + e.getMessage());
}
} catch (Exception e) {
System.err.println("Error: " + e.getMessage());
}
}
}
实际应用场景
HTTP 请求重试
import net.ymate.platform.commons.retry.RetryConfig;
import net.ymate.platform.commons.retry.RetryUtils;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
import java.io.IOException;
public class HttpRetryDemo {
public static String httpGetWithRetry(String url) throws Exception {
RetryConfig config = RetryConfig.custom()
.maxRetries(3)
.exponentialDelay(1000, 30000)
.retryableExceptions(IOException.class)
.build();
return RetryUtils.executeWithRetry(() -> {
try (CloseableHttpClient httpClient = HttpClients.createDefault()) {
HttpGet request = new HttpGet(url);
try (CloseableHttpResponse response = httpClient.execute(request)) {
return EntityUtils.toString(response.getEntity());
}
}
}, config);
}
}