【Java项目脚手架系列】第七篇:Spring Boot + Redis项目脚手架
【Java项目脚手架系列】第七篇:Spring Boot + Redis项目脚手架
前言
在前面的文章中,我们介绍了 Spring Boot + JPA 项目脚手架。今天,我们将介绍 Spring Boot + Redis 项目脚手架,这是一个用于快速搭建缓存应用的框架。
什么是 Spring Boot + Redis?
Spring Boot + Redis 是一个强大的组合,它提供了:
- Spring Boot 的快速开发能力
- Redis 的高性能缓存支持
- 完整的缓存操作支持
- 连接池管理能力
- 测试框架支持
技术栈
- Spring Boot 2.7.0:核心框架
- Spring Data Redis:缓存框架
- Redis 6.2:缓存数据库
- JUnit 5:测试框架
- Mockito:测试框架
- Maven 3.9.6:项目构建工具
Spring Boot + Redis 项目脚手架
1. 项目结构
src
├── main
│ ├── java
│ │ └── com
│ │ └── example
│ │ ├── RedisApplication.java # 应用程序入口
│ │ ├── config
│ │ │ └── RedisConfig.java # Redis 配置类(使用 StringRedisTemplate)
│ │ ├── controller
│ │ │ └── RedisController.java # Redis 操作控制器
│ │ └── service
│ │ └── RedisService.java # Redis 服务类(基于 StringRedisTemplate)
│ └── resources
│ └── application.yml # 主配置文件
└── test├── java│ └── com│ └── example│ ├── controller│ │ └── RedisControllerTest.java # 控制器测试类│ └── service│ └── RedisServiceTest.java # 服务层测试类└── resources└── application-test.yml # 测试环境配置文件
2. 核心文件内容
2.1 pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><groupId>com.example</groupId><artifactId>springboot-redis-scaffold</artifactId><version>1.0-SNAPSHOT</version><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.7.0</version><relativePath/></parent><properties><java.version>1.8</java.version><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding><project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding></properties><dependencies><!-- Spring Boot Starter --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><!-- Redis --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId></dependency><!-- Lombok --><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><optional>true</optional></dependency><!-- Test --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency><!-- Redis connection pool dependency --><dependency><groupId>org.apache.commons</groupId><artifactId>commons-pool2</artifactId><version>2.11.1</version></dependency></dependencies><build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId><configuration><excludes><exclude><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId></exclude></excludes></configuration></plugin></plugins></build>
</project>
2.2 application.yml
server:port: 8080spring:redis:host: localhostport: 6379database: 0timeout: 10000lettuce:pool:max-active: 8max-wait: -1max-idle: 8min-idle: 0logging:level:com.example: debugorg.springframework.data.redis: debug
2.3 application-test.yml
spring:redis:host: localhostport: 6379database: 1 # 使用不同的数据库进行测试timeout: 1000lettuce:pool:max-active: 8max-wait: -1max-idle: 8min-idle: 0
3. 核心功能实现
3.1 Redis配置类
@Configuration
public class RedisConfig {@Beanpublic RedisConnectionFactory redisConnectionFactory() {RedisStandaloneConfiguration redisConfig = new RedisStandaloneConfiguration();redisConfig.setHostName("localhost");redisConfig.setPort(6379);redisConfig.setDatabase(0);LettucePoolingClientConfiguration clientConfig = LettucePoolingClientConfiguration.builder().commandTimeout(Duration.ofSeconds(5)).clientOptions(ClientOptions.builder().socketOptions(SocketOptions.builder().connectTimeout(Duration.ofSeconds(5)).build()).build()).build();return new LettuceConnectionFactory(redisConfig, clientConfig);}@Beanpublic StringRedisTemplate stringRedisTemplate(RedisConnectionFactory connectionFactory) {return new StringRedisTemplate(connectionFactory);}
}
3.2 Redis服务类
@Service
@RequiredArgsConstructor
public class RedisService {private final StringRedisTemplate stringRedisTemplate;/*** 设置缓存*/public void set(String key, String value) {stringRedisTemplate.opsForValue().set(key, value);}/*** 设置缓存并设置过期时间*/public void set(String key, String value, long timeout, TimeUnit unit) {stringRedisTemplate.opsForValue().set(key, value, timeout, unit);}/*** 获取缓存*/public String get(String key) {return stringRedisTemplate.opsForValue().get(key);}/*** 删除缓存*/public Boolean delete(String key) {return stringRedisTemplate.delete(key);}/*** 判断key是否存在*/public Boolean exists(String key) {return stringRedisTemplate.hasKey(key);}/*** 设置过期时间*/public Boolean expire(String key, long timeout, TimeUnit unit) {return stringRedisTemplate.expire(key, timeout, unit);}
}
3.3 Redis控制器
@RestController
@RequestMapping("/api/redis")
@RequiredArgsConstructor
public class RedisController {private final RedisService redisService;private final RedisConnectionFactory redisConnectionFactory;@GetMapping("/health")public String health() {try {redisConnectionFactory.getConnection().ping();return "OK";} catch (Exception e) {return "Redis connection error: " + e.getMessage();}}@PostMapping("/{key}")public void setValue(@PathVariable String key, @RequestBody String value) {redisService.set(key, value);}@PostMapping("/{key}/expire/{timeout}")public void setValueWithExpire(@PathVariable String key, @RequestBody String value,@PathVariable long timeout) {redisService.set(key, value, timeout, TimeUnit.SECONDS);}@GetMapping("/{key}")public String getValue(@PathVariable String key) {return redisService.get(key);}@DeleteMapping("/{key}")public Boolean deleteValue(@PathVariable String key) {return redisService.delete(key);}@GetMapping("/{key}/exists")public Boolean hasKey(@PathVariable String key) {return redisService.exists(key);}
}
4. 测试实现
4.1 服务层测试
@SpringBootTest
@ActiveProfiles("test")
public class RedisServiceTest {@Autowiredprivate RedisService redisService;@Autowiredprivate StringRedisTemplate stringRedisTemplate;@BeforeEachvoid setUp() {// 每个测试前清理所有键stringRedisTemplate.getConnectionFactory().getConnection().flushAll();}@Testvoid testSetAndGet() {// GivenString key = "test:key";String value = "test:value";// WhenredisService.set(key, value);String result = redisService.get(key);// ThenassertEquals(value, result);}@Testvoid testDelete() {// GivenString key = "test:key";String value = "test:value";redisService.set(key, value);// WhenredisService.delete(key);String result = redisService.get(key);// ThenassertNull(result);}@Testvoid testSetWithExpiration() throws InterruptedException {// GivenString key = "test:expire:key";String value = "test:expire:value";long timeout = 1; // 1 second// WhenredisService.set(key, value, timeout, TimeUnit.SECONDS);String result = redisService.get(key);// ThenassertEquals(value, result);// Wait for expirationThread.sleep(1100);String expiredResult = redisService.get(key);assertNull(expiredResult);}@Testvoid testExists() {// GivenString key = "test:exists:key";String value = "test:exists:value";// WhenredisService.set(key, value);boolean exists = redisService.exists(key);boolean notExists = redisService.exists("non:existing:key");// ThenassertTrue(exists);assertFalse(notExists);}
}
4.2 控制器层测试
@SpringBootTest
@AutoConfigureMockMvc
@ActiveProfiles("test")
public class RedisControllerTest {@Autowiredprivate MockMvc mockMvc;@Autowiredprivate RedisService redisService;@BeforeEachvoid setUp() {// 每个测试前清理测试键redisService.delete("test:key");}@Testvoid testSetValue() throws Exception {mockMvc.perform(post("/api/redis/test:key").contentType(MediaType.TEXT_PLAIN).content("test:value")).andExpect(status().isOk());}@Testvoid testGetValue() throws Exception {// GivenredisService.set("test:key", "test:value");// When/ThenmockMvc.perform(get("/api/redis/test:key")).andExpect(status().isOk()).andExpect(content().string("test:value"));}@Testvoid testDeleteValue() throws Exception {// GivenredisService.set("test:key", "test:value");// When/ThenmockMvc.perform(delete("/api/redis/test:key")).andExpect(status().isOk());// Verify deletionmockMvc.perform(get("/api/redis/test:key")).andExpect(status().isNotFound());}@Testvoid testSetValueWithExpiration() throws Exception {mockMvc.perform(post("/api/redis/test:key/expire/1").contentType(MediaType.TEXT_PLAIN).content("test:value")).andExpect(status().isOk());}@Testvoid testCheckKeyExists() throws Exception {// GivenredisService.set("test:key", "test:value");// When/ThenmockMvc.perform(get("/api/redis/test:key/exists")).andExpect(status().isOk()).andExpect(content().string("true"));mockMvc.perform(get("/api/redis/non:existing:key/exists")).andExpect(status().isOk()).andExpect(content().string("false"));}@Testvoid testHealthCheck() throws Exception {mockMvc.perform(get("/api/redis/health")).andExpect(status().isOk()).andExpect(content().string("OK"));}
}
5. 使用说明
- 确保已安装并启动 Redis 服务器
- 修改
application.yml
中的 Redis 配置(如果需要) - 运行
RedisApplication
类启动应用 - 访问 API 接口:
- POST
/api/redis/{key}
- 设置字符串值 - POST
/api/redis/{key}/expire/{timeout}
- 设置带过期时间的字符串值 - GET
/api/redis/{key}
- 获取字符串值 - DELETE
/api/redis/{key}
- 删除值 - GET
/api/redis/{key}/exists
- 检查键是否存在 - GET
/api/redis/health
- 健康检查
- POST
6. 注意事项
- 确保 Redis 服务器已启动
- 默认使用本地 Redis 服务器(localhost:6379)
- 所有操作都使用 String 类型,确保类型安全
- 测试使用独立的数据库,不会影响生产数据
7. 遇到的问题
问题一:连接池依赖缺失
在项目启动时,遇到了以下错误:
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'redisConnectionFactory' defined in class path resource [com/example/config/RedisConfig.class]: Bean instantiation via factory method failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [org.springframework.data.redis.connection.RedisConnectionFactory]: Factory method 'redisConnectionFactory' threw exception; nested exception is java.lang.NoClassDefFoundError: org/apache/commons/pool2/impl/GenericObjectPoolConfig
问题原因
Spring Data Redis 的 Lettuce 连接池需要用到 Apache Commons Pool2 库,但 spring-boot-starter-data-redis 2.x 版本不会自动传递这个依赖。
解决方法
在 pom.xml
中添加 Apache Commons Pool2 依赖:
<dependency><groupId>org.apache.commons</groupId><artifactId>commons-pool2</artifactId><version>2.11.1</version>
</dependency>
添加依赖后,重新构建项目即可解决该问题。
问题二:Redis 连接被拒绝
在运行测试或启动应用时,遇到以下错误:
java.net.ConnectException: Connection refused: no further informationat sun.nio.ch.SocketChannelImpl.checkConnect(Native Method) ~[na:1.8.0_451]
问题原因
这个错误通常表示无法连接到 Redis 服务器,可能的原因有:
- Redis 服务器未启动
- Redis 服务器运行在不同的主机或端口
- 防火墙阻止了连接
- Redis 配置中的连接信息不正确
解决方法
-
确保 Redis 服务器已启动:
- Windows: 检查 Redis 服务是否在运行
- Linux/Mac: 使用
redis-server
命令启动 Redis
-
验证 Redis 连接信息:
- 检查
application.yml
中的 Redis 配置是否正确 - 默认配置为 localhost:6379
- 检查
-
使用 Redis 客户端测试连接:
redis-cli ping
如果返回 “PONG”,说明 Redis 服务器正常运行
-
检查防火墙设置:
- 确保 Redis 端口(默认 6379)未被防火墙阻止
- 如果使用远程 Redis,确保网络连接正常
-
如果使用测试配置:
- 确保
application-test.yml
中的配置正确 - 测试使用数据库 1,确保不会影响生产数据
- 确保
8. 自问自答
1. 为什么 Redis 可以直接使用而不需要重启服务?
Redis 可以直接使用而不需要重启服务,主要是因为:
- 连接池机制:Spring Boot 使用 Lettuce 连接池,允许应用程序在需要时获取和返回连接,而不需要每次都创建新的连接。
- 长连接支持:Lettuce 是一个异步、非阻塞的客户端,支持长连接(Keep-Alive),能够自动重连。
- 自动配置:Spring Boot 在启动时会自动配置 Redis 连接,当 Redis 服务可用时,连接会自动建立,并在连接丢失时尝试重连。
2. 如何确保 Redis 连接的安全性?
确保 Redis 连接的安全性可以通过以下方式:
- 使用密码:在 Redis 配置中设置密码,确保只有授权用户才能访问。
- 限制访问:通过防火墙或网络配置限制 Redis 的访问范围。
- 加密传输:使用 SSL/TLS 加密 Redis 连接,防止数据被窃取。
- 定期更新:保持 Redis 和 Spring Boot 的版本更新,以修复已知的安全漏洞。
3. 如何处理 Redis 连接失败的情况?
处理 Redis 连接失败的情况可以通过以下方式:
- 重试机制:在连接失败时,实现重试逻辑,尝试重新连接。
- 健康检查:定期检查 Redis 连接状态,确保连接正常。
- 日志记录:记录连接失败的原因,便于后续分析和调试。
- 降级策略:在 Redis 不可用时,实现降级策略,确保应用程序仍能正常运行。
4. 如何优化 Redis 的性能?
优化 Redis 的性能可以通过以下方式:
- 使用连接池:配置合适的连接池大小,避免频繁创建和销毁连接。
- 数据压缩:对于大对象,考虑使用压缩算法减少内存占用。
- 合理设置过期时间:为缓存数据设置合理的过期时间,避免内存占用过高。
- 使用 Redis 集群:对于高并发场景,考虑使用 Redis 集群分散负载。
5. 如何监控 Redis 的使用情况?
监控 Redis 的使用情况可以通过以下方式:
- 使用监控工具:如 Redis Desktop Manager、Grafana 等工具监控 Redis 的性能和状态。
- 日志记录:记录 Redis 的操作日志,便于分析使用情况。
- 健康检查接口:实现健康检查接口,定期检查 Redis 连接状态。
- 性能指标:收集 Redis 的性能指标,如内存使用、连接数等,便于优化。
参考资源
-
官方文档
- Spring Boot 官方文档
- Spring Data Redis 官方文档
- Redis 官方文档
-
推荐书籍
- 《Spring Boot 实战》
- 《Redis 实战》
- 《Spring Data Redis 实战》
-
在线教程
- Spring Boot 教程
- Spring Data Redis 教程
- Redis 教程
-
工具资源
- Spring Initializr
- Redis Desktop Manager
- Postman API 测试工具
总结
Spring Boot + Redis 脚手架提供了一个完整的缓存应用开发基础,包含了必要的配置和示例代码。通过这个项目,你可以:
- 快速搭建缓存应用
- 实现 Redis 操作
- 进行单元测试
- 使用开发工具
下期预告
在下一篇文章中,我们将介绍 Spring Boot + 安全框架项目脚手架,主要内容包括:
- Spring Security 基础配置
- 用户认证与授权
- JWT 令牌管理
- 安全过滤器链
- 权限控制实现
- 安全测试方案
敬请期待!
彩蛋:Redis 在 Windows 上的安装过程
1. 下载 Redis
- 访问 Redis 官方下载页面。
- 下载最新的 Redis for Windows 安装包(例如
Redis-x64-3.0.504.msi
)。
2. 安装 Redis
- 双击下载的安装包,启动安装向导。
- 按照向导的提示进行安装,选择安装路径和其他选项。
- 完成安装后,Redis 服务会自动启动。
3. 验证安装
- 打开命令提示符(CMD)。
- 输入
redis-cli
命令,进入 Redis 命令行界面。 - 输入
ping
命令,如果返回PONG
,则表示 Redis 服务正常运行。
4. 配置 Redis
- 打开 Redis 安装目录下的
redis.windows.conf
文件。 - 根据需要修改配置,例如设置密码、更改端口等。
- 保存文件后,重启 Redis 服务以应用更改。
5. 使用 Redis
- 在应用程序中配置 Redis 连接信息,确保与 Redis 服务连接正常。
- 使用 Redis 客户端或命令行工具进行数据操作。
补充:
如果你未使用 Docker 环境,建议将测试代码中的 Testcontainers 相关内容(如 @Testcontainers、@Container、@DynamicPropertySource 等)移除,直接连接本地 Redis 服务。同时可在 pom.xml 中移除 testcontainers 相关依赖,避免无用报错。