22.1 Introduction to Spring Web MVC framework
Spring Web model-view-controller (MVC)框架是围绕一个DispatcherServlet设计的,该DispatcherServlet将请求分派给处理程序,具有可配置的处理程序映射、视图解析、地区、时区和主题解析以及对上传文件的支持。默认处理程序基于@Controller和@RequestMapping注释,提供了大量灵活的处理方法。随着Spring 3.0的引入,@Controller机制还允许您通过@PathVariable注释和其他特性创建RESTful Web站点和应用程序。
在Spring Web MVC和Spring中,一个关键的设计原则是“对扩展开放,对修改关闭”。
Spring Web MVC核心类中的一些方法被标记为final。作为开发人员,您不能覆盖这些方法来提供自己的行为。这不是武断地做的,而是具体地考虑到这一原则。
关于这一原理的解释,请参考Seth Ladd等人的专家Spring Web MVC和Web Flow;具体参见第一版第117页的“A Look At Design”一节。另外,请看:
在使用Spring MVC时,您不能向最终方法添加建议。例如,您不能向AbstractController.setSynchronizeOnSession()方法添加通知。有关AOP代理的更多信息以及为什么不能向最终方法添加通知,请参阅第11.6.1节“理解AOP代理”。
在Spring Web MVC中,您可以使用任何对象作为命令或表单支持对象;您不需要实现特定于框架的接口或基类。Spring的数据绑定非常灵活:例如,它将类型不匹配视为可由应用程序评估的验证错误,而不是系统错误。因此,不需要在表单对象中将业务对象的属性复制为简单的非类型化字符串,只需处理无效提交或正确转换字符串即可。相反,最好直接绑定到业务对象。
Spring的视图非常灵活。控制器通常负责准备带有数据的模型映射并选择视图名称,但它也可以直接写入响应流并完成请求。模型(MVC中的M)是一个映射接口,它允许视图技术的完全抽象。您可以直接集成基于模板的呈现技术,如JSP、Velocity和Freemarker,或者直接生成XML、JSON、Atom和许多其他类型的内容。模型映射简单地转换为适当的格式,如JSP请求属性、速度模板模型。
22.1.1 Features of Spring Web MVC
Spring Web Flow
Spring Web Flow (SWF)旨在成为管理Web应用程序页面流的最佳解决方案。SWF在Servlet和Portlet环境中与Spring MVC和JSF等现有框架集成。如果您的业务流程(或多个流程)将受益于对话模型,而不是纯粹的请求模型,那么SWF可能是解决方案。
有关SWF的更多信息,请参阅Spring Web Flow网站。
Spring的web模块包含许多独特的web支持特性:
- 明确的角色划分。每个角色——控制器、验证器、命令对象、表单对象、模型对象、DispatcherServlet、处理程序映射、视图解析器等等——都可以由一个专门的对象来实现。
- 作为javabean的框架类和应用程序类的强大而简单的配置。这种配置功能包括跨上下文的简单引用,例如从web控制器到业务对象和验证器。
- 适应性、非侵入性和灵活性。为给定的场景定义您需要的任何控制器方法签名,可能使用其中一个参数注释(例如@RequestParam、@RequestHeader、@PathVariable等)。
- 可重用的业务代码,不需要复制。使用现有的业务对象作为命令或表单对象,而不是对它们进行镜像,以扩展特定的框架基类。
- 可自定义绑定和验证。类型不匹配是应用程序级验证错误,它会保留错误的值、本地化的日期和数字绑定,等等,而不是手动解析和转换为业务对象的仅使用字符串的表单对象。
- 可自定义处理程序映射和视图解析。处理程序映射和视图解析策略的范围从简单的基于url的配置到复杂的、专门构建的解析策略。Spring比要求特定技术的web MVC框架更灵活。
- 灵活的模式转移。具有名称/值映射的模型传输支持与任何视图技术的轻松集成。
- 可定制的语言环境、时区和主题解析、支持带或不带Spring标记库的jsp、支持JSTL、支持Velocity而不需要额外的桥接,等等。
- 一个简单但功能强大的JSP标记库,称为Spring标记库,它支持数据绑定和主题等特性。自定义标记在标记代码方面具有最大的灵活性。有关标记库描述符的信息,请参见附录43章,spring JSP标记库
- Spring 2.0中引入的JSP表单标记库使在JSP页面中编写表单变得更加容易。有关标记库描述符的信息,请参阅附录44章spring-form JSP标记库
- 生命周期限定为当前HTTP请求或HTTP会话的bean。这不是Spring MVC本身的特定特性,而是Spring MVC使用的WebApplicationContext容器的特性。这些bean作用域在7.5.4节中描述,“请求、会话、全局会话、应用程序和WebSocket作用域”
22.1.2 Pluggability of other MVC implementations 其他MVC实现的可插拔性
对于某些项目,非spring MVC实现更可取。许多团队希望利用他们在技能和工具方面的现有投资,例如JSF。
如果您不想使用Spring的Web MVC,但是希望利用Spring提供的其他解决方案,那么您可以轻松地将您选择的Web MVC框架与Spring集成。只需通过其ContextLoaderListener启动Spring根应用程序上下文,并通过其ServletContext属性(或Spring各自的助手方法)从任何操作对象中访问它。不涉及“插件”,因此不需要专门的集成。从web层的角度来看,您只需将Spring作为库使用,并将根应用程序上下文实例作为入口点。
即使没有Spring的Web MVC,您也可以随时使用注册的bean和Spring的服务。在这个场景中,Spring不与其他web框架竞争。它只是解决了许多纯web MVC框架没有涉及的领域,从bean配置到数据访问和事务处理。因此,您可以使用Spring中间层和/或数据访问层来丰富应用程序,即使您只是想使用JDBC或Hibernate的事务抽象。
22.2 The DispatcherServlet
Spring的web MVC框架与许多其他web MVC框架一样,是请求驱动的,围绕一个中心Servlet设计,该Servlet将请求分派给控制器,并提供其他功能,以促进web应用程序的开发。然而,Spring的DispatcherServlet做的不止这些。它与Spring IoC容器完全集成,因此允许您使用Spring的所有其他特性。
下图展示了Spring Web MVC DispatcherServlet的请求处理工作流。熟悉模式的读者会认识到DispatcherServlet是“前端控制器”设计模式(Spring Web MVC与许多其他领先的Web框架共享这种模式)的表达式。
Figure 22.1. The request processing workflow in Spring Web MVC (high level)
DispatcherServlet是一个实际的Servlet(它继承自HttpServlet基类),并在web应用程序中声明。
您需要使用URL映射来映射希望DispatcherServlet处理的请求。下面是Servlet 3.0+环境中的标准Java EE Servlet配置:
public class MyWebApplicationInitializer implements WebApplicationInitializer {
@Override
public void onStartup(ServletContext container) {
ServletRegistration.Dynamic registration = container.addServlet("example", new DispatcherServlet());
registration.setLoadOnStartup(1);
registration.addMapping("/example/*");
}
}
在前面的示例中,以/example开头的所有请求都将由名为example的DispatcherServlet实例处理。
WebApplicationInitializer是Spring MVC提供的接口,它可以确保检测到基于代码的配置,并自动用于初始化任何Servlet 3容器。
这个接口的抽象基类实现AbstractAnnotationConfigDispatcherServletInitializer通过简单地指定它的servlet映射和列出配置类,使得注册DispatcherServlet变得更加容易——这甚至是设置Spring MVC应用程序的推荐方法。有关详细信息,请参见基于代码的Servlet容器初始化。
DispatcherServlet是一个实际的Servlet(它继承自HttpServlet基类),因此在web中声明。web应用程序的xml。您需要映射希望DispatcherServlet处理的请求,方法是在相同的web中使用URL映射。xml文件。这是标准的Java EE Servlet配置;下面的示例展示了这样一个DispatcherServlet声明和映射:
<web-app>
<servlet>
<servlet-name>example</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>example</servlet-name>
<url-pattern>/example/*</url-pattern>
</servlet-mapping>
</web-app>
如第7.15节“ApplicationContext的附加功能”所述,Spring中的ApplicationContext实例可以限定作用域。在Web MVC框架中,每个DispatcherServlet都有自己的WebApplicationContext,它继承在根WebApplicationContext中已经定义的所有bean。根WebApplicationContext应该包含应该在其他上下文和Servlet实例之间共享的所有基础结构bean。可以在特定于Servlet的范围内覆盖这些继承的bean,并且可以在给定的Servlet实例的本地定义新的特定于范围的bean。
Figure 22.2. Typical context hierarchy in Spring Web MVC
在初始化DispatcherServlet时,Spring MVC寻找一个名为[servlet-name]-servlet.xml 的文件。在web应用程序的WEB-INF目录中创建,并在其中创建定义的bean,覆盖全局作用域中定义的具有相同名称的任何bean的定义。
考虑以下DispatcherServlet Servlet配置(在web.xml文件):
<web-app>
<servlet>
<servlet-name>golfing</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>golfing</servlet-name>
<url-pattern>/golfing/*</url-pattern>
</servlet-mapping>
</web-app>
有了上面的Servlet配置,您将需要在应用程序中有一个名为/WEB-INF/golfing-servlet.xml的文件;这个文件将包含所有Spring Web mvc特定的组件(bean)。您可以通过Servlet初始化参数更改该配置文件的确切位置(详细信息请参见下面)。
对于单个DispatcherServlet场景,也可能只有一个根上下文。
这可以通过设置一个空的contextConfigLocation servlet初始化参数来配置,如下所示:
<web-app>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/root-context.xml</param-value>
</context-param>
<servlet>
<servlet-name>dispatcher</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value></param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>dispatcher</servlet-name>
<url-pattern>/*</url-pattern>
</servlet-mapping>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
</web-app>
注意,我们可以通过基于java的配置实现相同的功能:
public class GolfingWebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
@Override
protected Class<?>[] getRootConfigClasses() {
// GolfingAppConfig defines beans that would be in root-context.xml
return new Class<?>[] { GolfingAppConfig.class };
}
@Override
protected Class<?>[] getServletConfigClasses() {
// GolfingWebConfig defines beans that would be in golfing-servlet.xml
return new Class<?>[] { GolfingWebConfig.class };
}
@Override
protected String[] getServletMappings() {
return new String[] { "/golfing/*" };
}
}
22.2.1 Special Bean Types In the WebApplicationContext
Spring DispatcherServlet使用特殊的bean来处理请求并呈现适当的视图。这些bean是Spring MVC的一部分。您可以通过在WebApplicationContext中配置一个或多个特殊bean来选择要使用的bean。但是,您最初不需要这样做,因为Spring MVC维护了一个默认bean列表,如果您不配置任何bean,就会使用这个列表。下一节将详细介绍这一点。首先,请参阅下表,其中列出了DispatcherServlet所依赖的特殊bean类型。
Table 22.1. Special bean types in the WebApplicationContext
Bean type | Explanation |
---|---|
根据某些标准将传入的请求映射到处理程序和预处理程序和后处理程序(处理程序拦截器)列表,这些标准的细节因HandlerMapping实现的不同而不同。最流行的实现支持带注释的控制器,但也存在其他实现。 | |
HandlerAdapter |
帮助DispatcherServlet调用映射到请求的处理程序,而不管实际调用的是哪个处理程序。例如,调用带注释的控制器需要解析各种注释。因此,HandlerAdapter的主要目的是保护DispatcherServlet不受这些细节的影响。 |
将异常映射到视图还允许更复杂的异常处理代码。 | |
将基于逻辑字符串的视图名称解析为实际的视图类型。 | |
解析客户端正在使用的语言环境,可能还有它们所在的时区,以便能够提供国际化的视图 | |
解析web应用程序可以使用的主题,例如,提供个性化的布局 |
|
解析多部分请求,例如支持处理来自HTML表单的文件上传。 | |
存储和检索“输入”和“输出”FlashMap,可以使用它们将属性从一个请求传递到另一个请求,通常是通过重定向。 |
22.2.2 Default DispatcherServlet Configuration
如上一节所述,对于每个特殊bean, DispatcherServlet维护一个默认使用的实现列表。这些信息保存在org.springframe .web.servlet包中DispatcherServlet.properties文件中。
所有特殊bean都有自己的一些合理的缺省值。不过,您迟早需要自定义这些bean提供的一个或多个属性。例如,将InternalResourceViewResolver的prefix属性配置为视图文件的父位置是非常常见的。
不管细节如何,这里需要理解的重要概念是,一旦您在WebApplicationContext中配置了一个特殊的bean(如InternalResourceViewResolver),您就可以有效地覆盖该特殊bean类型的默认实现列表。例如,如果您配置一个InternalResourceViewResolver,则会忽略ViewResolver实现的默认列表。
在22.16节“配置Spring MVC”中,您将了解配置Spring MVC的其他选项,包括MVC Java config和MVC XML命名空间,这两个选项都提供了一个简单的起点,并且假设您对Spring MVC的工作原理知之甚少。无论您选择如何配置您的应用程序,本节中解释的概念都应该对您有所帮助。
22.2.3 DispatcherServlet Processing Sequence DispatcherServlet处理顺序
在您设置了DispatcherServlet之后,一个特定DispatcherServlet的请求进入,DispatcherServlet开始按照以下方式处理请求:
在WebApplicationContext中声明的处理程序异常解析器获取在处理请求期间抛出的异常。使用这些异常解析器可以定义自定义行为来处理异常。
Spring DispatcherServlet还支持返回Servlet API指定的最后修改日期。确定特定请求的最后修改日期的过程非常简单:DispatcherServlet查找适当的处理程序映射并测试找到的处理程序是否实现了LastModified接口。如果是,LastModified接口的long getLastModified(request)方法的值将返回给客户机。
您可以通过向web.xml文件中的Servlet声明中添加Servlet初始化参数(int -param元素)来定制各个DispatcherServlet实例。有关支持的参数列表,请参见下表。
Table 22.2. DispatcherServlet initialization parameters
- 在请求中搜索WebApplicationContext并将其绑定为控制器和流程中的其他元素可以使用的属性。默认情况下,它绑定在DispatcherServlet.WEB_APPLICATION_CONTEXT_ATTRIBUTE键下。
- 区域设置解析器绑定到请求,以使流程中的元素能够解析在处理请求(呈现视图、准备数据等)时使用的区域设置。如果不需要区域设置解析,就不需要它。
- 主题解析器绑定到请求,以让视图等元素决定使用哪个主题。如果不使用主题,可以忽略它。
- 如果指定多部分文件解析器,将检查请求的多部分;如果找到多个部分,则将请求包装在MultipartHttpServletRequest中,以便由流程中的其他元素进行进一步处理。有关多部分处理的更多信息,请参见第22.10节“Spring的多部分(文件上传)支持”。
- 搜索适当的处理程序。如果找到处理程序,则执行与该处理程序(预处理程序、后处理程序和控制器)关联的执行链,以准备模型或呈现。
- 如果返回模型,则呈现视图。如果没有返回模型(可能是由于预处理程序或后处理程序拦截了请求,可能是出于安全原因),则没有呈现视图,因为请求可能已经被完成。
Parameter | Explanation | |
---|---|---|
|
类,该类实现ConfigurableWebApplicationContext,由此Servlet实例化并在本地配置。默认情况下,使用XmlWebApplicationContext。 | |
|
传递到上下文实例(由contextClass指定)的字符串,以指示在何处可以找到上下文。该字符串可能由多个字符串(使用逗号作为分隔符)组成,以支持多个上下文。对于定义了两次的bean的多个上下文位置,优先考虑最新的位置。 |
|
|
WebApplicationContext的命名空间。默认为 [servlet-name]-servlet. |
22.3 Implementing Controllers
控制器提供对通常通过服务接口定义的应用程序行为的访问。控制器解释用户输入并将其转换为由视图向用户表示的模型。Spring以一种非常抽象的方式实现了一个控制器,它使您能够创建各种各样的控制器。
Spring 2.5为MVC控制器引入了一个基于注释的编程模型,它使用@RequestMapping、@RequestParam、@ModelAttribute等注释。Servlet MVC和Portlet MVC都可以使用这种注释支持。以这种风格实现的控制器不必扩展特定的基类或实现特定的接口。此外,它们通常不直接依赖于Servlet或Portlet api,尽管您可以轻松地配置对Servlet或Portlet设施的访问。
在Github上的spring-projects Org中可以找到许多web应用程序,它们利用了本节中描述的注释支持,包括MvcShowcase、MvcAjax、MvcBasic、PetClinic、PetCare等。
@Controller
public class HelloWorldController {
@RequestMapping("/helloWorld")
public String helloWorld(Model model) {
model.addAttribute("message", "Hello World!");
return "helloWorld";
}
}
如您所见,@Controller和@RequestMapping注释允许灵活的方法名和签名。在这个特定的示例中,该方法接受一个模型,并将视图名作为字符串返回,但是可以使用其他各种方法参数和返回值,如本节后面所述。@Controller和@RequestMapping以及许多其他注释构成了Spring MVC实现的基础。本节记录这些注释以及它们如何在Servlet环境中最常用。
22.3.1 Defining a controller with @Controller
@Controller注释表示一个特定的类充当一个控制器的角色。Spring不要求您扩展任何控制器基类或引用Servlet API。但是,如果需要,您仍然可以引用servlet特定的特性。
@Controller注释充当带注释类的原型,指示其角色。dispatcher扫描此类带注释的类以查找映射方法,并检测@RequestMapping注释(参见下一节)。
您可以使用dispatcher上下文中的标准Spring bean定义显式地定义带注释的控制器bean。然而,@Controller构造型还允许自动检测,与Spring通用支持对齐,用于检测类路径中的组件类,并为它们自动注册bean定义。
要启用此类带注释控制器的自动检测,您可以将组件扫描添加到配置中。使用spring上下文模式,如下面的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
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<context:component-scan base-package="org.springframework.samples.petclinic.web"/>
<!-- ... -->
</beans>
22.3.2 Mapping Requests With @RequestMapping
您可以使用@RequestMapping注释将url(如/appointment)映射到整个类或特定的处理程序方法。通常,类级注释将特定的请求路径(或路径模式)映射到表单控制器上,附加的方法级注释缩小了特定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在许多地方使用。第一个用法是在type (class)级别上,它表示控制器中的所有处理程序方法都相对于/appointment路径。get()方法有进一步的@RequestMapping改进:它只接受get请求,这意味着用于/约会的HTTP get调用该方法。add()也有类似的改进,getNewForm()将HTTP方法和路径的定义合并到一起,这样就可以用该方法处理约会/新建的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等,因为@RequestMapping默认映射所有HTTP方法。使用@RequestMapping(method=GET)或@GetMapping来缩小映射。
Composed @RequestMapping Variants
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 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 Template Patterns
URI模板可用于方便地访问@RequestMapping方法中URL的选定部分。
URI模板是一个类似URI的字符串,包含一个或多个变量名。当您用值替换这些变量时,模板就变成了URI。URI模板的RFC定义了如何参数化URI。例如,URI模板http://www.example.com/users/{userId}包含变量userId。将fred值赋给变量将产生http://www.example.com/users/fred。
在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的适当部分中找到的值。例如,当请求/所有者/fred时,ownerId的值是fred。
要处理@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异常。您还可以注册对解析其他数据类型的支持。See the section called “Method Parameters And Type Conversion” and the section called “Customizing WebDataBinder initialization”.
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) {
// ...
}
Path Patterns
除了URI模板之外,@RequestMapping注释和所有组合的@RequestMapping变体还支持ant风格的路径模式(例如/myPath/*.do)。还支持URI模板变量和ant样式的全局变量的组合(例如/owners/*/pets/{petId})。
Pat