18.3 实现Controllers

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

Spring 2.5引入了一种基于注释的编程模型,用于使用诸如@RequestMapping,@RequestParam,@ModelAttribute等注释的MVC控制器。以这种风格实现的控制器不必扩展特定的基类或实现特定的接口。此外,它们通常不直接依赖于Servlet API,但是如果需要,您可以轻松地配置对Servlet设施的访问。

  1. @Controller
  2. public class HelloWorldController {
  3. @RequestMapping("/helloWorld")
  4. public String helloWorld(Model model) {
  5. model.addAttribute("message", "Hello World!");
  6. return "helloWorld";
  7. }
  8. }

您可以看到,@Controller和@RequestMapping注释允许灵活的方法名称和签名。在这个特殊的例子中,该方法接受一个Model并返回一个视图名称作为一个String,但是可以使用各种其他的方法参数和返回值,如本节稍后所述。 @Controller和@RequestMapping和许多其他注释构成了Spring MVC实现的基础。本节介绍这些注释以及它们在Servlet环境中最常用的注释。

18.3.1 使用@Controller定义控制器

@Controller注释表示特定的类用于控制器的角色。 Spring不需要扩展任何控制器基类或引用Servlet API。 但是,如果需要,您仍然可以参考Servlet特定的功能。

@Controller注释作为注释类的构造型,表示其作用。 调度程序扫描这些注释类的映射方法,并检测@RequestMapping注释(请参阅下一节)。

您可以使用调度程序上下文中的标准Spring bean定义来明确定义带注释的控制器bean。 但是,@Controller构造型还允许自动检测,与Spring通用支持对齐,用于检测类路径中的组件类并自动注册它们的bean定义。要启用自动检测这些带注释的控制器,您可以向组态添加组件扫描。 使用spring-context模式,如以下XML代码片段所示:

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <beans xmlns="http://www.springframework.org/schema/beans"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4. xmlns:p="http://www.springframework.org/schema/p"
  5. xmlns:context="http://www.springframework.org/schema/context"
  6. xsi:schemaLocation="
  7. http://www.springframework.org/schema/beans
  8. http://www.springframework.org/schema/beans/spring-beans.xsd
  9. http://www.springframework.org/schema/context
  10. http://www.springframework.org/schema/context/spring-context.xsd">
  11. <context:component-scan base-package="org.springframework.samples.petclinic.web"/>
  12. <!-- ... -->
  13. </beans>

18.3.2 使用@RequestMapping映射请求

您可以使用@RequestMapping注释将诸如/约会的URL映射到整个类或特定的处理程序方法。 通常,类级注释将特定的请求路径(或路径模式)映射到表单控制器上,其他方法级注释缩小了特定HTTP请求方法(“GET”,“POST”等)的主映射,或 HTTP请求参数条件。

Petcare示例中的以下示例显示了使用此注释的Spring MVC应用程序中的控制器:

  1. @Controller
  2. @RequestMapping("/appointments")
  3. public class AppointmentsController {
  4. private final AppointmentBook appointmentBook;
  5. @Autowired
  6. public AppointmentsController(AppointmentBook appointmentBook) {
  7. this.appointmentBook = appointmentBook;
  8. }
  9. @RequestMapping(method = RequestMethod.GET)
  10. public Map<String, Appointment> get() {
  11. return appointmentBook.getAppointmentsForToday();
  12. }
  13. @RequestMapping(path = "/{day}", method = RequestMethod.GET)
  14. public Map<String, Appointment> getForDay(@PathVariable @DateTimeFormat(iso=ISO.DATE) Date day, Model model) {
  15. return appointmentBook.getAppointmentsForDay(day);
  16. }
  17. @RequestMapping(path = "/new", method = RequestMethod.GET)
  18. public AppointmentForm getNewForm() {
  19. return new AppointmentForm();
  20. }
  21. @RequestMapping(method = RequestMethod.POST)
  22. public String add(@Valid AppointmentForm appointment, BindingResult result) {
  23. if (result.hasErrors()) {
  24. return "appointments/new";
  25. }
  26. appointmentBook.addAppointment(appointment);
  27. return "redirect:/appointments";
  28. }
  29. }

在上面的例子中,@RequestMapping用在很多地方。 第一个用法是类型(类)级别,这表示此控制器中的所有处理程序方法都相对于/约会路径。 get()方法还有一个@RequestMapping细化:它只接受GET请求,这意味着/appointments 的HTTP GET调用此方法。 add()有一个类似的细化,getNewForm()将HTTP方法和路径的定义组合成一个,以便通过该方法处理appointments /新的GET请求。

getForDay()方法显示了@RequestMapping:URI模板的另一种用法。 (参见“URI模板模式”一节)。

类级别上的@RequestMapping不是必需的。 没有它,所有的路径都是绝对的,而不是相对的。 PetClinic示例应用程序的以下示例显示了使用@RequestMapping的多操作控制器:

  1. @Controller
  2. public class ClinicController {
  3. private final Clinic clinic;
  4. @Autowired
  5. public ClinicController(Clinic clinic) {
  6. this.clinic = clinic;
  7. }
  8. @RequestMapping("/")
  9. public void welcomeHandler() {
  10. }
  11. @RequestMapping("/vets")
  12. public ModelMap vetsHandler() {
  13. return new ModelMap(this.clinic.getVets());
  14. }
  15. }

上述示例不指定GET与PUT,POST等,因为@RequestMapping默认映射所有HTTP方法。 使用@RequestMapping(method = GET)或@GetMapping来缩小映射。

组合@RequestMapping变体

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

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

以下示例显示了使用已组合的@RequestMapping注释简化的上一节中的AppointmentsController的修改版本。

  1. @Controller
  2. @RequestMapping("/appointments")
  3. public class AppointmentsController {
  4. private final AppointmentBook appointmentBook;
  5. @Autowired
  6. public AppointmentsController(AppointmentBook appointmentBook) {
  7. this.appointmentBook = appointmentBook;
  8. }
  9. @GetMapping
  10. public Map<String, Appointment> get() {
  11. return appointmentBook.getAppointmentsForToday();
  12. }
  13. @GetMapping("/{day}")
  14. public Map<String, Appointment> getForDay(@PathVariable @DateTimeFormat(iso=ISO.DATE) Date day, Model model) {
  15. return appointmentBook.getAppointmentsForDay(day);
  16. }
  17. @GetMapping("/new")
  18. public AppointmentForm getNewForm() {
  19. return new AppointmentForm();
  20. }
  21. @PostMapping
  22. public String add(@Valid AppointmentForm appointment, BindingResult result) {
  23. if (result.hasErrors()) {
  24. return "appointments/new";
  25. }
  26. appointmentBook.addAppointment(appointment);
  27. return "redirect:/appointments";
  28. }
  29. }

@Controller 和AOP 代理

在某些情况下,控制器可能需要在运行时用AOP代理进行装饰。 一个例子是如果您选择在控制器上直接使用@Transactional注释。 在这种情况下,对于控制器,我们建议使用基于类的代理。 这通常是控制器的默认选项。 但是,如果控制器必须实现不是Spring Context回调的接口(例如InitializingBean,* Aware等),则可能需要显式配置基于类的代理。 例如,使用<tx:annotation-driven />,更改为<tx:annotation-driven proxy-target-class =“true”/>。

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注释来缩小方法。
  • 依赖于方法名称作为一种落后机制,以消除两个@RequestMapping方法之间的差异,这两个方法没有明确的路径映射URL路径, 通过HTTP方法。 在新的支持类中,@RequestMapping方法必须被唯一地映射。
  • 如果没有其他控制器方法更具体地匹配,请使用单个默认方法(无显式路径映射)处理请求。 在新的支持类中,如果找不到匹配方法,则会引发404错误。 上述功能仍然支持现有的支持类。 不过要利用新的Spring MVC 3.1功能,您需要使用新的支持类。

URI 模版模式

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

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

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

  1. @GetMapping("/owners/{ownerId}")
  2. public String findOwner(@PathVariable String ownerId, Model model) {
  3. Owner owner = ownerService.findOwner(ownerId);
  4. model.addAttribute("owner", owner);
  5. return "displayOwner";
  6. }

URI模板“/ owners / {ownerId}”指定变量名ownerId。 当控制器处理此请求时,ownerId的值将设置为在URI的适当部分中找到的值。 例如,当/ owner / fred出现请求时,ownerId的值为fred。

要处理@PathVariable注释,Spring MVC需要按名称找到匹配的URI模板变量。 您可以在注释中指定它:

  1. @GetMapping("/owners/{ownerId}")
  2. public String findOwner(@PathVariable("ownerId") String theOwner, Model model) {
  3. // implementation omitted
  4. }

或者如果URI模板变量名称与方法参数名称匹配,则可以省略该详细信息。 只要您的代码使用调试信息或Java 8上的参数编译器标记进行编译,Spring MVC将将方法参数名称与URI模板变量名称相匹配:

  1. @GetMapping("/owners/{ownerId}")
  2. public String findOwner(@PathVariable String ownerId, Model model) {
  3. // implementation omitted
  4. }

一个方法能够有任意数量的@PathVariable注解:

  1. @GetMapping("/owners/{ownerId}/pets/{petId}")
  2. public String findPet(@PathVariable String ownerId, @PathVariable String petId, Model model) {
  3. Owner owner = ownerService.findOwner(ownerId);
  4. Pet pet = owner.getPet(petId);
  5. model.addAttribute("pet", pet);
  6. return "displayPet";
  7. }

当在Map <String,String>参数上使用@PathVariable注释时,映射将填充所有URI模板变量。

URI模板可以从类型和方法级别@RequestMapping注释中进行组合。 因此,可以使用/ owner / 42 / pets / 21等URL调用findPet()方法。

  1. @Controller
  2. @RequestMapping("/owners/{ownerId}")
  3. public class RelativePathUriTemplateController {
  4. @RequestMapping("/pets/{petId}")
  5. public void findPet(@PathVariable String ownerId, @PathVariable String petId, Model model) {
  6. // implementation omitted
  7. }
  8. }

一个@PathVariable参数可以是任何简单的类型,如int,long,Date等。如果没有这样做,Spring将自动转换为适当的类型或者抛出一个TypeMismatchException。 您还可以注册解析其他数据类型的支持。. See the section called “Method Parameters And Type Conversion” 和the section called “Customizing WebDataBinder initialization”.

具有正则表达式的URI模板模式

有时您需要更精确地定义URI模板变量。 考虑URL“/spring-web/spring-web-3.0.5.jar”。 你怎么把它分解成多个部分?

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