SpringBoot中通用工具类库(Utils)封装与使用实践
一、涉及的技术知识点
1.1 空值判断与防御性编程
| 知识点 |
说明 |
| 多类型空值判断 |
对 String/Collection/Map/Array/Optional/Object 统一判空 |
| 可变参数(varargs) |
isOrEmpty(Object...) 任一为空返回 true,isAndEmpty(Object...) 全部为空返回 true |
| Null Safety |
替代手写 != null && !"" 链式判断 |
1.2 日期时间处理
| 知识点 |
说明 |
java.util.Date(旧 API) |
DateUtil 基于旧 API 兼容存量代码 |
java.time.LocalDate/LocalDateTime(新 API) |
DateTimeUtil 基于 Java 8 时间 API |
| 智能日期解析 |
根据字符串长度自动识别格式(10位时间戳/13位时间戳/19位标准格式/23位含毫秒) |
| 正则模式匹配 |
用正则判断日期字符串格式(标准 yyyy-MM-dd/斜杠 yyyy/MM/dd) |
| TemporalAdjusters |
获取月初/月末/年初/年末/上月/下月等时间点 |
| 时区枚举 |
ZoneEnums 定义时区常量 |
| 日期格式枚举 |
DateTimeFormat 枚举统一管理 20+ 种日期格式 |
1.3 JSON 序列化/反序列化
| 知识点 |
说明 |
| Jackson ObjectMapper |
配置好的全局单例,线程安全 |
| TypeReference 泛型反序列化 |
解决 Java 泛型擦除问题 |
| Snake Case 支持 |
snakeMapper 自动驼峰 ↔ 下划线转换 |
| 容错处理 |
序列化/反序列化异常不抛出,返回 null 并记录日志 |
1.4 加密工具
| 知识点 |
说明 |
| AES 对称加密 |
AesUtil 提供简单的 AES 加解密(ECB 模式) |
| MD5 摘要 |
Md5Utils 提供 MD5 哈希(签名校验场景) |
| ThreadLocal MessageDigest |
MD5 计算使用 ThreadLocal 避免多线程竞争 |
| Hex 编码 |
字节数组转十六进制字符串 |
1.5 线程上下文管理
| 知识点 |
说明 |
| ThreadLocal |
Auth2SessionIdUtil 使用 ThreadLocal 存储请求级别的用户信息 |
| Token 传递 |
在请求处理链中透传 OAuth2 Token |
| 请求级隔离 |
每个请求有独立的 sessionId/token/loginName |
| 清理机制 |
delete() 方法清理 ThreadLocal 防止内存泄漏 |
1.6 国际化(i18n)
| 知识点 |
说明 |
MessageSource |
Spring 国际化消息源 |
| 资源文件 |
messages.properties / messages_zh_CN.properties |
| 参数化消息 |
getMsg(key, args...) 支持占位符 |
1.7 网络工具
| 知识点 |
说明 |
| 客户端 IP 获取 |
从 X-Forwarded-For / X-Real-IP 等 Header 解析 |
| 内网 IP 判断 |
isIntranetIp() 判断是否为 10.x/172.16-31.x/192.168.x |
| 主机名获取 |
getHostName() 获取当前服务器主机名 |
| ThreadLocal IP |
请求级别缓存客户端 IP |
注:
博客:
https://blog.csdn.net/badao_liumang_qizhi
二、包结构
xxx.xxx.xxx.utils
├── CheckEmptyUtil.java // 空值判断工具(使用频率最高)
├── DateUtil.java // 日期工具(旧API,java.util.Date)
├── StringUtil.java // 字符串/JSON工具(序列化+特殊字符处理)
├── JsonUtil.java // JSON 序列化工具(支持 Snake Case)
├── Auth2SessionIdUtil.java // OAuth2 会话上下文(ThreadLocal)
├── IpUtil.java // IP 地址工具
├── AesUtil.java // AES 加解密工具
├── Md5Utils.java // MD5 摘要工具
├── xxxI18nUtil.java // 国际化消息工具
├── FileUtil.java // 文件操作工具
├── JasperUtil.java // 报表导出工具(Jasper)
├── PdfUtil.java // PDF 生成工具
├── ClassNameUtil.java // 类名处理工具
├── xxxSerializationUtils.java // Java 序列化工具
├── PackageUtil.java // 包扫描工具
├── UrlUtil.java // URL 处理工具
├── cloud/
│ ├── AdapterHeader.java // 适配器 Header 工具
│ └── LoginToken.java // 登录 Token 封装
└── time/
├── DateTimeUtil.java // 日期时间工具(新API,java.time)
├── DateTimeFormat.java // 日期格式枚举(20+种预定义格式)
├── DateTimeConverter.java // 日期转换器
└── ZoneEnums.java // 时区枚举
无 spring.factories:纯工具类库,不涉及自动配置,直接引入静态方法调用。
三、通用示例代码
3.1 CheckEmptyUtil(空值判断工具)
package com.example.utils;
import java.lang.reflect.Array;
import java.util.Collection;
import java.util.Map;
import java.util.Optional;
/**
* 通用空值判断工具.
* 统一处理各种类型的空值判断,替代业务代码中的 != null && !isEmpty() 链式判断.
*
* 支持类型:
* - null
* - String(空字符串)
* - Collection(空集合)
* - Map(空Map)
* - Array(空数组)
* - Optional(空Optional)
* - 其他 Object(仅判null)
*/
public class CheckEmptyUtil {
/**
* 判断对象是否为空.
* 根据实际类型自动选择判空策略.
*/
public static boolean isEmpty(Object obj) {
if (obj == null) {
return true;
}
if (obj instanceof String) {
return ((String) obj).trim().isEmpty();
}
if (obj instanceof Collection) {
return ((Collection<?>) obj).isEmpty();
}
if (obj instanceof Map) {
return ((Map<?, ?>) obj).isEmpty();
}
if (obj.getClass().isArray()) {
return Array.getLength(obj) == 0;
}
if (obj instanceof Optional) {
return !((Optional<?>) obj).isPresent();
}
return false;
}
/**
* 判断对象是否非空.
*/
public static boolean isNotEmpty(Object obj) {
return !isEmpty(obj);
}
/**
* 任一参数为空返回true.
* 常用于参数校验:if (isOrEmpty(a, b)) throw ...
*/
public static boolean isOrEmpty(Object... objs) {
if (objs == null) {
return true;
}
for (Object obj : objs) {
if (isEmpty(obj)) {
return true;
}
}
return false;
}
/**
* 全部参数都为空返回true.
* 常用于条件判断:if (isAndEmpty(a, b)) 都没传
*/
public static boolean isAndEmpty(Object... objs) {
if (objs == null) {
return true;
}
for (Object obj : objs) {
if (isNotEmpty(obj)) {
return false;
}
}
return true;
}
}
3.2 DateUtil(旧版日期工具)
package com.example.utils;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.regex.Pattern;
/**
* 日期工具类(基于 java.util.Date).
*
* 核心特性:
* 1. 智能日期解析 - 根据字符串格式自动识别
* 2. 常用日期操作 - 月初/月末/日始/日终
* 3. 日期格式化 - 标准格式输出
*/
public class DateUtil {
private static final Pattern UNIX_TIMESTAMP = Pattern.compile("^\\d{10}$");
private static final Pattern JAVA_TIMESTAMP = Pattern.compile("^\\d{13}$");
private static final String STANDARD_DATETIME = "yyyy-MM-dd HH:mm:ss";
private static final String STANDARD_DATE = "yyyy-MM-dd";
/**
* 智能日期解析.
* 支持:时间戳(10位/13位)、标准格式、斜杠格式.
*
* @param text 日期字符串
* @return Date 对象,解析失败返回 null
*/
public static Date convertToDate(String text) {
if (text == null || text.trim().isEmpty()) {
return null;
}
text = text.trim();
try {
switch (text.length()) {
case 10:
// Unix 时间戳 或 yyyy-MM-dd
if (UNIX_TIMESTAMP.matcher(text).matches()) {
return new Date(Long.parseLong(text) * 1000);
}
return new SimpleDateFormat(STANDARD_DATE).parse(text);
case 13:
// Java 时间戳
if (JAVA_TIMESTAMP.matcher(text).matches()) {
return new Date(Long.parseLong(text));
}
return null;
case 19:
// yyyy-MM-dd HH:mm:ss
return new SimpleDateFormat(STANDARD_DATETIME).parse(text);
case 23:
// yyyy-MM-dd HH:mm:ss.SSS
return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS").parse(text);
default:
return new SimpleDateFormat(STANDARD_DATETIME).parse(text);
}
} catch (Exception e) {
return null;
}
}
/** 获取日期的起始时刻(00:00:00.000). */
public static Date getDayBegin(Date date) {
Calendar cal = Calendar.getInstance();
cal.setTime(date);
cal.set(Calendar.HOUR_OF_DAY, 0);
cal.set(Calendar.MINUTE, 0);
cal.set(Calendar.SECOND, 0);
cal.set(Calendar.MILLISECOND, 0);
return cal.getTime();
}
/** 获取日期的结束时刻(23:59:59.999). */
public static Date getDayEnd(Date date) {
Calendar cal = Calendar.getInstance();
cal.setTime(date);
cal.set(Calendar.HOUR_OF_DAY, 23);
cal.set(Calendar.MINUTE, 59);
cal.set(Calendar.SECOND, 59);
cal.set(Calendar.MILLISECOND, 999);
return cal.getTime();
}
/** 格式化为标准日期时间字符串. */
public static String formatStandardDateTime(Date date) {
if (date == null) return null;
return new SimpleDateFormat(STANDARD_DATETIME).format(date);
}
/** 获取 N 天后的日期. */
public static Date getDateAfter(Date date, int days) {
Calendar cal = Calendar.getInstance();
cal.setTime(date);
cal.add(Calendar.DAY_OF_MONTH, days);
return cal.getTime();
}
}
3.3 StringUtil(字符串/JSON 工具)
package com.example.utils;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import java.util.regex.Pattern;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* 字符串和JSON工具类.
*
* 核心功能:
* 1. JSON 序列化/反序列化(容错模式)
* 2. 特殊字符检测和替换(中文/Emoji/符号)
* 3. 驼峰拆分
*/
public class StringUtil {
private static final Logger logger = LoggerFactory.getLogger(StringUtil.class);
private static final Pattern CHINESE_CHAR = Pattern.compile("[\\u4e00-\\u9fa5]");
private static final Pattern EMOJI_CHAR = Pattern.compile("[\\ud800-\\udfff]");
// 全局单例 ObjectMapper(线程安全)
private static final ObjectMapper READ_MAPPER = new ObjectMapper()
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
private static final ObjectMapper WRITE_MAPPER = new ObjectMapper()
.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
/**
* 对象序列化为 JSON 字符串.
* 异常时返回空字符串,不抛出异常.
*/
public static String getJsonString(Object obj) {
if (obj == null) {
return "";
}
if (obj instanceof String) {
return (String) obj;
}
try {
return WRITE_MAPPER.writeValueAsString(obj);
} catch (Exception e) {
logger.error("JSON序列化失败", e);
return "";
}
}
/**
* JSON 字符串反序列化为对象.
*/
public static <T> T parseJsonString(String json, Class<T> clazz) {
if (json == null || json.isEmpty()) {
return null;
}
try {
return READ_MAPPER.readValue(json, clazz);
} catch (Exception e) {
logger.error("JSON反序列化失败", e);
return null;
}
}
/**
* JSON 字符串反序列化(泛型支持).
*/
public static <T> T parseJsonString(String json, TypeReference<T> typeRef) {
if (json == null || json.isEmpty()) {
return null;
}
try {
return READ_MAPPER.readValue(json, typeRef);
} catch (Exception e) {
logger.error("JSON反序列化失败", e);
return null;
}
}
/** 是否包含中文字符. */
public static boolean containsChinese(String str) {
return str != null && CHINESE_CHAR.matcher(str).find();
}
/** 是否包含 Emoji 字符. */
public static boolean containsEmoji(String str) {
return str != null && EMOJI_CHAR.matcher(str).find();
}
/** 替换 Emoji 为指定字符. */
public static String replaceEmoji(String str, String replacement) {
if (str == null) return null;
return EMOJI_CHAR.matcher(str).replaceAll(replacement);
}
}
3.4 Auth2SessionIdUtil(请求上下文工具)
package com.example.utils;
/**
* OAuth2 请求级别上下文工具.
* 通过 ThreadLocal 存储当前请求的用户信息,在同一请求内全局可访问.
*
* 使用场景:
* - Filter/Interceptor 中设置(请求进入时)
* - Service 层中读取(业务处理时)
* - Filter 中清理(请求结束时)
*
* 注意:必须在请求结束时调用 delete() 清理,防止线程池复用导致数据串线.
*/
public final class Auth2SessionIdUtil {
private static final ThreadLocal<String> sessionIdLocal = new ThreadLocal<>();
private static final ThreadLocal<String> tokenLocal = new ThreadLocal<>();
private static final ThreadLocal<String> loginNameLocal = new ThreadLocal<>();
/** 获取当前请求的会话ID. */
public static String getSessionId() {
return sessionIdLocal.get();
}
public static void setSessionId(String sessionId) {
sessionIdLocal.set(sessionId);
}
/** 获取当前请求的 OAuth2 Token. */
public static String getToken() {
return tokenLocal.get();
}
public static void setToken(String token) {
tokenLocal.set(token);
}
/** 获取当前登录用户名. */
public static String getLoginName() {
return loginNameLocal.get();
}
public static void setLoginName(String loginName) {
loginNameLocal.set(loginName);
}
/**
* 清理所有 ThreadLocal 数据.
* 必须在请求结束时调用!防止线程池复用导致数据泄露.
*/
public static void delete() {
sessionIdLocal.remove();
tokenLocal.remove();
loginNameLocal.remove();
}
}
3.5 pom.xml(工具库)
<project>
<groupId>com.example</groupId>
<artifactId>example-utils</artifactId>
<version>1.0.0</version>
<packaging>jar</packaging>
<dependencies>
<!-- JSON -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<scope>provided</scope>
</dependency>
<!-- Servlet(获取IP等) -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<scope>provided</scope>
</dependency>
<!-- Spring Context(国际化) -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<scope>provided</scope>
</dependency>
<!-- 日志 -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<scope>provided</scope>
</dependency>
</dependencies>
</project>
四、引入方使用
4.1 添加依赖
<dependency>
<groupId>com.example</groupId>
<artifactId>example-utils</artifactId>
<version>1.0.0</version>
</dependency>
4.2 使用示例
@Service
public class OrderService {
public void processOrder(OrderDto dto) {
// 参数校验
if (CheckEmptyUtil.isOrEmpty(dto.getOrderCode(), dto.getMemberId())) {
throw new IllegalArgumentException("参数不能为空");
}
// 日期处理
Date deliveryDate = DateUtil.convertToDate(dto.getDeliveryTime()); // 自动识别格式
Date deadline = DateUtil.getDateAfter(deliveryDate, 3); // 3天后
// JSON 序列化(记日志)
log.info("处理订单入参: {}", StringUtil.getJsonString(dto));
// 空值安全操作
if (CheckEmptyUtil.isNotEmpty(dto.getItemList())) {
dto.getItemList().forEach(item -> {
// 业务逻辑...
});
}
}
}
五、关键设计总结
| 设计要点 |
实现方式 |
收益 |
| 多类型统一判空 |
instanceof 分发 |
一个方法覆盖所有类型,减少重复代码 |
| 智能日期解析 |
按字符串长度 + 正则分发 |
无需调用方关心日期格式 |
| JSON 容错 |
序列化/反序列化异常返回 null |
不会因为一个字段异常导致整个请求失败 |
| 全局 ObjectMapper |
静态单例 + 线程安全配置 |
避免每次创建实例的开销 |
| ThreadLocal 上下文 |
请求级隔离用户信息 |
Service 层无需传参即可获取当前用户 |
| ThreadLocal 清理 |
delete() 方法 |
防止线程池复用导致数据串线 |
| 纯静态工具类 |
无 spring.factories,无 Bean 注册 |
任何项目引入即用,零配置 |
| provided scope |
核心依赖由引入方提供 |
不引入版本冲突 |