下载工作台
Spring Boot Web 开发

Spring MVC 与 REST Controller

试读上半部分 · 解锁后可读全文

第 3 章 · Spring MVC 与 REST Controller

本章目标:掌握 @RestController@GetMapping / @PostMapping 与路径变量、请求参数;设计 /api/v1 统一前缀与包结构;理解 DispatcherServlet 请求映射流程;能对照 shop-demo(Django)urls.py + views.py 说明路由、视图、响应的差异;在 shop-spring-demo 中完成商品 REST 只读 API 骨架。

学时建议:4~5 小时(含 2 小时跟练)

前置:完成 spring-boot-web ch02java-dev(集合与泛型、HTTP 基础)。


3.1 场景说明:统一 API 路径

虚构网关 https://api.example.com 约定:Java 侧 REST 一律挂在 /api/v1 下,与 Django shop-demo 的 HTML 页面路径分离。

类型shop-demo 示例shop-spring-demo 示例
HTML 列表GET /catalog/ch04 Thymeleaf /catalog
JSON APIDRF /api/v1/products/(规划)GET /api/v1/products
健康检查/accounts/health//health(ch01)
Admin/admin/无内置,ch06 自建登录

本章只做 JSON 只读 API,写操作与校验在 ch07 扩展。


3.2 Spring MVC 请求处理流程

客户端 HTTP 请求
    │
    ▼
① DispatcherServlet(Front Controller)
    │
    ▼
② HandlerMapping 查找 @GetMapping 等方法
    │
    ▼
③ HandlerAdapter 调用 Controller 方法
    │
    ▼
④ 返回值 → HttpMessageConverter(JSON 用 Jackson)
    │
    ▼
⑤ HTTP 响应(状态码 + Body)
阶段Django 对照
URL 路由urlpatterns + path()
视图views.product_list / CBV
响应JsonResponse / DRF Response
内容协商DRF rendererAccept 头 + MessageConverter

3.3 @Controller 与 @RestController

注解返回值典型用途
@Controller视图名 → Thymeleafch04 HTML 页面
@RestController直接写 Body(=@Controller + @ResponseBodyREST JSON
@RestController
@RequestMapping("/api/v1/products")
public class ProductApiController {
    // 类级别路径前缀
}

等价 Django:

# urls.py
path("api/v1/products/", include("catalog.api_urls"))
# views.py — DRF ViewSet 或 APIView

3.4 统一路径与包结构

推荐目录:

com.example.shop/
├── web/
│   ├── HealthController.java          # 根路径 /health
│   └── api/
│       └── v1/
│           └── ProductApiController.java
├── catalog/
│   ├── ProductCatalogService.java
│   └── ...
└── config/
    └── WebMvcConfig.java              # 可选:全局 CORS、路径规范

3.4.1 全局 API 前缀(可选两种)

方式 A — 类上 @RequestMapping("/api/v1/...")(本章采用,直观)

方式 B — WebMvcConfig 配置 pathPrefix(Boot 2.2+):

@Configuration
public class WebMvcConfig implements WebMvcConfigurer {

    @Override
    public void configurePathMatch(PathMatchConfigurer configurer) {
        configurer.addPathPrefix("/api/v1",
                c -> c.getPackageName().startsWith("com.example.shop.web.api.v1"));
    }
}

方式 B 下 Controller 可写 @GetMapping("/products"),实际映射为 /api/v1/products

策略优点缺点
类注解易读、IDE 跳转清晰前缀重复
pathPrefixDRY包结构必须规范

3.5 ProductApiController 完整示例

package com.example.shop.web.api.v1;

import com.example.shop.catalog.ProductCatalogService;
import com.example.shop.catalog.ProductDto;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.server.ResponseStatusException;

import java.util.List;

@RestController
@RequestMapping("/api/v1/products")
public class ProductApiController {

    private final ProductCatalogService catalogService;

    public ProductApiController(ProductCatalogService catalogService) {
        this.catalogService = catalogService;
    }

    @GetMapping
    public List<ProductDto> list(
            @RequestParam(defaultValue = "20") int limit,
            @RequestParam(required = false) String q) {
        // ch05 前:内存服务;q 过滤 ch07 实现
        return catalogService.listFeatured(limit);
    }

    @GetMapping("/{id}")
    public ProductDto detail(@PathVariable Long id) {
        return catalogService.listFeatured(100).stream()
                .filter(p -> p.id().equals(id))
                .findFirst()
                .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "商品不存在"));
    }
}

3.5.1 注解说明

注解作用Django/DRF 对照
@GetMappingGET 路由@api_view(['GET']) / ListAPIView
@PathVariableURL 路径段path("<int:pk>")
@RequestParamQuery Stringrequest.GET.get('limit')
defaultValue缺省参数request.GET.get('limit', 20)
ResponseStatusException快捷 404get_object_or_404 / Http404

3.6 HTTP 方法与映射对照

HTTPSpring 注解幂等shop-spring-demo 本章
GET@GetMappinglist / detail
POST@PostMappingch07 创建
PUT@PutMappingch07 全量更新
PATCH@PatchMappingch07 部分更新
DELETE@DeleteMappingch07 删除

预览 POST(ch07 实现 body 校验):

@PostMapping
@ResponseStatus(HttpStatus.CREATED)
public ProductDto create(@RequestBody ProductCreateRequest req) {
    throw new UnsupportedOperationException("ch07 实现");
}

以下内容需解锁后阅读

试读已结束。解锁本章 ¥5.00,或开通年度会员畅读全部教程。
年度会员 ¥199.00/年; 小紫 AI 工作台有效会员 ¥99.00/年

正文仅在服务端鉴权后下发,未付费无法获取下半部分内容。