尧图精选

SpringBoot项目必备工具类库盘点:从Hutool到MapStruct的实战选型指南

🕒 发布时间:2026/9/11 2:49:22 📁 来源:尧图网络
1. 为什么要专门整理一份工具类库清单先说说我为什么想写这篇东西。做了几年SpringBoot后端发现一个很有意思的现象很多项目组里每个程序员都有自己的“私藏工具包”有的人用Hutool用得飞起有的人只认Apache Commons还有的人干脆自己写工具类。结果就是同一个团队里字符串判空就有三四种写法对象拷贝一会儿用BeanUtils一会儿用MapStruct代码风格乱七八糟。SpringBoot本身确实帮我们解决了框架集成的问题但它并没有告诉我们业务代码里那些琐碎的活——集合判空、日期格式化、对象属性拷贝、Excel导入导出——该用什么工具类来处理。这些“脏活累活”如果全靠手写不仅浪费时间还容易埋bug。比如用String.split()处理空字符串、用SimpleDateFormat在多线程环境下撸日期都是经典翻车现场。所以这篇博文我把自己这些年实际在SpringBoot项目里用过、踩过坑、最后沉淀下来的工具类库清单整理出来。覆盖了集合操作、字符串处理、日期时间、对象拷贝、JSON序列化、Excel导入导出、文件解析等日常开发最高频的场景。每个库都给出真实的代码示例不是那种文档里复制出来的demo而是我在项目里实际这么写的写法。这篇内容适合谁看刚入行或者正在做毕业设计的同学可以直接把这些工具类用进自己的项目里代码质量立刻上一个台阶工作了三五年但一直在重复造轮子的老手也可以看看别人是怎么选型、怎么避坑的。我尽量把“为什么这样选”也讲清楚而不是只丢给你一堆工具类的API。2. 集合与通用工具Guava 和 Apache Commons 到底怎么选2.1 Guava 的不可变集合和 Multimap 是真香Guava是Google开源的一套Java核心库它在集合领域的功力可以说是“独步武林”。SpringBoot项目里引入Guava最常见的使用场景有三个不可变集合、Multimap、以及它的缓存模块。先看不可变集合。业务代码里经常要定义一些常量List或者Map如果直接new ArrayList()再往里add任何一个地方都能往里面塞数据很容易被无意修改。Guava的ImmutableList就解决了这个问题// 定义只读的白名单列表 private static final ImmutableListString WHITE_LIST ImmutableList.of( /api/login, /api/register, /api/captcha ); // 尝试修改会抛出 UnsupportedOperationException WHITE_LIST.add(/api/admin);这个写法在配置拦截器白名单、权限校验名单时非常实用。它的底层实现也是经过优化的遍历效率比普通ArrayList并不会差。再说Multimap。我们经常遇到一个场景一个Key对应多个Value比如一个用户可能有多个角色。如果用MapString, ListString每次put都要先判断key存不存在、不存在就新建一个List代码写起来很啰嗦。Guava的ArrayListMultimap直接帮你把这个逻辑封装好了MultimapString, String userRoles ArrayListMultimap.create(); userRoles.put(张三, admin); userRoles.put(张三, operator); userRoles.put(李四, viewer); // 直接获取张三的所有角色不会出现NullPointerException CollectionString roles userRoles.get(张三);Guava的缓存模块CacheBuilder也值得一说。本地缓存在很多场景下比Redis更合适——比如配置信息、数据字典这种更新频率极低但访问频率极高的数据。用Guava Cache可以设置过期时间、最大容量还能在缓存失效时自动加载LoadingCacheString, DictVO dictCache CacheBuilder.newBuilder() .maximumSize(1000) .expireAfterWrite(30, TimeUnit.MINUTES) .build(new CacheLoaderString, DictVO() { Override public DictVO load(String key) { // 这里写从数据库查询字典的逻辑 return dictMapper.selectByCode(key); } }); // 业务代码里直接用缓存没有就自动去load DictVO dict dictCache.get(order_status);2.2 Apache Commons Lang3 的字符串和对象操作Apache Commons家族太庞大了但在SpringBoot项目里我实际用得最多的还是commons-lang3。这个库里面的StringUtils类可以说是Java后端开发者的“瑞士军刀”。最常用的几个方法// 判空同时判断null、空字符串、纯空格 boolean blank StringUtils.isBlank(str); boolean notBlank StringUtils.isNotBlank(str); // 截取字符串 String sub StringUtils.substring(hello-world, 0, 5); // 判断是否包含忽略大小写 boolean contains StringUtils.containsIgnoreCase(Hello World, hello); // 数组转字符串 String joined StringUtils.join(new String[]{a, b, c}, ,);为什么推荐用StringUtils.isBlank()而不是自己写str ! null !str.isEmpty()因为isBlank把纯空格的情况也处理了。我之前就遇到过线上事故前端传了个全是空格的名字过来str.isEmpty()返回false程序把空格当成了合法输入存进了数据库最后查询的时候怎么都查不出来排查了半天。commons-lang3的RandomStringUtils也经常用到生成随机验证码、临时密码全靠它// 生成6位数字验证码 String code RandomStringUtils.randomNumeric(6); // 生成32位字母数字混合的临时密码 String tempPwd RandomStringUtils.randomAlphanumeric(32);不过这里要提醒一个坑RandomStringUtils生成的随机数是基于Random的不适合用来生成安全令牌、密钥这类对安全性要求极高的数据那种场景要用SecureRandom。2.3 Commons Collections4 和 BeanUtils 的实战用法commons-collections4是对JDK集合框架的补充它的CollectionUtils提供了很多集合判空和集合操作的方法。SpringBoot项目里的Controller层经常要判断前端传过来的List是否为空用这个就很方便import org.apache.commons.collections4.CollectionUtils; // 判断集合是否为空null或size为0都算空 if (CollectionUtils.isEmpty(list)) { // 参数校验不通过直接return } // 集合并集、交集、差集 CollectionString union CollectionUtils.union(list1, list2); CollectionString intersection CollectionUtils.intersection(list1, list2); CollectionString subtract CollectionUtils.subtract(list1, list2);这里我特别想说一下BeanUtils.copyProperties。Spring自带了一个Apache Commons也有一个两者方法签名一样但行为有区别Apache Commons的BeanUtils在拷贝时会自动做类型转换性能较差而且遇到不同类型还会悄悄忽略Spring的BeanUtils则以纯反射方式拷贝类型不一致直接抛异常。这个差异是面试高频题也是实际开发中容易踩的坑。我的建议是项目里如果需要对象属性拷贝优先用Spring的BeanUtils因为它在类型安全上更严而且Spring本身就在classpath里不需要额外引入依赖。但如果是大量、高频的对象转换场景比如DTO和Entity之间来回转换推荐用后面要讲的MapStruct性能碾压反射方案。3. 国产神器 Hutool一个库搞定日常开发3.1 为什么我强烈推荐Hutool如果说Guava算“精工细作”那Hutool就是“大而全”的代表。Hutool是国内程序员开源的一个Java工具类库它对JDK做了大量的封装API设计非常贴合中文开发者的使用习惯。文档全部是中文遇到问题直接看文档就能解决不用像看英文JavaDoc那样费劲。我早期对Hutool是有偏见的觉得“大而全”就意味着“不精”但实际用了之后发现它在绝大多数场景下都够用而且很好用。就说cn.hutool.core.util.IdUtil这个类生成唯一ID再也不用自己去写UUID拼接的代码了// 生成不带横线的UUID String uuid IdUtil.fastSimpleUUID(); // 生成带横线的UUID默认 String uuid2 IdUtil.fastUUID(); // 生成雪花算法ID适合做数据库主键 long snowflakeId IdUtil.getSnowflakeNextId();雪花算法生成的ID是Long类型在MySQL里用bigint做主键比用VARCHAR的UUID性能好不少因为B树对有序数字的插入更友好。以前团队里有人直接用UUID字符串做主键数据量到千万级别后索引膨胀得非常严重后来改成雪花算法ID才解决问题。3.2 Hutool 高频模块使用示例Hutool的工具类覆盖了开发中的方方面面我这里挑几个我项目里最常用的模块直接上代码。StrUtil是用来替代Apache Commons的StringUtils的import cn.hutool.core.util.StrUtil; // 格式化字符串类似slf4j的占位符方式 String msg StrUtil.format(用户{}登录成功IP: {}, userName, ip); // 驼峰转下划线配合MyBatis-Plus很好用 String columnName StrUtil.toUnderlineCase(userName); // 输出 user_name // 脱敏处理比如手机号、身份证号 String masked StrUtil.hide(13812345678, 3, 7); // 输出 138****5678DateUtil和LocalDateTimeUtil是处理日期时间的import cn.hutool.core.date.DateUtil; // 字符串转日期 DateTime dateTime DateUtil.parse(2024-06-01 10:30:00); // 日期格式化 String format DateUtil.format(dateTime, yyyy年MM月dd日); // 计算两个日期之间的间隔 long betweenDays DateUtil.between(date1, date2, DateUnit.DAY);尤其要提一下Hutool对LocalDateTime的支持。JDK8之后官方推荐用LocalDateTime替代Date但LocalDateTime的解析和格式化API用起来比较啰嗦。Hutool的LocalDateTimeUtil封装之后代码简洁很多import cn.hutool.core.date.LocalDateTimeUtil; LocalDateTime dateTime LocalDateTimeUtil.parse(2024-06-01 10:30:00, yyyy-MM-dd HH:mm:ss); String format LocalDateTimeUtil.format(dateTime, yyyy-MM-dd HH:mm:ss);Http请求工具HttpUtil同样非常实用。我在项目里做第三方接口回调通知时经常需要向外部系统发送HTTP请求用Hutool的HttpUtil几行代码就搞定了import cn.hutool.http.HttpUtil; // 发送GET请求 String result HttpUtil.get(https://api.example.com/order/123); // 发送POST请求传递JSON MapString, Object params new HashMap(); params.put(orderId, 123); String result2 HttpUtil.post(https://api.example.com/notify, JSONUtil.toJsonStr(params));3.3 使用Hutool的注意事项Hutool虽然好用但也有一些使用上的注意事项我踩过几次坑分享给大家。第一Hutool的BeanUtil.copyProperties性能一般。如果只是偶尔拷贝一两个对象没问题但如果是在循环里高频调用比如批量处理一万条数据性能会明显下降。这种场景建议用MapStruct或者写个静态方法手动set。第二Hutool的JSONUtil在做JSON序列化时对泛型的处理有时候会出现类型擦除问题。比如把ListOrderDTO从JSON字符串反序列化时JSONUtil.toList()方法能正常工作但如果是嵌套泛型MapString, ListOrderDTO它可能解析出来的还是JSONObject而不是OrderDTO。这种复杂场景我建议直接用Jackson的TypeReference。第三Hutool版本更新比较快有些API在旧版本里没有或者行为不同。建议直接使用最新稳定版我项目中用的是5.8.x版本API比较稳定。另外如果项目已经引入了Guava再引入Hutool确实会有一些功能重复但实际使用下来两者并不冲突Guava强在集合和缓存Hutool强在工具类覆盖面和中文文档。4. 代码简化类Lombok 和 MapStruct 的实践4.1 Lombok 在SpringBoot项目中的正确姿势Lombok在SpringBoot项目里的普及率已经非常高了它通过注解在编译期生成getter、setter、构造方法等样板代码让实体类保持简洁。我用它最频繁的注解就是Data和Builderimport lombok.Data; import lombok.Builder; Data Builder public class UserDTO { private Long id; private String userName; private String email; private Integer status; }有了Datagetter、setter、toString、equals、hashCode全部自动生成。有了Builder创建对象时可以非常优雅地链式赋值UserDTO dto UserDTO.builder() .id(1001L) .userName(张三) .email(zhangsanexample.com) .status(1) .build();在SpringBoot项目中还有几个高频组合是大家容易忽略的。Slf4j可以直接在类中注入log对象Slf4j Service public class OrderService { public void createOrder(OrderDTO dto) { log.info(开始创建订单订单号: {}, dto.getOrderNo()); // 业务逻辑... } }没有Slf4j的话你要在每个类里手动写private static final Logger log LoggerFactory.getLogger(OrderService.class);写了一百遍之后真的会烦。Slf4j直接解决。4.2 MapStruct对象属性拷贝的性能之王MapStruct是一个编译期生成对象映射代码的库它的性能比BeanUtils高一个数量级因为它在编译期就生成了原生的setter调用代码而不是靠运行时反射。SpringBoot项目里引入MapStruct很简单Maven依赖配置如下dependency groupIdorg.mapstruct/groupId artifactIdmapstruct/artifactId version1.5.5.Final/version /dependency然后在Maven的spring-boot-maven-plugin里加上lombok-mapstruct-binding不然Project Lombok和MapStruct一起用的时候Lombok生成的getter/setter在MapStruct编译期是看不到的会报找不到属性的错误plugin groupIdorg.apache.maven.plugins/groupId artifactIdmaven-compiler-plugin/artifactId configuration annotationProcessorPaths path groupIdorg.projectlombok/groupId artifactIdlombok/artifactId version1.18.30/version /path path groupIdorg.mapstruct/groupId artifactIdmapstruct-processor/artifactId version1.5.5.Final/version /path /annotationProcessorPaths /configuration /plugin创建一个Mapper接口import org.mapstruct.Mapper; import org.mapstruct.Mapping; import org.mapstruct.factory.Mappers; Mapper public interface UserMapper { UserMapper INSTANCE Mappers.getMapper(UserMapper.class); Mapping(source userName, target name) UserEntity dtoToEntity(UserDTO dto); }当DTO的属性名和Entity不一致时用Mapping来指定对应关系。编译之后MapStruct会生成一个实现类里面的代码就是你手写的那种逐属性set// 这是编译后生成的代码示意 public class UserMapperImpl implements UserMapper { Override public UserEntity dtoToEntity(UserDTO dto) { UserEntity entity new UserEntity(); entity.setName(dto.getUserName()); // ... return entity; } }没有任何反射开销性能可想而知。在批量处理数据、高频转换对象的场景下MapStruct的优势非常明显。5. JSON与文件处理Jackson、EasyExcel、Tika5.1 Jackson 的进阶玩法SpringBoot默认的JSON序列化框架就是Jackson所以只要项目里用了spring-boot-starter-webJackson就已经在classpath里了。但大部分人对Jackson的用法停留在ObjectMapper.writeValueAsString()的层面其实它还有很多实用特性。泛型反序列化要用TypeReference这个必须要会import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; ObjectMapper objectMapper new ObjectMapper(); // 反序列化成 ListOrderDTO ListOrderDTO orders objectMapper.readValue(jsonStr, new TypeReferenceListOrderDTO() {}); // 反序列化成 MapString, OrderDTO MapString, OrderDTO orderMap objectMapper.readValue(jsonStr, new TypeReferenceMapString, OrderDTO() {});如果不用TypeReference直接传List.class得到的结果是ListLinkedHashMap后续你调用getOrderNo()方法时会报ClassCastException这个坑我在实际项目里踩过不止一次。Jackson处理null值的策略也值得配置一下。默认情况下实体类里为null的字段在序列化成JSON时会被保留即{name: null}。如果你希望不输出null字段可以配置ObjectMapper objectMapper new ObjectMapper(); objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);在SpringBoot里可以直接通过配置文件全局生效spring.jackson.default-property-inclusionnon_null不过要提醒一下别把所有接口都设置成忽略null字段。有些前端需要根据接口返回的字段是否存在来判断某些逻辑如果后台一律不返回null字段可能导致前端拿到缺失字段而报错。这个要跟团队前端约定好再改。5.2 EasyExcel大文件导入导出的最佳选择Excel导入导出是后台管理系统躲不掉的需求。早期大家都用Apache POI直接操作写一个导出功能动不动两百行代码而且大文件导出时内存直接爆掉。阿里巴巴开源的EasyExcel就是为解决这个问题而生的它采用SAX模式解析Excel内存占用极低。SpringBoot项目里引入EasyExceldependency groupIdcom.alibaba/groupId artifactIdeasyexcel/artifactId version3.3.4/version /dependency定义一个和Excel表头对应的实体类import com.alibaba.excel.annotation.ExcelProperty; public class OrderExcelVO { ExcelProperty(订单号) private String orderNo; ExcelProperty(订单金额) private BigDecimal amount; ExcelProperty(下单时间) private String createTime; }导出数据到Excel文件ListOrderExcelVO dataList new ArrayList(); // 填充数据... String fileName /tmp/orders.xlsx; EasyExcel.write(fileName, OrderExcelVO.class) .sheet(订单数据) .doWrite(dataList);读取Excel文件也很简单EasyExcel.read(fileName, OrderExcelVO.class, new AnalysisEventListenerOrderExcelVO() { Override public void invoke(OrderExcelVO data, AnalysisContext context) { // 每解析到一行数据就会回调这个方法在这里做逐行处理 System.out.println(解析到一条数据: data); } Override public void doAfterAllAnalysed(AnalysisContext context) { // 所有数据解析完成后的回调 log.info(Excel解析完成); } }).sheet().doRead();这里要注意一个问题invoke方法回调是同步的默认是一个单元格一个单元格地解析如果数据量大处理逻辑尽量不要写在invoke里做耗时操作先把数据收集到List攒够一定数量再批量入库效率会高很多。5.3 Apache Tika从各类文档中提取文本Apache Tika是一个文件内容解析工具库专门用来从PDF、Word、Excel、HTML、图片等各种格式中提取文本内容和元数据。这个库在搜索引擎、知识库建设、内容审核系统里用得比较多。SpringBoot项目引入Tikadependency groupIdorg.apache.tika/groupId artifactIdtika-core/artifactId version2.9.0/version /dependency使用示例import org.apache.tika.Tika; Tika tika new Tika(); // 从文件路径直接提取文本 String text tika.parseToString(new File(/tmp/简历.pdf)); // 从字节数组提取文本适合上传文件的场景 byte[] fileBytes multipartFile.getBytes(); String content tika.parseToString(new FileInputStream(), new Metadata());需要注意Tika的parseToString方法依赖tika-parsers标准包才能解析各类格式只用tika-core的话只能处理少量文本格式。完整使用需要引入tika-parsers-standard-package。另外Tika处理超大PDF时耗时会比较长建议异步处理或者限制文件大小。6. 常见问题与排查技巧实录6.1 工具类静态引入带来的问题用好这些工具类库还得注意一个非常隐蔽的坑静态导入导致的代码可读性下降。有人为了少写几个字母喜欢这样写import static cn.hutool.core.util.StrUtil.*; // 然后代码里直接用 isBlank() if (isBlank(str)) { // ... }当项目里同时引入了Hutool和Apache Commons的StringUtils时两个库都有isBlank()方法静态导入会让IDE的自动导入变得混乱看代码的人根本不知道这个isBlank()是哪个库的。这个在Code Review时被我实名反对过。我的建议是保留类名的显式调用比如写StrUtil.isBlank(str)看起来没省多少事但代码的清晰度高了一整个档次。6.2 版本冲突处理策略SpringBoot项目引入多个工具类库后最常见的麻烦是依赖冲突。之前出现过一次比较典型的问题项目引了Hutool的5.8.x又引了依赖Hutool 4.x的第三方SDKMaven仲裁选了个4.x版本导致SDK正常但项目里用到5.8.x新API的代码直接编译不过。排查依赖冲突我是用Maven插件查看依赖树mvn dependency:tree -Dincludescn.hutool然后通过exclusion排除掉不需要的版本。这个操作一定要做不然项目跑一段时间后莫名其妙出现NoSuchMethodError非常折磨人。6.3 自制工具类什么时候才合理最后一点也是我认为最重要的工具类库已经这么丰富了什么情况下才值得自己写工具类我的判断标准是如果这个工具方法不是项目特有的业务逻辑而是通用的技术类操作先查一下Hutool和Apache Commons有没有现成的如果有就别自己写。自己写的工具类往往存在三个问题没有经过充分的边界测试、没有完整的文档说明、团队成员不知道有这个东西反而重复写了一遍。但如果是和业务紧密绑定的工具方法比如“订单号生成规则”“金额分转元并且去掉尾数0”这种那一定要自己写并且要把这类方法放到自己项目的utils或support包里加上清晰的注释和单元测试。工具类不是越少越好而是越准越好。我现在体会最深的一点工具类库选型不是越多越好也不是越少越好而是要让团队里每个成员都知道“这个东西我们已经有了不用重复造轮子”。建议在你的SpringBoot项目脚手架里把这些常用依赖提前配置好再在项目Wiki里建一个“常用工具类速查”文档列清楚什么场景该用哪个库的哪个方法。后面进来的新同事照着用就行不用再走一遍我踩过的坑。
上一篇/下一篇内容由系统自动关联 返回资讯列表 →