1. 简单使用
可以使用@RequestMapping注解将 /appointments 等URL映射到整个类或特定的处理器方法上。 通常,类级注解将特定请求路径(或路径模式)映射到表单控制器上,其他方法级注解通过HTTP请求方法(如GET、POST请求)或请求参数条件缩小主映射范围。
以下示例显示了使用注解的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模板的另一种用法。
不需要类级别的@RequestMapping。 没有它,所有路径都是绝对的,而不是相对的。 以下示例显示了使用@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等,因为@RequestMapping默认映射所有HTTP方法。 使用@RequestMapping(method = GET)或@GetMapping来缩小映射范围。
2. 组合@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";
}
}
3. @Controller和AOP代理
在某些情况下,控制器可能需要在运行时使用AOP代理进行修饰。 例如,如果选择在控制器上直接使用@Transactional注解。 在这种情况下,对于控制器而言,Spring建议使用基于类的代理。 这通常是控制器的默认选择。 但是,如果控制器必须实现不是Spring Context回调的接口(例如InitializingBean,* Aware等),则可能需要显式配置基于类的代理。 例如,使用<tx:annotation-driven />,更改为<tx:annotation-driven proxy-target-class =“true”/>。
4. Spring MVC 3.1中@RequestMapping方法的新支持类
Spring 3.1为@RequestMapping方法引入了一组新的支持类,分别称为RequestMappingHandlerMapping和RequestMappingHandlerAdapter。建议使用它们,甚至需要利用Spring MVC 3.1中的新功能并进一步使用。默认情况下,MVC命名空间和MVC Java配置启用新的支持类,但如果两者都不使用,则必须明确配置。本节介绍旧支持类和新支持类之间的一些重要差异。
到Spring 3.1之前,类型和方法级请求映射检查在两个分开的阶段:
- 第一,由DefaultAnnotationHandlerMapping选择控制器。
- 第二,由AnnotationMethodHandlerAdapter调用的实际方法缩小范围。
在Spring 3.1中新增新支持类后,RequestMappingHandlerMapping是唯一决定应该处理请求的方法的地方。将控制器方法视为一组唯一端点,其中每个方法的映射都是从类型和方法级别的@RequestMapping信息派生的。
这确保了一些新的可能性。HandlerInterceptor或HandlerExceptionResolver现在可以期望基于对象的处理器是HandlerMethod,那么,它允许它们检查确切的方法、参数和相关的注解。不再需要跨不同控制器分割URL的处理。
之前的一些匹配机制将不存在:
- 首先使用SimpleUrlHandlerMapping或BeanNameUrlHandlerMapping选择控制器,然后根据@RequestMapping注解缩小方法。
- 依靠方法名称作为后退机制来消除两个没有显式路径映射URL路径但是否相同匹配的@RequestMapping方法之间的歧义,例如, 通过HTTP方法。 在新的支持类中,必须唯一地映射@RequestMapping方法。
- 如果没有其他控制器方法更具体地匹配,则使用单个默认方法(没有显式路径映射)处理请求。 在新的支持类中,如果找不到匹配的方法,则会引发404错误。
现有支持类仍支持上述功能。 但是,要利用新的Spring MVC 3.1功能,需要使用新的支持类。
5. URI模板模式
URI模板可用于在@RequestMapping方法中方便地访问URL的选定部分。
URI模板是类似URI的字符串,包含一个或多个变量名称。 当为这些变量替换值时,模板将成为URI。 例如,URI模板http://www.example.com/users/{userId}包含变量userId。 将值 1 分配给变量会产生http://www.example.com/users/1。
在Spring MVC中,可以在方法参数上使用@PathVariable注解将其绑定到URI模板变量的值:
@GetMapping("/owners/{ownerId}")
public String findOwner(@PathVariable String ownerId, Model model) {
Owner owner = ownerService.findOwner(ownerId);
model.addAttribute("owner", owner);
return "displayOwner";
}
URI模板“/owners/{ownerId}`”指定变量名称`ownerId。 当控制器处理此请求时,ownerId的值将设置为URI的相应部分中找到的值。 例如,当/owner/1 的请求进入时,ownerId的值为 1。
注:
要处理@PathVariable注解,Spring MVC需要按名称查找匹配的URI模板变量。 可以在注解中指定它:
@GetMapping("/owners/{ownerId}")
public String findOwner(@PathVariable("ownerId") String theOwner, Model model) {
// implementation omitted
}
或者,如果URI模板变量名称与方法参数名称匹配,则可以省略该详细信息。 只要代码使用调试信息或Java 8上的-parameters编译器标志进行编译,Spring MVC就会将方法参数名称与URI模板变量名称进行匹配:
@GetMapping("/owners/{ownerId}")
public String findOwner(@PathVariable String ownerId, Model model) {
// implementation omitted
}
方法可以包含任意数量的@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";
}
在Map<String, String>参数上使用@PathVariable注解时,将使用所有URI模板变量填充映射。
可以从类型和方法级别@RequestMapping注释组装URI模板。 因此,可以使用诸如/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) {
// implementation omitted
}
}
@PathVariable参数可以是任何简单类型,例如int,long,Date等。如果未能执行此操作,Spring会自动转换为适当的类型或抛出TypeMismatchException。 还可以注册解析其他数据类型的支持。
6. 具有正则表达式的URI模板模式
有时需要更精确地定义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) {
// ...
}
7. 路径模式
除URI模板外,@RequestMapping注解和所有组合的@RequestMapping变体还支持Ant样式的路径模式(例如,/myPath/*.do)。 还支持URI模板变量和Ant样式globs的组合(例如/owners/*/pets/{petId})。
8. 路径模式比较
当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}更具体。
有关完整详细信息,请参阅AntPathMatcher中的AntPatternComparator。 可以自定义PathMatcher(请参见有关配置Spring MVC的部分中的第22.16.11节“路径匹配”)。
9. 带占位符的路径模式
@RequestMapping注解中的模式支持针对本地属性或系统属性和环境变量的$ {...}占位符。 在控制器映射到的路径可能需要通过配置进行自定义的情况下,这可能很有用。 有关占位符的更多信息,请参阅PropertyPlaceholderConfigurer类的javadoc。
10. 后缀模式匹配
默认情况下,Spring MVC执行“.*”后缀模式匹配,以便映射到/person的控制器也隐式映射到/person.*。这使得通过URL路径(例如/person.pdf,/person.xml)请求资源的不同表示变得容易。
可以关闭后缀模式匹配或将其限制为为内容协商目的明确注册的一组路径扩展。通常建议使用常见请求映射来最小化歧义,例如/ person/{id},其中点可能不代表文件扩展名,例如/person/joe@email.com vs /person/joe@email.com.json。此外,如下面的注解中所解释的,后缀模式匹配以及内容协商可能在某些情况下用于尝试恶意攻击,并且有充分的理由对其进行有意义的限制。
有关后缀模式匹配配置,请参见第22.16.11节“路径匹配”,对于内容协商配置,请参见第22.16.6节“内容协商”。
11. 后缀模式匹配和RFD
2014年,Trustwave在一篇论文中首次描述了反射文件下载(RFD:Reflected File Download)攻击。该攻击类似于XSS,因为它依赖于响应中反映的输入(例如查询参数,URI变量)。但是,如果基于文件扩展名(例如.bat,.cmd)双击,则RFD攻击不依赖于将JavaScript插入HTML,而是依赖浏览器切换来执行下载并将响应视为可执行脚本。
在Spring MVC @ResponseBody和ResponseEntity方法存在风险,因为它们可以呈现客户端可以请求的不同内容类型,包括通过URL路径扩展。但请注意,既不禁用后缀模式匹配也不禁用路径扩展仅用于内容协商目的,这对于防止RFD攻击都是有效的。
为了全面防范RFD,在展示响应体之前,Spring MVC添加了一个 Content-Disposition:inline;filename=f.txt 头,以建议一个固定且安全的下载文件文件名。仅当URL路径包含既未列入白名单也未明确注册以用于内容协商目的的文件扩展名时,才会执行此操作。但是,当直接在浏览器中输入URL时,它可能会产生副作用。
默认情况下,许多常见路径扩展名都列入白名单。此外,REST API调用通常不能直接在浏览器中用作URL。但是,使用自定义HttpMessageConverter实现的应用程序可以显式注册文件扩展名以进行内容协商,并且不会为此类扩展添加Content-Disposition头。
注:
这最初是作为CVE-2015-5211工作的一部分而引入的。 以下是报告中的其他建议:
- 编码而不是转义JSON响应。 这也是OWASP XSS的推荐。 有关如何使用Spring执行此操作的示例请参阅spring-jackson-owasp。
- 将后缀模式匹配配置为仅关闭或限制为仅显式注册的后缀。
- 配置内容协商,将属性“useJaf”和“ignoreUnknownPathExtensions”设置为false,这将导致对未知扩展名的URL进行406响应。 但请注意,如果URL自然希望在结尾处有一个点,则可能不会选择此选项。
- 添加X-Content-Type-Options:nosniff头到响应。 Spring Security 4默认执行此操作。
12. 矩阵变量
URI规范RFC 3986定义了在路径段中包含名称 - 值对的可能性。规范中没有使用特定术语。可以应用一般的“URI路径参数”,尽管源自Tim Berners-Lee的旧帖子的更独特的“矩阵URI”也经常被使用并且是众所周知的。在Spring MVC中,这些被称为矩阵变量。
矩阵变量可以出现在任何路径段中,每个矩阵变量用“;”分隔(分号)。例如:“/cars;color=red;year=2012”。多个值可以是“,”(逗号)分隔的“颜色=红色,绿色,蓝色”或变量名称可以重复“颜色=红色;颜色=绿色;颜色=蓝色”。
如果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]
}
请注意,要启用矩阵变量,必须将RequestMappingHandlerMapping的removeSemicolonContent属性设置为false。 默认情况下,它设置为true。
注:
MVC Java配置和MVC命名空间都提供了启用矩阵变量的选项。
如果使用的是Java配置,则“使用MVC Java配置进行高级自定义”部分介绍了如何自定义RequestMappingHandlerMapping。
在MVC命名空间中,<mvc:annotation-driven>元素具有应该设置为true的enable-matrix-variables属性。 默认情况下,它设置为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
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc.xsd">
<mvc:annotation-driven enable-matrix-variables="true"/>
</beans>
13. 媒体类型
可以通过指定可使用的媒体类型列表来缩小主映射。 仅当Content-Type请求头与指定的媒体类型匹配时,才会匹配请求。 例如:
@PostMapping(path = "/pets", consumes = "application/json")
public void addPet(@RequestBody Pet pet, Model model) {
// implementation omitted
}
可使用的媒体类型表达式也可以使用否定:!text/plain,以匹配除了Content-Type为text/plain之外的所有请求。 还要考虑使用MediaType中提供的常量,例如APPLICATION_JSON_VALUE和APPLICATION_JSON_UTF8_VALUE。
提示:类型和方法级别支持可使用媒体类型条件。 与大多数其他条件不同,在类型级别使用时,方法级可使用媒体类型会覆盖而不是扩展类型级可使用媒体类型。
14. 可生成的媒体类型
可以通过指定可生成的媒体类型列表来缩小主映射。 仅当Accept请求头与其中一个值匹配时,才会匹配请求。 此外,使用生成条件可确保用于生成响应的实际内容类型遵循生成条件中指定的媒体类型。 例如:
@GetMapping(path = "/pets/{petId}", produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
@ResponseBody
public Pet getPet(@PathVariable String petId, Model model) {
// implementation omitted
}
注:请注意,生成条件中指定的媒体类型也可以选择指定字符集。 例如,在上面的代码片段中,我们指定的媒体类型与MappingJackson2HttpMessageConverter中配置的默认媒体类型相同,包括UTF-8字符集。
就像使用消费一样,可生成的媒体类型表达式可以使用否定:!text/plain,以匹配除了具有text/plain的Accept头的请求之外的所有请求。 还要考虑使用MediaType中提供的常量,例如APPLICATION_JSON_VALUE和APPLICATION_JSON_UTF8_VALUE。
注:类型和方法级别支持生成条件。 与大多数其他条件不同,在类型级别使用时,方法级可生成类型会覆盖而不是扩展类型级可生成类型。
15. 请求参数和请求头值
可以通过请求参数条件缩小请求匹配,例如“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) {
// implementation omitted
}
}
可以执行相同的操作来测试请求头的存在/不存在,或者根据特定的请求头值进行匹配:
@Controller
@RequestMapping("/owners/{ownerId}")
public class RelativePathUriTemplateController {
@GetMapping(path = "/pets", headers = "myHeader=myValue")
public void findPet(@PathVariable String ownerId, @PathVariable String petId, Model model) {
// implementation omitted
}
}
提示:虽然可以使用媒体类型通配符匹配Content-Type和Accept头值(例如“content-type=text/*”将匹配“text/plain”和“text/html”),但建议使用 分别可以使用和产生的条件。 它们专门用于此目的。
16. HTTP HEAD和HTTP OPTIONS
映射到“GET”的@RequestMapping方法也隐式映射到“HEAD”,即不需要显式声明“HEAD”。处理HTTP HEAD请求就好像它是HTTP GET一样,除了不写入主体,只计算字节数并设置“Content-Length”标头。
@RequestMapping方法内置了对HTTP OPTIONS的支持。默认情况下,通过将“允许”响应头设置为在具有匹配URL模式的所有@RequestMapping方法上显式声明的HTTP方法来处理HTTP OPTIONS请求。当没有显式声明HTTP方法时,“Allow”标头设置为“GET,HEAD,POST,PUT,PATCH,DELETE,OPTIONS”。理想情况下,始终声明@RequestMapping方法要处理的HTTP方法,或者使用专用的@RequestMapping变体之一。
虽然没有必要,但@RequestMapping方法可以映射到并处理HTTP HEAD或HTTP OPTIONS,或两者兼而有之。