SpringBoot中通用工具类库(Utils)封装与使用实践

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(空值判断工具)

java 复制代码
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(旧版日期工具)

java 复制代码
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 工具)

java 复制代码
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(请求上下文工具)

java 复制代码
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(工具库)

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 添加依赖

xml 复制代码
<dependency>
    <groupId>com.example</groupId>
    <artifactId>example-utils</artifactId>
    <version>1.0.0</version>
</dependency>

4.2 使用示例

java 复制代码
@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 核心依赖由引入方提供 不引入版本冲突
相关推荐
Draina3 小时前
CBC填充预言攻击-CBC Padding Oracle Crypto Attack
python·安全·web安全·网络安全·密码学·安全性测试
Angel Q.3 小时前
特征提取 | DINO 到 DINOv3
图像处理·python
不如语冰4 小时前
AI大模型入门-pytorch-张量2 tensor创建
python
NPE~4 小时前
[AI]Agent开发——ADK框架使用
人工智能·python·ai·教程·adk·agent开发
矮个史蒂芬4 小时前
统计verilog .v文件中output 的bit数
python
gwf2164 小时前
磨损均衡算法(Wear Leveling)——SSD如何让每块闪存“公平退休“?
运维·数据库·人工智能·python·嵌入式硬件·算法·智能硬件
MemoleCard4 小时前
深度解析Java当中的锁机制
后端
wear工程师5 小时前
ThreadLocal 在线程池里为什么会串数据:别只会答内存泄漏
java·后端