SpringBoot异步处理@Async深度解析:从基础到高阶实战

一、异步编程基础概念

1.1 同步 vs 异步

特性 同步 异步
执行方式 顺序执行,阻塞调用 非阻塞,调用后立即返回
线程使用 单线程完成所有任务 多线程并行处理
响应性 较差,需等待前任务完成 较好,可立即响应新请求
复杂度 简单直观 较复杂,需处理线程安全
适用场景 简单流程,短时间任务 IO密集型,长时间任务

通俗理解:同步就像在银行柜台排队办理业务,必须等前面的人办完才能轮到你;异步则像取号后可以坐着玩手机,等叫号时再去办理。

1.2 为什么要使用异步

  • 提高吞吐量:服务器能同时处理更多请求
  • 增强用户体验:避免用户长时间等待
  • 资源优化:合理利用系统资源,避免阻塞主线程
  • 解耦:将耗时操作与主流程分离

1.3 Java中的异步编程方式

java 复制代码
// 1. 传统Thread方式
new Thread(() -> {
    // 异步任务
}).start();

// 2. Future模式
ExecutorService executor = Executors.newSingleThreadExecutor();
Future<String> future = executor.submit(() -> {
    Thread.sleep(1000);
    return "Result";
});

// 3. CompletableFuture (Java8+)
CompletableFuture.supplyAsync(() -> {
    // 异步任务
    return "Result";
}).thenAccept(result -> {
    // 处理结果
});

// 4. Spring @Async (本文重点)
@Async
public void asyncMethod() {
    // 异步方法体
}

二、@Async基础使用

2.1 启用@Async支持

步骤1 :在Spring Boot主类或配置类上添加@EnableAsync

java 复制代码
@SpringBootApplication
@EnableAsync  // 启用异步支持
public class AsyncApplication {
    public static void main(String[] args) {
        SpringApplication.run(AsyncApplication.class, args);
    }
}

步骤2:创建异步服务类

java 复制代码
@Service
public class EmailService {
    
    // 无返回值异步方法
    @Async
    public void sendEmail(String to, String content) {
        // 模拟邮件发送耗时
        try {
            Thread.sleep(3000);
            System.out.println("邮件已发送至: " + to + ", 内容: " + content);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    
    // 有返回值异步方法
    @Async
    public Future<String> sendEmailWithResult(String to, String content) {
        try {
            Thread.sleep(3000);
            String result = "邮件已发送至: " + to;
            return new AsyncResult<>(result);
        } catch (InterruptedException e) {
            return new AsyncResult<>("发送失败");
        }
    }
}

2.2 调用异步方法

java 复制代码
@RestController
@RequestMapping("/api/email")
public class EmailController {
    
    @Autowired
    private EmailService emailService;
    
    @GetMapping("/send")
    public String sendEmail() {
        long start = System.currentTimeMillis();
        
        // 调用异步方法
        emailService.sendEmail("[email protected]", "您的订单已创建");
        
        long elapsed = System.currentTimeMillis() - start;
        return "请求已处理,耗时: " + elapsed + "ms"; // 立即返回,不会等待邮件发送完成
    }
    
    @GetMapping("/send-with-result")
    public String sendEmailWithResult() throws Exception {
        Future<String> future = emailService.sendEmailWithResult("[email protected]", "订单详情");
        
        // 可以在这里做其他事情
        
        // 当需要结果时(阻塞等待)
        String result = future.get();
        return "处理结果: " + result;
    }
}

2.3 @Async方法限制

  1. 必须public修饰:因为基于Spring AOP实现
  2. 同类调用无效this.asyncMethod()不会异步执行
  3. 返回值限制
    • void
    • Future及其子类(如AsyncResult)
    • CompletableFuture (Spring 4.2+)
    • ListenableFuture (Spring 4.2+)

三、线程池配置详解

3.1 默认线程池问题

Spring默认使用SimpleAsyncTaskExecutor,它不重用线程,每次调用都创建新线程,生产环境不推荐使用。

3.2 自定义线程池配置

方式1:配置类方式(推荐)

java 复制代码
@Configuration
@EnableAsync
public class AsyncConfig implements AsyncConfigurer {
    
    @Override
    public Executor getAsyncExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        // 核心线程数:线程池创建时初始化的线程数
        executor.setCorePoolSize(5);
        // 最大线程数:线程池最大的线程数,只有在缓冲队列满了之后才会申请超过核心线程数的线程
        executor.setMaxPoolSize(10);
        // 缓冲队列:用来缓冲执行任务的队列
        executor.setQueueCapacity(50);
        // 线程名前缀
        executor.setThreadNamePrefix("Async-Executor-");
        // 线程池关闭时等待所有任务完成
        executor.setWaitForTasksToCompleteOnShutdown(true);
        // 等待时间
        executor.setAwaitTerminationSeconds(60);
        // 拒绝策略
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        executor.initialize();
        return executor;
    }
    
    @Override
    public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
        return new CustomAsyncExceptionHandler();
    }
}

// 自定义异常处理器
public class CustomAsyncExceptionHandler implements AsyncUncaughtExceptionHandler {
    @Override
    public void handleUncaughtException(Throwable ex, Method method, Object... params) {
        System.err.println("异步任务异常 - 方法: " + method.getName());
        System.err.println("异常信息: " + ex.getMessage());
        // 这里可以添加自定义处理逻辑,如记录日志、发送告警等
    }
}

方式2:使用@Bean定义多个线程池

java 复制代码
@Configuration
public class TaskExecutorConfig {
    
    @Bean(name = "emailExecutor")
    public Executor emailTaskExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(3);
        executor.setMaxPoolSize(5);
        executor.setQueueCapacity(30);
        executor.setThreadNamePrefix("Email-Executor-");
        executor.initialize();
        return executor;
    }
    
    @Bean(name = "reportExecutor")
    public Executor reportTaskExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(2);
        executor.setMaxPoolSize(4);
        executor.setQueueCapacity(20);
        executor.setThreadNamePrefix("Report-Executor-");
        executor.initialize();
        return executor;
    }
}

使用指定线程池:

java 复制代码
@Async("emailExecutor")
public void sendEmail(String to) { /*...*/ }

@Async("reportExecutor")
public void generateReport() { /*...*/ }

3.3 线程池参数详解

参数名 说明 推荐设置建议
corePoolSize 核心线程数,即使空闲也不会被回收 CPU密集型:CPU核数+1 IO密集型:2*CPU核数
maxPoolSize 最大线程数,当队列满时才会创建新线程直到此值 建议为核心线程数的2-3倍
queueCapacity 任务队列容量,超过核心线程数的任务会进入队列 根据业务量调整,太大可能导致OOM
keepAliveSeconds 非核心线程空闲存活时间(秒) 60-300秒
threadNamePrefix 线程名前缀,便于监控和日志追踪 建议按业务命名,如"Order-Async-"
allowCoreThreadTimeOut 是否允许核心线程超时退出 默认false,长时间空闲应用可设为true
waitForTasksToCompleteOnShutdown 应用关闭时是否等待异步任务完成 生产环境建议true
awaitTerminationSeconds 等待任务完成的超时时间 根据业务最长执行时间设置
rejectedExecutionHandler 拒绝策略,当线程池和队列都满时的处理方式 根据业务需求选择

拒绝策略选项

  • AbortPolicy:默认,直接抛出RejectedExecutionException
  • CallerRunsPolicy:由调用者线程执行该任务
  • DiscardPolicy:直接丢弃任务
  • DiscardOldestPolicy:丢弃队列中最老的任务并重试

3.4 线程池监控

java 复制代码
@Service
public class ThreadPoolMonitor {
    
    @Autowired
    private ThreadPoolTaskExecutor emailExecutor;
    
    @Scheduled(fixedRate = 5000)  // 每5秒监控一次
    public void monitor() {
        System.out.println("=== 线程池监控 ===");
        System.out.println("当前线程数: " + emailExecutor.getPoolSize());
        System.out.println("活跃线程数: " + emailExecutor.getActiveCount());
        System.out.println("完成任务数: " + emailExecutor.getCompletedTaskCount());
        System.out.println("队列剩余容量: " + emailExecutor.getThreadPoolExecutor().getQueue().remainingCapacity());
    }
}

四、@Async高级特性

4.1 返回值处理

1. Future模式

java 复制代码
@Async
public Future<String> processData(String input) {
    // 模拟处理耗时
    try {
        Thread.sleep(2000);
        return new AsyncResult<>("处理完成: " + input.toUpperCase());
    } catch (InterruptedException e) {
        Thread.currentThread().interrupt();
        return new AsyncResult<>("处理中断");
    }
}

// 调用方
Future<String> future = service.processData("hello");
// 可以做其他事情...
String result = future.get(3, TimeUnit.SECONDS); // 带超时的等待

2. CompletableFuture (Java8+)

java 复制代码
@Async
public CompletableFuture<String> fetchData(String param) {
    return CompletableFuture.supplyAsync(() -> {
        try {
            Thread.sleep(1000);
            return "Data for " + param;
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    });
}

// 链式调用
service.fetchData("user123")
       .thenApply(String::toUpperCase)
       .thenAccept(System.out::println)
       .exceptionally(ex -> {
           System.err.println("Error: " + ex.getMessage());
           return null;
       });

4.2 基于条件的异步执行

1. 使用Spring Expression Language (SpEL)

java 复制代码
@Async("#{systemProperties['async.enabled'] ? 'emailExecutor' : 'syncExecutor'}")
public void conditionalAsync() {
    // 根据系统属性决定使用哪个执行器
}

2. 基于配置的开关

java 复制代码
@Async
@ConditionalOnProperty(name = "app.async.enabled", havingValue = "true")
public void configBasedAsync() {
    // 当app.async.enabled=true时才异步执行
}

4.3 事务处理

异步方法与事务的特殊关系:

  1. 事务边界@Async方法会在新线程中执行,与原方法不在同一事务中
  2. 传播行为 :需要在异步方法上单独声明@Transactional
java 复制代码
@Async
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void asyncWithTransaction() {
    // 这个方法会在新事务中执行
    userRepository.save(new User("AsyncUser"));
    // 如果发生异常,只会回滚当前方法内的操作
}

4.4 组合异步操作

场景:需要等待多个异步任务全部完成

java 复制代码
@Async
public CompletableFuture<String> fetchUserInfo(String userId) {
    // 模拟获取用户信息
    return CompletableFuture.completedFuture("UserInfo-" + userId);
}

@Async
public CompletableFuture<String> fetchOrderInfo(String userId) {
    // 模拟获取订单信息
    return CompletableFuture.completedFuture("OrderInfo-" + userId);
}

// 组合多个异步任务
public CompletableFuture<Void> combineAsyncTasks(String userId) {
    return CompletableFuture.allOf(
            fetchUserInfo(userId),
            fetchOrderInfo(userId)
    ).thenRun(() -> {
        // 所有任务完成后的处理
        System.out.println("所有异步任务已完成");
    });
}

五、异常处理机制

5.1 异常处理方式对比

处理方式 适用场景 优点 缺点
AsyncUncaughtExceptionHandler 处理void返回类型的异步方法异常 集中处理,统一管理 无法获取方法返回值
Future.get() 处理有返回值的异步方法异常 可以获取具体异常信息 需要手动调用get()
CompletableFuture.exceptionally Java8+的优雅异常处理方式 链式调用,代码简洁 仅适用于CompletableFuture

5.2 实践示例

1. 全局异常处理器

java 复制代码
public class GlobalAsyncExceptionHandler implements AsyncUncaughtExceptionHandler {
    private static final Logger logger = LoggerFactory.getLogger(GlobalAsyncExceptionHandler.class);
    
    @Override
    public void handleUncaughtException(Throwable ex, Method method, Object... params) {
        logger.error("异步任务异常 - 方法: {}, 参数: {}", method.getName(), Arrays.toString(params), ex);
        
        // 可以根据异常类型进行不同处理
        if (ex instanceof BusinessException) {
            // 业务异常处理
            sendAlert("业务异常警报: " + ex.getMessage());
        } else if (ex instanceof TimeoutException) {
            // 超时处理
            retryTask(method, params);
        }
    }
    
    private void sendAlert(String message) { /*...*/ }
    private void retryTask(Method method, Object... params) { /*...*/ }
}

2. Future方式的异常处理

java 复制代码
@Async
public Future<String> asyncTaskWithException() {
    try {
        // 业务逻辑
        if (someCondition) {
            throw new BusinessException("业务异常");
        }
        return new AsyncResult<>("成功");
    } catch (BusinessException e) {
        return new AsyncResult<>("失败: " + e.getMessage());
    }
}

// 调用方处理
Future<String> future = service.asyncTaskWithException();
try {
    String result = future.get();
    if (result.startsWith("失败")) {
        // 处理失败情况
    }
} catch (ExecutionException e) {
    // 处理执行时异常
}

3. CompletableFuture的异常处理

java 复制代码
@Async
public CompletableFuture<String> asyncProcess(String input) {
    return CompletableFuture.supplyAsync(() -> {
        if (input == null) {
            throw new IllegalArgumentException("输入不能为空");
        }
        return "处理结果: " + input.toUpperCase();
    });
}

// 调用方处理
service.asyncProcess(null)
       .exceptionally(ex -> {
           System.err.println("发生异常: " + ex.getMessage());
           return "默认返回值";
       })
       .thenAccept(result -> {
           System.out.println("最终结果: " + result);
       });

六、性能优化与最佳实践

6.1 性能优化建议

  1. 线程池参数调优

    • 根据业务类型调整线程池大小
    • 监控线程池状态动态调整参数
    • 使用有界队列防止OOM
  2. 避免长时间阻塞

    • 异步方法内避免同步阻塞操作
    • 使用带超时的阻塞调用
  3. 资源清理

    • 确保异步方法正确释放资源
    • 使用try-with-resources管理资源
  4. 上下文传递

    • 注意ThreadLocal变量在异步线程中的传递问题
    • 使用TaskDecorator传递上下文
java 复制代码
executor.setTaskDecorator(new ContextCopyingDecorator());

public class ContextCopyingDecorator implements TaskDecorator {
    @Override
    public Runnable decorate(Runnable runnable) {
        // 获取当前线程的上下文
        RequestAttributes context = RequestContextHolder.currentRequestAttributes();
        return () -> {
            try {
                // 在新线程中设置上下文
                RequestContextHolder.setRequestAttributes(context);
                runnable.run();
            } finally {
                RequestContextHolder.resetRequestAttributes();
            }
        };
    }
}

6.2 最佳实践清单

  1. 命名规范

    • 异步方法名以Async后缀标识,如sendEmailAsync
    • 线程池按业务命名,如orderTaskExecutor
  2. 日志记录

    • 记录异步任务开始/结束时间
    • 为异步线程设置可追踪的上下文ID
java 复制代码
@Async
public void asyncWithLogging() {
    String traceId = UUID.randomUUID().toString();
    MDC.put("traceId", traceId);
    try {
        log.info("异步任务开始");
        // 业务逻辑
        log.info("异步任务完成");
    } finally {
        MDC.clear();
    }
}
  1. 防御性编程

    • 检查异步方法参数有效性
    • 添加合理的超时控制
  2. 资源限制

    • 限制并发异步任务数量
    • 对重要任务设置优先级
  3. 监控告警

    • 监控线程池关键指标
    • 设置异常告警阈值

七、与其他技术的整合

7.1 与Spring Retry整合

实现异步任务失败重试:

java 复制代码
@Async
@Retryable(value = {RemoteAccessException.class}, 
           maxAttempts = 3, 
           backoff = @Backoff(delay = 1000, multiplier = 2))
public CompletableFuture<String> callExternalService() {
    // 调用可能失败的外部服务
    return CompletableFuture.completedFuture(externalService.call());
}

// 重试全部失败后的处理
@Recover
public CompletableFuture<String> recover(RemoteAccessException e) {
    return CompletableFuture.completedFuture("默认返回值");
}

7.2 与Spring Cache整合

异步缓存更新:

java 复制代码
@Async
@CacheEvict(value = "users", key = "#userId")
public void evictUserCacheAsync(String userId) {
    // 异步清理缓存
}

@Async
@CachePut(value = "users", key = "#user.id")
public CompletableFuture<User> updateUserAsync(User user) {
    // 异步更新用户并更新缓存
    return CompletableFuture.completedFuture(userRepository.save(user));
}

7.3 与WebFlux响应式编程对比

特性 @Async WebFlux
编程模型 命令式 响应式
线程模型 线程池-based 事件循环
资源消耗 较高(每个请求一个线程) 较低(少量线程处理所有请求)
学习曲线 较低 较高
适用场景 传统Servlet应用 高并发IO密集型应用
背压支持 不支持 支持
集成复杂度 简单 中等

八、常见问题与解决方案

8.1 问题排查表

问题现象 可能原因 解决方案
@Async方法不异步执行 同类调用 确保通过Spring代理调用,使用@Autowired注入自身
未加@EnableAsync 在主配置类添加@EnableAsync
异步方法抛出异常不显示 未正确处理AsyncUncaughtException 实现AsyncUncaughtExceptionHandler
线程池不生效 未正确命名或注入 确保@Async("executorName")与@Bean名称一致
应用关闭时任务丢失 未配置优雅关闭 设置setWaitForTasksToCompleteOnShutdown(true)和awaitTerminationSeconds
性能未提升反而下降 线程池配置不合理 调整核心/最大线程数和队列容量
ThreadLocal值丢失 线程切换导致上下文丢失 使用TaskDecorator传递上下文

8.2 实战问题案例

案例1:数据库连接泄漏

java 复制代码
@Async
public void processData() {
    // 错误示范:未关闭Connection
    Connection conn = dataSource.getConnection();
    // 使用conn...
}

解决方案

java 复制代码
@Async
public void processData() {
    try (Connection conn = dataSource.getConnection()) {
        // 使用conn...
    } catch (SQLException e) {
        // 异常处理
    }
}

案例2:订单超时未支付取消

java 复制代码
@Async
@Scheduled(fixedDelay = 60000)  // 每分钟检查一次
public void cancelUnpaidOrders() {
    List<Order> unpaidOrders = orderRepository.findByStatusAndCreateTimeBefore(
        OrderStatus.UNPAID, 
        LocalDateTime.now().minusMinutes(30));
    
    unpaidOrders.forEach(order -> {
        order.setStatus(OrderStatus.CANCELLED);
        orderRepository.save(order);
        notificationService.sendCancelNotice(order);
    });
}

九、总结

9.1 核心要点总结

  1. 基础使用

    • @EnableAsync启用支持
    • @Async标注异步方法
    • 避免同类调用
  2. 线程池配置

    • 生产环境必须自定义线程池
    • 合理设置核心参数
    • 监控线程池状态
  3. 异常处理

    • 区分返回值类型选择处理方式
    • 实现全局异常处理器
    • 日志记录完整上下文
  4. 高级特性

    • 组合多个异步操作
    • 事务边界处理
    • 条件异步执行
  5. 最佳实践

    • 命名规范
    • 防御性编程
    • 资源清理
    • 上下文传递

9.2 完整示例代码

订单处理服务示例

java 复制代码
@Service
public class OrderProcessingService {
    
    private static final Logger logger = LoggerFactory.getLogger(OrderProcessingService.class);
    
    @Autowired
    private OrderRepository orderRepository;
    
    @Autowired
    private PaymentService paymentService;
    
    @Autowired
    private NotificationService notificationService;
    
    @Async("orderTaskExecutor")
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public CompletableFuture<OrderResult> processOrderAsync(Order order) {
        logger.info("开始异步处理订单: {}", order.getId());
        long startTime = System.currentTimeMillis();
        
        try {
            // 1. 保存订单
            Order savedOrder = orderRepository.save(order);
            
            // 2. 处理支付
            PaymentResult paymentResult = paymentService.processPayment(savedOrder);
            if (!paymentResult.isSuccess()) {
                throw new PaymentException("支付处理失败: " + paymentResult.getErrorMessage());
            }
            
            // 3. 更新订单状态
            savedOrder.setStatus(OrderStatus.PAID);
            orderRepository.save(savedOrder);
            
            // 4. 发送通知
            notificationService.sendOrderConfirmation(savedOrder);
            
            long elapsed = System.currentTimeMillis() - startTime;
            logger.info("订单处理完成: {}, 耗时: {}ms", savedOrder.getId(), elapsed);
            
            return CompletableFuture.completedFuture(
                new OrderResult(true, "订单处理成功", savedOrder));
                
        } catch (PaymentException e) {
            logger.error("订单支付异常: {}", order.getId(), e);
            return CompletableFuture.completedFuture(
                new OrderResult(false, e.getMessage(), order));
        } catch (Exception e) {
            logger.error("订单处理未知异常: {}", order.getId(), e);
            return CompletableFuture.failedFuture(e);
        }
    }
    
    // 批量异步处理订单
    @Async("batchOrderExecutor")
    public CompletableFuture<Void> processOrdersBatch(List<Order> orders) {
        List<CompletableFuture<OrderResult>> futures = orders.stream()
            .map(this::processOrderAsync)
            .collect(Collectors.toList());
            
        return CompletableFuture.allOf(futures.toArray(new CompletableFuture[0]))
            .exceptionally(ex -> {
                logger.error("批量处理订单异常", ex);
                return null;
            });
    }
}

// 配置类
@Configuration
@EnableAsync
public class AsyncConfig implements AsyncConfigurer {
    
    @Override
    public Executor getAsyncExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(10);
        executor.setMaxPoolSize(20);
        executor.setQueueCapacity(100);
        executor.setThreadNamePrefix("Order-Async-");
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        executor.setWaitForTasksToCompleteOnShutdown(true);
        executor.setAwaitTerminationSeconds(60);
        executor.initialize();
        return executor;
    }
    
    @Bean(name = "batchOrderExecutor")
    public Executor batchOrderExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(5);
        executor.setMaxPoolSize(10);
        executor.setQueueCapacity(50);
        executor.setThreadNamePrefix("Batch-Order-");
        executor.initialize();
        return executor;
    }
    
    @Override
    public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
        return new OrderAsyncExceptionHandler();
    }
}

// 全局异常处理器
public class OrderAsyncExceptionHandler implements AsyncUncaughtExceptionHandler {
    private static final Logger logger = LoggerFactory.getLogger(OrderAsyncExceptionHandler.class);
    
    @Override
    public void handleUncaughtException(Throwable ex, Method method, Object... params) {
        logger.error("异步订单处理异常 - 方法: {}, 参数: {}", method.getName(), Arrays.toString(params), ex);
        
        // 发送告警邮件
        if (ex instanceof CriticalOrderException) {
            sendCriticalAlert(method, ex, params);
        }
    }
    
    private void sendCriticalAlert(Method method, Throwable ex, Object... params) {
        // 实现告警逻辑
    }
}

喜欢的点个关注,想了解更多的可以关注微信公众号 "Eric的技术杂货库" ,提供更多的干货以及资料下载保存!

相关推荐
小林学习编程5 小时前
SpringBoot校园失物招领信息平台
java·spring boot·后端
愿你天黑有灯下雨有伞5 小时前
Spring Boot整合Kafka实战指南:从环境搭建到消息处理全解析
spring boot·kafka·linq
柯南二号8 小时前
【后端】SpringBoot用CORS解决无法跨域访问的问题
java·spring boot·后端
帮帮志9 小时前
vue实现与后台springboot传递数据【传值/取值 Axios 】
前端·vue.js·spring boot
杨不易呀11 小时前
Java面试高阶篇:Spring Boot+Quarkus+Redis高并发架构设计与性能优化实战
spring boot·redis·高并发·分布式锁·java面试·quarkus
Moshow郑锴12 小时前
Spring Boot 3 + Undertow 服务器优化配置
服务器·spring boot·后端
码农飞哥12 小时前
互联网大厂Java面试实战:Spring Boot到微服务的技术问答解析
java·数据库·spring boot·缓存·微服务·消息队列·面试技巧
愿你天黑有灯下雨有伞13 小时前
Spring Boot集成RabbitMQ高级篇:可靠性与性能提升
spring boot·rabbitmq·java-rabbitmq
曼岛_14 小时前
[Java实战]Spring Boot 整合 Redis(十八)
java·spring boot·redis