【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
<?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
yaml
server:
port: 8080
spring:
redis:
host: localhost
port: 6379
database: 0
timeout: 10000
lettuce:
pool:
max-active: 8
max-wait: -1
max-idle: 8
min-idle: 0
logging:
level:
com.example: debug
org.springframework.data.redis: debug
2.3 application-test.yml
yaml
spring:
redis:
host: localhost
port: 6379
database: 1 # 使用不同的数据库进行测试
timeout: 1000
lettuce:
pool:
max-active: 8
max-wait: -1
max-idle: 8
min-idle: 0
3. 核心功能实现
3.1 Redis配置类
java
@Configuration
public class RedisConfig {
@Bean
public 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);
}
@Bean
public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory connectionFactory) {
return new StringRedisTemplate(connectionFactory);
}
}
3.2 Redis服务类
java
@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控制器
java
@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 服务层测试
java
@SpringBootTest
@ActiveProfiles("test")
public class RedisServiceTest {
@Autowired
private RedisService redisService;
@Autowired
private StringRedisTemplate stringRedisTemplate;
@BeforeEach
void setUp() {
// 每个测试前清理所有键
stringRedisTemplate.getConnectionFactory().getConnection().flushAll();
}
@Test
void testSetAndGet() {
// Given
String key = "test:key";
String value = "test:value";
// When
redisService.set(key, value);
String result = redisService.get(key);
// Then
assertEquals(value, result);
}
@Test
void testDelete() {
// Given
String key = "test:key";
String value = "test:value";
redisService.set(key, value);
// When
redisService.delete(key);
String result = redisService.get(key);
// Then
assertNull(result);
}
@Test
void testSetWithExpiration() throws InterruptedException {
// Given
String key = "test:expire:key";
String value = "test:expire:value";
long timeout = 1; // 1 second
// When
redisService.set(key, value, timeout, TimeUnit.SECONDS);
String result = redisService.get(key);
// Then
assertEquals(value, result);
// Wait for expiration
Thread.sleep(1100);
String expiredResult = redisService.get(key);
assertNull(expiredResult);
}
@Test
void testExists() {
// Given
String key = "test:exists:key";
String value = "test:exists:value";
// When
redisService.set(key, value);
boolean exists = redisService.exists(key);
boolean notExists = redisService.exists("non:existing:key");
// Then
assertTrue(exists);
assertFalse(notExists);
}
}
4.2 控制器层测试
java
@SpringBootTest
@AutoConfigureMockMvc
@ActiveProfiles("test")
public class RedisControllerTest {
@Autowired
private MockMvc mockMvc;
@Autowired
private RedisService redisService;
@BeforeEach
void setUp() {
// 每个测试前清理测试键
redisService.delete("test:key");
}
@Test
void testSetValue() throws Exception {
mockMvc.perform(post("/api/redis/test:key")
.contentType(MediaType.TEXT_PLAIN)
.content("test:value"))
.andExpect(status().isOk());
}
@Test
void testGetValue() throws Exception {
// Given
redisService.set("test:key", "test:value");
// When/Then
mockMvc.perform(get("/api/redis/test:key"))
.andExpect(status().isOk())
.andExpect(content().string("test:value"));
}
@Test
void testDeleteValue() throws Exception {
// Given
redisService.set("test:key", "test:value");
// When/Then
mockMvc.perform(delete("/api/redis/test:key"))
.andExpect(status().isOk());
// Verify deletion
mockMvc.perform(get("/api/redis/test:key"))
.andExpect(status().isNotFound());
}
@Test
void testSetValueWithExpiration() throws Exception {
mockMvc.perform(post("/api/redis/test:key/expire/1")
.contentType(MediaType.TEXT_PLAIN)
.content("test:value"))
.andExpect(status().isOk());
}
@Test
void testCheckKeyExists() throws Exception {
// Given
redisService.set("test:key", "test:value");
// When/Then
mockMvc.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"));
}
@Test
void 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 依赖:
xml
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
<version>2.11.1</version>
</dependency>
添加依赖后,重新构建项目即可解决该问题。
问题二:Redis 连接被拒绝
在运行测试或启动应用时,遇到以下错误:
java.net.ConnectException: Connection refused: no further information
at 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 客户端测试连接:
bashredis-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 实战》
- 《Redis 实战》
- 《Spring Data Redis 实战》
-
在线教程
-
工具资源
总结
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 相关依赖,避免无用报错。