【技术解析】Spring Boot异步机制:实现高吞吐量的最佳实践

文章目录

  • [异步处理在Spring Boot中的应用](#异步处理在Spring Boot中的应用)
    • [1. 引言](#1. 引言)
    • [2. 异步处理的基本概念](#2. 异步处理的基本概念)
      • [2.1 同步与异步的区别](#2.1 同步与异步的区别)
      • [2.2 异步处理的工作原理](#2.2 异步处理的工作原理)
    • [3. 为什么需要异步处理](#3. 为什么需要异步处理)
      • [3.1 性能瓶颈分析](#3.1 性能瓶颈分析)
      • [3.2 并发能力的提升](#3.2 并发能力的提升)
      • [3.3 资源利用率最大化](#3.3 资源利用率最大化)
    • [4. Spring Boot中的异步支持](#4. Spring Boot中的异步支持)
      • [4.1 Spring Boot与异步处理](#4.1 Spring Boot与异步处理)
      • [4.2 Spring框架提供的异步解决方案](#4.2 Spring框架提供的异步解决方案)
      • [4.3 异步支持的核心组件介绍](#4.3 异步支持的核心组件介绍)
    • [5. 在Spring Boot中实现异步接口](#5. 在Spring Boot中实现异步接口)
      • [5.1 环境准备](#5.1 环境准备)
      • [5.2 调用异步方法](#5.2 调用异步方法)
    • [6. 基于Callable实现异步接口](#6. 基于Callable实现异步接口)
    • [7. 基于WebAsyncTask实现异步接口](#7. 基于WebAsyncTask实现异步接口)
    • [8. 基于DeferredResult实现异步接口](#8. 基于DeferredResult实现异步接口)
    • [9. 异步请求的应用场景](#9. 异步请求的应用场景)
    • [10. 异步处理的最佳实践](#10. 异步处理的最佳实践)
    • [11. 案例研究](#11. 案例研究)
    • [12. 结论](#12. 结论)

异步处理在Spring Boot中的应用

1. 引言

1. 异步处理的重要性

随着互联网应用的日益普及和用户需求的不断增长,现代软件系统面临着前所未有的挑战。其中之一便是如何在保证高性能的同时处理大量的并发请求。传统的同步处理模型虽然简单易懂,但在面对高并发场景时却显得力不从心。异步处理作为一种高效的编程范式,能够显著提高系统的响应速度和吞吐量,成为现代软件架构不可或缺的一部分。

2. Spring Boot中的异步机制概述

Spring Boot 是一款流行的Java框架,它简化了基于Spring的应用程序开发过程。Spring Boot 内置了对异步处理的支持,这使得开发者能够轻松地将异步机制引入到他们的应用程序中。Spring Boot 提供了一系列的工具和注解,如 @AsyncTaskExecutor,以及更高级的异步处理模型,如 Callable, WebAsyncTaskDeferredResult。通过这些工具,开发者可以灵活地设计和实现异步逻辑,从而提高应用程序的整体性能。

2. 异步处理的基本概念

2.1 同步与异步的区别

1. 同步处理的弊端

同步处理是指在执行某个操作时,必须等待该操作完成之后才能继续执行其他操作。这种模式在处理简单任务时非常直接且易于理解,但其缺点也非常明显:

  • 阻塞性:同步操作会阻塞线程,直到操作完成,这意味着线程在这段时间内不能处理其他任务。
  • 低效性:在处理耗时较长的任务(如数据库查询或远程服务调用)时,同步处理会导致线程长时间处于等待状态,从而降低系统的整体效率。
  • 可伸缩性差:由于同步操作会阻塞线程,因此在处理并发请求时,系统需要更多的线程来处理这些请求,这不仅增加了资源消耗,而且可能导致系统性能瓶颈。

2. 异步处理的优势

异步处理则允许系统在发起一个操作后立即返回,继续执行其他任务,而无需等待该操作完成。异步处理的主要优势如下:

  • 高效性:异步处理允许系统在等待耗时操作完成的同时处理其他任务,从而提高了资源利用率。
  • 可伸缩性:异步处理减少了对线程的需求,使得系统能够在较低的硬件资源下处理更多的并发请求。
  • 响应速度快:即使在高负载下,异步处理也能保持较快的响应速度,因为系统不会因为等待某个操作而被阻塞。

2.2 异步处理的工作原理

1. 异步任务的生命周期

异步任务的生命周期大致分为以下几个阶段:

  1. 提交:异步任务被提交给执行器。
  2. 执行:执行器调度线程来执行异步任务。
  3. 完成:异步任务执行完毕,结果可能通过回调或者事件通知的方式返回给调用者。

2. 异步任务的调度与执行

在Spring Boot中,异步任务通常由一个线程池执行器(TaskExecutor)进行调度和执行。线程池管理一组工作线程,这些线程负责执行提交的任务。当一个异步任务被提交时,执行器会根据线程池的状态选择一个可用的线程来执行该任务。如果所有的线程都在忙,任务会被放入队列中等待执行。一旦任务完成,执行器会通知调用方或者释放资源。


3. 为什么需要异步处理

3.1 性能瓶颈分析

在传统的同步模型中,系统性能往往受到以下几个方面的限制:

  • 线程阻塞:线程在等待某个耗时操作完成时会被阻塞,无法处理其他任务。
  • 资源竞争:多个线程同时访问共享资源时,可能会导致资源争用和死锁。
  • 硬件限制:服务器硬件资源(如CPU、内存)有限,过多的线程会消耗这些资源,从而影响系统的整体性能。

3.2 并发能力的提升

异步处理能够显著提升系统的并发处理能力。通过异步处理,系统可以在等待耗时操作的同时处理其他请求,这意味着:

  • 更高的吞吐量:系统能够同时处理更多的请求,从而提高了整体的吞吐量。
  • 更快的响应时间:由于系统不会因为等待某个操作而被阻塞,因此响应时间也会相应缩短。

3.3 资源利用率最大化

异步处理还有助于最大化系统资源的利用率:

  • 减少线程数量:异步处理减少了对线程的需求,因为系统不需要为每一个请求分配一个独立的线程。
  • 优化资源分配:通过合理地配置线程池,可以确保资源被有效地分配给最需要它们的任务。
  • 避免资源浪费:异步处理减少了不必要的线程创建和销毁,从而减少了资源的浪费。

4. Spring Boot中的异步支持

4.1 Spring Boot与异步处理

Spring Boot 作为一款基于 Spring 框架的快速应用开发框架,内置了对异步处理的支持。Spring Boot 通过提供一系列的工具和注解,使开发者能够轻松地在应用程序中实现异步处理逻辑。这对于提高应用程序的性能和响应速度至关重要。

4.2 Spring框架提供的异步解决方案

Spring 框架提供了多种异步处理的解决方案,包括但不限于:

  • @Async 注解:用于标记需要异步执行的方法。
  • TaskExecutor :执行异步任务的接口,可以通过 ThreadPoolTaskExecutorSimpleAsyncTaskExecutor 实现。
  • Callable:提供了一个返回结果的异步任务接口。
  • WebAsyncTask:为 Web 请求提供细粒度控制的异步任务。
  • DeferredResult:提供了一个非阻塞的方式来处理异步请求。

4.3 异步支持的核心组件介绍

1. TaskExecutor

TaskExecutor 是 Spring 中用于执行异步任务的核心接口。它定义了执行异步任务的基本方法,如 execute(Runnable task)。Spring 提供了多种实现方式,如 ThreadPoolTaskExecutorSimpleAsyncTaskExecutor

2. @Async 注解

@Async 注解用于标记需要异步执行的方法。它可以应用于任何类的方法上,只要该类被 Spring 管理即可。当方法被标记为 @Async 时,Spring 会使用指定的 TaskExecutor 来执行该方法。

3. Callable

Callable 接口用于定义那些需要返回结果的异步任务。它与 Runnable 类似,但 Callablecall() 方法可以返回一个结果,并且可以抛出异常。

4. WebAsyncTask

WebAsyncTask 是专门为 Web 请求设计的异步任务。它允许设置超时时间,并在超时后执行回调函数。

5. DeferredResult

DeferredResult 提供了一种非阻塞的方式来处理异步请求。它允许设置结果或错误,并且可以监听结果的变化。


5. 在Spring Boot中实现异步接口

5.1 环境准备

1. 项目初始化

假设我们已经创建了一个 Spring Boot 项目,接下来我们将在这个项目的基础上添加必要的依赖项,并启用异步支持。

2. 必要的依赖项

在项目的 pom.xml 文件中添加 Spring Boot 的核心依赖项以及 Spring Web 依赖项:

xml 复制代码
<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
</dependencies>

3. 启用异步支持

在 Spring Boot 应用程序的主类中,使用 @EnableAsync 注解启用异步支持:

java 复制代码
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableAsync;

@SpringBootApplication
@EnableAsync
public class AsyncApplication {
    public static void main(String[] args) {
        SpringApplication.run(AsyncApplication.class, args);
    }
}

4. 配置异步任务执行器

为了执行异步任务,我们需要配置一个 TaskExecutor Bean。下面是一个使用 ThreadPoolTaskExecutor 配置线程池的例子:

java 复制代码
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;

import java.util.concurrent.Executor;

@Configuration
@EnableAsync
public class AsyncConfig {

    @Bean(name = "taskExecutor")
    public Executor taskExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(2);
        executor.setMaxPoolSize(5);
        executor.setQueueCapacity(500);
        executor.setThreadNamePrefix("Async-");
        executor.initialize();
        return executor;
    }
}

5. 使用@Async注解实现异步方法

在需要异步执行的方法上使用 @Async 注解。这里我们定义一个简单的异步服务类:

java 复制代码
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;

@Service
public class AsyncService {

    @Async("taskExecutor")
    public void executeAsyncTask() {
        System.out.println("开始执行异步任务");
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("异步任务执行完成");
    }
}

6. 注解选项与最佳实践

  • 指定执行器 :可以通过 @Async("executorName") 指定特定的 TaskExecutor
  • 异步方法的返回类型 :异步方法可以返回 void 或者使用 Future 返回结果。
  • 异常处理 :通过 @Async 执行的方法抛出的异常默认会被包装成 CompletionException 抛出。

5.2 调用异步方法

在控制器中调用异步方法:

java 复制代码
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class AsyncController {

    @Autowired
    private AsyncService asyncService;

    @GetMapping("/async")
    public String async() {
        asyncService.executeAsyncTask();
        return "异步任务已提交";
    }
}

1. 异步方法的测试

为了确保异步方法按预期工作,我们可以编写单元测试来模拟异步行为。这里使用 JUnit 和 Mockito 进行测试:

java 复制代码
import org.junit.jupiter.api.Test;
import org.mockito.Mockito;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.test.web.servlet.MockMvc;

import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

@WebMvcTest(AsyncController.class)
public class AsyncControllerTest {

    @Autowired
    private MockMvc mockMvc;

    @MockBean
    private AsyncService asyncService;

    @Test
    public void shouldReturnDefaultMessage() throws Exception {
        mockMvc.perform(get("/async"))
               .andExpect(status().isOk())
               .andExpect(content().string("异步任务已提交"));
    }
}

6. 基于Callable实现异步接口

1. Callable接口简介

Callable 接口是 Java 标准库中提供的一个接口,它类似于 Runnable,但是它允许有返回值。在 Spring 中,我们可以使用 Callable 来执行那些需要返回结果的异步任务。

2. 创建异步任务

下面是一个使用 Callable 创建异步任务的例子:

java 复制代码
import org.springframework.stereotype.Service;

import java.util.concurrent.Callable;

@Service
public class CallableService {

    public Callable<String> executeCallableTask() {
        return () -> {
            System.out.println("开始执行Callable异步任务");
            Thread.sleep(2000);
            return "Callable异步任务执行完成";
        };
    }
}

3. 控制器中使用Callable

在控制器中调用 Callable 异步任务:

java 复制代码
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.concurrent.Callable;

@RestController
public class CallableController {

    @Autowired
    private CallableService callableService;

    @GetMapping("/callable")
    public Callable<String> callable() {
        return callableService.executeCallableTask();
    }
}

4. 处理Callable的返回值

Callable 的返回值可以通过 Future 对象获取。在 Spring Boot 中,我们可以直接返回 Callable,Spring 会自动将其转换为 Future


根据您的要求,下面是第七章至第九章的内容:


7. 基于WebAsyncTask实现异步接口

1. WebAsyncTask的特点

WebAsyncTask 是 Spring MVC 提供的一个专门针对 Web 请求的异步任务处理类。它允许设置超时时间,并在超时后执行回调函数,从而为异步请求提供了更细粒度的控制。

2. 创建异步任务

下面是一个使用 WebAsyncTask 创建异步任务的例子:

java 复制代码
import org.springframework.stereotype.Service;
import org.springframework.web.context.request.async.WebAsyncTask;

@Service
public class WebAsyncTaskService {

    public WebAsyncTask<String> executeWebAsyncTask() {
        Callable<String> callable = () -> {
            System.out.println("开始执行WebAsyncTask异步任务");
            Thread.sleep(2000);
            return "WebAsyncTask异步任务执行完成";
        };
        return new WebAsyncTask<>(3000, callable); // 设置超时时间为3秒
    }
}

3. 控制器中使用WebAsyncTask

在控制器中调用 WebAsyncTask 异步任务:

java 复制代码
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.request.async.WebAsyncTask;

@RestController
public class WebAsyncTaskController {

    @Autowired
    private WebAsyncTaskService webAsyncTaskService;

    @GetMapping("/webasynctask")
    public WebAsyncTask<String> webAsyncTask() {
        return webAsyncTaskService.executeWebAsyncTask();
    }
}

4. 设置超时时间和超时回调

WebAsyncTask 允许设置超时时间和超时后的回调函数。下面是一个示例,展示了如何设置超时时间为 5 秒,并在超时时返回一个错误消息:

java 复制代码
import org.springframework.stereotype.Service;
import org.springframework.web.context.request.async.WebAsyncTask;

import java.util.concurrent.Callable;

@Service
public class WebAsyncTaskService {

    public WebAsyncTask<String> executeWebAsyncTask() {
        Callable<String> callable = () -> {
            System.out.println("开始执行WebAsyncTask异步任务");
            Thread.sleep(2000);
            return "WebAsyncTask异步任务执行完成";
        };
        return new WebAsyncTask<>(5000, callable, (error) -> "任务超时"); // 设置超时时间为5秒,并在超时时返回"任务超时"
    }
}

8. 基于DeferredResult实现异步接口

1. DeferredResult的概念

DeferredResult 提供了一种非阻塞的方式来处理异步请求。它允许设置结果或错误,并且可以监听结果的变化。这使得 DeferredResult 成为处理那些需要长时间等待才能完成的任务的理想选择。

2. 创建异步任务

下面是一个使用 DeferredResult 创建异步任务的例子:

java 复制代码
import org.springframework.stereotype.Service;
import org.springframework.web.context.request.async.DeferredResult;

import java.util.concurrent.TimeUnit;

@Service
public class DeferredResultService {

    public DeferredResult<String> executeDeferredResultTask() {
        DeferredResult<String> deferredResult = new DeferredResult<>();
        new Thread(() -> {
            System.out.println("开始执行DeferredResult异步任务");
            try {
                TimeUnit.SECONDS.sleep(2); // 模拟耗时操作
                deferredResult.setResult("DeferredResult异步任务执行完成");
            } catch (InterruptedException e) {
                deferredResult.setErrorResult(e);
            }
        }).start();
        return deferredResult;
    }
}

3. 控制器中使用DeferredResult

在控制器中调用 DeferredResult 异步任务:

java 复制代码
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.request.async.DeferredResult;

@RestController
public class DeferredResultController {

    @Autowired
    private DeferredResultService deferredResultService;

    @GetMapping("/deferredresult")
    public DeferredResult<String> deferredResult() {
        return deferredResultService.executeDeferredResultTask();
    }
}

4. DeferredResult的高级用法

DeferredResult 支持一些高级功能,如设置超时时间、监听结果变化等。下面是一个示例,展示了如何设置超时时间,并在超时时返回一个错误消息:

java 复制代码
import org.springframework.stereotype.Service;
import org.springframework.web.context.request.async.DeferredResult;

import java.util.concurrent.TimeUnit;

@Service
public class DeferredResultService {

    public DeferredResult<String> executeDeferredResultTask() {
        DeferredResult<String> deferredResult = new DeferredResult<>(5000L, "任务超时"); // 设置超时时间为5秒
        new Thread(() -> {
            System.out.println("开始执行DeferredResult异步任务");
            try {
                TimeUnit.SECONDS.sleep(2); // 模拟耗时操作
                deferredResult.setResult("DeferredResult异步任务执行完成");
            } catch (InterruptedException e) {
                deferredResult.setErrorResult(e);
            }
        }).start();
        return deferredResult;
    }
}

9. 异步请求的应用场景

1. 长时间运行的任务

对于那些需要较长时间才能完成的任务,如文件上传、复杂计算、大量数据处理等,异步处理是非常有效的。这些任务往往会导致主线程阻塞,从而影响系统的响应速度。通过使用异步处理,可以确保主线程在等待这些任务完成的同时能够处理其他请求。

2. I/O操作优化

I/O 操作(如数据库查询、调用外部 API、文件读写等)通常比较耗时。在等待 I/O 操作完成时,如果使用同步处理,主线程将会被阻塞。通过异步处理,可以确保主线程在等待 I/O 操作的同时能够处理其他请求,从而提高系统的并发能力和吞吐量。

3. 资源密集型任务处理

资源密集型任务,如图像处理、视频编码等,往往会占用大量的 CPU 或内存资源。在处理这些任务时,如果采用同步处理,可能会导致系统资源的过度消耗,进而影响系统的稳定性。通过异步处理,可以更好地控制资源的使用,避免资源瓶颈。


根据您的要求,下面是第十章至第十二章的内容:


10. 异步处理的最佳实践

1. 异常处理与错误传播

在异步处理中,异常处理变得尤为重要,因为异步方法的异常并不会像同步方法那样直接抛出。下面是一些处理异步方法中异常的最佳实践:

  • 使用Future :当使用 @Async 时,异步方法可以返回 Future 对象。这样可以在 get() 方法中捕获异常。
  • 自定义异常处理器 :可以通过实现 AsyncUncaughtExceptionHandler 接口来自定义异常处理逻辑。
  • 使用CompletableFuture :使用 CompletableFuture 可以更容易地处理异常,并且可以链式调用各种完成和异常处理方法。

2. 测试异步代码

测试异步代码通常比测试同步代码更为复杂,但也是非常重要的。下面是一些测试异步代码的策略:

  • 使用Mockito :可以使用 Mockito 模拟异步行为,例如通过 verify 方法检查方法是否被正确调用。
  • 使用Spring Test :Spring 提供了专门的测试框架,如 SpringBootTestWebMvcTest,可以用来测试异步控制器。
  • 使用MockWebServer:对于涉及到 HTTP 请求的异步测试,可以使用 MockWebServer 来模拟服务器响应。

3. 监控与日志记录

监控和日志记录对于诊断和优化异步处理非常重要。以下是一些建议:

  • 日志记录:确保所有重要的异步任务都有详细的日志记录,以便于追踪任务的状态和异常情况。
  • 监控工具:使用监控工具如 Prometheus 和 Grafana 来收集和展示有关异步任务的指标。
  • 跟踪ID:为每个请求生成唯一的跟踪ID,并将其记录在日志中,以便于关联日志记录。

4. 性能调优技巧

为了确保异步处理达到最佳性能,可以考虑以下调优技巧:

  • 合理的线程池配置:根据系统的实际负载和资源限制来调整线程池的大小。
  • 超时设置:为异步任务设置合理的超时时间,以避免长时间阻塞。
  • 缓存策略:使用缓存来减少重复的异步请求,尤其是在处理耗时的 I/O 操作时。

11. 案例研究

假设有一个在线购物网站,用户在下单时需要上传图片。为了提高用户体验,我们决定将图片上传过程异步化。下面是如何实现这一点的具体步骤:

1. 环境准备

  • 使用 Spring Boot 创建一个新项目。
  • 添加 Spring Boot 的核心依赖项和 Spring Web 依赖项。

2. 配置异步支持

  • 在主类中启用异步支持。
  • 配置一个线程池执行器。

3. 实现异步服务

  • 定义一个 UploadService 类,其中包含一个 @Async 标记的方法来处理图片上传。
  • 使用 CallableFuture 来处理上传结果。

4. 控制器中调用异步方法

  • 在控制器中调用 UploadService 中的异步方法。
  • 返回一个适当的响应给前端。

5. 测试

  • 编写单元测试来验证异步方法的行为。
  • 使用集成测试来模拟整个流程。

效果对比与分析

  • 性能提升:通过异步处理图片上传,用户可以立即看到订单提交成功的页面,而无需等待图片上传完成。这大大提升了用户体验。
  • 资源利用率:异步处理减少了主线程的等待时间,使得服务器能够处理更多的并发请求,提高了资源利用率。
  • 故障恢复:通过异步处理,可以更好地处理失败的情况,例如重试上传或者记录错误日志。

12. 结论

异步处理带来的好处总结

  • 提高吞吐量:异步处理可以显著提高系统的吞吐量,因为它允许多个任务并发执行。
  • 改善用户体验:异步处理可以提高系统的响应速度,从而改善用户体验。
  • 资源优化:通过合理配置线程池,可以最大限度地减少资源浪费,并提高资源利用率。

未来发展趋势展望

随着云计算和微服务架构的普及,异步处理的重要性将进一步增加。未来的技术趋势包括:

  • 更强大的异步框架:随着 Spring 和其他框架的发展,异步处理将变得更加容易和强大。
  • 分布式异步处理:在分布式系统中,异步处理将更加普遍,以支持大规模的数据处理和事务管理。
  • 智能化监控与调优:随着 AI 技术的进步,未来的监控工具将能够智能地识别性能瓶颈,并提供调优建议。

相关推荐
九圣残炎1 分钟前
【从零开始的LeetCode-算法】3354. 使数组元素等于零
java·算法·leetcode
IT书架14 分钟前
golang面试题
开发语言·后端·golang
天天扭码35 分钟前
五天SpringCloud计划——DAY1之mybatis-plus的使用
java·spring cloud·mybatis
程序猿小柒41 分钟前
leetcode hot100【LeetCode 4.寻找两个正序数组的中位数】java实现
java·算法·leetcode
机器之心1 小时前
全球十亿级轨迹点驱动,首个轨迹基础大模型来了
人工智能·后端
不爱学习的YY酱1 小时前
【操作系统不挂科】<CPU调度(13)>选择题(带答案与解析)
java·linux·前端·算法·操作系统
丁总学Java1 小时前
Maven项目打包,com.sun.tools.javac.processing
java·maven
kikyo哎哟喂2 小时前
Java 代理模式详解
java·开发语言·代理模式
duration~2 小时前
SpringAOP模拟实现
java·开发语言
小码ssim2 小时前
IDEA使用tips(LTS✍)
java·ide·intellij-idea