@RequestMapping的基本用法

可以使用@RequestMapping标注来将请求URL,如/appointments等,映射到整个类上或某个特定的处理器方法上。一般来说,类级别的标注负责将一个特定(或符合某种模式)的请求路径映射到一个控制器上,同时通过方法级别的标注来细化映射,即根据特定的HTTP请求方法(“GET”和“POST”方法等)、HTTP请求中是否携带特定参数等条件,将请求映射到匹配的方法上。

@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、HTTP方法为GET的请求将会被这个方法所处理。

getForDay()方法则展示了使用@RequestMapping标注中的URI模式

类级别的@RequestMapping标注并不是必须的。不配置的话则所有的路径都是绝对路径,而非相对路径。以下的代码示例来自PetClinic,它展示了一个具有多个处理器方法的控制器:

@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)以缩小范围。

URI模式

URI模式为获取@RequestMapping中指定的URL的眸一个特定部分提供很大的方便。

URI模式是一个类似于URI的字符串,只不过其中包含了一个或多个的变量名。当使用实际的值去填充这些变量名的时候,模式就成为一个URI。比如说,一个这个URI模式http://www.example.com/users/{userId}就包含了一个变量名userId。将值fred赋给这个变量名后,它就变成了一个URI:http://www.example.com/users/fred

Spring MVC中可以在方法参数上使用@PathVariable标注,将其与URI模式中的参数绑定起来:

@RequestMapping(path="/owners/{ownerId}", method=RequestMethod.GET)
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模式中对应部分的值所填充。比如说,如果请求的URI是/owners/fred,此时变量ownerId的值就是fred

为了处理@PathVariables标注,Spring MVC必须通过变量名来找到URI模式中相对应的变量。可以在标注中直接声明:

@RequestMapping(path="/owners/{ownerId}}", method=RequestMethod.GET)
public String findOwner(@PathVariable("ownerId") String theOwner, Model model) {
    // ......
}

或者,如果URI模式中的变量名与方法的参数名是相同的,则可以不必再指定一次。Spring MVC就可以自动匹配URL模式中与方法参数名相同的变量名。

@RequestMapping(path="/owners/{ownerId}", method=RequestMethod.GET)
public String findOwner(@PathVariable String ownerId, Model model) {
    // ......
}

一个方法可以拥有任意数量的@PathVariable标注:

@RequestMapping(path="/owners/{ownerId}/pets/{petId}", method=RequestMethod.GET)
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模式变量来填充map。

URI模式可以从类级别和方法级别的@RequestMapping标注获取数据。因此,像这样的findPet()方法可以被类似于/owners/42/pets/21这样的URL路由并调用到:

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

    @RequestMapping("/pets/{petId}")
    public void findPet(_@PathVariable_ String ownerId, _@PathVariable_ String petId, Model model) {
        // ......
    }

}

@PathVariable可以被应用于所有简单类型的参数上,比如intlongDate等类型。Spring会自动地帮你把参数转化成合适的类型,如果转换失败,就抛出一个TypeMismatchException。如果需要处理其他数据类型的转换,也可以注册自定义的类。

带正则表达式的URI模式

有时候可能需要更准确地描述一个URI模式的变量,比如:"/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) {
        // ......
    }
}

除了URI模式外,@RequestMapping标注还支持Ant风格的路径模式(如/myPath/*.do等)。不仅如此,还可以把URI模式变量和Ant风格的glob组合起来使用(比如/owners/*/pets/{petId}这样的用法等)。

路径模式的匹配规则

当一个URL同时匹配多个模式时,只会选择最匹配的一个:

  • URI模式变量的数目和通配符数量的总和最少的那个路径模式更准确。比如,/hotels/{hotel}/*这个路径拥有一个URI变量和一个通配符,而/hotels/{hotel}/**这个路径则拥有一个URI变量和两个通配符,因此前者是更准确的路径模式。

  • 如果两个模式的URI模式数量和通配符数量总和一致,则路径更长的那个模式更准确。举个例子,/foo/bar*就被认为比/foo/*更准确,因为前者的路径更长。

  • 如果两个模式的数量和长度均一致,则那个具有更少通配符的模式是更加准确的。比如,/hotels/{hotel}就比/hotels/*更精确。

  • 默认的通配模式/**比其他所有的模式都更“不准确”。比方说,/api/{a}/{b}/{c}就比默认的通配模式/**要更准确

  • 前缀通配(比如/public/**)被认为比其他任何不包括双通配符的模式更不准确。比如说,/public/path3/{a}/{b}/{c}就比/public/**更准确

消费的媒体类型

可以指定一组可消费的媒体类型,缩小映射的范围。这样只有当请求头中Content-Type 的值与指定可消费的媒体类型中有相同的时候,请求才会被匹配。比如:

@Controller
@RequestMapping(path = "/pets", method = RequestMethod.POST, 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属性提供的是方法级的类型支持。与其他属性不同,当在类型级使用时,方法级的消费类型将覆盖类型级的配置,而非继承关系。

生产的媒体类型

可以指定一组生产的媒体类型,缩小映射的范围。这样只有当请求头中Accept的值与指定可生产的媒体类型中有相同的时候,请求才会被匹配。而且,使用produces条件可以确保用于生成响应的内容与指定的可生产的媒体类型是相同的。比如:

@Controller
@RequestMapping(path = "/pets/{petId}", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
@ResponseBody
public Pet getPet(@PathVariable String petId, Model model) {
    // ......
}

需要注意的是通过condition条件指定的媒体类型也可以指定字符集。比如在上面的小段代码中,还是覆写了MappingJackson2HttpMessageConverter类中默认配置的媒体类型,同时还指定了使用UTF-8的字符集。

consumes条件类似,可生产的媒体类型表达式也可以使用否定。比如,可以使用!text/plain来匹配所有请求头Accept中不含text/plain的请求。同时,在MediaType类中还定义了一些常量,比如APPLICATION_JSON_VALUEAPPLICATION_JSON_UTF8_VALUE等,推荐更多地使用它们。

produces属性提供的是方法级的类型支持。与其他属性不同,当在类型级使用时,方法级的消费类型将覆盖类型级的配置,而非继承关系。

请求参数与请求头的值

可以筛选请求参数的条件来缩小请求匹配范围,比如"myParam""!myParam""myParam=myValue"等。前两个条件用于筛选存在/不存在某些请求参数的请求,第三个条件筛选具有特定参数值的请求。下面的例子展示了如何使用请求参数值的筛选条件:

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

    @RequestMapping(path = "/pets/{petId}", method = RequestMethod.GET, params="myParam=myValue")
    public void findPet(@PathVariable String ownerId, @PathVariable String petId, Model model) {
        // ......
    }

}

也可以用相同的条件来筛选请求头的出现与否,或者筛选出一个具有特定值的请求头:

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

    @RequestMapping(path = "/pets", method = RequestMethod.GET, headers="myHeader=myValue")
    public void findPet(@PathVariable String ownerId, @PathVariable String petId, Model model) {
        // ......
    }

}

尽管可以使用媒体类型的通配符(比如 "content-type=text/*")来匹配请求头Content-TypeAccept的值,但更推荐独立使用consumesproduces条件来筛选各自的请求,它们就是专门为区分这两种不同的场景而设计的。

进一步阅读

Spring MVC的入门实例

更深入地学习Spring MVC,请大家参考Spring MVC实战入门训练

登录发表评论 注册

反馈意见