背景

本文是《Java 后端从小白到大神》修仙系列之框架学习,Java框架之 SpringBoot 框架第四篇SpringBoot框架 可以说是微服务的基石,很多复杂的系统几乎都是通过微服务构造而来,若想详细学习请点击首篇博文开始,现在开始学习。

文章概览

  1. Spring Boot 整合 Redis 缓存
  2. Spring Boot 中的定时任务 @Scheduled
  3. Spring Boot 集成 RabbitMQ 消息队列
  4. Spring Boot 异步编程 @Async 实现并发处理
  5. 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 扩展配置文件RedisRabbitMQ定时任务
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 扩展配置文件RedisRabbitMQ定时任务
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 框架是微服务生态的基石,必知必会。