3.Implementing Controllers

时间:2024-07-20 07:16:57

Implementing Controllers

控制器提供了对应用程序行为的访问,这些行为通常通过一个服务接口来定义。控制器解释用户输入,并将其转换为由视图展示给用户的模型。Spring 以非常抽象的方式实现了控制器,使得你能够创建各种各样的控制器。

Spring 2.5 引入了一种基于注解的编程模型,用于 MVC 控制器,这种模型使用了如 @RequestMapping@RequestParam@ModelAttribute 等注解。此注解支持适用于 Servlet MVC 和 Portlet MVC。以这种风格实现的控制器不需要扩展特定的基类或实现特定的接口。此外,这些控制器通常不直接依赖于 Servlet 或 Portlet API,尽管你可以轻松地配置对 Servlet 或 Portlet 功能的访问。

@Controller
public class HelloWorldController {

    @RequestMapping("/helloWorld")
    public String helloWorld(Model model) {
        model.addAttribute("message", "Hello World!");
        return "helloWorld";
    }
}

在 Spring MVC 中,@Controller@RequestMapping 注解提供了灵活的方法命名和签名方式。在这个具体的例子中,方法接受一个 Model 参数并返回一个 String 类型的视图名称,但在后面的章节中解释了可以使用各种其他方法参数和返回值。@Controller@RequestMapping 以及许多其他注解构成了 Spring MVC 实现的基础。本节将记录这些注解及其在 Servlet 环境中最常用的用法。

  1. @Controller 注解
    • 这个注解用于标记一个类作为 Spring MVC 控制器。被标记为 @Controller 的类会被 Spring 框架扫描和注册为一个处理器,能够处理 Web 请求。
  2. @RequestMapping 注解
    • 用于定义请求 URL 与处理器方法之间的映射关系。可以应用于类上和方法上,灵活地定义请求路径。
    • 方法级别的 @RequestMapping 注解覆盖类级别的注解,并可以指定请求类型(GET, POST 等)以及路径变量、请求参数等。
  3. 方法参数和返回值
    • 在 Spring MVC 中,控制器方法可以接受多种类型的参数和返回多种类型的值。例如:
      • Model:用于在视图中传递数据。
      • HttpServletRequest 和 HttpServletResponse:直接访问原始的请求和响应对象。
      • @RequestParam:绑定请求参数到方法参数。
      • @PathVariable:绑定 URL 路径变量到方法参数。
      • @ModelAttribute:将请求参数绑定到一个模型对象,并将其添加到模型中。
      • String:作为视图名称返回。
      • ResponseEntity:用于构建复杂的响应,包括响应头和状态码。
  4. 其他常用注解
    • @GetMapping, @PostMapping 等:这些注解是 @RequestMapping 的特定类型,用于简化常见 HTTP 方法的映射。
    • @RequestBody:将请求体绑定到方法参数。
    • @ResponseBody:将方法返回值直接写入 HTTP 响应体,而不是解析为视图名称。

示例代码

以下是一些常用注解的示例:

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;

@Controller
public class ExampleController {

    // 处理 GET 请求 /hello
    @GetMapping("/hello")
    public String hello(@RequestParam(name = "name", defaultValue = "World") String name, Model model) {
        model.addAttribute("name", name);
        return "hello"; // 返回视图名称 "hello"
    }

    // 处理 GET 请求 /user/{id}
    @GetMapping("/user/{id}")
    public String getUser(@PathVariable("id") Long userId, Model model) {
        // 模拟获取用户数据
        User user = userService.findById(userId);
        model.addAttribute("user", user);
        return "userProfile"; // 返回视图名称 "userProfile"
    }

    // 处理 POST 请求 /submit
    @PostMapping("/submit")
    public ResponseEntity<String> submit(@RequestBody FormData formData) {
        // 处理表单数据
        formService.process(formData);
        return ResponseEntity.ok("Form submitted successfully");
    }
}
  • @Controller:标记类为控制器。
  • @RequestMapping:定义请求映射。
  • 多种参数和返回值:灵活处理请求和响应。
  • 常用注解:简化开发过程,提供更直观的映射方式。

这些注解和方法构成了 Spring MVC 实现的基础,使得开发人员可以以非常灵活和简洁的方式处理 Web 请求。

Defining a controller with @Controller

@Controller 注解表明一个特定的类充当控制器的角色。Spring 并不要求你继承任何控制器基类或引用 Servlet API。然而,如果需要,你仍然可以引用特定于 Servlet 的特性。

@Controller 注解作为被注解类的一个立场标志,表明其角色。调度器会扫描这些被注解的类,以查找映射的方法,并检测 @RequestMapping 注解(见下一节)。

你可以在调度器的上下文中使用标准的 Spring bean 定义显式地定义带注解的控制器 bean。然而,@Controller 立场标志也允许自动检测,这与 Spring 通用的类路径组件类检测和自动注册 bean 定义的支持相一致。

为了启用此类注解控制器的自动检测,你需要在配置中添加组件扫描。使用 spring-context 模式,如以下 XML 片段所示:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:p="http://www.springframework.org/schema/p"
    xmlns:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        https://www.springframework.org/schema/context/spring-context.xsd">

    <context:component-scan base-package="org.springframework.samples.petclinic.web"/>

    <!-- ... -->

</beans>

解释

  • @Controller 注解:用于标记一个类为控制器。这个类不需要继承任何特定的基类,也不需要直接引用 Servlet API,但如果需要,可以引用特定的 Servlet 特性。
  • @Controller 作为立场标志:表明被注解类的角色。Spring 的调度器会扫描这些类以查找映射的方法,并检测 @RequestMapping 注解。
  • 显式定义控制器 bean:可以在调度器的上下文中显式定义带注解的控制器 bean。
  • 自动检测和注册@Controller 允许自动检测,并与 Spring 的类路径组件类检测和自动注册支持一致。
  • 组件扫描:为了启用自动检测,需要在配置中添加组件扫描。示例中展示了如何使用 spring-context 模式来进行组件扫描。

Mapping Requests With @RequestMapping

你可以使用 @RequestMapping 注解将 URL(例如 /appointments)映射到整个类或特定的处理方法上。通常情况下,类级别的注解将特定的请求路径(或路径模式)映射到一个表单控制器上,而方法级别的注解则通过特定的 HTTP 请求方法(如 “GET”、“POST” 等)或 HTTP 请求参数条件来缩小主要映射范围。

以下来自 Petcare 示例的例子展示了一个在 Spring MVC 应用中使用此注解的控制器:

@Controller
@RequestMapping("/appointments")
public class AppointmentsController {

    private final AppointmentBook appointmentBook;

    @Autowired
    public AppointmentsController(AppointmentBook appointmentBook) {
        this.appointmentBook = appointmentBook;
    }

    @RequestMapping(method = RequestMethod.GET)
    public Map<String, Appointment> get() {
        return appointmentBook.getAppointmentsForToday();
    }

    @RequestMapping(path = "/{day}", method = RequestMethod.GET)
    public Map<String, Appointment> getForDay(@PathVariable @DateTimeFormat(iso=ISO.DATE) Date day, Model model) {
        return appointmentBook.getAppointmentsForDay(day);
    }

    @RequestMapping(path = "/new", method = RequestMethod.GET)
    public AppointmentForm getNewForm() {
        return new AppointmentForm();
    }

    @RequestMapping(method = RequestMethod.POST)
    public String add(@Valid AppointmentForm appointment, BindingResult result) {
        if (result.hasErrors()) {
            return "appointments/new";
        }
        appointmentBook.addAppointment(appointment);
        return "redirect:/appointments";
    }
}

在上面的例子中,@RequestMapping 被多次使用。首先在类型(类)级别使用,表明该控制器中的所有处理方法都相对于 /appointments 路径。get() 方法进一步使用了 @RequestMapping 注解:它只接受 GET 请求,这意味着对 /appointments 的 HTTP GET 请求将调用这个方法。add() 方法有类似的定义,而 getNewForm() 方法则将 HTTP 方法和路径的定义结合在一起,因此对 appointments/new 的 GET 请求将由该方法处理。

getForDay() 方法展示了 @RequestMapping 的另一种用法:URI 模板。(参见名为“URI 模板模式”的部分)

类级别的 @RequestMapping 不是必须的。如果没有它,所有路径都是绝对路径,而不是相对路径。以下来自 PetClinic 示例应用程序的例子展示了一个使用 @RequestMapping 的多操作控制器:

@Controller
public class ClinicController {

    private final Clinic clinic;

    @Autowired
    public ClinicController(Clinic clinic) {
        this.clinic = clinic;
    }

    @RequestMapping("/")
    public void welcomeHandler() {
    }

    @RequestMapping("/vets")
    public ModelMap vetsHandler() {
        return new ModelMap(this.clinic.getVets());
    }

}

上面的例子没有指定 GET、PUT、POST 等 HTTP 方法,因为 @RequestMapping 默认映射所有的 HTTP 方法。使用 @RequestMapping(method=GET)@GetMapping 可以缩小映射范围。

Composed @RequestMapping Variants

组合的 @RequestMapping 变体
Spring Framework 4.3 引入了以下方法级别的组合变体 @RequestMapping 注解,这些注解有助于简化常见 HTTP 方法的映射,并更好地表达被注解处理方法的语义。例如,@GetMapping 可以被理解为 GET @RequestMapping

  • @GetMapping
  • @PostMapping
  • @PutMapping
  • @DeleteMapping
  • @PatchMapping

以下示例展示了一个修改后的 AppointmentsController,与上一节相比,使用组合的 @RequestMapping 注解进行了简化。

@Controller
@RequestMapping("/appointments")
public class AppointmentsController {

    private final AppointmentBook appointmentBook;

    @Autowired
    public AppointmentsController(AppointmentBook appointmentBook) {
        this.appointmentBook = appointmentBook;
    }

    @GetMapping
    public Map<String, Appointment> get() {
        return appointmentBook.getAppointmentsForToday();
    }

    @GetMapping("/{day}")
    public Map<String, Appointment> getForDay(@PathVariable @DateTimeFormat(iso=ISO.DATE) Date day, Model model) {
        return appointmentBook.getAppointmentsForDay(day);
    }

    @GetMapping("/new")
    public AppointmentForm getNewForm() {
        return new AppointmentForm();
    }

    @PostMapping
    public String add(@Valid AppointmentForm appointment, BindingResult result) {
        if (result.hasErrors()) {
            return "appointments/new";
        }
        appointmentBook.addAppointment(appointment);
        return "redirect:/appointments";
    }
}

@Controller and AOP Proxying

在某些情况下,控制器可能需要在运行时被 AOP 代理装饰。一个例子是如果你选择在控制器上直接使用 @Transactional 注解。当这种情况发生时,特别是对于控制器,我们建议使用基于类的代理。这通常是控制器的默认选择。然而,如果控制器必须实现一个非 Spring 上下文回调的接口(例如 InitializingBean*Aware 等),你可能需要显式配置基于类的代理。例如,使用 <tx:annotation-driven/> 时,可以更改为 <tx:annotation-driven proxy-target-class="true"/>

New Support Classes for @RequestMapping methods in Spring MVC 3.1

Spring MVC 3.1 中 @RequestMapping 方法的新支持类

Spring 3.1 引入了一组新的 @RequestMapping 方法支持类,分别是 RequestMappingHandlerMappingRequestMappingHandlerAdapter。推荐使用这些支持类,它们甚至是利用 Spring MVC 3.1 及以后版本的新特性所必需的。通过 MVC 命名空间和 MVC Java 配置,这些新的支持类默认启用,但如果不使用这些配置,则必须显式配置。以下部分描述了旧支持类和新支持类之间的一些重要区别。

在 Spring 3.1 之前,类型级和方法级的请求映射分两个阶段进行检查——首先由 DefaultAnnotationHandlerMapping 选择控制器,然后由 AnnotationMethodHandlerAdapter 缩小要调用的具体方法。

在 Spring 3.1 中,新支持类 RequestMappingHandlerMapping 是唯一决定哪个方法应该处理请求的地方。可以将控制器方法视为一组独特的端点,每个方法的映射信息都来自类型和方法级的 @RequestMapping 信息。

这使得一些新的可能性成为现实。例如,HandlerInterceptorHandlerExceptionResolver 现在可以期望基于对象的处理器是 HandlerMethod,这使得它们可以检查确切的方法、其参数和关联的注解。URL 的处理不再需要分布在不同的控制器中。

此外,有些事情不再可能:

  • 先使用 SimpleUrlHandlerMappingBeanNameUrlHandlerMapping 选择一个控制器,然后根据 @RequestMapping 注解缩小方法范围。
  • 依赖方法名作为回退机制来消除两个没有明确路径映射但在其他方面匹配的 @RequestMapping 方法之间的歧义,例如通过 HTTP 方法。在新支持类中,@RequestMapping 方法必须唯一映射。
  • 只有一个默认方法(没有明确路径映射),在没有其他控制器方法更具体匹配时处理请求。在新支持类中,如果找不到匹配的方法,则会引发 404 错误。

以上特性在现有的支持类中仍然受支持。然而,要利用 Spring MVC 3.1 的新特性,你需要使用新的支持类。

URI Template Patterns

URI 模板可以用于在 @RequestMapping 方法中方便地访问 URL 的选定部分。

URI 模板是一个类似 URI 的字符串,包含一个或多个变量名。当你为这些变量替换值时,模板就变成了一个 URI。URI 模板的建议 RFC 定义了 URI 如何参数化。例如,URI 模板 https://www.example.com/users/{userId} 包含变量 userId。将值 fred 分配给变量后,得到的 URI 是 https://www.example.com/users/fred

在 Spring MVC 中,你可以在方法参数上使用 @PathVariable 注解,将其绑定到 URI 模板变量的值:

@GetMapping("/users/{userId}")
public String getUser(@PathVariable String userId, Model model) {
    // 使用 userId 参数进行处理
    return "userProfile";
}

在这个例子中,@PathVariable 注解将方法参数 userId 绑定到 URI 模板变量 userId 的值。这样,/users/fred 的请求会将字符串 fred 作为 userId 参数传递给 getUser 方法。

  • 为了处理 @PathVariable 注解,Spring MVC 需要通过名称找到匹配的 URI 模板变量。你可以在注解中指定它:
@GetMapping("/owners/{ownerId}")
public String findOwner(@PathVariable("ownerId") String theOwner, Model model) {
    // 实现省略
}
  • 或者,如果 URI 模板变量名与方法参数名匹配,你可以省略该细节。只要你的代码是用调试信息编译的,或者在 Java 8 上使用 parameters 编译标志,Spring MVC 就会将方法参数名与 URI 模板变量名匹配:
@GetMapping("/owners/{ownerId}")
public String findOwner(@PathVariable String ownerId, Model model) {
    // 实现省略
}
  • 一个方法可以有任意数量的 @PathVariable 注解:
@GetMapping("/owners/{ownerId}/pets/{petId}")
public String findPet(@PathVariable String ownerId, @PathVariable String petId, Model model) {
    Owner owner = ownerService.findOwner(ownerId);
    Pet pet = owner.getPet(petId);
    model.addAttribute("pet", pet);
    return "displayPet"; 
}
  • @PathVariable 注解用于 Map<String, String> 参数时,地图会填充所有的 URI 模板变量。

  • 一个 URI 模板可以通过类型和方法级别的 @RequestMapping 注解来组装。因此,可以使用类似 /owners/42/pets/21 的 URL 来调用 findPet() 方法。

@Controller
@RequestMapping("/owners/{ownerId}")
public class RelativePathUriTemplateController {

    @RequestMapping("/pets/{petId}")
    public void findPet(@PathVariable String ownerId, @PathVariable String petId, Model model) {
        // 实现省略
    }

}

@PathVariable 参数可以是任何简单类型,例如 intlongDate 等。Spring 会自动转换为适当的类型,如果转换失败,会抛出 TypeMismatchException。你也可以注册支持解析其他数据类型的支持。请参阅“方法参数和类型转换”以及“自定义 WebDataBinder 初始化”部分。

URI Template Patterns with Regular Expressions

有时你需要更精确地定义 URI 模板变量。考虑 URL "/spring-web/spring-web-3.0.5.jar",如何将其分解成多个部分?

@RequestMapping 注解支持在 URI 模板变量中使用正则表达式。语法是 {varName:regex},其中第一部分定义变量名,第二部分是正则表达式。例如:

@RequestMapping("/spring-web/{symbolicName:[a-z-]+}-{version:\\d\\.\\d\\.\\d}{extension:\\.[a-z]+}")
public void handle(@PathVariable String version, @PathVariable String extension) {
    // ...
}

在这个例子中,@RequestMapping 注解使用了正则表达式来匹配 URI 模板变量。{symbolicName:[a-z-]+} 匹配符号名称部分,{version:\\d\\.\\d\\.\\d} 匹配版本号部分,而 {extension:\\.[a-z]+} 匹配扩展名部分。这样,你可以更精确地处理复杂的 URL 模式。

Path Patterns

路径模式

除了 URI 模板,@RequestMapping 注解和所有组合的 @RequestMapping 变体还支持 Ant 风格的路径模式(例如,/myPath/*.do)。URI 模板变量和 Ant 风格的通配符也可以组合使用(例如,/owners/*/pets/{petId})。

Path Pattern Comparison

路径模式比较

当一个 URL 匹配多个模式时,使用排序来找到最具体的匹配。

具有较少 URI 变量和通配符的模式被认为是更具体的。例如,/hotels/{hotel}/* 具有 1 个 URI 变量和 1 个通配符,被认为比 /hotels/{hotel}/** 更具体,后者有 1 个 URI 变量和 2 个通配符。

如果两个模式的变量和通配符数量相同,较长的模式被认为更具体。例如,/foo/bar*/foo/* 更长,因此被认为更具体。

当两个模式的变量和长度相同时,通配符较少的模式被认为更具体。例如,/hotels/{hotel}/hotels/* 更具体。

还有一些其他的特殊规则:

  • 默认映射模式 /** 比其他任何模式都不具体。例如,/api/{a}/{b}/{c} 更具体。
  • /public/** 这样的前缀模式比不包含双通配符的其他模式更不具体。例如,/public/path3/{a}/{b}/{c} 更具体。

详细信息请参阅 AntPatternComparatorAntPathMatcher 中。请注意,PathMatcher 可以自定义(详见配置 Spring MVC 部分的 “路径匹配”)。

Path Patterns with Placeholders

使用占位符的路径模式

@RequestMapping 注解中的模式支持使用 ${…} 占位符来匹配本地属性和/或系统属性以及环境变量。这在控制器映射的路径需要通过配置进行定制时非常有用。有关占位符的更多信息,请参阅 PropertyPlaceholderConfigurer 类的 Java 文档。

Suffix Pattern Matching

后缀模式匹配

默认情况下,Spring MVC 执行 “.*” 后缀模式匹配,因此映射到 /person 的控制器也隐式地映射到 /person.*。这使得通过 URL 路径请求资源的不同表示变得容易(例如 /person.pdf, /person.xml)。

可以关闭后缀模式匹配或将其限制为一组明确注册用于内容协商的路径扩展名。通常建议这样做以最小化与常见请求映射(如 /person/{id})的歧义,其中点可能并不代表文件扩展名,例如 /person/joe@email.com/person/joe@email.com.json。此外,正如下面的注释中所解释的,后缀模式匹配以及内容协商在某些情况下可能被用于尝试恶意攻击,因此有充分的理由有意义地限制它们。

有关后缀模式匹配配置的信息,请参阅 Section 22.16.11, “Path Matching”;有关内容协商配置的信息,请参阅 Section 22.16.6, “Content Negotiation”。

Suffix Pattern Matching and RFD

后缀模式匹配与反射文件下载(RFD)

反射文件下载(RFD)攻击首次在 Trustwave 2014 年的一篇论文中描述。该攻击类似于 XSS,因为它依赖于输入(例如查询参数、URI 变量)在响应中反射。然而,与在 HTML 中插入 JavaScript 不同,RFD 攻击依赖于浏览器切换到执行下载并根据文件扩展名(例如 .bat, .cmd)将响应视为可执行脚本(如果双击)。

在 Spring MVC 中,@ResponseBodyResponseEntity 方法处于风险中,因为它们可以呈现不同的内容类型,客户端可以通过 URL 路径扩展名请求这些内容。然而,请注意,仅禁用后缀模式匹配或禁用用于内容协商的路径扩展名使用不足以防止 RFD 攻击。

为了全面防范 RFD,Spring MVC 在呈现响应体之前添加了 Content-Disposition:inline;filename=f.txt 标头,以建议一个固定且安全的下载文件名。仅当 URL 路径包含一个既不在白名单中也未明确注册用于内容协商的文件扩展名时才会这样做。然而,当 URL 直接输入到浏览器中时,这可能会有副作用。

许多常见的路径扩展名默认情况下被列入白名单。此外,REST API 调用通常不应直接在浏览器中使用。然而,使用自定义 HttpMessageConverter 实现的应用程序可以明确注册文件扩展名用于内容协商,对于这些扩展名将不会添加 Content-Disposition 标头。参见 Section 22.16.6, “Content Negotiation”。

注意 这最初是作为 CVE-2015-5211 的一部分引入的。以下是报告中的其他建议:

  1. 编码而不是转义 JSON 响应。这也是 OWASP XSS 的建议。有关如何使用 Spring 进行此操作的示例,请参见 spring-jackson-owasp。
  2. 将后缀模式匹配配置为关闭或限制为仅显式注册的后缀。
  3. 使用 useJafignoreUnknownPathExtensions 属性将内容协商配置为 false,这将导致对于具有未知扩展名的 URL 返回 406 响应。然而,如果 URL 自然地预期在末尾有一个点,这可能不是一个选项。
  4. 向响应添加 X-Content-Type-Options: nosniff 标头。Spring Security 4 默认执行此操作。

Matrix Variables

矩阵变量
URI 规范 RFC 3986 定义了在路径段中包含键值对的可能性。规范中没有使用特定的术语。通常使用“URI 路径参数”这一通用术语,虽然来源于 Tim Berners-Lee 的旧帖子中的更独特的“矩阵 URI”也常被使用并且相当知名。在 Spring MVC 中,这些被称为矩阵变量。

矩阵变量可以出现在任何路径段中,每个矩阵变量用“;”(分号)分隔。例如:“/cars;color=red;year=2012”。多个值可以用“,”(逗号)分隔“color=red,green,blue”,或者变量名可以重复“color=red;color=green;color=blue”。

如果预期 URL 包含矩阵变量,请求映射模式必须使用 URI 模板表示。这确保了无论矩阵变量是否存在以及以何种顺序提供,请求都可以正确匹配。

下面是提取矩阵变量“q”的示例:

// GET /pets/42;q=11;r=22

@GetMapping("/pets/{petId}")
public void findPet(@PathVariable String petId, @MatrixVariable int q) {
    // petId == 42
    // q == 11
}

由于所有路径段都可能包含矩阵变量,有时需要更具体地标识预期变量的位置:

// GET /owners/42;q=11/pets/21;q=22

@GetMapping("/owners/{ownerId}/pets/{petId}")
public void findPet(
        @MatrixVariable(name="q", pathVar="ownerId") int q1,
        @MatrixVariable(name="q", pathVar="petId") int q2) {
    // q1 == 11
    // q2 == 22
}

可以将矩阵变量定义为可选,并指定默认值:

// GET /pets/42

@GetMapping("/pets/{petId}")
public void findPet(@MatrixVariable(required=false, defaultValue="1") int q) {
    // q == 1
}

可以在 Map 中获取所有矩阵变量:

// GET /owners/42;q=11;r=12/pets/21;q=22;s=23

@GetMapping("/owners/{ownerId}/pets/{petId}")
public void findPet(
        @MatrixVariable MultiValueMap<String, String> matrixVars,
        @MatrixVariable(pathVar="petId") MultiValueMap<String, String> petMatrixVars) {
    // matrixVars: ["q" : [11,22], "r" : 12, "s" : 23]
    // petMatrixVars: ["q" : 11, "s" : 23]
}

请注意,要启用矩阵变量的使用,必须将 RequestMappingHandlerMappingremoveSemicolonContent 属性设置为 false。默认情况下,它设置为 true。

【提示】
MVC Java 配置和 MVC 命名空间都提供了启用矩阵变量的选项。

如果使用 Java 配置,“使用 MVC Java 配置进行高级自定义”部分描述了如何自定义 RequestMappingHandlerMapping

在 MVC 命名空间中,<mvc:annotation-driven> 元素有一个 enable-matrix-variables 属性,应将其设置为 true。默认情况下,它设置为 false。

示例配置:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:mvc="http://www.springframework.org/schema/mvc"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/mvc
        https://www.springframework.org/schema/mvc/spring-mvc.xsd">

    <mvc:annotation-driven enable-matrix-variables="true"/>

</beans>

Consumable Media Types

可消费的媒体类型

你可以通过指定可消费的媒体类型列表来缩小主映射范围。只有当请求头中的 Content-Type 与指定的媒体类型匹配时,请求才会被匹配。例如:

@PostMapping(path = "/pets", consumes = "application/json")
public void addPet(@RequestBody Pet pet, Model model) {
    // 省略实现
}

可消费的媒体类型表达式也可以使用否定形式,如 !text/plain,以匹配除 Content-Type 为 text/plain 的所有请求。还可以考虑使用 MediaType 提供的常量,如 APPLICATION_JSON_VALUEAPPLICATION_JSON_UTF8_VALUE

【提示】
consumes 条件支持在类型级别和方法级别使用。与大多数其他条件不同,在类型级别使用时,方法级别的可消费类型会覆盖类型级别的可消费类型,而不是扩展它们。

Producible Media Types

可生成的媒体类型

你可以通过指定一组可生成的媒体类型来缩小主映射范围。只有当 Accept 请求头匹配这些值之一时,请求才会被匹配。此外,使用 produces 条件可以确保用于生成响应的实际内容类型符合 produces 条件中指定的媒体类型。例如:

@GetMapping(path = "/pets/{petId}", produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
@ResponseBody
public Pet getPet(@PathVariable String petId, Model model) {
    // 实现省略
}

[注意]
请注意,produces 条件中指定的媒体类型也可以可选地指定字符集。例如,在上面的代码片段中,我们指定了与 MappingJackson2HttpMessageConverter 中默认配置相同的媒体类型,包括 UTF-8 字符集。

就像 consumes 一样,可生成的媒体类型表达式也可以用否定形式,例如 !text/plain,以匹配所有 Accept 请求头值不是 text/plain 的请求。同时可以考虑使用 MediaType 提供的常量,如 APPLICATION_JSON_VALUEAPPLICATION_JSON_UTF8_VALUE

[提示]
produces 条件支持在类型级别和方法级别使用。与大多数其他条件不同,当在类型级别使用时,方法级别的可生成类型会覆盖而不是扩展类型级别的可生成类型。

Request Parameters and Header Values

请求参数条件

你可以通过请求参数条件来缩小请求匹配范围,比如 "myParam""!myParam",或 "myParam=myValue"。前两个用于测试请求参数的存在/不存在,第三个用于测试特定参数值。以下是一个带有请求参数值条件的示例:

@Controller
@RequestMapping("/owners/{ownerId}")
public class RelativePathUriTemplateController {

    @GetMapping(path = "/pets/{petId}", params = "myParam=myValue")
    public void findPet(@PathVariable String ownerId, @PathVariable String petId, Model model) {
        // 实现省略
    }

}

同样的方式可以用于测试请求头的存在/不存在或根据特定请求头值进行匹配:

@Controller
@RequestMapping("/owners/{ownerId}")
public class RelativePathUriTemplateController {

    @GetMapping(path = "/pets", headers = "myHeader=myValue")
    public void findPet(@PathVariable String ownerId, @PathVariable String petId, Model model) {
        // 实现省略
    }

}

[提示]
虽然你可以使用媒体类型通配符(例如 "content-type=text/*" 会匹配 "text/plain""text/html")来匹配 Content-TypeAccept 请求头的值,但推荐分别使用 consumesproduces 条件。这些条件专门用于此目的。

HTTP HEAD and HTTP OPTIONS

@RequestMapping 方法和 HTTP 方法的映射

映射到 “GET” 的 @RequestMapping 方法也会隐式映射到 “HEAD”,即不需要显式声明 “HEAD”。HTTP HEAD 请求的处理方式类似于 HTTP GET 请求,只是不会写入响应体,而是计算字节数并设置 “Content-Length” 头。

@RequestMapping 方法内置对 HTTP OPTIONS 的支持。默认情况下,HTTP OPTIONS 请求通过设置 “Allow” 响应头来处理,该响应头包含所有与匹配的 URL 模式显式声明的 HTTP 方法。当没有显式声明 HTTP 方法时,“Allow” 头会被设置为 “GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS”。理想情况下,应始终声明 @RequestMapping 方法要处理的 HTTP 方法,或者使用专门的组合 @RequestMapping 变体(参见“组合 @RequestMapping 变体”部分)。

尽管没有必要,@RequestMapping 方法也可以映射到并处理 HTTP HEAD 或 HTTP OPTIONS,或两者。

Defining @RequestMapping handler methods

@RequestMapping 处理方法的灵活签名

@RequestMapping 处理方法可以有非常灵活的签名。支持的方法参数和返回值在以下部分中进行了描述。大多数参数可以以任意顺序使用,唯一的例外是 BindingResult 参数。这将在下一节中详细描述。

注意:
Spring 3.1 引入了一组用于 @RequestMapping 方法的支持类,分别称为 RequestMappingHandlerMapping 和 RequestMappingHandlerAdapter。建议使用它们,甚至在利用 Spring MVC 3.1 及以后版本的新功能时是必须的。这些新的支持类默认在 MVC 命名空间中启用,并且可以与 MVC Java 配置一起使用,但如果两者都不使用,则必须显式配置。

Supported method argument types

支持的方法参数类型

以下是支持的方法参数类型:

  • 请求或响应对象(Servlet API):选择任何特定的请求或响应类型,例如 ServletRequest 或 HttpServletRequest。

  • 会话对象(Servlet API):类型为 HttpSession。这种类型的参数强制要求存在相应的会话。因此,这样的参数永远不会为 null。

    注意: 会话访问可能不是线程安全的,特别是在 Servlet 环境中。如果允许多个请求并发访问会话,请考虑将 RequestMappingHandlerAdapter 的 “synchronizeOnSession” 标志设置为 “true”。

  • org.springframework.web.context.request.WebRequest 或 org.springframework.web.context.request.NativeWebRequest:允许进行通用的请求参数访问以及请求/会话属性访问,而无需绑定到原生的 Servlet/Portlet API。

  • java.util.Locale:用于当前请求的区域设置,由最具体的区域解析器确定,在 MVC 环境中为配置的 LocaleResolver / LocaleContextResolver。

  • java.util.TimeZone(Java 6+) / java.time.ZoneId(在 Java 8 上):用于与当前请求关联的时区,由 LocaleContextResolver 确定。

  • java.io.InputStream / java.io.Reader:用于访问请求的内容。这是 Servlet API 暴露的原始 InputStream/Reader。

  • java.io.OutputStream / java.io.Writer:用于生成响应的内容。这是 Servlet API 暴露的原始 OutputStream/Writer。

  • org.springframework.http.HttpMethod:用于 HTTP 请求方法。

  • java.security.Principal:包含当前已认证的用户。

  • 使用 @PathVariable 注解的参数:用于访问 URI 模板变量。参见“URI 模板模式”部分。

  • 使用 @MatrixVariable 注解的参数:用于访问 URI 路径段中的名值对。参见“矩阵变量”部分。

  • 使用 @RequestParam 注解的参数:用于访问特定的 Servlet 请求参数。参数值将转换为声明的方法参数类型。参见“使用 @RequestParam 将请求参数绑定到方法参数”部分。

  • 使用 @RequestHeader