ASP.NET Core 迁移至 Spring系列:类库框架篇

本文档是《ASP.NET Core 迁移至 Spring系列:类库框架篇》的类库框架篇分篇。

版本基线

类别 版本
C# 12(.NET 8)
Java 17(LTS)
Spring Boot 3.2.x
ASP.NET Core 8.0
NuGet 6.x
Maven 3.9.x

主流选型

维度 选型 备注
Java 版本 Java 17 LTS ---
Web 框架 Spring Boot 3.2.x 默认 Tomcat
ORM MyBatis-Plus
构建 Maven
日志 SLF4J + Logback
测试 JUnit 5
缓存 Redis + Caffeine + Redisson 二级缓存 + 分布式协调(锁/限流/集合)
MQ RocketMQ / Kafka 二选一

十、异步编程对照

10.1 C# async/await vs Java CompletableFuture

csharp 复制代码
// C#
public async Task<string> FetchDataAsync(string url) {
    var client = new HttpClient();
    var response = await client.GetStringAsync(url);
    return response;
}

// 调用
var data = await FetchDataAsync("https://api.com");
java 复制代码
// Java
public CompletableFuture<String> fetchDataAsync(String url) {
    HttpClient client = HttpClient.newHttpClient();
    return client.sendAsync(
        HttpRequest.newBuilder(URI.create(url)).build(),
        HttpResponse.BodyHandlers.ofString()
    ).thenApply(HttpResponse::body);
}

// 调用
String data = fetchDataAsync("https://api.com").join();

10.2 异步模型对比

维度 C# async/await Java CompletableFuture Java Reactive
模型 状态机自动管理 Future 链式调用 响应式流
错误处理 try/catch exceptionally / handle onError
组合 await Task.WhenAll allOf / anyOf merge / zip
取消 CancellationToken cancel(true) dispose
10.2.1 异步调用时序图

HTTP客户端 CompletableFuture 调用方 HTTP客户端 CompletableFuture 调用方 #mermaid-svg-LRso5u4w1QjZIYIJ{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-LRso5u4w1QjZIYIJ .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-LRso5u4w1QjZIYIJ .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-LRso5u4w1QjZIYIJ .error-icon{fill:#552222;}#mermaid-svg-LRso5u4w1QjZIYIJ .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-LRso5u4w1QjZIYIJ .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-LRso5u4w1QjZIYIJ .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-LRso5u4w1QjZIYIJ .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-LRso5u4w1QjZIYIJ .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-LRso5u4w1QjZIYIJ .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-LRso5u4w1QjZIYIJ .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-LRso5u4w1QjZIYIJ .marker{fill:#333333;stroke:#333333;}#mermaid-svg-LRso5u4w1QjZIYIJ .marker.cross{stroke:#333333;}#mermaid-svg-LRso5u4w1QjZIYIJ svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-LRso5u4w1QjZIYIJ p{margin:0;}#mermaid-svg-LRso5u4w1QjZIYIJ .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-LRso5u4w1QjZIYIJ text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-LRso5u4w1QjZIYIJ .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-LRso5u4w1QjZIYIJ .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-LRso5u4w1QjZIYIJ .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-LRso5u4w1QjZIYIJ .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-LRso5u4w1QjZIYIJ #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-LRso5u4w1QjZIYIJ .sequenceNumber{fill:white;}#mermaid-svg-LRso5u4w1QjZIYIJ #sequencenumber{fill:#333;}#mermaid-svg-LRso5u4w1QjZIYIJ #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-LRso5u4w1QjZIYIJ .messageText{fill:#333;stroke:none;}#mermaid-svg-LRso5u4w1QjZIYIJ .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-LRso5u4w1QjZIYIJ .labelText,#mermaid-svg-LRso5u4w1QjZIYIJ .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-LRso5u4w1QjZIYIJ .loopText,#mermaid-svg-LRso5u4w1QjZIYIJ .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-LRso5u4w1QjZIYIJ .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-LRso5u4w1QjZIYIJ .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-LRso5u4w1QjZIYIJ .noteText,#mermaid-svg-LRso5u4w1QjZIYIJ .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-LRso5u4w1QjZIYIJ .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-LRso5u4w1QjZIYIJ .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-LRso5u4w1QjZIYIJ .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-LRso5u4w1QjZIYIJ .actorPopupMenu{position:absolute;}#mermaid-svg-LRso5u4w1QjZIYIJ .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-LRso5u4w1QjZIYIJ .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-LRso5u4w1QjZIYIJ .actor-man circle,#mermaid-svg-LRso5u4w1QjZIYIJ line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-LRso5u4w1QjZIYIJ :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} fetchDataAsync()sendAsync()完成回调thenApply结果


十一、依赖注入对照

11.1 数据结构

#mermaid-svg-pIgirK5uU0XowWgr{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-pIgirK5uU0XowWgr .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-pIgirK5uU0XowWgr .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-pIgirK5uU0XowWgr .error-icon{fill:#552222;}#mermaid-svg-pIgirK5uU0XowWgr .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-pIgirK5uU0XowWgr .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-pIgirK5uU0XowWgr .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-pIgirK5uU0XowWgr .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-pIgirK5uU0XowWgr .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-pIgirK5uU0XowWgr .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-pIgirK5uU0XowWgr .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-pIgirK5uU0XowWgr .marker{fill:#333333;stroke:#333333;}#mermaid-svg-pIgirK5uU0XowWgr .marker.cross{stroke:#333333;}#mermaid-svg-pIgirK5uU0XowWgr svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-pIgirK5uU0XowWgr p{margin:0;}#mermaid-svg-pIgirK5uU0XowWgr .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-pIgirK5uU0XowWgr .cluster-label text{fill:#333;}#mermaid-svg-pIgirK5uU0XowWgr .cluster-label span{color:#333;}#mermaid-svg-pIgirK5uU0XowWgr .cluster-label span p{background-color:transparent;}#mermaid-svg-pIgirK5uU0XowWgr .label text,#mermaid-svg-pIgirK5uU0XowWgr span{fill:#333;color:#333;}#mermaid-svg-pIgirK5uU0XowWgr .node rect,#mermaid-svg-pIgirK5uU0XowWgr .node circle,#mermaid-svg-pIgirK5uU0XowWgr .node ellipse,#mermaid-svg-pIgirK5uU0XowWgr .node polygon,#mermaid-svg-pIgirK5uU0XowWgr .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-pIgirK5uU0XowWgr .rough-node .label text,#mermaid-svg-pIgirK5uU0XowWgr .node .label text,#mermaid-svg-pIgirK5uU0XowWgr .image-shape .label,#mermaid-svg-pIgirK5uU0XowWgr .icon-shape .label{text-anchor:middle;}#mermaid-svg-pIgirK5uU0XowWgr .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-pIgirK5uU0XowWgr .rough-node .label,#mermaid-svg-pIgirK5uU0XowWgr .node .label,#mermaid-svg-pIgirK5uU0XowWgr .image-shape .label,#mermaid-svg-pIgirK5uU0XowWgr .icon-shape .label{text-align:center;}#mermaid-svg-pIgirK5uU0XowWgr .node.clickable{cursor:pointer;}#mermaid-svg-pIgirK5uU0XowWgr .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-pIgirK5uU0XowWgr .arrowheadPath{fill:#333333;}#mermaid-svg-pIgirK5uU0XowWgr .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-pIgirK5uU0XowWgr .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-pIgirK5uU0XowWgr .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-pIgirK5uU0XowWgr .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-pIgirK5uU0XowWgr .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-pIgirK5uU0XowWgr .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-pIgirK5uU0XowWgr .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-pIgirK5uU0XowWgr .cluster text{fill:#333;}#mermaid-svg-pIgirK5uU0XowWgr .cluster span{color:#333;}#mermaid-svg-pIgirK5uU0XowWgr div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-pIgirK5uU0XowWgr .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-pIgirK5uU0XowWgr rect.text{fill:none;stroke-width:0;}#mermaid-svg-pIgirK5uU0XowWgr .icon-shape,#mermaid-svg-pIgirK5uU0XowWgr .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-pIgirK5uU0XowWgr .icon-shape p,#mermaid-svg-pIgirK5uU0XowWgr .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-pIgirK5uU0XowWgr .icon-shape .label rect,#mermaid-svg-pIgirK5uU0XowWgr .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-pIgirK5uU0XowWgr .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-pIgirK5uU0XowWgr .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-pIgirK5uU0XowWgr :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 依赖注入容器
服务注册
服务解析
生命周期管理
Singleton
Scoped
Transient
ASP.NET Core DI
Spring IoC
Guice / Dagger

11.2 代码对照

csharp 复制代码
// C# Program.cs
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddSingleton<IMyService, MyService>();
builder.Services.AddScoped<IRepository, Repository>();
builder.Services.AddTransient<IValidator, Validator>();
var app = builder.Build();

// 构造函数注入
public class MyController {
    public MyController(IMyService service) { _service = service; }
}
java 复制代码
// Java Spring
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Scope;

@Configuration
public class AppConfig {
    // Spring 中无状态默认 bean 本身就是单例,不需要 @Singleton
    // 若要显式标注,可使用 @Scope(ConfigurableBeanFactory.SCOPE_SINGLETON)
    @Bean
    public MyService myService() { return new MyService(); }

    @Bean
    @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
    public Validator validator() { return new Validator(); }
}

// 构造函数注入
@RestController
public class MyController {
    private final MyService service;
    public MyController(MyService service) { this.service = service; }
}

11.3 生命周期对照

重要说明ASP.NET Core 的 Scoped 与 Spring 的 request 作用域语义近似但不完全相同ASP.NET Core 一个 HTTP 请求内 Scoped 实例复用;Spring 的 request 作用域只在 Web 容器内生效(需 RequestContextFilter),且 Session 作用域依赖 Session。当前表给出"近似关系",不是注解级一一对应。

ASP.NET Core Spring 近似关系 说明
Singleton 默认无状态 bean(@Scope("singleton") 容器内单例
Scoped @Scope("request") / @Scope("session") 请求/会话级,语义不完全相同
Transient @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE) 每次注入新建

十二、配置文件对照

12.1 数据结构

#mermaid-svg-6auidQfvJcYbdh8j{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-6auidQfvJcYbdh8j .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-6auidQfvJcYbdh8j .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-6auidQfvJcYbdh8j .error-icon{fill:#552222;}#mermaid-svg-6auidQfvJcYbdh8j .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-6auidQfvJcYbdh8j .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-6auidQfvJcYbdh8j .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-6auidQfvJcYbdh8j .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-6auidQfvJcYbdh8j .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-6auidQfvJcYbdh8j .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-6auidQfvJcYbdh8j .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-6auidQfvJcYbdh8j .marker{fill:#333333;stroke:#333333;}#mermaid-svg-6auidQfvJcYbdh8j .marker.cross{stroke:#333333;}#mermaid-svg-6auidQfvJcYbdh8j svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-6auidQfvJcYbdh8j p{margin:0;}#mermaid-svg-6auidQfvJcYbdh8j .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-6auidQfvJcYbdh8j .cluster-label text{fill:#333;}#mermaid-svg-6auidQfvJcYbdh8j .cluster-label span{color:#333;}#mermaid-svg-6auidQfvJcYbdh8j .cluster-label span p{background-color:transparent;}#mermaid-svg-6auidQfvJcYbdh8j .label text,#mermaid-svg-6auidQfvJcYbdh8j span{fill:#333;color:#333;}#mermaid-svg-6auidQfvJcYbdh8j .node rect,#mermaid-svg-6auidQfvJcYbdh8j .node circle,#mermaid-svg-6auidQfvJcYbdh8j .node ellipse,#mermaid-svg-6auidQfvJcYbdh8j .node polygon,#mermaid-svg-6auidQfvJcYbdh8j .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-6auidQfvJcYbdh8j .rough-node .label text,#mermaid-svg-6auidQfvJcYbdh8j .node .label text,#mermaid-svg-6auidQfvJcYbdh8j .image-shape .label,#mermaid-svg-6auidQfvJcYbdh8j .icon-shape .label{text-anchor:middle;}#mermaid-svg-6auidQfvJcYbdh8j .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-6auidQfvJcYbdh8j .rough-node .label,#mermaid-svg-6auidQfvJcYbdh8j .node .label,#mermaid-svg-6auidQfvJcYbdh8j .image-shape .label,#mermaid-svg-6auidQfvJcYbdh8j .icon-shape .label{text-align:center;}#mermaid-svg-6auidQfvJcYbdh8j .node.clickable{cursor:pointer;}#mermaid-svg-6auidQfvJcYbdh8j .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-6auidQfvJcYbdh8j .arrowheadPath{fill:#333333;}#mermaid-svg-6auidQfvJcYbdh8j .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-6auidQfvJcYbdh8j .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-6auidQfvJcYbdh8j .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-6auidQfvJcYbdh8j .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-6auidQfvJcYbdh8j .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-6auidQfvJcYbdh8j .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-6auidQfvJcYbdh8j .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-6auidQfvJcYbdh8j .cluster text{fill:#333;}#mermaid-svg-6auidQfvJcYbdh8j .cluster span{color:#333;}#mermaid-svg-6auidQfvJcYbdh8j div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-6auidQfvJcYbdh8j .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-6auidQfvJcYbdh8j rect.text{fill:none;stroke-width:0;}#mermaid-svg-6auidQfvJcYbdh8j .icon-shape,#mermaid-svg-6auidQfvJcYbdh8j .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-6auidQfvJcYbdh8j .icon-shape p,#mermaid-svg-6auidQfvJcYbdh8j .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-6auidQfvJcYbdh8j .icon-shape .label rect,#mermaid-svg-6auidQfvJcYbdh8j .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-6auidQfvJcYbdh8j .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-6auidQfvJcYbdh8j .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-6auidQfvJcYbdh8j :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 配置文件
ASP.NET Core
Spring Boot
appsettings.json
appsettings.Development.json
IConfiguration
application.yml
application-dev.yml
@Value / @ConfigurationProperties

12.2 代码对照

json 复制代码
// ASP.NET Core: appsettings.json
{
  "Logging": {
    "LogLevel": {
      "Default": "Information"
    }
  },
  "ConnectionStrings": {
    "DefaultConnection": "Server=localhost;Database=mydb"
  }
}
yaml 复制代码
# Spring Boot: application.yml
logging:
  level:
    root: INFO
spring:
  datasource:
    url: jdbc:mysql://localhost:3306/mydb

12.3 读取配置

csharp 复制代码
// C#
public class MyService(IConfiguration config) {
    private string connStr = config.GetConnectionString("DefaultConnection");
}
java 复制代码
// Java
@Service
public class MyService {
    @Value("${spring.datasource.url}")
    private String url;
}

十三、Web 框架对照

Spring Boot 默认 Tomcat,不是 Jetty 或 Nettyspring-boot-starter-web 使用 Servlet 栈并默认装配 Tomcat;spring-boot-starter-webflux 才是 Reactive 栈,默认装配 Reactor Netty。Jetty 和 Undertow 都需要显式切换,详见第三十三章。

13.1 数据结构

#mermaid-svg-PQW0O0m1gcFhMXIe{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-PQW0O0m1gcFhMXIe .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-PQW0O0m1gcFhMXIe .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-PQW0O0m1gcFhMXIe .error-icon{fill:#552222;}#mermaid-svg-PQW0O0m1gcFhMXIe .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-PQW0O0m1gcFhMXIe .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-PQW0O0m1gcFhMXIe .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-PQW0O0m1gcFhMXIe .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-PQW0O0m1gcFhMXIe .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-PQW0O0m1gcFhMXIe .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-PQW0O0m1gcFhMXIe .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-PQW0O0m1gcFhMXIe .marker{fill:#333333;stroke:#333333;}#mermaid-svg-PQW0O0m1gcFhMXIe .marker.cross{stroke:#333333;}#mermaid-svg-PQW0O0m1gcFhMXIe svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-PQW0O0m1gcFhMXIe p{margin:0;}#mermaid-svg-PQW0O0m1gcFhMXIe .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-PQW0O0m1gcFhMXIe .cluster-label text{fill:#333;}#mermaid-svg-PQW0O0m1gcFhMXIe .cluster-label span{color:#333;}#mermaid-svg-PQW0O0m1gcFhMXIe .cluster-label span p{background-color:transparent;}#mermaid-svg-PQW0O0m1gcFhMXIe .label text,#mermaid-svg-PQW0O0m1gcFhMXIe span{fill:#333;color:#333;}#mermaid-svg-PQW0O0m1gcFhMXIe .node rect,#mermaid-svg-PQW0O0m1gcFhMXIe .node circle,#mermaid-svg-PQW0O0m1gcFhMXIe .node ellipse,#mermaid-svg-PQW0O0m1gcFhMXIe .node polygon,#mermaid-svg-PQW0O0m1gcFhMXIe .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-PQW0O0m1gcFhMXIe .rough-node .label text,#mermaid-svg-PQW0O0m1gcFhMXIe .node .label text,#mermaid-svg-PQW0O0m1gcFhMXIe .image-shape .label,#mermaid-svg-PQW0O0m1gcFhMXIe .icon-shape .label{text-anchor:middle;}#mermaid-svg-PQW0O0m1gcFhMXIe .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-PQW0O0m1gcFhMXIe .rough-node .label,#mermaid-svg-PQW0O0m1gcFhMXIe .node .label,#mermaid-svg-PQW0O0m1gcFhMXIe .image-shape .label,#mermaid-svg-PQW0O0m1gcFhMXIe .icon-shape .label{text-align:center;}#mermaid-svg-PQW0O0m1gcFhMXIe .node.clickable{cursor:pointer;}#mermaid-svg-PQW0O0m1gcFhMXIe .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-PQW0O0m1gcFhMXIe .arrowheadPath{fill:#333333;}#mermaid-svg-PQW0O0m1gcFhMXIe .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-PQW0O0m1gcFhMXIe .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-PQW0O0m1gcFhMXIe .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-PQW0O0m1gcFhMXIe .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-PQW0O0m1gcFhMXIe .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-PQW0O0m1gcFhMXIe .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-PQW0O0m1gcFhMXIe .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-PQW0O0m1gcFhMXIe .cluster text{fill:#333;}#mermaid-svg-PQW0O0m1gcFhMXIe .cluster span{color:#333;}#mermaid-svg-PQW0O0m1gcFhMXIe div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-PQW0O0m1gcFhMXIe .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-PQW0O0m1gcFhMXIe rect.text{fill:none;stroke-width:0;}#mermaid-svg-PQW0O0m1gcFhMXIe .icon-shape,#mermaid-svg-PQW0O0m1gcFhMXIe .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-PQW0O0m1gcFhMXIe .icon-shape p,#mermaid-svg-PQW0O0m1gcFhMXIe .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-PQW0O0m1gcFhMXIe .icon-shape .label rect,#mermaid-svg-PQW0O0m1gcFhMXIe .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-PQW0O0m1gcFhMXIe .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-PQW0O0m1gcFhMXIe .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-PQW0O0m1gcFhMXIe :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} Web 框架
ASP.NET Core MVC
Spring Boot MVC
Controller
Action
Filter
Middleware
@RestController
@GetMapping/@PostMapping
Filter / Interceptor
HandlerInterceptor

13.2 Controller 对照

csharp 复制代码
// C# ASP.NET Core
[ApiController]
[Route("api/[controller]")]
public class UserController : ControllerBase {
    private readonly IUserService _userService;
    
    public UserController(IUserService userService) {
        _userService = userService;
    }
    
    [HttpGet("{id}")]
    public async Task<ActionResult<User>> GetUser(int id) {
        var user = await _userService.GetByIdAsync(id);
        return user == null ? NotFound() : Ok(user);
    }
    
    [HttpPost]
    public async Task<ActionResult<User>> CreateUser(User user) {
        var created = await _userService.CreateAsync(user);
        return CreatedAtAction(nameof(GetUser), new { id = created.Id }, created);
    }
}
java 复制代码
// Java Spring Boot
@RestController
@RequestMapping("/api/user")
public class UserController {
    private final UserService userService;

    public UserController(UserService userService) {
        this.userService = userService;
    }

    @GetMapping("/{id}")
    public ResponseEntity<User> getUser(@PathVariable int id) {
        User user = userService.getById(id);
        return user == null ? ResponseEntity.notFound().build() : ResponseEntity.ok(user);
    }

    @PostMapping
    public ResponseEntity<User> createUser(@RequestBody User user) {
        User created = userService.create(user);
        return ResponseEntity.status(HttpStatus.CREATED).body(created);
    }
}
13.2.1 Web 请求处理时序图

数据库 Repository Service DI 容器 Controller Middleware/Filter 链 Kestrel/Tomcat 客户端 数据库 Repository Service DI 容器 Controller Middleware/Filter 链 Kestrel/Tomcat 客户端 #mermaid-svg-NfDQ5lICV39mbwHi{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-NfDQ5lICV39mbwHi .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-NfDQ5lICV39mbwHi .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-NfDQ5lICV39mbwHi .error-icon{fill:#552222;}#mermaid-svg-NfDQ5lICV39mbwHi .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-NfDQ5lICV39mbwHi .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-NfDQ5lICV39mbwHi .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-NfDQ5lICV39mbwHi .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-NfDQ5lICV39mbwHi .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-NfDQ5lICV39mbwHi .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-NfDQ5lICV39mbwHi .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-NfDQ5lICV39mbwHi .marker{fill:#333333;stroke:#333333;}#mermaid-svg-NfDQ5lICV39mbwHi .marker.cross{stroke:#333333;}#mermaid-svg-NfDQ5lICV39mbwHi svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-NfDQ5lICV39mbwHi p{margin:0;}#mermaid-svg-NfDQ5lICV39mbwHi .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-NfDQ5lICV39mbwHi text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-NfDQ5lICV39mbwHi .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-NfDQ5lICV39mbwHi .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-NfDQ5lICV39mbwHi .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-NfDQ5lICV39mbwHi .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-NfDQ5lICV39mbwHi #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-NfDQ5lICV39mbwHi .sequenceNumber{fill:white;}#mermaid-svg-NfDQ5lICV39mbwHi #sequencenumber{fill:#333;}#mermaid-svg-NfDQ5lICV39mbwHi #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-NfDQ5lICV39mbwHi .messageText{fill:#333;stroke:none;}#mermaid-svg-NfDQ5lICV39mbwHi .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-NfDQ5lICV39mbwHi .labelText,#mermaid-svg-NfDQ5lICV39mbwHi .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-NfDQ5lICV39mbwHi .loopText,#mermaid-svg-NfDQ5lICV39mbwHi .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-NfDQ5lICV39mbwHi .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-NfDQ5lICV39mbwHi .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-NfDQ5lICV39mbwHi .noteText,#mermaid-svg-NfDQ5lICV39mbwHi .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-NfDQ5lICV39mbwHi .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-NfDQ5lICV39mbwHi .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-NfDQ5lICV39mbwHi .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-NfDQ5lICV39mbwHi .actorPopupMenu{position:absolute;}#mermaid-svg-NfDQ5lICV39mbwHi .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-NfDQ5lICV39mbwHi .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-NfDQ5lICV39mbwHi .actor-man circle,#mermaid-svg-NfDQ5lICV39mbwHi line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-NfDQ5lICV39mbwHi :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} HTTP 请求1进入中间件链2路由匹配 + 鉴权3注入 Service4返回 Service 实例5调用业务方法6数据操作7SQL8结果集9实体10业务结果11ResponseEntity12HTTP 响应(JSON)13

13.3 注解对照

ASP.NET Core Spring MVC 说明
[ApiController] @RestController + 参数校验/异常处理配置 功能相近但非注解级一一对应
[Route] @RequestMapping 路由映射
[HttpGet] @GetMapping GET 请求
[HttpPost] @PostMapping POST 请求
[HttpPut] @PutMapping PUT 请求
[HttpDelete] @DeleteMapping DELETE 请求
[FromBody] @RequestBody 请求体
[FromQuery] @RequestParam 查询参数
[FromRoute] @PathVariable 路径参数
[FromHeader] @RequestHeader 请求头
[Authorize] @PreAuthorize 权限校验

十四、ORM 对照(主流选型:MyBatis-Plus)

14.1 MyBatis-Plus 是什么

MyBatis-Plus 是 MyBatis 的增强工具,在保留 SQL 可见性和可控性的基础上,补齐通用 CRUD、条件构造器、分页、逻辑删除、乐观锁和代码生成能力。对 ASP.NET Core 人员来说,它更接近"EF Core 的仓储效率 + 显式 SQL 的控制力",而不是把数据库行为完全藏起来的全自动 ORM。

主线只需要先记住四个入口:BaseMapper 提供 Mapper 层通用 CRUD,IService / ServiceImpl 提供 Service 层增强,QueryWrapper / LambdaQueryWrapper 构造条件,MybatisPlusInterceptor 挂载分页、租户和数据权限等插件。

14.2 C# EF Core 对照 MyBatis-Plus

维度 EF Core MyBatis-Plus
数据访问模型 DbContext + DbSet<T> BaseMapper<T> + IService<T>
查询方式 LINQ 表达式由框架翻译为 SQL Wrapper、注解或 XML 显式组织 SQL
SQL 可见性 通过日志和拦截器观察生成 SQL 自定义 SQL 与执行计划更直接可见
实体映射 Fluent API / Data Annotations @TableName@TableId@TableField
通用 CRUD AddUpdateRemoveSaveChanges insertupdateByIddeleteByIdselectById
分页 LINQ Skip / Take 或扩展库 PaginationInnerInterceptor + IPage
批量 / 事务 SaveChanges + TransactionScope Service 方法 + Spring @Transactional
迁移时的注意点 表达式、跟踪状态和连接生命周期 SQL、结果映射、事务边界和分页 count 由开发者关注

14.3 MyBatis-Plus 核心组件

#mermaid-svg-3wcSuOZeY4B9NeHn{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-3wcSuOZeY4B9NeHn .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-3wcSuOZeY4B9NeHn .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-3wcSuOZeY4B9NeHn .error-icon{fill:#552222;}#mermaid-svg-3wcSuOZeY4B9NeHn .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-3wcSuOZeY4B9NeHn .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-3wcSuOZeY4B9NeHn .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-3wcSuOZeY4B9NeHn .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-3wcSuOZeY4B9NeHn .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-3wcSuOZeY4B9NeHn .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-3wcSuOZeY4B9NeHn .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-3wcSuOZeY4B9NeHn .marker{fill:#333333;stroke:#333333;}#mermaid-svg-3wcSuOZeY4B9NeHn .marker.cross{stroke:#333333;}#mermaid-svg-3wcSuOZeY4B9NeHn svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-3wcSuOZeY4B9NeHn p{margin:0;}#mermaid-svg-3wcSuOZeY4B9NeHn .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-3wcSuOZeY4B9NeHn .cluster-label text{fill:#333;}#mermaid-svg-3wcSuOZeY4B9NeHn .cluster-label span{color:#333;}#mermaid-svg-3wcSuOZeY4B9NeHn .cluster-label span p{background-color:transparent;}#mermaid-svg-3wcSuOZeY4B9NeHn .label text,#mermaid-svg-3wcSuOZeY4B9NeHn span{fill:#333;color:#333;}#mermaid-svg-3wcSuOZeY4B9NeHn .node rect,#mermaid-svg-3wcSuOZeY4B9NeHn .node circle,#mermaid-svg-3wcSuOZeY4B9NeHn .node ellipse,#mermaid-svg-3wcSuOZeY4B9NeHn .node polygon,#mermaid-svg-3wcSuOZeY4B9NeHn .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-3wcSuOZeY4B9NeHn .rough-node .label text,#mermaid-svg-3wcSuOZeY4B9NeHn .node .label text,#mermaid-svg-3wcSuOZeY4B9NeHn .image-shape .label,#mermaid-svg-3wcSuOZeY4B9NeHn .icon-shape .label{text-anchor:middle;}#mermaid-svg-3wcSuOZeY4B9NeHn .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-3wcSuOZeY4B9NeHn .rough-node .label,#mermaid-svg-3wcSuOZeY4B9NeHn .node .label,#mermaid-svg-3wcSuOZeY4B9NeHn .image-shape .label,#mermaid-svg-3wcSuOZeY4B9NeHn .icon-shape .label{text-align:center;}#mermaid-svg-3wcSuOZeY4B9NeHn .node.clickable{cursor:pointer;}#mermaid-svg-3wcSuOZeY4B9NeHn .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-3wcSuOZeY4B9NeHn .arrowheadPath{fill:#333333;}#mermaid-svg-3wcSuOZeY4B9NeHn .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-3wcSuOZeY4B9NeHn .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-3wcSuOZeY4B9NeHn .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-3wcSuOZeY4B9NeHn .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-3wcSuOZeY4B9NeHn .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-3wcSuOZeY4B9NeHn .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-3wcSuOZeY4B9NeHn .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-3wcSuOZeY4B9NeHn .cluster text{fill:#333;}#mermaid-svg-3wcSuOZeY4B9NeHn .cluster span{color:#333;}#mermaid-svg-3wcSuOZeY4B9NeHn div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-3wcSuOZeY4B9NeHn .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-3wcSuOZeY4B9NeHn rect.text{fill:none;stroke-width:0;}#mermaid-svg-3wcSuOZeY4B9NeHn .icon-shape,#mermaid-svg-3wcSuOZeY4B9NeHn .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-3wcSuOZeY4B9NeHn .icon-shape p,#mermaid-svg-3wcSuOZeY4B9NeHn .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-3wcSuOZeY4B9NeHn .icon-shape .label rect,#mermaid-svg-3wcSuOZeY4B9NeHn .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-3wcSuOZeY4B9NeHn .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-3wcSuOZeY4B9NeHn .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-3wcSuOZeY4B9NeHn :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} MyBatis-Plus
BaseMapper
IService / ServiceImpl
Wrapper 条件构造器
MybatisPlusInterceptor
注解映射
代码生成器
通用 CRUD
业务服务复用
QueryWrapper
LambdaQueryWrapper
分页
多租户 / 数据权限
@TableName / @TableId
AutoGenerator

组件 作用 迁移时对应理解
BaseMapper<T> 提供单表常用 CRUD 省掉大量 Repository 样板代码
IService<T> / ServiceImpl 在 Service 层复用 CRUD、批量和分页能力 业务服务仍应保留事务和领域规则
QueryWrapper<T> 用字符串列名拼接动态条件 灵活,但列名重构缺少类型检查
LambdaQueryWrapper<T> 用方法引用构造条件 常规查询优先使用,重构更安全
MybatisPlusInterceptor 注册分页、租户、数据权限等插件 统一放在 Spring 配置中管理
@TableName / @TableId / @TableField 声明表名、主键和字段映射 对应 EF Core 的实体映射配置

14.4 上手第一步:在 Spring Boot 17 项目中集成 MyBatis-Plus

把最小闭环压缩成三步:加依赖、配置数据源、写实体和接口。示例使用 MySQL 和逻辑删除字段;生产项目应把密码放在环境变量或密钥管理服务中。

xml 复制代码
<!-- 1. 加依赖 -->
<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-spring-boot3-starter</artifactId>
    <version>3.5.9</version>
</dependency>
<dependency>
    <groupId>com.mysql</groupId>
    <artifactId>mysql-connector-j</artifactId>
    <scope>runtime</scope>
</dependency>
yaml 复制代码
# 2. application.yml
spring:
  datasource:
    url: jdbc:mysql://localhost:3306/demo
    username: root
    password: root
    driver-class-name: com.mysql.cj.jdbc.Driver

mybatis-plus:
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
  global-config:
    db-config:
      logic-delete-field: deleted
      id-type: auto
java 复制代码
// 3. 写代码
@Data
@TableName("t_user")
public class User {
    @TableId(type = IdType.AUTO)
    private Long id;
    private String name;
}

public interface UserMapper extends BaseMapper<User> {}

public interface UserService extends IService<User> {}

@Service
@AllArgsConstructor
public class UserServiceImpl extends ServiceImpl<UserMapper, User>
        implements UserService {
    public boolean saveUser(User user) { return save(user); }
}

@RestController
@RequestMapping("/api/user")
@AllArgsConstructor
public class UserController {
    private final UserService userService;
    @GetMapping("/{id}")
    public User getById(@PathVariable Long id) {
        return userService.getById(id);
    }
}

跑起来:mvn spring-boot:run

示例中的 @Data@AllArgsConstructor 来自 Lombok;如果项目没有统一引入 Lombok,改成显式 getter、setter 和构造器即可。StdOutImpl 适合本地确认 SQL,生产环境继续使用 SLF4J + Logback 的日志配置。

14.5 Wrapper 条件构造器

java 复制代码
// QueryWrapper(字符串列名,适合动态拼接但重构时缺少类型检查)
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.eq("name", "张三")
       .ge("age", 18)
       .like("email", "@gmail.com")
       .orderByDesc("create_time");
List<User> users = userMapper.selectList(wrapper);

// LambdaQueryWrapper(类型安全,推荐)
LambdaQueryWrapper<User> lambda = new LambdaQueryWrapper<>();
lambda.eq(User::getName, "张三")
      .ge(User::getAge, 18)
      .like(User::getEmail, "@gmail.com")
      .orderByDesc(User::getCreateTime);
List<User> users = userMapper.selectList(lambda);

LambdaQueryWrapper 解决的是 Java 属性重命名时的编译期约束,不会替开发者检查索引、分页规模或 SQL 执行计划;复杂查询仍应回到 SQL 和数据库指标本身。

14.6 FAQ

  • Q1:MyBatis-Plus 与 JPA / Hibernate 如何选型?
    • 按本指南的新项目口径直接使用 MyBatis-Plus;接手老项目时先识别现有持久化边界,不要为了换框架而重写稳定查询。
  • Q2:MyBatis-Plus 分页插件原理?
    • PaginationInnerInterceptor 根据数据库类型改写分页 SQL,并生成 count 查询后封装为 IPage;大表要关注 count 成本、索引和深分页。
  • Q3:MyBatis-Plus 乐观锁如何实现?
    • 实体增加 @Version 字段并注册 OptimisticLockerInnerInterceptor;更新受影响行数为 0 时由业务决定重试、提示或放弃。
  • Q4:LambdaQueryWrapper 与 QueryWrapper 区别?
    • QueryWrapper 使用字符串列名,动态性高但重构不安全;LambdaQueryWrapper 使用方法引用,常规 Java 查询优先使用后者。
  • Q5:MyBatis-Plus 与 Spring Data JPA 性能如何比较?
    • 没有脱离查询、索引、连接池、返回列数和数据量的固定结论;应固定数据集和并发模型,比较执行计划、P95/P99、数据库 CPU、网络传输量和 GC。
  • Q6:在 Spring Boot 17 项目中如何选择 MyBatis-Plus vs Spring Data JPA?
    • 新项目按本指南统一 MyBatis-Plus;只有在接手已有 JPA 领域模型且改造收益明确时才继续沿用,避免同一聚合混用两套持久化语义。

14.7 老项目速览(一句话带过)

老方案 现在还用吗 应对
Spring Data JPA / Hibernate 老项目 / 金融行业仍有 看一眼 JpaRepository 即可
MyBatis 原生 部分国产老项目 @Mapper 注解,无 BaseMapper
JOOQ / Exposed 几乎不用 跳过

十五、日志对照

15.1 数据结构

#mermaid-svg-V8xJjMXGm5kD78IP{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-V8xJjMXGm5kD78IP .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-V8xJjMXGm5kD78IP .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-V8xJjMXGm5kD78IP .error-icon{fill:#552222;}#mermaid-svg-V8xJjMXGm5kD78IP .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-V8xJjMXGm5kD78IP .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-V8xJjMXGm5kD78IP .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-V8xJjMXGm5kD78IP .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-V8xJjMXGm5kD78IP .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-V8xJjMXGm5kD78IP .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-V8xJjMXGm5kD78IP .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-V8xJjMXGm5kD78IP .marker{fill:#333333;stroke:#333333;}#mermaid-svg-V8xJjMXGm5kD78IP .marker.cross{stroke:#333333;}#mermaid-svg-V8xJjMXGm5kD78IP svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-V8xJjMXGm5kD78IP p{margin:0;}#mermaid-svg-V8xJjMXGm5kD78IP .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-V8xJjMXGm5kD78IP .cluster-label text{fill:#333;}#mermaid-svg-V8xJjMXGm5kD78IP .cluster-label span{color:#333;}#mermaid-svg-V8xJjMXGm5kD78IP .cluster-label span p{background-color:transparent;}#mermaid-svg-V8xJjMXGm5kD78IP .label text,#mermaid-svg-V8xJjMXGm5kD78IP span{fill:#333;color:#333;}#mermaid-svg-V8xJjMXGm5kD78IP .node rect,#mermaid-svg-V8xJjMXGm5kD78IP .node circle,#mermaid-svg-V8xJjMXGm5kD78IP .node ellipse,#mermaid-svg-V8xJjMXGm5kD78IP .node polygon,#mermaid-svg-V8xJjMXGm5kD78IP .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-V8xJjMXGm5kD78IP .rough-node .label text,#mermaid-svg-V8xJjMXGm5kD78IP .node .label text,#mermaid-svg-V8xJjMXGm5kD78IP .image-shape .label,#mermaid-svg-V8xJjMXGm5kD78IP .icon-shape .label{text-anchor:middle;}#mermaid-svg-V8xJjMXGm5kD78IP .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-V8xJjMXGm5kD78IP .rough-node .label,#mermaid-svg-V8xJjMXGm5kD78IP .node .label,#mermaid-svg-V8xJjMXGm5kD78IP .image-shape .label,#mermaid-svg-V8xJjMXGm5kD78IP .icon-shape .label{text-align:center;}#mermaid-svg-V8xJjMXGm5kD78IP .node.clickable{cursor:pointer;}#mermaid-svg-V8xJjMXGm5kD78IP .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-V8xJjMXGm5kD78IP .arrowheadPath{fill:#333333;}#mermaid-svg-V8xJjMXGm5kD78IP .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-V8xJjMXGm5kD78IP .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-V8xJjMXGm5kD78IP .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-V8xJjMXGm5kD78IP .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-V8xJjMXGm5kD78IP .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-V8xJjMXGm5kD78IP .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-V8xJjMXGm5kD78IP .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-V8xJjMXGm5kD78IP .cluster text{fill:#333;}#mermaid-svg-V8xJjMXGm5kD78IP .cluster span{color:#333;}#mermaid-svg-V8xJjMXGm5kD78IP div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-V8xJjMXGm5kD78IP .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-V8xJjMXGm5kD78IP rect.text{fill:none;stroke-width:0;}#mermaid-svg-V8xJjMXGm5kD78IP .icon-shape,#mermaid-svg-V8xJjMXGm5kD78IP .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-V8xJjMXGm5kD78IP .icon-shape p,#mermaid-svg-V8xJjMXGm5kD78IP .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-V8xJjMXGm5kD78IP .icon-shape .label rect,#mermaid-svg-V8xJjMXGm5kD78IP .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-V8xJjMXGm5kD78IP .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-V8xJjMXGm5kD78IP .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-V8xJjMXGm5kD78IP :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 日志框架
ASP.NET Core ILogger
Java SLF4J / Logback
ILogger
LogLevel
ILoggerProvider
Logger
LoggerFactory
Appender
Layout

15.2 代码对照

csharp 复制代码
// C# ILogger
public class MyService(ILogger<MyService> logger) {
    public void DoWork(string step) {
        logger.LogInformation("Starting work");
        try {
            DoSomethingRisky();
        } catch (Exception ex) {
            // 结构化日志:第 1 个参数异常,第 2 个参数模板,第 3+ 个参数占位值
            logger.LogError(ex, "Failed at {Step}", step);
            throw;
        }
    }
}
java 复制代码
// Java SLF4J + Logback
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;

@Service
public class MyService {
    private static final Logger logger = LoggerFactory.getLogger(MyService.class);

    public void doWork(String step) {
        logger.info("Starting work");
        try {
            doSomethingRisky();
        } catch (Exception ex) {
            // SLF4J: 最后一个参数若为 Throwable 则判定为异常 + 占位符填充
            logger.error("Failed at {}", step, ex);
            throw ex;
        }
    }
}

15.3 日志级别对照

C# Java SLF4J 用途
Trace TRACE 最详细
Debug DEBUG 调试
Information INFO 信息
Warning WARN 警告
Error ERROR 错误
Critical ERROR 严重

十六、测试对照

16.1 数据结构

#mermaid-svg-XtPYSh604ahPY1VE{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-XtPYSh604ahPY1VE .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-XtPYSh604ahPY1VE .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-XtPYSh604ahPY1VE .error-icon{fill:#552222;}#mermaid-svg-XtPYSh604ahPY1VE .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-XtPYSh604ahPY1VE .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-XtPYSh604ahPY1VE .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-XtPYSh604ahPY1VE .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-XtPYSh604ahPY1VE .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-XtPYSh604ahPY1VE .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-XtPYSh604ahPY1VE .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-XtPYSh604ahPY1VE .marker{fill:#333333;stroke:#333333;}#mermaid-svg-XtPYSh604ahPY1VE .marker.cross{stroke:#333333;}#mermaid-svg-XtPYSh604ahPY1VE svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-XtPYSh604ahPY1VE p{margin:0;}#mermaid-svg-XtPYSh604ahPY1VE .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-XtPYSh604ahPY1VE .cluster-label text{fill:#333;}#mermaid-svg-XtPYSh604ahPY1VE .cluster-label span{color:#333;}#mermaid-svg-XtPYSh604ahPY1VE .cluster-label span p{background-color:transparent;}#mermaid-svg-XtPYSh604ahPY1VE .label text,#mermaid-svg-XtPYSh604ahPY1VE span{fill:#333;color:#333;}#mermaid-svg-XtPYSh604ahPY1VE .node rect,#mermaid-svg-XtPYSh604ahPY1VE .node circle,#mermaid-svg-XtPYSh604ahPY1VE .node ellipse,#mermaid-svg-XtPYSh604ahPY1VE .node polygon,#mermaid-svg-XtPYSh604ahPY1VE .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-XtPYSh604ahPY1VE .rough-node .label text,#mermaid-svg-XtPYSh604ahPY1VE .node .label text,#mermaid-svg-XtPYSh604ahPY1VE .image-shape .label,#mermaid-svg-XtPYSh604ahPY1VE .icon-shape .label{text-anchor:middle;}#mermaid-svg-XtPYSh604ahPY1VE .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-XtPYSh604ahPY1VE .rough-node .label,#mermaid-svg-XtPYSh604ahPY1VE .node .label,#mermaid-svg-XtPYSh604ahPY1VE .image-shape .label,#mermaid-svg-XtPYSh604ahPY1VE .icon-shape .label{text-align:center;}#mermaid-svg-XtPYSh604ahPY1VE .node.clickable{cursor:pointer;}#mermaid-svg-XtPYSh604ahPY1VE .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-XtPYSh604ahPY1VE .arrowheadPath{fill:#333333;}#mermaid-svg-XtPYSh604ahPY1VE .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-XtPYSh604ahPY1VE .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-XtPYSh604ahPY1VE .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-XtPYSh604ahPY1VE .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-XtPYSh604ahPY1VE .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-XtPYSh604ahPY1VE .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-XtPYSh604ahPY1VE .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-XtPYSh604ahPY1VE .cluster text{fill:#333;}#mermaid-svg-XtPYSh604ahPY1VE .cluster span{color:#333;}#mermaid-svg-XtPYSh604ahPY1VE div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-XtPYSh604ahPY1VE .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-XtPYSh604ahPY1VE rect.text{fill:none;stroke-width:0;}#mermaid-svg-XtPYSh604ahPY1VE .icon-shape,#mermaid-svg-XtPYSh604ahPY1VE .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-XtPYSh604ahPY1VE .icon-shape p,#mermaid-svg-XtPYSh604ahPY1VE .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-XtPYSh604ahPY1VE .icon-shape .label rect,#mermaid-svg-XtPYSh604ahPY1VE .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-XtPYSh604ahPY1VE .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-XtPYSh604ahPY1VE .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-XtPYSh604ahPY1VE :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 测试框架
xUnit / NUnit
JUnit 5

Fact

Theory

Trait

@Test
@ParameterizedTest
@Tag

16.2 代码对照

csharp 复制代码
// C# xUnit
public class Calculator {
    public int Add(int a, int b) => a + b;
}

using Xunit;

public class CalculatorTest {
    [Fact]
    public void Add_TwoNumbers_ReturnsSum() {
        var calc = new Calculator();
        var result = calc.Add(2, 3);
        Assert.Equal(5, result);
    }

    [Theory]
    [InlineData(2, 3, 5)]
    [InlineData(-1, 1, 0)]
    public void Add_MultipleInputs_ReturnsExpected(int a, int b, int expected) {
        var calc = new Calculator();
        Assert.Equal(expected, calc.Add(a, b));
    }
}
java 复制代码
// Java JUnit 5
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;
import static org.junit.jupiter.api.Assertions.assertEquals;

class Calculator {
    int add(int a, int b) {
        return a + b;
    }
}

class CalculatorTest {
    @Test
    void add_TwoNumbers_ReturnsSum() {
        Calculator calc = new Calculator();
        assertEquals(5, calc.add(2, 3));
    }

    @ParameterizedTest
    @CsvSource({"2, 3, 5", "-1, 1, 0"})
    void add_MultipleInputs_ReturnsExpected(int a, int b, int expected) {
        Calculator calc = new Calculator();
        assertEquals(expected, calc.add(a, b));
    }
}

十七、构建工具对照

17.1 数据结构

#mermaid-svg-1knwqr3wjo9HDlgw{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-1knwqr3wjo9HDlgw .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-1knwqr3wjo9HDlgw .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-1knwqr3wjo9HDlgw .error-icon{fill:#552222;}#mermaid-svg-1knwqr3wjo9HDlgw .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-1knwqr3wjo9HDlgw .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-1knwqr3wjo9HDlgw .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-1knwqr3wjo9HDlgw .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-1knwqr3wjo9HDlgw .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-1knwqr3wjo9HDlgw .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-1knwqr3wjo9HDlgw .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-1knwqr3wjo9HDlgw .marker{fill:#333333;stroke:#333333;}#mermaid-svg-1knwqr3wjo9HDlgw .marker.cross{stroke:#333333;}#mermaid-svg-1knwqr3wjo9HDlgw svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-1knwqr3wjo9HDlgw p{margin:0;}#mermaid-svg-1knwqr3wjo9HDlgw .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-1knwqr3wjo9HDlgw .cluster-label text{fill:#333;}#mermaid-svg-1knwqr3wjo9HDlgw .cluster-label span{color:#333;}#mermaid-svg-1knwqr3wjo9HDlgw .cluster-label span p{background-color:transparent;}#mermaid-svg-1knwqr3wjo9HDlgw .label text,#mermaid-svg-1knwqr3wjo9HDlgw span{fill:#333;color:#333;}#mermaid-svg-1knwqr3wjo9HDlgw .node rect,#mermaid-svg-1knwqr3wjo9HDlgw .node circle,#mermaid-svg-1knwqr3wjo9HDlgw .node ellipse,#mermaid-svg-1knwqr3wjo9HDlgw .node polygon,#mermaid-svg-1knwqr3wjo9HDlgw .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-1knwqr3wjo9HDlgw .rough-node .label text,#mermaid-svg-1knwqr3wjo9HDlgw .node .label text,#mermaid-svg-1knwqr3wjo9HDlgw .image-shape .label,#mermaid-svg-1knwqr3wjo9HDlgw .icon-shape .label{text-anchor:middle;}#mermaid-svg-1knwqr3wjo9HDlgw .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-1knwqr3wjo9HDlgw .rough-node .label,#mermaid-svg-1knwqr3wjo9HDlgw .node .label,#mermaid-svg-1knwqr3wjo9HDlgw .image-shape .label,#mermaid-svg-1knwqr3wjo9HDlgw .icon-shape .label{text-align:center;}#mermaid-svg-1knwqr3wjo9HDlgw .node.clickable{cursor:pointer;}#mermaid-svg-1knwqr3wjo9HDlgw .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-1knwqr3wjo9HDlgw .arrowheadPath{fill:#333333;}#mermaid-svg-1knwqr3wjo9HDlgw .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-1knwqr3wjo9HDlgw .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-1knwqr3wjo9HDlgw .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-1knwqr3wjo9HDlgw .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-1knwqr3wjo9HDlgw .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-1knwqr3wjo9HDlgw .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-1knwqr3wjo9HDlgw .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-1knwqr3wjo9HDlgw .cluster text{fill:#333;}#mermaid-svg-1knwqr3wjo9HDlgw .cluster span{color:#333;}#mermaid-svg-1knwqr3wjo9HDlgw div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-1knwqr3wjo9HDlgw .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-1knwqr3wjo9HDlgw rect.text{fill:none;stroke-width:0;}#mermaid-svg-1knwqr3wjo9HDlgw .icon-shape,#mermaid-svg-1knwqr3wjo9HDlgw .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-1knwqr3wjo9HDlgw .icon-shape p,#mermaid-svg-1knwqr3wjo9HDlgw .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-1knwqr3wjo9HDlgw .icon-shape .label rect,#mermaid-svg-1knwqr3wjo9HDlgw .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-1knwqr3wjo9HDlgw .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-1knwqr3wjo9HDlgw .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-1knwqr3wjo9HDlgw :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 构建工具
dotnet CLI
Maven
dotnet build
dotnet test
dotnet publish
dotnet run
mvn compile
mvn test
mvn package
mvn spring-boot:run

17.2 命令对照

功能 .NET Maven
编译 dotnet build mvn compile
清理 dotnet clean mvn clean
测试 dotnet test mvn test
打包 dotnet pack mvn package
运行 dotnet run mvn spring-boot:run
发布 dotnet publish -c Release mvn package -DskipTests
依赖还原 dotnet restore mvn dependency:resolve
测试覆盖率 coverlet jacoco
静态分析 dotnet format checkstyle

相关推荐
PieroPC2 小时前
Windows 驱动备份与恢复工具 CMD bat
后端
Csvn2 小时前
📊 SQL 入门 Day 12: 窗口函数基础
后端·sql
摇滚侠2 小时前
《SpringBoot 3:入门与应用实战》第 15 章 生产级特性 监控指标 Metrics 阅读笔记
java·spring boot·笔记
玖石书2 小时前
ASP.NET Core 迁移至 Spring系列:编译生态篇
后端·spring·asp.net
Java技术小馆2 小时前
AI 如何理解对话
后端
小满zs2 小时前
Go语言第五章(数组和切片)
后端·go
弹简特2 小时前
【Java项目-企悦抽】11-用户与认证模块-实现用户登录03(结束登录功能)-完成测试+对接前端+登录拦截器
java·开发语言
大模型丫丫2 小时前
Loop Engineering:从强化学习视角看 Agent 循环的本质
java·人工智能·学习
数电发票API2 小时前
税局接口,需要的来~~
java