外观
Spring Boot 常用工具
约 4387 字大约 15 分钟
2025-08-16
一、为什么需要工具类
在 Spring Boot 项目开发中,我们经常遇到重复性工作:
- 对象属性拷贝
- HTTP 请求处理
- 日期格式化
- 参数校验
- Redis 操作
直接使用原生代码实现这些功能不仅繁琐,而且容易出错。而专业工具类能够:
- 减少样板代码:避免重复编写相同逻辑
- 提高代码质量:经过广泛测试,可靠性高
- 提升开发效率:一行代码替代多行实现
- 统一编码规范:团队内使用一致的工具方法
本文精选了 Spring Boot 项目中最常用的 6 大工具类,覆盖 80% 的日常开发场景,助你写出更简洁、更专业的代码。
二、核心工具类详解
1. Redis 工具类(RedisUtils)
为什么需要 RedisUtils
Spring Boot 虽然集成了 RedisTemplate,但原生 API 使用繁琐:
- 需要频繁进行类型转换
- 缺少批量操作支持
- 没有封装常用操作模式
基本配置
添加依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>配置 Redis(application.yml):
spring:
redis:
host: localhost
port: 6379
password:
database: 0
timeout: 5000
lettuce:
pool:
max-active: 8
max-wait: -1
max-idle: 8
min-idle: 0实用 RedisUtils 实现
@Component
public class RedisUtils {
private final RedisTemplate<String, Object> redisTemplate;
public RedisUtils(RedisTemplate<String, Object> redisTemplate) {
this.redisTemplate = redisTemplate;
}
/**
* 设置缓存
*/
public void set(String key, Object value) {
redisTemplate.opsForValue().set(key, value);
}
/**
* 设置缓存并指定过期时间
*/
public void set(String key, Object value, long time, TimeUnit timeUnit) {
redisTemplate.opsForValue().set(key, value, time, timeUnit);
}
/**
* 获取缓存
*/
public <T> T get(String key, Class<T> clazz) {
Object value = redisTemplate.opsForValue().get(key);
return value == null ? null : clazz.cast(value);
}
/**
* 删除缓存
*/
public boolean delete(String key) {
return Boolean.TRUE.equals(redisTemplate.delete(key));
}
/**
* 判断缓存是否存在
*/
public boolean hasKey(String key) {
return Boolean.TRUE.equals(redisTemplate.hasKey(key));
}
/**
* 设置过期时间
*/
public boolean expire(String key, long time, TimeUnit timeUnit) {
return Boolean.TRUE.equals(redisTemplate.expire(key, time, timeUnit));
}
/**
* Hash 类型操作
*/
public <T> T hGet(String key, Object hashKey, Class<T> clazz) {
Object value = redisTemplate.opsForHash().get(key, hashKey);
return value == null ? null : clazz.cast(value);
}
public void hSet(String key, Object hashKey, Object value) {
redisTemplate.opsForHash().put(key, hashKey, value);
}
}常用场景示例
// 1. 缓存用户信息
User user = userService.getUserById(1L);
redisUtils.set("user:1", user, 30, TimeUnit.MINUTES);
// 2. 获取缓存用户
User cachedUser = redisUtils.get("user:1", User.class);
// 3. 限流(每分钟最多10次)
String limitKey = "api:limit:user:" + userId;
if (redisUtils.hasKey(limitKey)) {
Long count = redisUtils.get(limitKey, Long.class);
if (count >= 10) {
throw new RuntimeException("请求过于频繁");
}
redisUtils.set(limitKey, count + 1, 1, TimeUnit.MINUTES);
} else {
redisUtils.set(limitKey, 1L, 1, TimeUnit.MINUTES);
}最佳实践:使用前缀规范命名空间(如
user:,order:),避免 key 冲突
2. JWT 工具类(JwtUtil)
为什么需要 JwtUtil
在微服务架构中,JWT(JSON Web Token)是实现无状态认证的最佳实践:
- 避免服务端存储 session
- 适合分布式系统
- 支持跨域认证
添加依赖
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>实用 JwtUtil 实现
@Component
public class JwtUtil {
// 密钥(应从配置文件获取,生产环境不应硬编码)
private static final String SECRET_KEY = "your-256-bit-secret";
private static final long EXPIRATION = 3600000; // 1小时
/**
* 生成 JWT token
*/
public String generateToken(String subject, Map<String, Object> claims) {
return Jwts.builder()
.setClaims(claims)
.setSubject(subject)
.setIssuedAt(new Date())
.setExpiration(new Date(System.currentTimeMillis() + EXPIRATION))
.signWith(SignatureAlgorithm.HS512, SECRET_KEY)
.compact();
}
/**
* 生成简单 token(只有用户名)
*/
public String generateToken(String username) {
Map<String, Object> claims = new HashMap<>();
return generateToken(username, claims);
}
/**
* 从 token 中获取用户名
*/
public String getUsernameFromToken(String token) {
return getClaimFromToken(token, Claims::getSubject);
}
/**
* 从 token 中获取特定声明
*/
public <T> T getClaimFromToken(String token, Function<Claims, T> claimsResolver) {
final Claims claims = getAllClaimsFromToken(token);
return claimsResolver.apply(claims);
}
/**
* 获取所有声明
*/
private Claims getAllClaimsFromToken(String token) {
return Jwts.parser()
.setSigningKey(SECRET_KEY)
.parseClaimsJws(token)
.getBody();
}
/**
* 验证 token 是否有效
*/
public boolean validateToken(String token, UserDetails userDetails) {
final String username = getUsernameFromToken(token);
return (username.equals(userDetails.getUsername()) && !isTokenExpired(token));
}
/**
* 检查 token 是否过期
*/
private boolean isTokenExpired(String token) {
final Date expiration = getClaimFromToken(token, Claims::getExpiration);
return expiration.before(new Date());
}
/**
* 从请求中解析 token
*/
public String getTokenFromRequest(HttpServletRequest request) {
String bearerToken = request.getHeader("Authorization");
if (StringUtils.hasText(bearerToken) && bearerToken.startsWith("Bearer ")) {
return bearerToken.substring(7);
}
return null;
}
}常用场景示例
// 1. 登录生成 token
@PostMapping("/login")
public Result login(@RequestBody LoginRequest request) {
// 验证用户名密码
UserDetails userDetails = userDetailsService.loadUserByUsername(request.getUsername());
if (!passwordEncoder.matches(request.getPassword(), userDetails.getPassword())) {
return Result.error("用户名或密码错误");
}
// 生成 token
Map<String, Object> claims = new HashMap<>();
claims.put("role", userDetails.getAuthorities());
String token = jwtUtil.generateToken(userDetails.getUsername(), claims);
return Result.success(Collections.singletonMap("token", token));
}
// 2. 拦截器验证 token
public class JwtInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
String token = jwtUtil.getTokenFromRequest(request);
if (token == null) {
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
return false;
}
try {
String username = jwtUtil.getUsernameFromToken(token);
// 这里可以添加更多验证逻辑
if (jwtUtil.validateToken(token, userDetailsService.loadUserByUsername(username))) {
return true;
}
} catch (Exception e) {
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
}
return false;
}
}最佳实践:
- 密钥不要硬编码,应从配置中心获取
- 设置合理的过期时间,重要操作需二次验证
- 敏感操作建议使用 refresh token 机制
3. 统一响应结果类(Result)
为什么需要 Result
在 RESTful API 开发中,统一响应格式至关重要:
- 前后端约定一致的数据结构
- 简化前端错误处理逻辑
- 便于全局异常处理
- 提升 API 可用性
实用 Result 实现
@Data
public class Result<T> {
private int code;
private String message;
private T data;
public static <T> Result<T> success(T data) {
Result<T> result = new Result<>();
result.setCode(200);
result.setMessage("成功");
result.setData(data);
return result;
}
public static Result error(String message) {
return error(500, message);
}
public static Result error(int code, String message) {
Result result = new Result();
result.setCode(code);
result.setMessage(message);
return result;
}
public static Result unauthorized(String message) {
return error(401, message);
}
public static Result forbidden(String message) {
return error(403, message);
}
public static Result notFound(String message) {
return error(404, message);
}
public static Result validateFailed(String message) {
return error(400, message);
}
}常用错误码规范
| 错误码 | 含义 | 场景 |
|---|---|---|
| 200 | 成功 | 正常响应 |
| 400 | 参数错误 | 客户端传参错误 |
| 401 | 未授权 | 未登录或token失效 |
| 403 | 禁止访问 | 无权限操作 |
| 404 | 资源不存在 | URL错误或资源被删除 |
| 500 | 服务器错误 | 服务端异常 |
| 1000+ | 业务错误 | 自定义业务异常 |
使用示例
// 1. 成功响应
@GetMapping("/users/{id}")
public Result<User> getUser(@PathVariable Long id) {
User user = userService.getUserById(id);
return Result.success(user);
}
// 2. 错误响应
@PostMapping("/login")
public Result login(@RequestBody LoginRequest request) {
if (StringUtils.isEmpty(request.getUsername()) ||
StringUtils.isEmpty(request.getPassword())) {
return Result.validateFailed("用户名或密码不能为空");
}
// ...
}
// 3. 全局异常处理中使用
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(BusinessException.class)
public Result handleBusinessException(BusinessException e) {
return Result.error(e.getErrorCode().getCode(), e.getMessage());
}
}最佳实践:
- 保持响应结构一致性,不要随意更改
- 业务错误使用 1000+ 的错误码,避免与 HTTP 状态码冲突
- 敏感信息不要放入错误消息中
4. HTTP 工具类(HttpUtils)
为什么需要 HttpUtils
在微服务架构中,服务间调用非常频繁:
- RestTemplate 使用繁琐
- 需要处理连接超时、读取超时
- 需要处理异常情况
- 需要支持 JSON 序列化/反序列化
添加依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>实用 HttpUtils 实现
@Component
public class HttpUtils {
private final RestTemplate restTemplate;
private final ObjectMapper objectMapper;
@Autowired
public HttpUtils(RestTemplateBuilder restTemplateBuilder, ObjectMapper objectMapper) {
this.restTemplate = restTemplateBuilder
.setConnectTimeout(Duration.ofMillis(5000))
.setReadTimeout(Duration.ofMillis(10000))
.build();
this.objectMapper = objectMapper;
}
/**
* GET 请求
*/
public <T> T get(String url, Class<T> responseType, Map<String, String> headers, Object... uriVariables) {
try {
HttpHeaders httpHeaders = new HttpHeaders();
if (headers != null) {
httpHeaders.setAll(headers);
}
HttpEntity<Void> requestEntity = new HttpEntity<>(httpHeaders);
ResponseEntity<String> response = restTemplate.exchange(
url, HttpMethod.GET, requestEntity, String.class, uriVariables);
return parseResponse(response, responseType);
} catch (Exception e) {
throw new RuntimeException("GET 请求失败: " + url, e);
}
}
/**
* POST 请求(JSON)
*/
public <T> T post(String url, Object requestBody, Class<T> responseType, Map<String, String> headers) {
try {
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.setContentType(MediaType.APPLICATION_JSON);
if (headers != null) {
httpHeaders.setAll(headers);
}
String jsonBody = objectMapper.writeValueAsString(requestBody);
HttpEntity<String> requestEntity = new HttpEntity<>(jsonBody, httpHeaders);
ResponseEntity<String> response = restTemplate.postForEntity(url, requestEntity, String.class);
return parseResponse(response, responseType);
} catch (Exception e) {
throw new RuntimeException("POST 请求失败: " + url, e);
}
}
/**
* 解析响应
*/
private <T> T parseResponse(ResponseEntity<String> response, Class<T> responseType) {
if (response.getStatusCode().is2xxSuccessful()) {
try {
return objectMapper.readValue(response.getBody(), responseType);
} catch (JsonProcessingException e) {
throw new RuntimeException("响应解析失败", e);
}
} else {
throw new RuntimeException("HTTP 错误: " + response.getStatusCodeValue() + ", 响应: " + response.getBody());
}
}
}使用示例
// 1. 调用用户服务获取用户信息
public User getUserFromUserService(Long userId) {
String url = "http://user-service/api/users/{userId}";
Map<String, String> headers = Collections.singletonMap("Authorization", "Bearer " + token);
return httpUtils.get(url, User.class, headers, userId);
}
// 2. 调用订单服务创建订单
public Order createOrder(OrderRequest orderRequest) {
String url = "http://order-service/api/orders";
Map<String, String> headers = new HashMap<>();
headers.put("Authorization", "Bearer " + token);
headers.put("X-Request-Id", UUID.randomUUID().toString());
return httpUtils.post(url, orderRequest, Order.class, headers);
}
// 3. 文件上传
public String uploadFile(MultipartFile file) {
String url = "http://file-service/api/upload";
// 创建 multipart 请求
MultiValueMap<String, Object> body = new LinkedMultiValueMap<>();
body.add("file", new ByteArrayResource(file.getBytes()) {
@Override
public String getFilename() {
return file.getOriginalFilename();
}
});
// 设置 headers
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.MULTIPART_FORM_DATA);
// 发送请求
HttpEntity<MultiValueMap<String, Object>> requestEntity = new HttpEntity<>(body, headers);
ResponseEntity<String> response = restTemplate.postForEntity(url, requestEntity, String.class);
if (response.getStatusCode().is2xxSuccessful()) {
return response.getBody();
}
throw new RuntimeException("文件上传失败");
}最佳实践:
- 为每个外部服务调用设置合理的超时时间
- 重要调用添加重试机制(可结合 Spring Retry)
- 记录关键请求日志,便于问题排查
- 对于高频率调用,考虑使用 Feign 客户端替代
5. Hutool 工具库
为什么选择 Hutool
Hutool 是一个小而全的 Java 工具库,相比 Apache Commons 更简洁易用:
- API 设计更符合 Java 习惯
- 无额外依赖,轻量级
- 文档完善,中文支持好
- 涵盖开发中 80% 的常用场景
添加依赖
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.16</version>
</dependency>常用功能详解
5.1 日期时间处理
// 1. 当前时间
DateTime now = DateUtil.date();
String nowStr = DateUtil.now(); // "2023-08-24 15:30:45"
// 2. 日期格式化
String format = DateUtil.format(now, "yyyy-MM-dd HH:mm:ss");
// 3. 日期解析
DateTime date = DateUtil.parse("2023-08-24", "yyyy-MM-dd");
// 4. 日期计算
DateTime tomorrow = DateUtil.offsetDay(now, 1);
DateTime lastWeek = DateUtil.offsetWeek(now, -1);
// 5. 日期比较
long between = DateUtil.between(now, tomorrow, DateUnit.DAY); // 1
boolean isAfter = DateUtil.isAfter(now, tomorrow); // false
// 6. 日期截取(取当天开始)
DateTime beginOfDay = DateUtil.beginOfDay(now);5.2 字符串处理
// 1. 判空
boolean isEmpty = StrUtil.isEmpty(null); // true
boolean isBlank = StrUtil.isBlank(" "); // true
// 2. 字符串截取
String sub = StrUtil.sub("Hello World", 0, 5); // "Hello"
// 3. 字符串格式化
String format = StrUtil.format("姓名:{},年龄:{}", "张三", 25); // "姓名:张三,年龄:25"
// 4. 字符串转义/反转义
String escape = StrUtil.escapeHtml4("<div>test</div>"); // "<div>test</div>"
String unescape = StrUtil.unescapeHtml4(escape); // "<div>test</div>"
// 5. 驼峰与下划线转换
String camel = StrUtil.toCamelCase("user_name"); // "userName"
String underline = StrUtil.toUnderlineCase("userName"); // "user_name"5.3 JSON 处理
// 1. 对象转 JSON
User user = new User(1L, "张三", 25);
String json = JSONUtil.toJsonStr(user);
// 2. JSON 转对象
User user2 = JSONUtil.toBean(json, User.class);
// 3. JSON 转 List
String jsonArray = "[{\"id\":1,\"name\":\"张三\"},{\"id\":2,\"name\":\"李四\"}]";
List<User> userList = JSONUtil.toList(JSONUtil.parseArray(jsonArray), User.class);
// 4. JSON 转 Map
Map<String, Object> map = JSONUtil.toBean(json, Map.class);5.4 文件处理
// 1. 读取文件
String content = FileUtil.readUtf8String("test.txt");
// 2. 写入文件
FileUtil.writeUtf8String("Hello World", "test.txt");
// 3. 文件复制
FileUtil.copy("source.txt", "dest.txt", true);
// 4. 创建临时文件
File tempFile = FileUtil.createTempFile();
// 5. 文件类型检测
String type = FileTypeUtil.getType("test.jpg"); // "jpg"5.5 加密解密
// 1. MD5 加密
String md5 = SecureUtil.md5("123456");
// 2. SHA256 加密
String sha256 = SecureUtil.sha256("123456");
// 3. AES 加密
String key = "0123456789abcdef"; // 16位
String encrypt = SecureUtil.aes(key.getBytes()).encryptHex("Hello World");
String decrypt = SecureUtil.aes(key.getBytes()).decryptStr(encrypt);
// 4. RSA 加密
KeyPair pair = SecureUtil.generateKeyPair("RSA");
String publicKey = Base64.encode(pair.getPublic().getEncoded());
String privateKey = Base64.encode(pair.getPrivate().getEncoded());
// 加密
String encrypted = SecureUtil.rsa(null, privateKey).encryptBcd("Hello World", CharsetUtil.CHARSET_UTF_8);
// 解密
String decrypted = SecureUtil.rsa(publicKey, null).decryptStr(encrypted, CharsetUtil.CHARSET_UTF_8);最佳实践:
- 优先使用 Hutool 替代 Apache Commons Lang
- 对于复杂 JSON 操作,Hutool + Jackson 组合使用
- 日期处理统一使用 Hutool 的 DateUtil,避免 JDK 8 之前的日期问题
6. Google Guava
为什么选择 Guava
Google Guava 是 Google 开源的 Java 核心库,提供许多高质量的工具类:
- 经过 Google 内部大规模验证
- 提供 JDK 没有的高级功能
- 代码质量高,设计优雅
- 广泛应用于各大开源项目
添加依赖
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>32.1.2-jre</version>
</dependency>常用功能详解
6.1 集合工具(Collections)
// 1. 创建不可变集合
List<String> list = ImmutableList.of("a", "b", "c");
Set<String> set = ImmutableSet.of("a", "b", "c");
Map<String, Integer> map = ImmutableMap.of("a", 1, "b", 2, "c", 3);
// 2. 创建带初始值的 Map
Map<String, Integer> map2 = Maps.newHashMap();
map2.put("a", 1);
map2.put("b", 2);
// 3. 多值映射(一个 key 对应多个 value)
Multimap<String, String> multimap = ArrayListMultimap.create();
multimap.put("fruits", "apple");
multimap.put("fruits", "banana");
multimap.put("vegetables", "carrot");
List<String> fruits = (List<String>) multimap.get("fruits"); // ["apple", "banana"]
// 4. 双向映射
BiMap<String, String> biMap = HashBiMap.create();
biMap.put("Apple", "苹果");
biMap.put("Banana", "香蕉");
String chinese = biMap.get("Apple"); // "苹果"
String english = biMap.inverse().get("苹果"); // "Apple"6.2 前置条件检查(Preconditions)
// 1. 检查参数
public void setUserAge(int age) {
// 如果 age < 0,抛出 IllegalArgumentException
Preconditions.checkArgument(age >= 0, "年龄不能为负数: %s", age);
// ...
}
// 2. 检查状态
public void start() {
// 如果 isStarted() 为 true,抛出 IllegalStateException
Preconditions.checkState(!isStarted(), "已经启动了");
// ...
}
// 3. 检查对象非空
public void process(User user) {
// 如果 user 为 null,抛出 NullPointerException
User validUser = Preconditions.checkNotNull(user, "用户不能为空");
// ...
}6.3 缓存工具(Cache)
// 1. 创建缓存
LoadingCache<String, User> userCache = CacheBuilder.newBuilder()
.maximumSize(1000) // 最多缓存 1000 个条目
.expireAfterWrite(10, TimeUnit.MINUTES) // 10 分钟未写入则过期
.refreshAfterWrite(5, TimeUnit.MINUTES) // 5 分钟后异步刷新
.build(
new CacheLoader<String, User>() {
public User load(String userId) {
return userService.getUserById(userId);
}
});
// 2. 使用缓存
User user = userCache.get("123"); // 如果缓存中没有,会调用 load 方法
// 3. 手动放入缓存
userCache.put("456", new User("456", "李四"));
// 4. 手动移除缓存
userCache.invalidate("456");6.4 字符串处理(Strings)
// 1. 字符串连接
String joined = Joiner.on(",").join(Arrays.asList("a", "b", "c")); // "a,b,c"
// 2. 字符串分割
List<String> split = Splitter.on(",")
.trimResults()
.omitEmptyStrings()
.splitToList("a, b, c, , d"); // ["a", "b", "c", "d"]
// 3. 字符串前缀/后缀处理
boolean startsWith = Strings.commonPrefix("hello world", "hello java"); // "hello "
boolean endsWith = Strings.commonSuffix("hello world", "good world"); // " world"6.5 函数式编程(Functional)
// 1. 转换集合
List<String> names = Lists.transform(users, User::getName);
// 2. 过滤集合
Predicate<User> adultFilter = user -> user.getAge() >= 18;
List<User> adults = Lists.newArrayList(Collections2.filter(users, adultFilter));
// 3. 函数组合
Function<String, Integer> strToInt = Integer::parseInt;
Function<Integer, Double> intToDouble = i -> (double) i;
Function<String, Double> strToDouble = Functions.compose(intToDouble, strToInt);
Double result = strToDouble.apply("123"); // 123.0最佳实践:
- 集合操作优先使用 Guava 的 Immutable 集合
- 参数校验使用 Preconditions 替代 if-else
- 简单缓存场景使用 Guava Cache,复杂场景考虑 Caffeine
- 字符串处理使用 Splitter/Joiner 替代 String.split()
三、工具类对比与选择建议
1. 集合操作对比
| 工具 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| Guava | 不可变集合、多值映射、双向映射 | 学习曲线稍高 | 需要高级集合功能 |
| Hutool | 简单易用、中文文档 | 功能相对简单 | 快速开发、小型项目 |
| JDK Stream | 原生支持、无需额外依赖 | 语法相对复杂 | 简单集合操作 |
2. 字符串处理对比
| 工具 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| Hutool | 丰富的字符串工具 | 无 | 日常开发首选 |
| Guava | Splitter/Joiner 高级功能 | API 较复杂 | 复杂字符串处理 |
| Apache Commons | 功能全面 | 依赖较重 | 旧项目迁移 |
3. 缓存工具对比
| 工具 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| Guava Cache | 轻量、API 简单 | 分布式支持弱 | 本地缓存 |
| Caffeine | 性能更高、功能更全 | 较新、文档较少 | 高性能本地缓存 |
| Spring Cache | 与 Spring 集成好 | 依赖 Spring | Spring 项目 |
| Redis | 分布式、持久化 | 需要额外服务 | 分布式系统 |
四、最佳实践总结
1. 工具类使用原则
- 不要重复造轮子:优先使用成熟工具库
- 保持一致性:团队内统一使用同一套工具
- 了解原理:不要盲目使用,了解工具内部实现
- 避免过度依赖:简单操作可直接使用 JDK 原生 API
2. 推荐工具组合
| 场景 | 推荐工具 | 说明 |
|---|---|---|
| 通用工具 | Hutool | 覆盖 80% 的日常开发场景 |
| 集合操作 | Guava | 处理复杂集合关系 |
| HTTP 调用 | Spring RestTemplate + HttpUtils | 封装后更易用 |
| 缓存 | Guava Cache (本地) + Redis (分布式) | 根据场景选择 |
| 日期处理 | Hutool DateUtil | 比 JDK 日期 API 更友好 |
3. 避坑指南
- 不要过度封装:简单的操作直接使用原生 API
- 注意线程安全:如 SimpleDateFormat 不是线程安全的
- 避免内存泄漏:缓存需要设置合理的过期策略
- 谨慎处理异常:不要吞掉异常,至少记录日志
- 性能考量:高频调用的方法避免使用反射
五、完整示例:工具类整合应用
1. 用户服务示例
@Service
public class UserService {
private final UserRepository userRepository;
private final RedisUtils redisUtils;
private final HttpUtils httpUtils;
@Autowired
public UserService(UserRepository userRepository,
RedisUtils redisUtils,
HttpUtils httpUtils) {
this.userRepository = userRepository;
this.redisUtils = redisUtils;
this.httpUtils = httpUtils;
}
public User getUserById(String userId) {
// 1. 先查缓存
String cacheKey = "user:" + userId;
User user = redisUtils.get(cacheKey, User.class);
if (user != null) {
return user;
}
// 2. 缓存未命中,查数据库
user = userRepository.findById(userId)
.orElseThrow(() -> new BusinessException(ErrorCode.USER_NOT_FOUND));
// 3. 写入缓存
redisUtils.set(cacheKey, user, 30, TimeUnit.MINUTES);
return user;
}
public User register(UserRegisterDTO registerDTO) {
// 1. 参数校验
Preconditions.checkNotNull(registerDTO, "注册信息不能为空");
Preconditions.checkArgument(StrUtil.isNotBlank(registerDTO.getUsername()), "用户名不能为空");
Preconditions.checkArgument(ReUtil.isMatch("^[a-zA-Z0-9_]{4,16}$", registerDTO.getUsername()),
"用户名格式不正确");
Preconditions.checkArgument(registerDTO.getPassword().length() >= 6,
"密码长度不能少于6位");
// 2. 检查用户名是否已存在
if (userRepository.existsByUsername(registerDTO.getUsername())) {
throw new BusinessException(ErrorCode.USERNAME_EXISTS);
}
// 3. 创建用户
User user = new User();
user.setUsername(registerDTO.getUsername());
user.setPassword(EncryptUtil.md5(registerDTO.getPassword()));
user.setCreateTime(DateUtil.date());
// 4. 保存用户
User savedUser = userRepository.save(user);
// 5. 发送欢迎消息(异步)
new Thread(() -> {
try {
Map<String, Object> params = MapUtil.builder("username", user.getUsername())
.put("time", DateUtil.now()).map();
String content = TemplateUtil.render("welcome.ftl", params);
Map<String, String> headers = Collections.singletonMap("Authorization", "Bearer " + getToken());
httpUtils.post("http://message-service/api/send",
new MessageRequest("welcome", content),
String.class,
headers);
} catch (Exception e) {
LogUtil.error("发送欢迎消息失败", e);
}
}).start();
return savedUser;
}
private String getToken() {
// 获取 token 的逻辑
return "token";
}
}2. 全局异常处理整合
@RestControllerAdvice
@Slf4j
public class GlobalExceptionHandler {
@ExceptionHandler(BusinessException.class)
public Result handleBusinessException(BusinessException e) {
log.warn("业务异常 [{}]: {}", e.getErrorCode().getCode(), e.getMessage());
return Result.error(e.getErrorCode().getCode(), e.getMessage());
}
@ExceptionHandler(MethodArgumentNotValidException.class)
public Result handleValidationException(MethodArgumentNotValidException e) {
String message = e.getBindingResult().getFieldError().getDefaultMessage();
log.warn("参数验证异常: {}", message);
return Result.validateFailed(message);
}
@ExceptionHandler(Exception.class)
public Result handleOtherException(HttpServletRequest request, Exception e) {
String requestId = request.getHeader("X-Request-Id");
log.error("系统异常 [{}]: {}", requestId, e.getMessage(), e);
return Result.error("服务器内部错误");
}
}六、总结
本文介绍了 Spring Boot 项目中最常用的 6 大工具类,覆盖了 80% 的日常开发场景:
- RedisUtils:简化 Redis 操作,提升缓存使用效率
- JwtUtil:实现安全的 JWT 令牌生成与验证
- Result:统一 API 响应格式,提升前后端协作效率
- HttpUtils:封装 HTTP 调用,简化微服务间通信
- Hutool:小而全的 Java 工具库,解决日常开发痛点
- Google Guava:高质量工具库,提供高级功能支持
