背景
本文是《Java 后端从小白到大神》修仙系列之框架学习,Java框架之 SpringBoot
框架第四篇
。SpringBoot框架
可以说是微服务的基石,很多复杂的系统几乎都是通过微服务构造而来,若想详细学习请点击首篇博文开始,现在开始学习。
文章概览
- Spring Boot 整合 Redis 缓存
- Spring Boot 中的定时任务 @Scheduled
- Spring Boot 集成 RabbitMQ 消息队列
- Spring Boot 异步编程 @Async 实现并发处理
- Spring Boot 集成 Nacos 配置中心
1. Spring Boot 整合 Redis 缓存
一、添加依赖
1
2
3
4
|
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
|
二、配置连接
1
2
3
4
|
spring:
redis:
host: localhost
port: 6379
|
三、使用 RedisTemplate
1
2
3
4
5
|
@Autowired
private StringRedisTemplate redisTemplate;
redisTemplate.opsForValue().set("key", "value");
String value = redisTemplate.opsForValue().get("key");
|
2. Spring Boot 中的定时任务 @Scheduled
一、启用定时任务
1
2
3
|
@SpringBootApplication
@EnableScheduling
public class App {}
|
二、编写定时任务
1
2
3
4
5
6
7
|
@Component
public class ScheduledTasks {
@Scheduled(fixedRate = 5000)
public void reportTime() {
System.out.println("现在时间:" + LocalDateTime.now());
}
}
|
三、配置 cron 表达式
1
|
@Scheduled(cron = "0 0/1 * * * ?") // 每分钟执行
|
3. Spring Boot 集成 RabbitMQ 消息队列
一、添加依赖
1
2
3
4
|
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
|
二、配置连接
1
2
3
4
5
6
|
spring:
rabbitmq:
host: localhost
port: 5672
username: guest
password: guest
|
三、定义生产者/消费者
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
@Component
public class Sender {
@Autowired
private RabbitTemplate rabbitTemplate;
public void send() {
rabbitTemplate.convertAndSend("queueName", "Hello MQ");
}
}
@Component
public class Receiver {
@RabbitListener(queues = "queueName")
public void receive(String msg) {
System.out.println("接收到消息: " + msg);
}
}
|
4. Spring Boot 异步编程 @Async 实现并发处理
一、启用异步处理
1
2
3
|
@SpringBootApplication
@EnableAsync
public class App {}
|
二、编写异步方法
1
2
3
4
5
6
7
8
|
@Component
public class AsyncService {
@Async
public void runTask() {
Thread.sleep(2000);
System.out.println("异步任务完成");
}
}
|
三、调用方式
1
|
asyncService.runTask(); // 会异步执行
|
5. Spring Boot 集成 Nacos 配置中心
一、引入依赖
1
2
3
4
|
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
|
二、配置 bootstrap.yml
1
2
3
4
5
6
7
8
|
spring:
application:
name: nacos-client
cloud:
nacos:
config:
server-addr: localhost:8848
file-extension: yaml
|
三、配置动态刷新
1
2
3
4
5
6
7
8
9
10
11
|
@RestController
@RefreshScope
public class ConfigController {
@Value("${custom.value}")
private String value;
@GetMapping("/value")
public String getValue() {
return value;
}
}
|
6. 综合上述内容练习
项目名称:任务调度与通知平台(Task Notification Platform),这是一个模拟企业日常使用的微型系统,主要用于处理“用户创建任务 → 系统缓存 → 异步分析 → 消息通知 → 配置动态更新”的完整业务链条。
项目架构设计:
task-platform/
├── src/
│ ├── main/
│ │ ├── java/com/example/taskplatform/
│ │ │ ├── controller/ // 控制层
│ │ │ ├── service/ // 业务层
│ │ │ ├── config/ // 配置类(Redis、RabbitMQ、Nacos、Async等)
│ │ │ ├── model/ // 实体类
│ │ │ ├── repository/ // 数据保存层
│ │ │ ├── listener/ // 消息监听器(RabbitMQ)
│ │ │ ├── schedule/ // 定时任务模块
│ │ │ └── TaskPlatformApplication.java
│ ├── resources/
│ │ ├── application.yml // 引入 Nacos 配置
│ │ └── bootstrap.yml // nacos server 地址
│── pom.xml
项目运行环境:需要Nacos、Redis、RabbitMQ,我本地使用docker启动,这里默认已启动,Nacos运行连接的数据为本地MYSQL环境。
项目配置类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
|
package com.yutao.config;
import lombok.Data;
import org.springframework.amqp.core.*;
import org.springframework.amqp.support.converter.DefaultClassMapper;
import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* RabbitMQ 配置类,用于定义交换机、队列、绑定关系,以及消息序列化方式
*/
@Data // Lombok 提供的注解,自动生成 Getter、Setter、toString 等方法
@Configuration // 表示这是一个配置类,Spring 启动时会自动加载
@RefreshScope // 支持 Nacos 配置动态刷新,配置更新后自动刷新字段值
@ConfigurationProperties(prefix = "mq") // 将配置文件中以 "mq" 开头的配置注入到本类字段中
public class RabbitMQConfig {
// 从配置文件中读取交换机名称,例如:mq.exchange=task.exchange
private String exchange;
// 从配置文件中读取路由键,例如:mq.routing-key=task.done
private String routingKey;
// 队列名称常量,可根据需要也放到配置中
private final static String QUEUE_NAME = "task-queue";
/**
* 定义一个持久化的队列,队列名为 task-queue
*/
@Bean
public Queue taskQueue() {
return new Queue(QUEUE_NAME, true); // true 表示队列持久化
}
/**
* 定义一个直连类型(Direct)的交换机,名称来自配置文件
*/
@Bean
public DirectExchange taskExchange() {
return new DirectExchange(exchange);
}
/**
* 将队列绑定到交换机,并指定路由键
*/
@Bean
public Binding taskBinding() {
return BindingBuilder
.bind(taskQueue()) // 绑定的队列
.to(taskExchange()) // 指定的交换机
.with(routingKey); // 路由键,用于精确匹配消息
}
/**
* 定义消息转换器,使用 Jackson 将消息对象转换为 JSON 格式
*/
@Bean
public Jackson2JsonMessageConverter jackson2JsonMessageConverter() {
Jackson2JsonMessageConverter converter = new Jackson2JsonMessageConverter();
converter.setClassMapper(classMapper()); // 配置类型映射器,防止反序列化漏洞
return converter;
}
/**
* 定义消息类型映射器,限制反序列化时允许的包路径,防止反序列化攻击
*/
@Bean
public DefaultClassMapper classMapper() {
DefaultClassMapper classMapper = new DefaultClassMapper();
classMapper.setTrustedPackages(
"com.yutao.model", // 你的业务模型包
"java.util", // 常用集合类(List、Map 等)
"org.springframework.amqp" // Spring AMQP 相关类
);
return classMapper;
}
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
|
package com.yutao.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.serializer.*;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import java.time.Duration;
/**
* Redis 缓存管理器配置类,设置缓存的序列化方式和默认过期时间。
*/
@Configuration
public class RedisCacheConfig {
/**
* 自定义 RedisCacheManager Bean,用于替代 Spring Boot 默认的缓存管理器。
*
* @param factory Redis 连接工厂,由 Spring 自动注入
* @return 自定义的 RedisCacheManager
*/
@Bean
public RedisCacheManager cacheManager(RedisConnectionFactory factory) {
// 创建 ObjectMapper,用于序列化 Java 对象为 JSON
ObjectMapper objectMapper = new ObjectMapper();
// 注册 Java 8 时间模块,支持 LocalDateTime 等
objectMapper.registerModule(new JavaTimeModule());
// 禁用时间戳格式,改用 ISO 格式(更可读)
objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
// 创建 Jackson 序列化器(用于将对象转为 JSON 存入 Redis)
Jackson2JsonRedisSerializer<Object> jacksonSerializer = new Jackson2JsonRedisSerializer<>(Object.class);
// 设置自定义的 ObjectMapper 给序列化器
// 使用新的构造方法替代废弃的 setObjectMapper 方法
jacksonSerializer = new Jackson2JsonRedisSerializer<>(objectMapper, Object.class);
// 定义 Redis 缓存配置
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
// 设置 Key 使用字符串序列化器(可读性好)
.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(RedisSerializer.string()))
// 设置 Value 使用 Jackson JSON 序列化器(避免默认的 JDK 序列化)
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(jacksonSerializer))
// 设置缓存默认过期时间:1 小时
.entryTtl(Duration.ofHours(1));
// 构建 Redis 缓存管理器,应用上面的配置
return RedisCacheManager.builder(factory)
.cacheDefaults(config) // 设置默认配置
.build();
}
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
|
package com.yutao.config;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.stereotype.Component;
// Lombok 注解:自动为本类及内部类生成 getter/setter、toString、equals/hashCode 等方法
@Data
// 让 Spring 管理此类为一个组件(Bean),可在其他类中 @Autowired 注入
@Component
// 支持 Nacos 等配置中心的“动态刷新”功能(配置变更后自动更新)
@RefreshScope
// 告诉 Spring Boot:将配置文件中以 "task" 开头的配置项注入到这个类中
@ConfigurationProperties(prefix = "task")
public class TaskProperties {
// 映射配置项:task.process.*
// 初始化为 new Process(),避免 NullPointerException
private Process process = new Process();
// 映射配置项:task.scan.*
private Scan scan = new Scan();
/**
* 静态内部类,用于映射配置项 task.process.delayMs
* 比如在 application.yml 中这样写:
* task:
* process:
* delayMs: 1000
*/
@Data
public static class Process {
private long delayMs; // 延迟毫秒数
}
/**
* 静态内部类,用于映射配置项 task.scan.interval
* 比如在 application.yml 中这样写:
* task:
* scan:
* interval: 5000
*/
@Data
public static class Scan {
private long interval; // 扫描间隔
}
}
|
控制层
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
|
package com.yutao.controller;
import jakarta.annotation.Resource;
import org.springframework.web.bind.annotation.*;
import com.yutao.model.Task;
import com.yutao.service.TaskService;
@RestController
@RequestMapping("/api/task")
public class TaskController {
@Resource
private TaskService service;
@PostMapping("/submit")
public Task submit(@RequestParam String name) {
return service.createAndSubmitTask(name);
}
@GetMapping("/{id}")
public Task query(@PathVariable String id) {
return service.getTask(id);
}
@PostMapping("/create-failed")
public Task createFailedTask(@RequestParam String name) {
return service.createFailedTask(name);
}
}
|
RabbitMQ监听类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
|
package com.yutao.listener;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
import com.yutao.model.Task;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@Component
public class RabbitMQListener {
@RabbitListener(queues = "task-queue")
public void onMessage(Task task) {
// 这里参数直接是 Task 对象,Spring会用Jackson2JsonMessageConverter自动转换
log.info("接收到消息:{}", task);
try {
// 业务处理
processMessage(task);
} catch (Exception e) {
log.error("处理消息异常", e);
// 这里可以根据需求抛异常让消息重试或死信,或者自行处理
}
}
private void processMessage(Task task) {
log.info("处理业务消息:{}", task);
// 这里写具体的业务处理逻辑
}
}
|
实体类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
|
package com.yutao.model;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
import java.time.LocalDateTime;
import java.util.UUID;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Task implements Serializable {
private String id;
private String name;
private TaskStatus status; // NEW, PROCESSING, SUCCESS, FAILED
private String result;
private LocalDateTime createdAt;
private LocalDateTime completedAt;
public static Task create(String name) {
Task task = new Task();
task.setId(UUID.randomUUID().toString());
task.setName(name);
task.setStatus(TaskStatus.NEW);
task.setCreatedAt(LocalDateTime.now());
return task;
}
public static Task createFailTask(String name) {
Task task = new Task();
task.setId(UUID.randomUUID().toString());
task.setName(name);
task.setStatus(TaskStatus.FAILED);
task.setResult("测试失败任务");
task.setCreatedAt(LocalDateTime.now());
task.setCompletedAt(LocalDateTime.now());
return task;
}
}
|
1
2
3
4
5
6
7
8
9
|
package com.yutao.model;
public enum TaskStatus {
PENDING, // 等待中
PROCESSING, // 处理中
SUCCESS, // 成功
FAILED, // 失败
NEW // 新创建
}
|
数据保存层
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
package com.yutao.repository;
import org.springframework.stereotype.Repository;
import com.yutao.model.Task;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
@Repository
public class TaskRepository {
private final Map<String, Task> store = new ConcurrentHashMap<>();
public Task save(Task task) {
store.put(task.getId(), task);
return task;
}
public Task findById(String id) {
return store.get(id);
}
public Map<String, Task> findAll() {
return store;
}
}
|
任务定时器
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
|
package com.yutao.schedule;
import com.yutao.repository.TaskRepository;
import com.yutao.service.TaskService;
import com.yutao.config.TaskProperties;
import com.yutao.model.Task;
import com.yutao.model.TaskStatus;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.scheduling.annotation.SchedulingConfigurer;
import org.springframework.scheduling.config.ScheduledTaskRegistrar;
import org.springframework.stereotype.Component;
import java.time.Instant;
import java.time.LocalDateTime;
import java.util.Date;
import java.util.Map;
// Lombok 注解:自动生成日志对象 log,可用 log.info(), log.error() 等方法
@Slf4j
// 将本类交由 Spring 容器管理,可被自动注入
@Component
// 支持配置项动态刷新(如来自 Nacos 的 task.scan.interval 变化后无需重启)
@RefreshScope
// 实现 Spring 的 SchedulingConfigurer 接口,支持自定义定时任务调度行为
public class TaskSchedulerRunner implements SchedulingConfigurer {
// 注入任务仓库(假设这是一个模拟数据库)
@Resource
private TaskRepository repository;
// 注入任务服务类(提供状态更新等操作)
@Resource
private TaskService taskService;
// 注入任务配置类,读取任务调度相关参数
@Resource
private TaskProperties taskProperties;
// 使用 Spring 注解方式定义定时任务,固定周期由配置文件控制
// 如 application.yml 中配置:task.scan.interval: 5000
@Scheduled(fixedRateString = "${task.scan.interval}")
public void scanTasks() {
log.info("[定时任务] 扫描所有任务状态");
// 遍历任务仓库中的所有任务
for (Map.Entry<String, Task> entry : repository.findAll().entrySet()) {
Task task = entry.getValue();
log.info("任务ID={}, 状态={}", task.getId(), task.getStatus());
// 示例逻辑:如果任务是 FAILED,则开始处理它
if (task.getStatus() == TaskStatus.FAILED) {
log.info("任务 {} 状态为 FAILED,开始处理...", task.getId());
// 修改状态为 PROCESSING,并更新到数据库和缓存
task.setStatus(TaskStatus.PROCESSING);
taskService.updateTaskStatus(task);
// 模拟执行任务的业务逻辑
boolean success = executeTask(task);
// 更新任务的执行结果
task.setName("测试失败任务转成功");
task.setResult("执行成功");
task.setCompletedAt(LocalDateTime.now());
task.setStatus(success ? TaskStatus.SUCCESS : TaskStatus.FAILED);
taskService.updateTaskStatus(task);
log.info("任务 {} 执行完成,状态更新为 {}", task.getId(), task.getStatus());
}
}
// 打印当前扫描周期
log.info("当前扫描周期:{}ms", taskProperties.getScan().getInterval());
}
/**
* 模拟任务的执行逻辑(这里只是简单返回 true)
*/
private boolean executeTask(Task task) {
log.info("开始处理真实业务逻辑:" + task.getId());
return true;
}
/**
* 重写 SchedulingConfigurer 的 configureTasks 方法
* 使用 TriggerTask 支持“动态周期”调度(相比 @Scheduled 更灵活)
*/
@Override
public void configureTasks(ScheduledTaskRegistrar registrar) {
registrar.addTriggerTask(
// 要执行的任务
this::scanTasks,
// 调度器,根据上次执行时间 + 配置的间隔时间,计算下一次时间
triggerContext -> {
long interval = taskProperties.getScan().getInterval();
log.info("当前调度周期为:{} ms", interval);
Date lastTime = triggerContext.lastActualExecutionTime();
if (lastTime == null) {
// 第一次执行
return Instant.now().plusMillis(interval);
}
// 下一次执行 = 上一次执行时间 + 周期
return Instant.ofEpochMilli(lastTime.getTime()).plusMillis(interval);
}
);
}
}
|
服务层
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
|
package com.yutao.service;
import jakarta.annotation.PostConstruct;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import com.yutao.model.Task;
@Slf4j // Lombok 注解,自动为类生成 log 日志对象,可直接使用 log.info(...) 打印日志
@Component // 标识该类为一个 Spring 管理的组件(Bean),会被自动扫描并注入到 Spring 容器中
public class TaskMessageSender {
@Resource // 按名称注入 RabbitTemplate(用于发送消息)
private RabbitTemplate rabbitTemplate;
@Value("${mq.exchange:task.exchange}") // 从配置中获取 exchange 名,默认值为 "task.exchange"
private String exchange;
@Value("${mq.routing-key:task.done}") // 从配置中获取 routingKey,默认值为 "task.done"
private String routingKey;
@Autowired // 按类型注入 Jackson JSON 消息转换器
private Jackson2JsonMessageConverter jackson2JsonMessageConverter;
@PostConstruct // 在依赖注入完成后执行该方法,相当于初始化逻辑
public void init() {
// 设置 RabbitTemplate 使用 Jackson 消息转换器(将 Java 对象转为 JSON)
rabbitTemplate.setMessageConverter(jackson2JsonMessageConverter);
}
/**
* 发送任务完成的消息
* @param task 要发送的任务对象,会被自动序列化为 JSON 格式发送到 MQ
*/
public void sendTaskCompleted(Task task) {
// 将 task 对象发送到指定的交换机和路由键绑定的队列中
rabbitTemplate.convertAndSend(exchange, routingKey, task);
// 打印日志,记录任务已发送
log.info("[消息发送] 任务已完成:{}", task.getId());
}
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
|
package com.yutao.service;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import com.yutao.model.Task;
import com.yutao.model.TaskStatus;
import com.yutao.repository.TaskRepository;
import java.time.LocalDateTime;
import java.util.concurrent.TimeUnit;
@Slf4j
@Service
public class TaskService {
@Resource
private TaskRepository repository;
@Resource
private TaskMessageSender messageSender;
@Value("${task.process.delay-ms}")
private long processDelay;
@CacheEvict(value = "task-cache", key = "#task.id") // 清除旧缓存,还有一个注解@CachePut,更新缓存
public void updateTaskStatus(Task task) {
repository.save(task); // 更新任务状态
}
public Task createAndSubmitTask(String name) {
Task task = Task.create(name);
repository.save(task);
processTaskAsync(task.getId());
return task;
}
@Cacheable(value = "task-cache", key = "#id")
public Task getTask(String id) {
return repository.findById(id);
}
@Async
public void processTaskAsync(String taskId) {
log.info("[任务异步] 调用 processTaskAsync, 任务ID={}", taskId);
Task task = repository.findById(taskId);
if (task == null) {
log.warn("找不到任务,ID={}", taskId);
return;
}
log.info("异步处理后查询任务状态:{}", task.getStatus());
if (task.getStatus() != TaskStatus.NEW) {
log.warn("任务状态不是 NEW,直接返回,状态={}", task.getStatus());
return;
}
try {
task.setStatus(TaskStatus.PROCESSING);
repository.save(task);
log.info("[任务异步] 开始处理任务:{}", task.getId());
TimeUnit.MILLISECONDS.sleep(processDelay); // 模拟处理耗时
task.setResult("处理成功");
task.setStatus(TaskStatus.SUCCESS);
task.setCompletedAt(LocalDateTime.now());
repository.save(task);
messageSender.sendTaskCompleted(task); // 发送到消息队列
log.info("[任务异步] 任务完成:{}", task.getId());
} catch (Exception e) {
task.setStatus(TaskStatus.FAILED);
task.setResult(e.getMessage());
repository.save(task);
log.error("[任务异步] 任务失败:{}", task.getId(), e);
}
}
public Task createFailedTask(String name) {
Task task = Task.createFailTask(name);
repository.save(task);
return task;
}
}
|
启动类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
package com.yutao;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.annotation.EnableScheduling;
@SpringBootApplication
@EnableScheduling // 开启定时任务支持
@EnableAsync // 开启异步方法支持
@EnableCaching // 开启缓存支持
public class TaskPlatformApplication {
public static void main(String[] args) {
SpringApplication.run(TaskPlatformApplication.class, args);
}
}
|
项目配置文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
|
文件名称:src/main/resources/application.properties
# Server
server.port=8080
# 应用名称
spring.application.name=task-platform
# 配置导入:使用 Nacos 作为配置中心(Spring Boot 3.2 推荐方式)
spring.config.import=optional:nacos:127.0.0.1:8848
# Nacos 配置
spring.cloud.nacos.config.server-addr=127.0.0.1:8848
spring.cloud.nacos.config.file-extension=yaml
spring.cloud.nacos.config.group=DEFAULT_GROUP
spring.cloud.nacos.config.namespace=public
# Nacos 扩展配置文件:Redis、RabbitMQ、定时任务
spring.cloud.nacos.config.extension-configs[0].dataId=redis-config.yaml
spring.cloud.nacos.config.extension-configs[0].group=DEFAULT_GROUP
spring.cloud.nacos.config.extension-configs[0].refresh=true
spring.cloud.nacos.config.extension-configs[1].dataId=mq-config.yaml
spring.cloud.nacos.config.extension-configs[1].group=DEFAULT_GROUP
spring.cloud.nacos.config.extension-configs[1].refresh=true
spring.cloud.nacos.config.extension-configs[2].dataId=task-platform.yaml
spring.cloud.nacos.config.extension-configs[2].group=DEFAULT_GROUP
spring.cloud.nacos.config.extension-configs[2].refresh=true
spring.data.redis.host=localhost
spring.data.redis.port=6379
spring.cache.type=redis
spring.cache.redis.time-to-live=300000
# 日志级别
logging.level.root=info
logging.level.com.yutao=debug
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
文件名称:src/main/resources/application.properties
spring.application.name=task-platform
spring.cloud.nacos.config.server-addr=127.0.0.1:8848
spring.cloud.nacos.config.file-extension=yaml
spring.cloud.nacos.config.namespace=public
spring.cloud.nacos.config.group=DEFAULT_GROUP
# Nacos 扩展配置文件:Redis、RabbitMQ、定时任务
spring.cloud.nacos.config.extension-configs[0].dataId=redis-config.yaml
spring.cloud.nacos.config.extension-configs[0].group=DEFAULT_GROUP
spring.cloud.nacos.config.extension-configs[0].refresh=true
spring.cloud.nacos.config.extension-configs[1].dataId=mq-config.yaml
spring.cloud.nacos.config.extension-configs[1].group=DEFAULT_GROUP
spring.cloud.nacos.config.extension-configs[1].refresh=true
spring.cloud.nacos.config.extension-configs[2].dataId=task-platform.yaml
spring.cloud.nacos.config.extension-configs[2].group=DEFAULT_GROUP
spring.cloud.nacos.config.extension-configs[2].refresh=true
|
docker配置文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
|
文件名称:dev-env/docker-compose.yml
services:
redis:
image: redis:7.2
container_name: redis
ports:
- "6379:6379"
restart: always
rabbitmq:
image: rabbitmq:3-management
container_name: rabbitmq
ports:
- "5672:5672" # 应用连接端口
- "15672:15672" # 管理后台
environment:
RABBITMQ_DEFAULT_USER: guest
RABBITMQ_DEFAULT_PASS: guest
restart: always
nacos:
image: nacos/nacos-server:v2.3.0
platform: linux/amd64
container_name: nacos
ports:
- "8848:8848"
- "9848:9848"
environment:
MODE: standalone
SPRING_DATASOURCE_PLATFORM: mysql
MYSQL_SERVICE_HOST: 192.168.1.3
MYSQL_SERVICE_PORT: 3306
MYSQL_SERVICE_USER: root
MYSQL_SERVICE_DB_PARAM: characterEncoding=utf8&connectTimeout=2000&allowPublicKeyRetrieval=true&socketTimeout=3000&autoReconnect=true&useUnicode=true&useSSL=false&serverTimezone=Asia/Shanghai
MYSQL_SERVICE_PASSWORD: root
MYSQL_SERVICE_DB_NAME: nacos_config
volumes:
- /Users/yutao/Downloads/sourcecode/taskplatform/dev-env/jdbc.properties:/home/nacos/init.d/jdbc.properties
restart: always
|
1
2
3
4
5
6
7
|
文件名称:dev-env/jdbc.properties
spring.sql.init.platform=mysql
db.num=1
db.url.0=jdbc:mysql://192.168.1.3:3306/nacos_config?characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=true&useUnicode=true&useSSL=false&serverTimezone=Asia/Shanghai
db.user.0=root
db.password.0=root
|
项目pom文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
|
<?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.yutao</groupId>
<artifactId>taskplatform</artifactId>
<version>1.0-SNAPSHOT</version>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.2.2</version>
<relativePath/>
</parent>
<name>taskplatform</name>
<description>Spring Boot 实践项目:异步、定时、消息、缓存、配置中心</description>
<properties>
<java.version>21</java.version>
<spring-cloud.version>2023.0.1</spring-cloud.version>
<nacos.version>2022.0.0.0</nacos.version>
</properties>
<!-- 🔧 管理 Spring Cloud 依赖版本 -->
<dependencyManagement>
<dependencies>
<!-- Spring Cloud 2023 BOM -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!-- Spring Cloud Alibaba BOM -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>${nacos.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<!-- Web、定时任务 -->
<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>
<!-- RabbitMQ -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
<!-- Nacos Config -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
</dependency>
<!-- 引入 spring-cloud-starter-bootstrap 依赖 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bootstrap</artifactId>
</dependency>
<!-- 其他通用工具 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<!-- Lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!-- 测试 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<pluginManagement><!-- lock down plugins versions to avoid using Maven defaults (may be moved to parent pom) -->
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.11.0</version>
<configuration>
<source>${java.version}</source>
<target>${java.version}</target>
</configuration>
</plugin>
</plugins>
</pluginManagement>
</build>
</project>
|
常用注解:
注解 / 特性 |
作用说明 |
常用注解示例 |
底层实现机制或说明 |
定时任务调度 |
启用和配置定时任务 |
@EnableScheduling |
注册调度器,执行被 @Scheduled 注解的方法 |
定时任务执行 |
在指定时间或周期执行方法 |
@Scheduled |
由调度器触发执行 |
异步执行 |
启用方法异步执行 |
@EnableAsync |
使用代理拦截,提交到线程池执行 |
异步方法 |
声明异步执行的方法 |
@Async |
代理拦截执行,放入线程池 |
缓存功能 |
启用方法缓存管理 |
@EnableCaching |
缓存管理器 + 代理拦截,实现缓存 |
缓存注解 |
声明缓存行为,如缓存、更新、删除缓存 |
@Cacheable 、@CachePut 等 |
代理拦截,结合缓存管理器操作缓存 |
日志 |
自动生成日志对象 log |
@Slf4j |
Lombok注解,编译时注入 org.slf4j.Logger 类型字段 |
依赖注入 |
按名称注入Bean |
@Resource |
JDK注解,默认按名称注入 |
|
按类型注入Bean |
@Autowired |
Spring注解,按类型注入,支持按名称 |
配置值注入 |
从配置环境中注入值 |
@Value |
Spring环境解析配置,注入基本类型 |
配置类注解 |
一次性注入整个配置类,支持复杂结构 |
@ConfigurationProperties |
支持嵌套、集合、Map等复杂结构,类型转换强 |
配置注入方式对比 |
@Value(单字段注入) |
@ConfigurationProperties(整类注入) |
注入方式 |
每个字段单独写一个 @Value 注解 |
一次性注入整个配置类,支持复杂嵌套结构 |
配置结构支持 |
不支持复杂嵌套,必须自己拼接完整key |
支持嵌套、集合、Map 等复杂结构 |
类型转换能力 |
仅支持基础类型 |
支持复杂类型(嵌套对象、集合等) |
是否推荐 |
简单场景可用 |
推荐在配置项较多或结构复杂时使用 |
总结
SpringBoot 框架是微服务生态的基石,必知必会。