声明:本篇文档主要是用于参考帮助文档,没有实例,但几乎包含了SpringMVC 4.2版本的全部核心技术,当前最新版本是4.3,4.2的版本已经经是很新的了,因此很是值得你们一读,对于读完这篇文档感受还不错的麻烦给个推荐,毕竟花了我一个星期时间才整理出来的,因此请多多支持 。 对于以为篇幅长的文档,建议你们使用快捷键crtl + F,搜索关键字查询较为方便.css
Spring的模型-视图-控制器(MVC)框架是围绕一个 DispatcherServlet 来设计的,这个Servlet会把请求分发给各个处理器,并支持可配置的处理器映射、视图渲染、本地化、时区与主题渲染等,甚至还能支持文件上传。处理器是你的应用中注解了 @Controller 和 @RequestMapping 的类和方法,Spring为处理器方法提供了极其多样灵活的配置。Spring 3.0之后提供了 @Controller 注解机制、 @PathVariable 注解以及一些其余的特性,你能够使用它们来进行RESTful web站点和应用的开发。html
“对扩展开放”是Spring Web MVC框架一个重要的设计原则,而对于Spring的整个完整框架来讲,其设计原则则是“对扩展开放,对修改闭合”。前端
Spring Web MVC核心类库中的一些方法被定义为
final
方法。做为开发人员,你不能覆写这些方法以定制其行为。固然,不是说绝对不行,但请记住这条原则,绝大多数状况下不是好的实践。java关于该原则的详细解释,你能够参考Seth Ladd等人所著的“深刻解析Spring Web MVC与Web Flow”一书。相关信息在第117页,“设计初探(A Look At Design)”一节。或者,你能够参考:git
你没法加强Spring MVC中的
final
方法,好比 AbstractController .setSynchronizeOnSession() 方法等。请参考10.6.1 理解AOP代理一节,其中解释了AOP代理的相关知识,论述了为何你不能对final
方法进行加强。github
在Spring Web MVC中,你能够使用任何对象来做为命令对象或表单返回对象,而无须实现一个框架相关的接口或基类。Spring的数据绑定很是灵活:好比,它会把数据类 型不匹配当成可由应用自行处理的运行时验证错误,而非系统错误。你可能会为了不非法的类型转换在表单对象中使用字符串来存储数据,但无类型的字符串没法 描述业务数据的真正含义,而且你还须要把它们转换成对应的业务对象类型。有了Spring的验证机制,意味着你不再需这么作了,而且直接将业务对象绑定 到表单对象上一般是更好的选择。web
Spring的视图解析也是设计得异常灵活。控制器通常负责准备一个Map
模型、填充数据、返回一个合适的视图名等,同时它也能够直接将数据写到响应流中。视图名的解析高度灵活,支持多种配置,包括经过文件扩展名、Accept
内容头、bean、配置文件等的配置,甚至你还能够本身实现一个视图解析器 ViewResolver 。模型(MVC中的M,model)实际上是一个Map
类型的接口,完全地把数据从视图技术中抽象分离了出来。你能够与基于模板的渲染技术直接整合,如JSP、Velocity和Freemarker等,或者你还能够直接生成XML、JSON、Atom以及其余多种类型的内容。Map
模型会简单地被转换成合适的格式,好比JSP的请求属性(attribute),一个Velocity模板的模型等。正则表达式
Spring Web Flow算法
Spring Web Flow (SWF) 意在成为web应用中的页面流(page flow)管理中最好的解决方案。spring
SWF在Servlet环境和Portlet环境下集成了现有的框架,如Spring MVC和JSF等。若是你的业务流程有一个贯穿始终的模型,而非单纯分立的请求,那么SWF多是适合你的解决方案。
SWF 容许你将逻辑上的页面流抽取成独立可复用的模块,这对于构建一个web应用的多个模块是有益的。that guide the user through controlled navigations that drive business processes.
关于SWF的更多信息,请访问Spring Web Flow的官网。
Spring的web模块支持许多web相关的特性:
等)有些项目可能更倾向于使用非Spring的MVC框架。 许多团队但愿仍然使用现有的技术栈,好比JSF等,这样他们掌握的技能和工具依然能发挥做用。
若是你确实不想使用Spring的Web MVC,但又但愿能从Spring提供的一些解决方案中受益,那么将你所使用的框架和Spring进行集成也很容易。只须要在 ContextLoaderListener 中启动一个Spring的根应用上下文(root application context),而后你就能够在任何action对象中经过其 ServletContext 属性(或经过Spring对应的helper方法)取得。不须要任何侵入性的插件,所以不须要复杂的集成。从应用层的视角来看,你只是将Spring当成依赖库使用,而且将它的根应用上下文实例做为应用进入点。
即 使不用Spring的Web MVC框架,你配置的其余Spring的 bean 和服务也都能很方便地取得。在这种场景下,Spring与其余web框架的使用不冲突。Spring只是 在许多问题上提出了其余纯web MVC框架不曾提出过的解决方案,好比 bean 的配置、数据存取、事务处理等,仅此而已。所以,若是你只是想使用Spring的一部分特性来加强你的应 用,好比Spring提供的JDBC/Hibernate事务抽象等,那么你能够将Spring做为一个中间层和/或数据存取层来使用。
Spring MVC框架,与其余不少web的MVC框架同样:请求驱动;全部设计都围绕着一个中央Servlet来展开,它负责把全部请求分发到控制器;同时提供其余web应用开发所须要的功能。不过Spring的中央处理器, DispatcherServlet ,能作的比这更多。它与Spring IoC容器作到了无缝集成,这意味着,Spring提供的任何特性,在Spring MVC中你均可以使用。
下图展现了Spring Web MVC的 DispatcherServlet 处理请求的工做流。熟悉设计模式的朋友会发现, DispatcherServlet 应用的其实就是一个“前端控制器”的设计模式(其余不少优秀的web框架也都使用了这个设计模式)。
DispatcherServlet 其实就是个 Servlet (它继承自 HttpServlet 基类),一样也须要在你web应用的web.xml
配置文件下声明。你须要在 web.xml 文件中把你但愿 DispatcherServlet 处理的请求映射到对应的URL上去。这就是标准的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>
In the preceding example, all requests starting with
/example
will be handled by theDispatcherServlet
instance named example. In a Servlet 3.0+ environment, you also have the option of configuring the Servlet container programmatically. Below is the code based equivalent of the above web.xml example:
在上面的例子中,全部路径以 /example 开头的请求都会被名字为example
的 DispatcherServlet 处理。在Servlet 3.0+的环境下,你还能够用编程的方式配置Servlet容器。下面是一段这种基于代码配置的例子,它与上面定义的 web.xml 配置文件是等效的。
public class MyWebApplicationInitializer implements WebApplicationInitializer { @Override public void onStartup(ServletContext container) { ServletRegistration.Dynamic registration = container.addServlet("dispatcher", new DispatcherServlet()); registration.setLoadOnStartup(1); registration.addMapping("/example/*"); } }
WebApplicationInitializer 是Spring MVC提供的一个接口,它会查找你全部基于代码的配置,并应用它们来初始化Servlet 3版本以上的web容器。它有一个抽象的实现 AbstractDispatcherServletInitializer ,用以简化 DispatcherServlet 的注册工做:你只须要指定其servlet映射(mapping)便可。若想了解更多细节,能够参考基于代码的Servlet容器初始化一节。
上面只是配置Spring Web MVC的第一步,接下来你须要配置其余的一些bean(除了 DispatcherServlet 之外的其余bean),它们也会被Spring Web MVC框架使用到。
在6.15 应用上下文ApplicationContext的其余做用)一节中咱们聊到,Spring中的 ApplicationContext 实例是能够有范围(scope)的。在Spring MVC中,每一个 DispatcherServlet 都持有一个本身的上下文对象 WebApplicationContext ,它又继承了根(root) WebApplicationContext 对象中已经定义的全部bean。这些继承的bean能够在具体的Servlet实例中被重载,在每一个Servlet实例中你也能够定义其scope下的新bean。
DispatcherServlet 的初始化过程当中,Spring MVC会在你web应用的WEB-INF
目录下查找一个名为 [servlet-name]-servlet.xml 的配置文件,并建立其中所定义的 bean 。若是在全局上下文中存在相同名字的 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 MVC相关的组件(好比bean等)。你能够经过servlet初始化参数为这个配置文件指定其余的路径(见下面的例子):
当你的应用中只须要一个 DispatcherServlet 时,只配置一个根 contex t对象也是可行的。
要配置一个惟一的根 context 对象,能够经过在 servlet 初始化参数中配置一个空的 contextConfigLocation 来作到,以下所示:
<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>
WebApplicationContext 继承自 ApplicationContext ,它提供了一些web应用常常须要用到的特性。它与普通的 ApplicationContext 不一样的地方在于,它支持主题的解析(详见21.9 主题Themes一小节),而且知道它关联到的是哪一个servlet(它持有一个该 ServletContext 的引用)。 WebApplicationContext 被绑定在 ServletContext 中。若是须要获取它,你能够经过 RequestContextUtils 工具类中的静态方法来拿到这个web应用的上下文 WebApplicationContext 。
Spring的 DispatcherServlet 使用了特殊的 bean 来处理请求、渲染视图等,这些特定的bean是Spring MVC框架的一部分。若是你想指定使用哪一个特定的 bean ,你能够在web应用上下文 WebApplicationContext 中简单地配置它们。固然这只是可选的,Spring MVC维护了一个默认的 bean 列表,若是你没有进行特别的配置,框架将会使用默认的bean。下一小节会介绍更多的细节,这里,咱们将先快速地看一下, DispatcherServlet 都依赖于哪些特殊的bean来进行它的初始化。
bean的类型 | 做用 |
---|---|
HandlerMapping |
处理器映射。它会根据某些规则将进入容器的请求映射到具体的处理器以及一系列前处理器和后处理器(即处理器拦截器)上。具体的规则视 HandlerMapping 类的实现不一样而有所不一样。其最经常使用的一个实现支持你在控制器上添加注解,配置请求路径。固然,也存在其余的实现。 |
HandlerAdapter |
处理器适配器。拿到请求所对应的处理器后,适配器将负责去调用该处理器,这使得 DispatcherServlet 无需关心具体的调用细节。比方说,要调用的是一个基于注解配置的控制器,那么调用前还须要从许多注解中解析出一些相应的信息。所以, HandlerAdapter 的主要任务就是对 DispatcherServlet 屏蔽这些具体的细节。 |
HandlerExceptionResolver |
处理器异常解析器。它负责将捕获的异常映射到不一样的视图上去,此外还支持更复杂的异常处理代码。 |
ViewResolver |
视图解析器。它负责将一个表明逻辑视图名的字符串(String)映射到实际的视图类型View 上。 |
LocaleResolver & LocaleContextResolver |
地区解析器 和 地区上下文解析器。它们负责解析客户端所在的地区信息甚至时区信息,为国际化的视图定制提供了支持。 |
ThemeResolver |
主题解析器。它负责解析你web应用中可用的主题,好比,提供一些个性化定制的布局等。 |
MultipartResolver |
解析multi-part的传输请求,好比支持经过HTML表单进行的文件上传等。 |
FlashMapManager |
FlashMap管理器。它可以存储并取回两次请求之间的 FlashMap 对象。后者可用于在请求之间传递数据,一般是在请求重定向的情境下使用。 |
上一小节讲到, DispatcherServlet 维护了一个列表,其中保存了其所依赖的全部 bean 的默认实现。这个列表保存在包 org.springframework.web.servlet 下的 DispatcherServlet.properties 文件中。
这些特殊的bean都有一些基本的默认行为。或早或晚,你可能须要对它们提供的一些默认配置进行定制。好比说,一般你须要配置 InternalResourceViewResolver 类提供的 prefix 属性,使其指向视图文件所在的目录。 这里须要理解的一个事情是,一旦你在web应用上下文 WebApplicationContext 中配置了某个特殊 bean 之后(好比 InternalResourceViewResolver ),实际上你也覆写了该 bean 的默认实现。比方说,若是你配置了 InternalResourceViewResolver ,那么框架就不会再使用 beanViewResolver 的默认实现。
在21.16节 Spring MVC的配置中, 咱们介绍了其余配置Spring MVC的方式,好比经过Java编程配置或者经过MVC XML命名空间进行配置。它们为配置一个Spring MVC应用提供了简易的开始方式,也不须要你对框架实现细节有太多了解。固然,不管你选用何种方式开始配置,本节所介绍的一些概念都是基础且普适的,它们 对你后续的学习都应有所助益。
配置好 DispatcherServlet 之后,开始有请求会通过这个 DispatcherServlet 。此时, DispatcherServlet 会依照如下的次序对请求进行处理:
t
对象,以便处理链中的其余组件对它作进一步的处理。关于Spring对multipart文件传输处理的支持,读者能够参考21.10 Spring的multipart(文件上传)支持一小节若是在处理请求的过程当中抛出了异常,那么上下文 WebApplicationContext 对象中所定义的异常处理器将会负责捕获这些异常。经过配置你本身的异常处理器,你能够定制本身处理异常的方式。
Spring的 DispatcherServlet 也容许处理器返回一个Servlet API规范中定义的 最后修改时间戳(last-modification-date) 值。决定请求最后修改时间的方式很直接: DispatcherServlet 会先查找合适的处理器映射来找到请求对应的处理器,而后检测它是否实现了 LastModified 接口。如果,则调用接口的 long getLastModified(request) 方法,并将该返回值返回给客户端。
你能够定制 DispatcherServlet 的配置,具体的作法,是在 web.xml 文件中,Servlet的声明元素上添加一些Servlet的初始化参数(经过 init-param 元素)。该元素可选的参数列表以下:
可选参数 | 解释 |
---|---|
contextClass |
任意实现了 WebApplicationContext 接口的类。这个类会初始化该 servlet 所须要用到的上下文对象。默认状况下,框架会使用一个 XmlWebApplicationContext 对象。 |
contextConfigLocation |
一个指定了上下文配置文件路径的字符串,该值会被传入给 contextClass 所指定的上下文实例对象。该字符串内能够包含多个字符串,字符串之间以逗号分隔,以此支持你进行多个上下文的配置。在多个上下文中重复定义的 bean ,以最后加载的bean定义为准 |
namespace |
WebApplicationContext 的命名空间。默认是 [servlet-name]-servlet |
...Spring implements a controller in a very abstract way, which enables you to create a wide variety of controllers.
控制器做为应用程序逻辑的处理入口,它会负责去调用你已经实现的一些服务。一般,一个控制器会接收并解析用户的请求,而后把它转换成一个模型交给视图,由视图渲染出页面最终呈现给用户。Spring对控制器的定义很是宽松,这意味着你在实现控制器时很是自由。
Spring 2.5之后引入了基于注解的编程模型,你能够在你的控制器实现上添加 @RequestMapping、@RequestParam、@ModelAttribute 等 注解。注解特性既支持基于Servlet的MVC,也可支持基于Portlet的MVC。经过此种方式实现的控制器既无需继承某个特定的基类,也无需实现 某些特定的接口。并且,它一般也不会直接依赖于Servlet或Portlet的API来进行编程,不过你仍然能够很容易地获取Servlet或 Portlet相关的变量、特性和设施等。
在Spring项目的官方Github上你能够找到许多项目,它们对本节所述之后的注解支持提供了进一步加强,好比说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 注解支持多样的方法名和方法签名。在上面这个例子中,方法接受一个Model
类型的参数并返回一个字符串String
类型的视图名。但事实上,方法所支持的参数和返回值有很是多的选择,这个咱们在本小节的后面部分会说起。 @Controller 和 @RequestMapping 及其余的一些注解,共同构成了Spring MVC框架的基本实现。本节将详细地介绍这些注解,以及它们在一个Servlet环境下最常被使用到的一些场景。
[Original] The
@Controller
annotation indicates that a particular class serves the role of a controller. Spring does not require you to extend any controller base class or reference the Servlet API. However, you can still reference Servlet-specific features if you need to.
@Controller 注解代表了一个类是做为控制器的角色而存在的。Spring不要求你去继承任何控制器基类,也不要求你去实现Servlet的那套API。固然,若是你须要的话也能够去使用任何与Servlet相关的特性和设施。
[Original] The
@Controller
annotation acts as a stereotype for the annotated class, indicating its role. The dispatcher scans such annotated classes for mapped methods and detects@RequestMapping
annotations (see the next section).
@Controller 注解能够认为是被标注类的原型(stereotype),代表了这个类所承担的角色。分派器( DispatcherServlet )会扫描全部注解了 @Controller 的类,检测其中经过 @RequestMapping 注解配置的方法(详见下一小节)。
[Original] You can define annotated controller beans explicitly, using a standard Spring bean definition in the dispatcher’s context. However, the
@Controller
stereotype also allows for autodetection, aligned with Spring general support for detecting component classes in the classpath and auto-registering bean definitions for them.
固然,你也能够不使用 @Controller 注解而显式地去定义被注解的bean,这点经过标准的Spring bean的定义方式,在dispather的上下文属性下配置便可作到。可是 @Controller 原型是能够被框架自动检测的,Spring支持classpath路径下组件类的自动检测,以及对已定义bean的自动注册。
[Original] To enable autodetection of such annotated controllers, you add component scanning to your configuration. Use the spring-context schema as shown in the following XML snippet:
你须要在配置中加入组件扫描的配置代码来开启框架对注解控制器的自动检测。请使用下面XML代码所示的spring-context schema:
<?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>
你能够使用 @RequestMapping 注解来将请求URL,如 /appointments 等, 映射到整个类上或某个特定的处理器方法上。通常来讲,类级别的注解负责将一个特定(或符合某种模式)的请求路径映射到一个控制器上,同时经过方法级别的注 解来细化映射,即根据特定的HTTP请求方法(“GET”“POST”方法等)、HTTP请求中是否携带特定参数等条件,将请求映射到匹配的方法上。
下面这段代码示例来自Petcare,它展现了在Spring MVC中如何在控制器上使用 @RequestMapping 注解:
@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模板。(关于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) 以缩小范围。
有时,咱们但愿在运行时使用AOP代理来装饰控制器,好比当你直接在控制器上使用 @Transactional 注解时。这种状况下,咱们推荐使用类级别(在控制器上使用)的代理方式。这通常是代理控制器的默认作法。若是控制器必须实现一些接口,而该接口又不支持Spring Context的回调(好比 InitializingBean, *Aware 等接口),那要配置类级别的代理就必须手动配置了。好比,原来的配置文件 <tx:annotation-driven/> 须要显式配置为 <tx:annotation-driven proxy-target-class="true"/> 。
They are recommended for use and even required to take advantage of new features in Spring MVC 3.1 and going forward.
Spring 3.1中新增了一组类用以加强 @RequestMapping ,分别是 RequestMappingHandlerMapping 和 RequestMappingHandlerAdapter 。 咱们推荐你用一用。有部分Spring MVC 3.1以后新增的特性,这两个注解甚至是必须的。在MVC命名空间和MVC Java编程配置方式下,这组类及其新特性默认是开启的。但若你使用其余配置方式,则该特性必须手动配置才能使用。本小节将简要介绍一下,新类相比以前的 一些重要变化。
在Spring 3.1以前,框架会在两个不一样的阶段分别检查类级别和方法级别的请求映射——首先, DefaultAnnotationHanlderMapping 会先在类级别上选中一个控制器,而后再经过 AnnotationMethodHandlerAdapter 定位到具体要调用的方法。
[Original] With the new support classes in Spring 3.1, the
RequestMappingHandlerMapping
is the only place where a decision is made about which method should process the request. Think of controller methods as a collection of unique endpoints with mappings for each method derived from type and method-level@RequestMapping
information.
如今有了Spring 3.1后引入的这组新类, RequestMappingHandlerMapping 成为了这两个决策实际发生的惟一一个地方。你能够把控制器中的一系列处理方法当成是一系列独立的服务节点,每一个从类级别和方法级别的 @RequestMapping 注解中获取到足够请求1路径映射信息。
[Original] This enables some new possibilities. For once a
HandlerInterceptor
or aHandlerExceptionResolver
can now expect the Object-based handler to be aHandlerMethod
, which allows them to examine the exact method, its parameters and associated annotations. The processing for a URL no longer needs to be split across different controllers.
这种新的处理方式带来了新的可能性。以前的 HandlerInterceptor 或 HandlerExceptionResolver 如今能够肯定拿到的这个处理器确定是一个 HandlerMethod 类型,所以它可以精确地了解这个方法的全部信息,包括它的参数、应用于其上的注解等。这样,内部对于一个URL的处理流程不再须要分隔到不一样的控制器里面去执行了。
[Original] There are also several things no longer possible: [Original] Select a controller first with a
SimpleUrlHandlerMapping
orBeanNameUrlHandlerMapping
and then narrow the method based on@RequestMapping
annotations. [Original] Rely on method names as a fall-back mechanism to disambiguate between two@RequestMapping
methods that don’t have an explicit path mapping URL path but otherwise match equally, e.g. by HTTP method. In the new support classes@RequestMapping
methods have to be mapped uniquely. [Original] * Have a single default method (without an explicit path mapping) with which requests are processed if no other controller method matches more concretely. In the new support classes if a matching method is not found a 404 error is raised.
同时,也有其余的一些变化,好比有些事情就无法这么玩儿了:
[Original] The above features are still supported with the existing support classes. However to take advantage of new Spring MVC 3.1 features you’ll need to use the new support classes.
若是使用原来的类,以上的功能仍是能够作到。可是,若是要享受Spring MVC 3.1版本带来的方便特性,你就须要去使用新的类。
[Original] ## URI Template Patterns
[Original] URI templates can be used for convenient access to selected parts of a URL in a
@RequestMapping
method.
URI模板能够为快速访问@RequestMapping
中指定的URL的一个特定的部分提供很大的便利。
[Original] A URI Template is a URI-like string, containing one or more variable names. When you substitute values for these variables, the template becomes a URI. The proposed RFC for URI Templates defines how a URI is parameterized. For example, the URI Template
http://www.example.com/users/{userId}
contains the variable userId. Assigning the value fred to the variable yieldshttp://www.example.com/users/fred
.
URI模板是一个相似于URI的字符串,只不过其中包含了一个或多个的变量名。当你使用实际的值去填充这些变量名的时候,模板就退化成了一个URI。在URI模板的RFC提议中定义了一个URI是如何进行参数化的。好比说,一个这个URI模板 http://www.example.com/users/{userId} 就包含了一个变量名userId。将值fred赋给这个变量名后,它就变成了一个URI: http://www.example.com/users/fred 。
[Original] In Spring MVC you can use the
@PathVariable
annotation on a method argument to bind it to the value of a URI template variable:
在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"; }
[Original] The URI Template " /owners/{ownerId} " specifies the variable name
ownerId
. When the controller handles this request, the value ofownerId
is set to the value found in the appropriate part of the URI. For example, when a request comes in for/owners/fred
, the value ofownerId
isfred
.
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模板中的变量名与方法的参数名是相同的,则你能够没必要再指定一次。只要你在编译的时候留下debug信息,Spring MVC就能够自动匹配URL模板中与方法参数名相同的变量名。
@RequestMapping(path="/owners/{ownerId}", method=RequestMethod.GET) public String findOwner(@PathVariable String ownerId, Model model) { // 具体的方法代码… }
[Original] A method can have any number of
@PathVariable
annotations:
一个方法能够拥有任意数量的 @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"; }
[Original] When a @PathVariable annotation is used on a Map<String, String> argument, the map is populated with all URI template variables.
当 @PathVariable 注解被应用于 Map<String, String> 类型的参数上时,框架会使用全部URI模板变量来填充这个map。
[Original] A URI template can be assembled from type and path level @RequestMapping annotations. As a result the
findPet()
method can be invoked with a URL such as/owners/42/pets/21
.
URI模板能够从类级别和方法级别的 @RequestMapping 注解获取数据。所以,像这样的 findPet() 方法能够被相似于 /owners/42/pets/21 这样的URL路由并调用到:
_@Controller_ @RequestMapping("/owners/{ownerId}") public class RelativePathUriTemplateController { @RequestMapping("/pets/{petId}") public void findPet(_@PathVariable_ String ownerId, _@PathVariable_ String petId, Model model) { // 方法实现体这里忽略 } }
[Original] A @PathVariable argument can be of any simple type such as int, long, Date, etc. Spring automatically converts to the appropriate type or throws a TypeMismatchException if it fails to do so. You can also register support for parsing additional data types. See the section called "Method Parameters And Type Conversion" and the section called "Customizing WebDataBinder initialization".
@PathVariable 能够被应用于全部 简单类型 的参数上,好比int、long、Date等类型。Spring会自动地帮你把参数转化成合适的类型,若是转换失败,就抛出一个 TypeMismatchException 。若是你须要处理其余数据类型的转换,也能够注册本身的类。若须要更详细的信息能够参考“方法参数与类型转换”一节和“定制WebDataBinder初始化过程”一节
[Original] Sometimes you need more precision in defining URI template variables. Consider the URL
" /spring-web/spring-web-3.0.5.jar "
. How do you break it down into multiple parts?
有时候你可能须要更准确地描述一个URI模板的变量,好比说这个URL:" /spring-web/spring-web-3.0.5.jar
。你要怎么把它分解成几个有意义的部分呢?
[Original] The @RequestMapping annotation supports the use of regular expressions in URI template variables. The syntax is {varName:regex} where the first part defines the variable name and the second - the regular expression.For example:
@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) { // 代码部分省略... } }
[Original] In addition to URI templates, the @RequestMapping
annotation also supports Ant-style path patterns (for example, /myPath/*.do ). A combination of URI template variables and Ant-style globs is also supported (e.g. /owners/*/pets/{petId} ).
除了URI模板外, @RequestMapping 注解还支持Ant风格的路径模式(如 /myPath/*.do 等)。不只如此,还能够把URI模板变量和Ant风格的glob组合起来使用(好比 /owners/*/pets/{petId} 这样的用法等)。
[Original] When a URL matches multiple patterns, a sort is used to find the most specific match.
当一个URL同时匹配多个模板(pattern)时,咱们将须要一个算法来决定其中最匹配的一个。
[Original] A pattern with a lower count of URI variables and wild cards is considered more specific. For example /hotels/{hotel}/* has 1 URI variable and 1 wild card and is considered more specific than /hotels/{hotel}/** which as 1 URI variable and 2 wild cards.
URI模板变量的数目和通配符数量的总和最少的那个路径模板更准确。举个例子, /hotels/{hotel}/* 这个路径拥有一个URI变量和一个通配符,而 /hotels/{hotel}/** 这个路径则拥有一个URI变量和两个通配符,所以,咱们认为前者是更准确的路径模板。
[Original] If two patterns have the same count, the one that is longer is considered more specific. For example /foo/bar* is longer and considered more specific than /foo/* .
若是两个模板的URI模板数量和通配符数量总和一致,则路径更长的那个模板更准确。举个例子,/foo/bar*
就被认为比/foo/*
更准确,由于前者的路径更长。
[Original] When two patterns have the same count and length, the pattern with fewer wild cards is considered more specific. For example
/hotels/{hotel}
is more specific than/hotels/*
.
若是两个模板的数量和长度均一致,则那个具备更少通配符的模板是更加准确的。好比,/hotels/{hotel}
就比/hotels/*
更精确。
[Original] There are also some additional special rules:
除此以外,还有一些其余的规则:
[Original] The default mapping pattern `/*
is less specific than any other pattern. For example
/api/{a}/{b}/{c}` is more specific.[Original] A prefix pattern such as `/public/*
is less specific than any other pattern that doesn't contain double wildcards. For example
/public/path3/{a}/{b}/{c}` is more specific.
/**
比其余全部的模式都更“不许确”。比方说,/api/{a}/{b}/{c}
就比默认的通配模式/**
要更准确/public/**
)被认为比其余任何不包括双通配符的模式更不许确。好比说,/public/path3/{a}/{b}/{c}
就比/public/**
更准确[Original] For the full details see
AntPatternComparator
inAntPathMatcher
. Note that the PathMatcher can be customized (see Section 21.16.11, "Path Matching" in the section on configuring Spring MVC).
更多的细节请参考这两个类: AntPatternComparator 和 AntPathMatcher 。值得一提的是, PathMatcher 类是能够配置的(见“配置Spring MVC”一节中的21.16.11 路径的匹配一节)。
[Original] Patterns in
@RequestMapping
annotations support ${…} placeholders against local properties and/or system properties and environment variables. This may be useful in cases where the path a controller is mapped to may need to be customized through configuration. For more information on placeholders, see the javadocs of thePropertyPlaceholderConfigurer
class.
@RequestMapping 注解支持在路径中使用占位符,以取得一些本地配置、系统配置、环境变量等。这个特性有时颇有用,好比说控制器的映射路径须要经过配置来定制的场景。若是想了解更多关于占位符的信息,能够参考 PropertyPlaceholderConfigurer 这个类的文档。
[Original] By default Spring MVC performs
" .* "
suffix pattern matching so that a controller mapped to/person
is also implicitly mapped to/person.*
. This makes it easy to request different representations of a resource through the URL path (e.g. /person.pdf, /person.xml ).
Spring MVC默认采用".*"
的后缀模式匹配来进行路径匹配,所以,一个映射到/person
路径的控制器也会隐式地被映射到 /person.* 。这使得经过URL来请求同一资源文件的不一样格式变得更简单(好比 /person.pdf,/person.xml )。
[Original] Suffix pattern matching can be turned off or restricted to a set of path extensions explicitly registered for content negotiation purposes. This is generally recommended to minimize ambiguity with common request mappings such as /person/{id} where a dot might not represent a file extension, e.g. /person/joe@email.com vs /person/joe@email.com.json
)
. Furthermore as explained in the note below suffix pattern matching as well as content negotiation may be used in some circumstances to attempt malicious attacks and there are good reasons to restrict them meaningfully.
你能够关闭默认的后缀模式匹配,或者显式地将路径后缀限定到一些特定格式上for content negotiation purpose。咱们推荐这样作,这样能够减小映射请求时能够带来的一些二义性,好比请求如下路径 /person/{id} 时,路径中的点号后面带的可能不是描述内容格式,好比/person/joe@email.com
vs /person/joe@email.com.json
。并且正以下面立刻要提到的,后缀模式通配以及内容协商有时可能会被黑客用来进行攻击,所以,对后缀通配进行有意义的限定是有好处的。
[Original] See Section 21.16.11, "Path Matching" for suffix pattern matching configuration and also Section 21.16.6, "Content Negotiation" for content negotiation configuration.
关于后缀模式匹配的配置问题,能够参考第21.16.11小节 "路径匹配";关于内容协商的配置问题,能够参考第21.16.6小节 "内容协商"的内容。
[Original] Reflected file download (RFD) attack was first described in a paper by Trustwave in 2014. The attack is similar to XSS in that it relies on input (e.g. query parameter, URI variable) being reflected in the response. However instead of inserting JavaScript into HTML, an RFD attack relies on the browser switching to perform a download and treating the response as an executable script if double-clicked based on the file extension (e.g. .bat, .cmd).
RFD(Reflected file download)攻击最早是2014年在Trustwave的一篇论文中 被提出的。它与XSS攻击有些类似,由于这种攻击方式也依赖于某些特征,即须要你的输入(好比查询参数,URI变量等)等也在输出(response)中 以某种形式出现。不一样的是,RFD攻击并非经过在HTML中写入JavaScript代码进行,而是依赖于浏览器来跳转到下载页面,并把特定格式(比 如.bat,.cmd等)的response当成是可执行脚本,双击它就会执行。
[Original] In Spring MVC @ResponseBody and ResponseEntity methods are at risk because they can render different content types which clients can request including via URL path extensions. Note however that neither disabling suffix pattern matching nor disabling the use of path extensions for content negotiation purposes alone are effective at preventing RFD attacks.
Spring MVC的 @ResponseBody 和 ResponseEntity 方法是有风险的,由于它们会根据客户的请求——包括URL的路径后缀,来渲染不一样的内容类型。所以,禁用后缀模式匹配或者禁用仅为内容协商开启的路径文件后缀名携带,都是防范RFD攻击的有效方式。
[Original] For comprehensive protection against RFD, prior to rendering the response body Spring MVC adds a Content-Disposition:inline;filename=f.txt header to suggest a fixed and safe download file filename. This is done only if the URL path contains a file extension that is neither whitelisted nor explicitly registered for content negotiation purposes. However it may potentially have side effects when URLs are typed directly into a browser.
若要开启对RFD更高级的保护模式,能够在Spring MVC渲染开始请求正文以前,在请求头中增长一行配置 Content-Disposition:inline;filename=f.txt ,指定固定的下载文件的文件名。这仅在URL路径中包含了一个文件符合如下特征的拓展名时适用:该扩展名既不在信任列表(白名单)中,也没有被显式地被注册于内容协商时使用。而且这种作法还能够有一些反作用,好比,当URL是经过浏览器手动输入的时候。
[Original] Many common path extensions are whitelisted by default. Furthermore REST API calls are typically not meant to be used as URLs directly in browsers. Nevertheless applications that use custom HttpMessageConverter implementations can explicitly register file extensions for content negotiation and the Content-Disposition header will not be added for such extensions. See Section 21.16.6, "Content Negotiation".
不少经常使用的路径文件后缀默认是被信任的。另外,REST的API通常是不该该直接用作URL的。不过,你能够本身定制 HttpMessageConverter 的实现,而后显式地注册用于内容协商的文件类型,这种情形下Content-Disposition头将不会被加入到请求头中。详见第21.16.6节中“内容协商”的内容。
[Original] This was originally introduced as part of work for CVE-2015-5211. Below are additional recommendations from the report:
- Encode rather than escape JSON responses. This is also an OWASP XSS recommendation. For an example of how to do that with Spring see spring-jackson-owasp.
- Configure suffix pattern matching to be turned off or restricted to explicitly registered suffixes only.
- Configure content negotiation with the properties "useJaf" and "ignoreUnknownPathExtensions" set to false which would result in a 406 response for URLs with unknown extensions. Note however that this may not be an option if URLs are naturally expected to have a dot towards the end.
- Add
X-Content-Type-Options: nosniff
header to responses. Spring Security 4 does this by default.
感受这节的翻译质量还有限,须要继续了解XSS攻击和RFD攻击的细节再翻。
[Original] The URI specification RFC 3986 defines the possibility of including name-value pairs within path segments. There is no specific term used in the spec. The general "URI path parameters" could be applied although the more unique "Matrix URIs", originating from an old post by Tim Berners-Lee, is also frequently used and fairly well known. Within Spring MVC these are referred to as matrix variables.
原来的URI规范RFC 3986中容许在路径段落中携带键值对,但规范没有明确给这样的键值对定义术语。有人叫“URI路径参数”,也有叫“矩阵URI”的。后者是Tim Berners-Lee首先在其博客中提到的术语,被使用得要更加频繁一些,知名度也更高些。而在Spring MVC中,咱们称这样的键值对为矩阵变量。
[Original] Matrix variables can appear in any path segment, each matrix variable separated with a ";" (semicolon). For example:
"/cars;color=red;year=2012"
. Multiple values may be either "," (comma) separated"color=red,green,blue"
or the variable name may be repeated"color=red;color=green;color=blue"
.
矩阵变量能够在任何路径段落中出现,每对矩阵变量之间使用一个分号“;”隔开。好比这样的URI:" /cars;color=red;year=2012 "
。多个值能够用逗号隔开" color=red,green,blue "
,或者重复变量名屡次" color=red;color=green;color=blue "
。
[Original] If a URL is expected to contain matrix variables, the request mapping pattern must represent them with a URI template. This ensures the request can be matched correctly regardless of whether matrix variables are present or not and in what order they are provided.
若是一个URL有可能须要包含矩阵变量,那么在请求路径的映射配置上就须要使用URI模板来体现这一点。这样才能确保请求能够被正确地映射,而无论矩阵变量在URI中是否出现、出现的次序是怎样等。
[Original] Below is an example of extracting the matrix variable "q":
下面是一个例子,展现了咱们如何从矩阵变量中获取到变量“q”的值:
// GET /pets/42;q=11;r=22 @RequestMapping(path = "/pets/{petId}", method = RequestMethod.GET) public void findPet(@PathVariable String petId, @MatrixVariable int q) { // petId == 42 // q == 11 }
[Original] Since all path segments may contain matrix variables, in some cases you need to be more specific to identify where the variable is expected to be:
因为任意路径段落中均可以含有矩阵变量,在某些场景下,你须要用更精确的信息来指定一个矩阵变量的位置:
// GET /owners/42;q=11/pets/21;q=22 @RequestMapping(path = "/owners/{ownerId}/pets/{petId}", method = RequestMethod.GET) public void findPet( @MatrixVariable(name="q", pathVar="ownerId") int q1, @MatrixVariable(name="q", pathVar="petId") int q2) { // q1 == 11 // q2 == 22 }
[Original] A matrix variable may be defined as optional and a default value specified:
你也能够声明一个矩阵变量不是必须出现的,并给它赋一个默认值:
// GET /pets/42 @RequestMapping(path = "/pets/{petId}", method = RequestMethod.GET) public void findPet(@MatrixVariable(required=false, defaultValue="1") int q) { // q == 1 }
[Original] All matrix variables may be obtained in a Map:
也能够经过一个Map来存储全部的矩阵变量:
// GET /owners/42;q=11;r=12/pets/21;q=22;s=23 @RequestMapping(path = "/owners/{ownerId}/pets/{petId}", method = RequestMethod.GET) public void findPet( @MatrixVariable Map<String, String> matrixVars, @MatrixVariable(pathVar="petId") Map<String, String> petMatrixVars) { // matrixVars: ["q" : [11,22], "r" : 12, "s" : 23] // petMatrixVars: ["q" : 11, "s" : 23] }
[Original] Note that to enable the use of matrix variables, you must set the removeSemicolonContent property of RequestMappingHandlerMapping to false . By default it is set to true .
若是要容许矩阵变量的使用,你必须把 RequestMappingHandlerMapping 类的 removeSemicolonContent 属性设置为 false 。该值默认是 true 的。
[Original] The MVC Java config and the MVC namespace both provide options for enabling the use of matrix variables.
MVC的Java编程配置和命名空间配置都提供了启用矩阵变量的方式。
[Original] If you are using Java config, The Advanced Customizations with MVC Java Config section describes how the
RequestMappingHandlerMapping
can be customized.若是你是使用Java编程的方式,“MVC Java高级定制化配置”一节描述了如何对 RequestMappingHandlerMapping 进行定制。
[Original] In the MVC namespace, the <mvc:annotation-driven> element has an enable-matrix-variables attribute that should be set to
true
. By default it is set tofalse
.而使用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 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>
[Original] You can narrow the primary mapping by specifying a list of consumable media types. The request will be matched only if the Content-Type request header matches the specified media type. For example:
你能够指定一组可消费的媒体类型,缩小映射的范围。这样只有当请求头中 Content-Type 的值与指定可消费的媒体类型中有相同的时候,请求才会被匹配。好比下面这个例子:
@Controller @RequestMapping(path = "/pets", method = RequestMethod.POST, consumes="application/json") public void addPet(@RequestBody Pet pet, Model model) { // 方法实现省略 }
[Original] Consumable media type expressions can also be negated as in !text/plain to match to all requests other than those with Content-Type of text/plain. Also consider using constants provided in
MediaType
such asAPPLICATION_JSON_VALUE
andAPPLICATION_JSON_UTF8_VALUE
.
指定可消费媒体类型的表达式中还能够使用否认,好比,能够使用 !text/plain 来匹配全部请求头 Content-Type 中不含 text/plain 的请求。同时,在MediaType
类中还定义了一些常量,好比 APPLICATION_JSON_VALUE、APPLICATION_JSON_UTF8_VALUE 等,推荐更多地使用它们。
[Original] The consumes condition is supported on the type and on the method level. Unlike most other conditions, when used at the type level, method-level consumable types override rather than extend type-level consumable types.
consumes 属性提供的是方法级的类型支持。与其余属性不一样,当在类型级使用时,方法级的消费类型将覆盖类型级的配置,而非继承关系。
[Original] You can narrow the primary mapping by specifying a list of producible media types. The request will be matched only if the Accept request header matches one of these values. Furthermore, use of the produces condition ensures the actual content type used to generate the response respects the media types specified in the produces condition. For example:
你能够指定一组可生产的媒体类型,缩小映射的范围。这样只有当请求头中 Accept 的值与指定可生产的媒体类型中有相同的时候,请求才会被匹配。并且,使用 produces 条件能够确保用于生成响应(response)的内容与指定的可生产的媒体类型是相同的。举个例子:
@Controller @RequestMapping(path = "/pets/{petId}", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_UTF8_VALUE) @ResponseBody public Pet getPet(@PathVariable String petId, Model model) { // 方法实现省略 }
[Original] Be aware that the media type specified in the produces condition can also optionally specify a character set. For example, in the code snippet above we specify the same media type than the default one configured in
MappingJackson2HttpMessageConverter
, including theUTF-8
charset.要注意的是,经过 condition 条件指定的媒体类型也能够指定字符集。好比在上面的小段代码中,咱们仍是覆写了 MappingJackson2HttpMessageConverter 类中默认配置的媒体类型,同时,还指定了使用
UTF-8
的字符集。[Original] Just like with consumes, producible media type expressions can be negated as in !text/plain to match to all requests other than those with an Accept header value of text/plain. Also consider using constants provided in MediaType such as
APPLICATION_JSON_VALUE
andAPPLICATION_JSON_UTF8_VALUE
.
与 consumes 条件相似,可生产的媒体类型表达式也能够使用否认。好比,能够使用 !text/plain 来匹配全部请求头 Accept 中不含 text/plain 的请求。同时,在 MediaType 类中还定义了一些常量,好比 APPLICATION_JSON_VALUE、APPLICATION_JSON_UTF8_VALUE 等,推荐更多地使用它们。
[Original] The produces condition is supported on the type and on the method level. Unlike most other conditions, when used at the type level, method-level producible types override rather than extend type-level producible types.
produces 属性提供的是方法级的类型支持。与其余属性不一样,当在类型级使用时,方法级的消费类型将覆盖类型级的配置,而非继承关系。
[Original] You can narrow request matching through request parameter conditions such as
"myParam"
,"!myParam"
, or"myParam=myValue"
. The first two test for request parameter presence/absence and the third for a specific parameter value. Here is an example with a request parameter value condition:
你能够筛选请求参数的条件来缩小请求匹配范围,好比"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) { // 实际实现省略 } }
[Original] The same can be done to test for request header presence/absence or to match based on a specific request header value:
一样,你能够用相同的条件来筛选请求头的出现与否,或者筛选出一个具备特定值的请求头:
@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) { // 方法体实现省略 } }
[Original] Although you can match to Content-Type and Accept header values using media type wild cards (for example "content-type=text/*" will match to "text/plain" and "text/html"), it is recommended to use the consumes and produces conditions respectively instead. They are intended specifically for that purpose.
尽管,你能够使用媒体类型的通配符(好比 "content-type=text/*")来匹配请求头 Content-Type和 Accept的值,但咱们更推荐独立使用 consumes和 produces条件来筛选各自的请求。由于它们就是专门为区分这两种不一样的场景而生的。
使用 @RequestMapping 注解的处理方法能够拥有很是灵活的方法签名,它支持的方法参数及返回值类型将在接下来的小节讲述。大多数参数均可以任意的次序出现,除了惟一的一个例外: BindingResult 参数。这在下节也会详细描述。
Spring 3.1中新增了一些类,用以加强注解了 @RequestMapping 的处理方法,分别是 RequestMappingHandlerMapping 类和 RequestMappingHandlerAdapter 类。咱们鼓励使用这组新的类,若是要使用Spring 3.1及之后版本的新特性,这组类甚至是必须使用的。这些加强类在MVC的命名空间配置和MVC的Java编程方式配置中都是默认开启的,若是不是使用这两种方法,那么就须要显式地配置。
下面列出全部支持的方法参数类型:
存 取session可能不是线程安全的,特别是在一个Servlet的运行环境中。若是应用可能有多个请求同时并发存取一个session场景,请考虑将 RequestMappingHandlerAdapter 类中的"synchronizeOnSession"标志设置为"true"。
@RequestPart
注解的参数,提供了对一个"multipart/form-data请求块(request part)内容的存取。更多的信息请参考21.10.5 “处理客户端文件上传的请求”一节和21.10 “Spring对多部分文件上传的支持”一节 java.util.Map/org.springframework.io.Model/org.springframework.ui.ModelMap
类型的参数,用以加强默认暴露给视图层的模型(model)的功能 org.springframework.validation.Errors / org.springframework.validation.BindingResult
验证结果对象,用于存储前面的命令或表单对象的验证结果(紧接其前的第一个方法参数)。在参数列表中, Errors 或 BindingResult 参数必须紧跟在其所绑定的验证对象后面。这是由于,在参数列表中容许有多于一个的模型对象,Spring会为它们建立不一样的 BindingResult 实例。所以,下面这样的代码是不能工做的:
BindingResult与@ModelAttribute错误的参数次序
@RequestMapping(method = RequestMethod.POST) public String processSubmit(@ModelAttribute("pet") Pet pet, Model model, BindingResult result) { ... }
上例中,由于在模型对象Pet
和验证结果对象BindingResult
中间还插了一个Model
参数,这是不行的。要达到预期的效果,必须调整一下参数的次序:
@RequestMapping(method = RequestMethod.POST) public String processSubmit(@ModelAttribute("pet") Pet pet, BindingResult result, Model model) { ... }
对于一些带有
required
属性的注解(好比 @RequestParam、@RequestHeader 等),JDK 1.8的 java.util.Optional 能够做为被它们注解的方法参数。在这种状况下,使用 java.util.Optional 与 required=false 的做用是相同的。
如下是handler方法容许的全部返回类型:
ModelAndView
对象,其中model隐含填充了命令对象,以及注解了@ModelAttribute
字段的存取器被调用所返回的值。Model
对象,其中视图名称默认由RequestToViewNameTranslator
决定,model隐含填充了命令对象以及注解了@ModelAttribute
字段的存取器被调用所返回的值Map
对象,用于暴露model,其中视图名称默认由RequestToViewNameTranslator
决定,model隐含填充了命令对象以及注解了@ModelAttribute
字段的存取器被调用所返回的值View
对象。其中model隐含填充了命令对象,以及注解了@ModelAttribute
字段的存取器被调用所返回的值。handler方法也能够增长一个Model
类型的方法参数来加强modelString
对象,其值会被解析成一个逻辑视图名。其中,model将默认填充了命令对象以及注解了@ModelAttribute
字段的存取器被调用所返回的值。handler方法也能够增长一个Model
类型的方法参数来加强modelvoid
。若是处理器方法中已经对response响应数据进行了处理(好比在方法参数中定义一个ServletResponse
或HttpServletResponse
类型的参数并直接向其响应体中写东西),那么方法能够返回void。handler方法也能够增长一个Model
类型的方法参数来加强modelResponseBody
,那么返回类型将被写到HTTP的响应体中,而返回值会被HttpMessageConverters
转换成所方法声明的参数类型。详见使用"@ResponseBody注解映射响应体"一节HttpEntity<?>
或ResponseEntity<?>
对象,用于提供对Servlet HTTP响应头和响应内容的存取。对象体会被HttpMessageConverters
转换成响应流。详见使用HttpEntity一节HttpHeaders
对象,返回一个不含响应体的responseCallable<?>
对象。当应用但愿异步地返回方法值时使用,这个过程由Spring MVC自身的线程来管理DeferredResult<?>
对象。当应用但愿方法的返回值交由线程自身决定时使用ListenableFuture<?>
对象。当应用但愿方法的返回值交由线程自身决定时使用ResponseBodyEmitter
对象,可用它异步地向响应体中同时写多个对象,also supported as the body within a ResponseEntity
SseEmitter
对象,可用它异步地向响应体中写服务器端事件(Server-Sent Events),also supported as the body within a ResponseEntity
StreamingResponseBody
对象,可用它异步地向响应对象的输出流中写东西。also supported as the body within a ResponseEntity
@ModelAttribute
所注解的字段名(或者以返回类型的类名做为默认的属性名)。model隐含填充了命令对象以及注解了@ModelAttribute
字段的存取器被调用所返回的值你能够使用 @RequestParam 注解将请求参数绑定到你控制器的方法参数上。
下面这段代码展现了它的用法:
@Controller @RequestMapping("/pets") @SessionAttributes("pet") public class EditPetForm { // ... @RequestMapping(method = RequestMapping.GET) public String setupForm(@RequestParam("petId") int petId, ModelMap model) { Pet pet = this.clinic.loadPet(petId); model.addAttribute("pet", pet); return "petForm"; } // ,.. }
若参数使用了该注解,则该参数默认是必须提供的,但你也能够把该参数标注为非必须的:只须要将 @RequestParam 注解的 required 属性设置为 false 便可(好比, @RequestParam(path="id", required=false) )。
若所注解的方法参数类型不是String
,则类型转换会自动地发生。详见"方法参数与类型转换"一节
若 @RequestParam 注解的参数类型是 Map<String, String> 或者 MultiValueMap<String, String> ,则该Map中会自动填充全部的请求参数。
方法参数中的 @RequestBody 注解暗示了方法参数应该被绑定了HTTP请求体的值。举个例子:
@RequestMapping(path = "/something", method = RequestMethod.PUT) public void handle(@RequestBody String body, Writer writer) throws IOException { writer.write(body); }
请求体到方法参数的转换是由 HttpMessageConverter 完成的。 HttpMessageConverter 负责将HTTP请求信息转换成对象,以及将对象转换回一个HTTP响应体。对于 @RequestBody 注解, RequestMappingHandlerAdapter 提供了如下几种默认的 HttpMessageConverter 支持:
关于这些转换器的更多信息,请参考"HTTP信息转换器"一节。另外,若是使用的是MVC命名空间或Java编程的配置方式,会有更多默认注册的消息转换器。更多信息,請參考"启用MVC Java编程配置或MVC XML命令空间配置"一节。
若你更倾向于阅读和编写XML文件,那么你须要配置一个 MarshallingHttpMessageConverter 并为其提供 org.springframework.oxm 包下的一个 Marshaller 和 Unmarshaller 实现。下面的示例就为你展现如何直接在配置文件中配置它。但若是你的应用是使用MVC命令空间或MVC Java编程的方式进行配置的,则请参考"启用MVC Java编程配置或MVC XML命令空间配置"这一节。
<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter"> <property name="messageConverters"> <util:list id="beanList"> <ref bean="stringHttpMessageConverter"/> <ref bean="marshallingHttpMessageConverter"/> </util:list> </property </bean> <bean id="stringHttpMessageConverter" class="org.springframework.http.converter.StringHttpMessageConverter"/> <bean id="marshallingHttpMessageConverter" class="org.springframework.http.converter.xml.MarshallingHttpMessageConverter"> <property name="marshaller" ref="castorMarshaller"/> <property name="unmarshaller" ref="castorMarshaller"/> </bean> <bean id="castorMarshaller" class="org.springframework.oxm.castor.CastorMarshaller"/>
注解了 @RequestBody 的方法参数还能够被 @Valid 注解,这样框架会使用已配置的 Validator 实例来对该参数进行验证。若你的应用是使用MVC命令空间或MVC Java编程的方式配置的,框架会假设在classpath路径下存在一个符合JSR-303规范的验证器,并自动将其做为默认配置。
与 @ModelAttribute 注解的参数同样,Errors
也能够被传入为方法参数,用于检查错误。若是没有声明这样一个参数,那么程序会抛出一个 MethodArgumentNotValidException 异常。该异常默认由 DefaultHandlerExceptionResolver 处理,处理程序会返回一个400
错误给客户端。
关于如何经过MVC命令空间或MVC Java编程的方式配置消息转换器和验证器,也请参考"启用MVC Java编程配置或MVC XML命令空间配置"一节。
@ResponseBody 注解与 @RequestBody 注解相似。 @ResponseBody 注解可被应用于方法上,标志该方法的返回值(更正,原文是return type,看起来应该是返回值)应该被直接写回到HTTP响应体中去(而不会被被放置到Model中或被解释为一个视图名)。举个例子:
@RequestMapping(path = "/something", method = RequestMethod.PUT) @ResponseBody public String helloWorld() { return "Hello World" }
上面的代码结果是文本Hello World
将被写入HTTP的响应流中。
与 @RequestBody 注解相似,Spring使用了一个 HttpMessageConverter 来将返回对象转换到响应体中。关于这些转换器的更多信息,请参考"HTTP信息转换器"一节。
当今让控制器实现一个REST API是很是常见的,这种场景下控制器只须要提供JSON、XML或其余自定义的媒体类型内容便可。你不须要在每一个 @RequestMapping 方法上都增长一个 @ResponseBody 注解,更简明的作法是,给你的控制器加上一个 @RestController 的注解。
@RestController
是一个原生内置的注解,它结合了 @ResponseBody 与 @Controller 注解的功能。不只如此,它也让你的控制器更表义,并且在框架将来的发布版本中,它也可能承载更多的意义。
与普通的 @Controller 无异, @RestController 也能够与 @ControllerAdvicebean 配合使用。更多细节,请见使用@ControllerAdvice辅助控制器。
HttpEntity 与 @RequestBody 和 @ResponseBody 很类似。除了能得到请求体和响应体中的内容以外, HttpEntity (以及专门负责处理响应的 ResponseEntity 子类)还能够存取请求头和响应头,像下面这样:
@RequestMapping("/something") public ResponseEntity<String> handle(HttpEntity<byte[]> requestEntity) throws UnsupportedEncodingException { String requestHeader = requestEntity.getHeaders().getFirst("MyRequestHeader"); byte[] requestBody = requestEntity.getBody(); // do something with request header and body HttpHeaders responseHeaders = new HttpHeaders(); responseHeaders.set("MyResponseHeader", "MyValue"); return new ResponseEntity<String>("Hello World", responseHeaders, HttpStatus.CREATED); }
上面这段示例代码先是获取了 MyRequestHeader 请求头的值,而后读取请求体的主体内容。读完之后往影响头中添加了一个本身的响应头 MyResponseHeader ,而后向响应流中写了字符串Hello World
,最后把响应状态码设置为201(建立成功)。
与 @RequestBody 与 @ResponseBody 注解同样,Spring使用了 HttpMessageConverter 来对请求流和响应流进行转换。关于这些转换器的更多信息,请阅读上一小节以及"HTTP信息转换器"这一节。
@ModelAttribute 注解可被应用在方法或方法参数上。本节将介绍其被注解于方法上时的用法,下节会介绍其被用于注解方法参数的用法。
注解在方法上的 @ModelAttribute 说明了方法的做用是用于添加一个或多个属性到model上。这样的方法能接受与 @RequestMapping 注解相同的参数类型,只不过不能直接被映射到具体的请求上。在同一个控制器中,注解了 @ModelAttribute 的方法实际上会在 @RequestMappin g
方法以前被调用。如下是几个例子:
// Add one attribute // The return value of the method is added to the model under the name "account" // You can customize the name via @ModelAttribute("myAccount") @ModelAttribute public Account addAccount(@RequestParam String number) { return accountManager.findAccount(number); } // Add multiple attributes @ModelAttribute public void populateModel(@RequestParam String number, Model model) { model.addAttribute(accountManager.findAccount(number)); // add more ... }
@ModelAttribute 方法一般被用来填充一些公共须要的属性或数据,好比一个下拉列表所预设的几种状态,或者宠物的几种类型,或者去取得一个HTML表单渲染所须要的命令对象,好比Account
等。
留意 @ModelAttribute 方法的两种风格。在第一种写法中,方法经过返回值的方式默认地将添加一个属性;在第二种写法中,方法接收一个Model
对象,而后能够向其中添加任意数量的属性。你能够在根据须要,在两种风格中选择合适的一种。
一个控制器能够拥有数量不限的 @ModelAttribute 方法。同个控制器内的全部这些方法,都会在 @RequestMapping 方法以前被调用。
@ModelAttribute
方法也能够定义在 @ControllerAdvice 注解的类中,而且这些 @ModelAttribute 能够同时对许多控制器生效。具体的信息能够参考使用@ControllerAdvice辅助控制器。
属性名没有被显式指定的时候又当如何呢?在这种状况下,框架将根据属性的类型给予一个默认名称。举个例子,若方法返回一个
Account
类型的对象,则默认的属性名为"account"。你能够经过设置 @ModelAttribute 注解的值来改变默认值。当向Model
中直接添加属性时,请使用合适的重载方法 addAttribute(..) -即,带或不带属性名的方法。
@ModelAttribute 注解也能够被用在 @RequestMapping 方法上。这种状况下,@RequestMapping
方法的返回值将会被解释为model的一个属性,而非一个视图名。此时视图名将以视图命名约定来方式来决议,与返回值为void的方法所采用的处理方法相似——请见视图:请求与视图名的对应。
如上一小节所解释, @ModelAttribute 注解既能够被用在方法上,也能够被用在方法参数上。这一小节将介绍它注解在方法参数上时的用法。
注解在方法参数上的 @ModelAttribute 说 明了该方法参数的值将由model中取得。若是model中找不到,那么该参数会先被实例化,而后被添加到model中。在model中存在之后,请求中 全部名称匹配的参数都会填充到该参数中。这在Spring MVC中被称为数据绑定,一个很是有用的特性,节约了你每次都须要手动从表格数据中转换这些字段数据的时间。
@RequestMapping(path = "/owners/{ownerId}/pets/{petId}/edit", method = RequestMethod.POST) public String processSubmit(@ModelAttribute Pet pet) { }
以上面的代码为例,这个Pet类型的实例可能来自哪里呢?有几种可能:
@ModelAttribute 方法经常使用于从数据库中取一个属性值,该值可能经过 @SessionAttributes 注解在请求中间传递。在一些状况下,使用URI模板变量和类型转换的方式来取得一个属性是更方便的方式。这里有个例子:
@RequestMapping(path = "/accounts/{account}", method = RequestMethod.PUT) public String save(@ModelAttribute("account") Account account) { }
上面这个例子中,model属性的名称("account")与URI模板变量的名称相匹配。若是你配置了一个能够将String
类型的帐户值转换成Account
类型实例的转换器 Converter<String, Account> ,那么上面这段代码就能够工做的很好,而不须要再额外写一个 @ModelAttribute 方法。
下一步就是数据的绑定。 WebDataBinder 类能将请求参数——包括字符串的查询参数和表单字段等——经过名称匹配到model的属性上。成功匹配的字段在须要的时候会进行一次类型转换(从String类型到目标字段的类型),而后被填充到model对应的属性中。数据绑定和数据验证的问题在第8章 验证,数据绑定和类型转换中提到。如何在控制器层来定制数据绑定的过程,在这一节 "定制WebDataBinder的初始化"中说起。
进行了数据绑定后,则可能会出现一些错误,好比没有提供必须的字段、类型转换过程的错误等。若想检查这些错误,能够在注解了 @ModelAttribute 的参数紧跟着声明一个 BindingResult 参数:
@RequestMapping(path = "/owners/{ownerId}/pets/{petId}/edit", method = RequestMethod.POST) public String processSubmit(@ModelAttribute("pet") Pet pet, BindingResult result) { if (result.hasErrors()) { return "petForm"; } // ... }
拿到 BindingResult 参数后,你能够检查是否有错误。有时你能够经过Spring的 <errors> 表单标签来在同一个表单上显示错误信息。
BindingResult 被用于记录数据绑定过程的错误,所以除了数据绑定外,你还能够把该对象传给本身定制的验证器来调用验证。这使得数据绑定过程和验证过程出现的错误能够被搜集到一处,而后一并返回给用户:
@RequestMapping(path = "/owners/{ownerId}/pets/{petId}/edit", method = RequestMethod.POST) public String processSubmit(@ModelAttribute("pet") Pet pet, BindingResult result) { new PetValidator().validate(pet, result); if (result.hasErrors()) { return "petForm"; } // ... }
又或者,你能够经过添加一个JSR-303规范的 @Valid 注解,这样验证器会自动被调用。
@RequestMapping(path = "/owners/{ownerId}/pets/{petId}/edit", method = RequestMethod.POST) public String processSubmit(@Valid @ModelAttribute("pet") Pet pet, BindingResult result) { if (result.hasErrors()) { return "petForm"; } // ... }
关于如何配置并使用验证,能够参考第8.8小节 "Spring验证"和第8章 验证,数据绑定和类型转换。
类型级别的 @SessionAttributes 注解声明了某个特定处理器所使用的会话属性。一般它会列出该类型但愿存储到 session 或 converstaion 中的model属性名或model的类型名,通常是用于在请求之间保存一些表单数据的 bean 。
如下的代码段演示了该注解的用法,它指定了模型属性的名称
@Controller @RequestMapping("/editPet.do") @SessionAttributes("pet") public class EditPetForm { // ... }
上一小节讲述了如何使用 @ModelAttribute 支 持客户端浏览器的屡次表单提交请求。对于不是使用的浏览器的客户端,咱们也推荐使用这个注解来处理请求。但当请求是一个HTTP PUT方法的请求时,有一个事情须要注意。浏览器能够经过HTTP的GET方法或POST方法来提交表单数据,非浏览器的客户端还能够经过HTTP的 PUT方法来提交表单。这就设计是个挑战,由于在Servlet规范中明确规定, ServletRequest.getParameter*() 系列的方法只能支持经过HTTP POST方法的方式提交表单,而不支持HTTP PUT的方式。
为了支持HTTP的PUT类型和PATCH类型的请求,Spring的spring-web
模块提供了一个过滤器 HttpPutFormContentFilter 。你能够在web.xml
文件中配置它:
<filter> <filter-name>httpPutFormFilter</filter-name> <filter-class>org.springframework.web.filter.HttpPutFormContentFilter</filter-class> </filter> <filter-mapping> <filter-name>httpPutFormFilter</filter-name> <servlet-name>dispatcherServlet</servlet-name> </filter-mapping> <servlet> <servlet-name>dispatcherServlet</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> </servlet>
上面的过滤器将会拦截内容类型(content type)为 application/x-www-form-urlencoded 、HTTP方法为PUT或PATCH类型的请求,而后从请求体中读取表单数据,把它们包装在 ServletRequest 中。这是为了使表单数据可以经过 ServletRequest.getParameter*() 系列的方法来拿到。
由于 HttpPutFormContentFilter 会消费请求体的内容,所以,它不该该用于处理那些依赖于其余 application/x-www-form-urlencoded 转换器的PUT和PATCH请求,这包括了 @RequestBodyMultiValueMap<String, String> 和 HttpEntity<MultiValueMap<String, String>> 。
@CookieValue 注解能将一个方法参数与一个HTTP cookie的值进行绑定。
看一个这样的场景:如下的这个cookie存储在一个HTTP请求中:
JSESSIONID=415A4AC178C59DACE0B2C9CA727CDD84
下面的代码演示了拿到JSESSIONID
这个cookie值的方法:
@RequestMapping("/displayHeaderInfo.do") public void displayHeaderInfo(@CookieValue("JSESSIONID") String cookie) { //... }
若注解的目标方法参数不是String
类型,则类型转换会自动进行。详见"方法参数与类型转换"一节。
这个注解能够注解处处理器方法上,在Servlet环境和Portlet环境都能使用。
@RequestHeader
注解映射请求头属性@RequestHeader 注解能将一个方法参数与一个请求头属性进行绑定。
如下是一个请求头的例子:
Host localhost:8080 Accept text/html,application/xhtml+xml,application/xml;q=0.9 Accept-Language fr,en-gb;q=0.7,en;q=0.3 Accept-Encoding gzip,deflate Accept-Charset ISO-8859-1,utf-8;q=0.7,*;q=0.7 Keep-Alive 300
如下的代码片断展现了如何取得Accept-Encoding
请求头和Keep-Alive
请求头的值:
@RequestMapping("/displayHeaderInfo.do") public void displayHeaderInfo(@RequestHeader("Accept-Encoding") String encoding, @RequestHeader("Keep-Alive") long keepAlive) { //... }
若注解的目标方法参数不是String
类型,则类型转换会自动进行。"方法参数与类型转换"一节。
若是 @RequestHeader 注解应用在 Map<String, String>、MultiValueMap<String, String> 或 HttpHeaders 类型的参数上,那么全部的请求头属性值都会被填充到map中。
Spring内置支持将一个逗号分隔的字符串(或其余类型转换系统所能识别的类型)转换成一个String类型的列表/集合。举个例子,一个注解了 @RequestHeader("Accept") 的方法参数能够是一个
String
类型,但也能够是String[]
或List<String>
类型的。
这个注解能够注解处处理器方法上,在Servlet环境和Portlet环境都能使用。
从请求参数、路径变量、请求头属性或者cookie中抽取出来的String
类型的值,可能须要被转换成其所绑定的目标方法参数或字段的类型(好比,经过 @ModelAttribute 将请求参数绑定到方法参数上)。若是目标类型不是String
,Spring会自动进行类型转换。全部的简单类型诸如int、long、Date都有内置的支持。若是想进一步定制这个转换过程,你能够经过 WebDataBinder (详见"定制WebDataBinder的初始化"一节),或者为 Formatters 配置一个 FormattingConversionService (详见8.6节 "Spring字段格式化"一节)来作到。
若是想经过Spring的 WebDataBinder 在属性编辑器中作请求参数的绑定,你能够使用在控制器内使用 @InitBinder @InitBinder 注解的方法、在注解了 @ControllerAdvice 的类中使用 @InitBinder 注解的方法,或者提供一个定制的 WebBindingInitializer 。更多的细节,请参考使用@ControllerAdvice辅助控制器一节。
使用 @InitBinder 注解控制器的方法,你能够直接在你的控制器类中定制应用的数据绑定。 @InitBinder 用来标记一些方法,这些方法会初始化一个WebDataBinder
并用觉得处理器方法填充命令对象和表单对象的参数。
除了命令/表单对象以及相应的验证结果对象,这样的“绑定器初始化”方法可以接收 @RequestMapping 所支持的全部参数类型。“绑定器初始化”方法不能有返回值,所以,通常将它们声明为void
返回类型。特别地,当 WebDataBinder 与 WebRequest 或 java.util.Locale 一块儿做为方法参数时,你能够在代码中注册上下文相关的编辑器。
下面的代码示例演示了如何使用 @InitBinder 来配置一个 CustomerDateEditor ,后者会对全部 java.util.Date 类型的表单字段进行操做:
@Controller public class MyFormController { @InitBinder public void initBinder(WebDataBinder binder) { SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd"); dateFormat.setLenient(false); binder.registerCustomEditor(Date.class, new CustomDateEditor(dateFormat, false)); } // ... }
或者,你能够使用Spring 4.2提供的 addCustomFormatter 来指定 Formatter 的实现,而非经过 PropertyEditor 实例。这在你拥有一个须要Formatter
的setup方法,而且该方法位于一个共享的 FormattingConversionService中 时很是有用。这样对于控制器级别的绑定规则的定制,代码更容易被复用。
@Controller public class MyFormController { @InitBinder public void initBinder(WebDataBinder binder) { binder.addCustomFormatter(new DateFormatter("yyyy-MM-dd")); } // ... }
为了externalize数据绑定的初始化过程,你能够为 WebBindingInitializer 接口提供一个本身的实现,在其中你能够为 AnnotationMethodHandlerAdapter 提供一个默认的配置 bean ,以此来覆写默认的配置。
如下的代码来自PetClinic的应用,它展现了为 WebBindingInitializer 接口提供一个自定义实现: org.springframework.samples.petclinic.web.ClinicBindingInitializer org.springframework.samples.petclinic.web.ClinicBindingInitializer 完整的配置过程。后者中配置了PetClinic应用中许多控制器所须要的属性编辑器PropertyEditors。
<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter"> <property name="cacheSeconds" value="0"/> <property name="webBindingInitializer"> <bean class="org.springframework.samples.petclinic.web.ClinicBindingInitializer"/> </property> </bean>
@InitBinder
方法也能够定义在 @ControllerAdvice 注解的类上,这样配置能够为许多控制器所共享。这提供了除使用 WebBindingInitializer 外的另一种方法。更多细节请参考使用@ControllerAdvice辅助控制器一节。
@ControllerAdvice 是一个组件注解,它使得其实现类可以被classpath扫描自动发现。若应用是经过MVC命令空间或MVC Java编程方式配置,那么该特性默认是自动开启的。
注解 @ControllerAdvice 的类能够拥有 @ExceptionHandler、@InitBinder 或 @ModelAttribute 注解的方法,而且这些方法会被应用至控制器类层次??的全部 @RequestMapping 方法上。
你也能够经过@ControllerAdvice
的属性来指定其只对一个子集的控制器生效:
// Target all Controllers annotated with @RestController @ControllerAdvice(annotations = RestController.class) public class AnnotationAdvice {} // Target all Controllers within specific packages @ControllerAdvice("org.example.controllers") public class BasePackageAdvice {} // Target all Controllers assignable to specific classes @ControllerAdvice(assignableTypes = {ControllerInterface.class, AbstractController.class}) public class AssignableTypesAdvice {}
更多的细节,请查阅@ControllerAdvice
的文档。
下面两节,还看不太懂,待译。
It can sometimes be useful to filter contextually the object that will be serialized to the HTTP response body. In order to provide such capability, Spring MVC has built-in support for rendering with Jackson's Serialization Views.
To use it with an @ResponseBody
controller method or controller methods that return ResponseEntity , simply add the @JsonView
annotation with a class argument specifying the view class or interface to be used:
_@RestController_ public class UserController { _@RequestMapping(path = "/user", method = RequestMethod.GET)_ _@JsonView(User.WithoutPasswordView.class)_ public User getUser() { return new User("eric", "7!jd#h23"); } } public class User { public interface WithoutPasswordView {}; public interface WithPasswordView extends WithoutPasswordView {}; private String username; private String password; public User() { } public User(String username, String password) { this.username = username; this.password = password; } _@JsonView(WithoutPasswordView.class)_ public String getUsername() { return this.username; } _@JsonView(WithPasswordView.class)_ public String getPassword() { return this.password; } }
![]() |
Note |
---|---|
Note that despite @JsonView allowing for more than one class to be specified, the use on a controller method is only supported with exactly one class argument. Consider the use of a composite interface if you need to enable multiple views.
For controllers relying on view resolution, simply add the serialization view class to the model:
_@Controller_ public class UserController extends AbstractController { _@RequestMapping(path = "/user", method = RequestMethod.GET)_ public String getUser(Model model) { model.addAttribute("user", new User("eric", "7!jd#h23")); model.addAttribute(JsonView.class.getName(), User.WithoutPasswordView.class); return "userView"; } }
In order to enable JSONP support for @ResponseBody and ResponseEntity methods, declare an @ControllerAdvice bean that extends AbstractJsonpResponseBodyAdvice as shown below where the constructor argument indicates the JSONP query parameter name(s):
_@ControllerAdvice_ public class JsonpAdvice extends AbstractJsonpResponseBodyAdvice { public JsonpAdvice() { super("callback"); } }
For controllers relying on view resolution, JSONP is automatically enabled when the request has a query parameter named jsonp or callback . Those names can be customized through jsonpParameterNames property.
Spring MVC 3.2开始引入了基于Servlet 3的异步请求处理。相比之前,控制器方法已经不必定须要返回一个值,而是能够返回一个 java.util.concurrent.Callable 的对象,并经过Spring MVC所管理的线程来产生返回值。与此同时,Servlet容器的主线程则能够退出并释放其资源了,同时也容许容器去处理其余的请求。经过一个 TaskExecutor ,Spring MVC能够在另外的线程中调用 Callable 。当 Callable 返回时,请求再携带 Callable 返回的值,再次被分配到Servlet容器中恢复处理流程。如下代码给出了一个这样的控制器方法做为例子:
@RequestMapping(method=RequestMethod.POST) public Callable<String> processUpload(final MultipartFile file) { return new Callable<String>() { public String call() throws Exception { // ... return "someView"; } }; }
另外一个选择,是让控制器方法返回一个 DeferredResult 的实例。这种场景下,返回值能够由任何一个线程产生,也包括那些不是由Spring MVC管理的线程。举个例子,返回值多是为了响应某些外部事件所产生的,好比一条JMS的消息,一个计划任务,等等。如下代码给出了一个这样的控制器做为例子:
@RequestMapping("/quotes") @ResponseBody public DeferredResult<String> quotes() { DeferredResult<String> deferredResult = new DeferredResult<String>(); // Save the deferredResult somewhere.. return deferredResult; } // In some other thread... deferredResult.setResult(data);
若是对Servlet 3.0的异步请求处理特性没有了解,理解这个特性可能会有点困难。所以,阅读一下前者的文档将会颇有帮助。如下给出了这个机制运做背后的一些原理:
有了上面的知识,下面能够来看一下 Callable 的异步请求被处理时所依次发生的事件:
对 DeferredResult 异步请求的处理顺序也很是相似,区别仅在于应用能够经过任何线程来计算返回一个结果:
关于引入异步请求处理的背景和缘由,以及何时使用它、为何使用异步请求处理等问题,你能够从这个系列的博客中了解更多信息。
若控制器返回的 Callable 在执行过程当中抛出了异常,又会发生什么事情?简单来讲,这与通常的控制器方法抛出异常是同样的。它会被正常的异常处理流程捕获处理。更具体地说呢,当 Callable 抛出异常时,Spring MVC会把一个 Exception 对象分派给Servlet容器进行处理,而不是正常返回方法的返回值,而后容器恢复对此异步请求异常的处理。若方法返回的是一个 DeferredResult 对象,你能够选择调 Exception 实例的 setResult 方法仍是 setErrorResult 方法。
处理器拦截器 HandlerInterceptor 能够实现 AsyncHandlerInterceptor 接口拦截异步请求,由于在异步请求开始时,被调用的回调方法是该接口的 afterConcurrentHandlingStarted 方法,而非通常的 postHandle 和 afterCompletion 方法。
若是须要与异步请求处理的生命流程有更深刻的集成,好比须要处理timeout的事件等,则 HandlerInterceptor 须要注册一个 CallableProcessingInterceptor 或 DeferredResultProcessingInterceptor 拦截器。具体的细节能够参考 AsyncHandlerInterceptor 类的Java文档。
DeferredResult 类还提供了 onTimeout(Runnable) 和 onCompletion(Runnable) 等方法,具体的细节能够参考 DeferredResult 类的Java文档。
Callable 须要请求过时(timeout)和完成后的拦截时,能够把它包装在一个 WebAsyncTask 实例中,后者提供了相关的支持。
如前所述,控制器能够使用 DeferredResult 或 Callable 对象来异步地计算其返回值,这能够用于实现一些有用的技术,好比 long polling技术,让服务器能够尽量快地向客户端推送事件。
若是你想在一个HTTP响应中同时推送多个事件,怎么办?这样的技术已经存在,与"Long Polling"相关,叫"HTTP Streaming"。Spring MVC支持这项技术,你能够经过让方法返回一个 ResponseBodyEmitte r
类型对象来实现,该对象可被用于发送多个对象。一般咱们所使用的 @ResponseBody 只能返回一个对象,它是经过 HttpMessageConverter 写到响应体中的。
下面是一个实现该技术的例子:
@RequestMapping("/events") public ResponseBodyEmitter handle() { ResponseBodyEmitter emitter = new ResponseBodyEmitter(); // Save the emitter somewhere.. return emitter; } // In some other thread emitter.send("Hello once"); // and again later on emitter.send("Hello again"); // and done at some point emitter.complete();
ResponseBodyEmitter 也能够被放到 ResponseEntity 体里面使用,这能够对响应状态和响应头作一些定制。
Note that ResponseBodyEmitter
can also be used as the body in a ResponseEntity
in order to customize the status and headers of the response.
SseEmitter 是 ResponseBodyEmitter 的一个子类,提供了对服务器端事件推送的技术的支持。服务器端事件推送其实只是一种HTTP Streaming的相似实现,只不过它服务器端所推送的事件遵循了W3C Server-Sent Events规范中定义的事件格式。
“服务器端事件推送”技术正如其名,是用于由服务器端向客户端进行的事件推送。这在Spring MVC中很容易作到,只须要方法返回一个 SseEmitter 类型的对象便可。
需 要注意的是,Internet Explorer并不支持这项服务器端事件推送的技术。另外,对于更大型的web应用及更精致的消息传输场景——好比在线游戏、在线协做、金融应用等—— 来讲,使用Spring的WebSocket(包含SockJS风格的实时WebSocket)更成熟一些,由于它支持的浏览器范围很是广(包括IE), 而且,对于一个以消息为中心的架构中,它为服务器端-客户端间的事件发布-订阅模型的交互提供了更高层级的消息模式(messaging patterns)的支持。
ResponseBodyEmitter ResponseBodyEmitter 也容许经过 HttpMessageConverter 向响应体中支持写事件对象。这多是最多见的情形,好比写返回的JSON数据的时候。但有时,跳过消息转换的阶段,直接把数据写回响应的输出流 OutputStream 可能更有效,好比文件下载这样的场景。这能够经过返回一个 StreamingResponseBody 类型的对象来实现。
如下是一个实现的例子:
@RequestMapping("/download") public StreamingResponseBody handle() { return new StreamingResponseBody() { @Override public void writeTo(OutputStream outputStream) throws IOException { // write... } }; }
ResponseBodyEmitter 也能够被放到 ResponseEntity 体里面使用,这能够对响应状态和响应头作一些定制。
对于那些使用web.xml
配置文件的应用,请确保 web.xml 的版本更新到3.0:
<web-app xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" version="3.0"> ... </web-app>
异步请求必须在 web.xml 将 DispatcherServlet 下的子元素 <async-supported>true</async-supported> 设置为true。此外,全部可能参与异步请求处理的过滤器 Filter 都必须配置为支持ASYNC类型的请求分派。在Spring框架中为过滤器启用支持ASYNC类型的请求分派应是安全的,由于这些过滤器通常都继承了基类 OncePerRequestFilter ,后者在运行时会检查该过滤器是否须要参与到异步分派的请求处理中。
如下是一个例子,展现了web.xml
的配置:
<web-app xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation=" http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" version="3.0"> <filter> <filter-name>Spring OpenEntityManagerInViewFilter</filter-name> <filter-class>org.springframework.~.OpenEntityManagerInViewFilter</filter-class> <async-supported>true</async-supported> </filter> <filter-mapping> <filter-name>Spring OpenEntityManagerInViewFilter</filter-name> <url-pattern>/*</url-pattern> <dispatcher>REQUEST</dispatcher> <dispatcher>ASYNC</dispatcher> </filter-mapping> </web-app>
若是应用使用的是Servlet 3规范基于Java编程的配置方式,好比经过 WebApplicationInitializer ,那么你也须要设置"asyncSupported"标志和ASYNC分派类型的支持,就像你在 web.xml 中所配置的同样。你能够考虑直接继承 AbstractDispatcherServletInitializer 或 AbstractAnnotationConfigDispatcherServletInitializer 来简化配置,它们都自动地为你设置了这些配置项,并使得注册 Filter 过滤器实例变得很是简单。
MVC Java编程配置和MVC命名空间配置方式都提供了配置异步请求处理支持的选择。 WebMvcConfigurer 提供了 configureAsyncSupport 方法,而 <mvc:annotation-driven> 有一个子元素 <async-support> ,它们都用觉得此提供支持。
这些配置容许你覆写异步请求默认的超时时间,在未显式设置时,它们的值与所依赖的Servlet容器是相关的(好比,Tomcat设置的超时时间是10秒)。你也能够配置用于执行控制器返回值 Callable 的执行器 AsyncTaskExecutor 。Spring强烈推荐你配置这个选项,由于Spring MVC默认使用的是普通的执行器 SimpleAsyncTaskExecutor 。MVC Java编程配置及MVC命名空间配置的方式都容许你注册本身的 CallableProcessingInterceptor 和 DeferredResultProcessingInterceptor 拦截器实例。
若你须要为特定的 DeferredResult 覆写默认的超时时间,你能够选用合适的构造方法来实现。相似,对于 Callable 返回,你能够把它包装在一个 WebAsyncTask 对象中,并使用合适的构造方法定义超时时间。 WebAsyncTask 类的构造方法同时也能接受一个任务执行器 AsyncTaskExecutor 类型的参数。
spring-test
模块对测试控制器 @Controller 提供了最原生的支持。详见14.6 "Spring MVC测试框架"一节。
在Spring的上个版本中,用户须要在web应用的上下文中定义一个或多个的 HandlerMappingbean ,用以将进入容器的web请求映射到合适的处理器方法上。容许在控制器上添加注解后,一般你就没必要这么作了,由于 RequestMappingHandlerMapping 类会自动查找全部注解了 @RequestMapping 的 @Controller 控制器bean。同时也请知道,全部继承自 AbstractHandlerMapping 的处理器方法映射 HandlerMapping 类都拥有下列的属性,你能够对它们进行定制:
order
,根据order(见 org.springframework.core.Ordered 接口)属性的值,Spring会对上下文可用的全部处理器映射进行排序,并应用第一个匹配成功的处理器/testing/*
,而且 alwaysUseFullPath 属性被设置为 true ,此时用于查找处理器的路径将是/testing/viewPage.html
;而若 alwaysUseFullPath 属性的值为 false ,则此时查找路径是/viewPage.html
下面的代码展现了配置一个拦截器的方法:
<beans> <bean id="handlerMapping" class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping"> <property name="interceptors"> <bean class="example.MyInterceptor"/> </property> </bean> <beans>
Spring的处理器映射机制包含了处理器拦截器。拦截器在你须要为特定类型的请求应用一些功能时可能颇有用,好比,检查用户身份等。
处理器映射处理过程配置的拦截器,必须实现 org.springframework.web.servlet 包下的 HandlerInterceptor 接口。这个接口定义了三个方法: preHandle(..) ,它在处理器实际执行 以前 会被执行; postHandle(..) ,它在处理器执行 完毕 之后被执行; afterCompletion(..) ,它在 整个请求处理完成 以后被执行。这三个方法为各类类型的前处理和后处理需求提供了足够的灵活性。
preHandle(..) 方法返回一个 boolean 值。你能够经过这个方法来决定是否继续执行处理链中的部件。当方法返回 true 时,处理器链会继续执行;若方法返回 false , DispatcherServlet 即认为拦截器自身已经完成了对请求的处理(好比说,已经渲染了一个合适的视图),那么其他的拦截器以及执行链中的其余处理器就不会再被执行了。
拦截器能够经过 interceptors 属性来配置,该选项在全部继承了 AbstractHandlerMapping 的处理器映射类 HandlerMapping 都提供了配置的接口。以下面代码样例所示:
<beans> <bean id="handlerMapping" class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping"> <property name="interceptors"> <list> <ref bean="officeHoursInterceptor"/> </list> </property> </bean> <bean id="officeHoursInterceptor" class="samples.TimeBasedAccessInterceptor"> <property name="openingTime" value="9"/> <property name="closingTime" value="18"/> </bean> <beans>
package samples; public class TimeBasedAccessInterceptor extends HandlerInterceptorAdapter { private int openingTime; private int closingTime; public void setOpeningTime(int openingTime) { this.openingTime = openingTime; } public void setClosingTime(int closingTime) { this.closingTime = closingTime; } public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { Calendar cal = Calendar.getInstance(); int hour = cal.get(HOUR_OF_DAY); if (openingTime <= hour && hour < closingTime) { return true; } response.sendRedirect("http://host.com/outsideOfficeHours.html"); return false; } }
在上面的例子中,全部被此处理器处理的请求都会被 TimeBasedAccessInterceptor 拦截器拦截。若是当前时间在工做时间之外,那么用户就会被重定向到一个HTML文件提示用户,好比显示“你只有在工做时间才能够访问本网站”之类的信息。
使用 RequestMappingHandlerMapping 时,实际的处理器是一个处理器方法 HandlerMethod 的实例,它标识了一个将被用于处理该请求的控制器方法。
如你所见,Spring的拦截器适配器 HandlerInterceptorAdapter 让继承 HandlerInterceptor 接口变得更简单了。
上面的例子中,全部控制器方法处理的请求都会被配置的拦截器先拦截到。若是你想进一步缩小拦截的URL范围,你能够经过MVC命名空间或MVC Java编程的方式来配置,或者,声明一个 MappedInterceptor 类型的bean实例来处理。具体请见 21.16.1 启用MVC Java编程配置或MVC命名空间配置一小节。
须要注意的是, HandlerInterceptor 的后拦截 postHandle 方法不必定老是适用于注解了 @ResponseBody 或 ResponseEntity 的方法。这些场景中, HttpMessageConverter 会在拦截器的 postHandle 方法被调以前就把信息写回响应中。这样拦截器就没法再改变响应了,好比要增长一个响应头之类的。若是有这种需求,请让你的应用实现 ResponseBodyAdvice 接口,并将其定义为一个 @ControllerAdvicebean 或直接在 RequestMappingHandlerMapping 中配置。
所 有web应用的MVC框架都提供了视图相关的支持。Spring提供了一些视图解析器,它们让你可以在浏览器中渲染模型,并支持你自由选用适合的视图技术 而没必要与框架绑定到一块儿。Spring原生支持JSP视图技术、Velocity模板技术和XSLT视图等。你能够阅读文档的第22章 视图技术一章,里面讨论了如何集成并使用许多独立的视图技术。
有两个接口在Spring处理视图相关事宜时相当重要,分别是视图解析器接口 ViewResolver 和视图接口自己View
。视图解析器 ViewResolver 负责处理视图名与实际视图之间的映射关系。视图接口View
负责准备请求,并将请求的渲染交给某种具体的视图技术实现。
正如在21.3 控制器的实现一节中所讨论的,Spring MVC中全部控制器的处理器方法都必须返回一个逻辑视图的名字,不管是显式返回(好比返回一个 String、View 或者 ModelAndView )仍是隐式返回(好比基于约定的返回)。Spring中的视图由一个视图名标识,并由视图解析器来渲染。Spring有很是多内置的视图解析器。下表列出了大部分,表后也给出了一些例子。
表21.3 视图解析器
视图解析器 | 描述 |
---|---|
AbstractCachingViewResolver |
一个抽象的视图解析器类,提供了缓存视图的功能。一般视图在可以被使用以前须要通过准备。继承这个基类的视图解析器便可以得到缓存视图的能力。 |
XmlViewResolver |
视图解析器接口 ViewResolver 的一个实现,该类接受一个XML格式的配置文件。该XML文件必须与Spring XML的bean工厂有相同的DTD。默认的配置文件名是 /WEB-INF/views.xml |
ResourceBundleViewResolver |
视图解析器接口 ViewResolver 的一个实现,采用bundle根路径所指定的 ResourceBundle 中的 bean 定义做为配置。通常bundle都定义在classpath路径下的一个配置文件中。默认的配置文件名为 views.properties 。 |
UrlBasedViewResolver |
ViewResolver 接口的一个简单实现。它直接使用URL来解析到逻辑视图名,除此以外不须要其余任何显式的映射声明。若是你的逻辑视图名与你真正的视图资源名是直接对应的,那么这种直接解析的方式就很方便,不须要你再指定额外的映射。 |
InternalResourceViewResolver |
UrlBasedViewResolver 的一个好用的子类。它支持内部资源视图(具体来讲,Servlet和JSP)、以及诸如 JstlView和TilesView 等类的子类。You can specify the view class for all views generated by this resolver by using setViewClass(..) 。更多的细节,请见 UrlBasedViewResolver 类的java文档。 |
VelocityViewResolver / FreeMarkerViewResolver
|
UrlBasedViewResolver 下的实用子类,支持 Velocity 视图 VelocityView (Velocity模板)和 FreeMarker 视图 FreeMarkerView 以及它们对应子类。 |
ContentNegotiatingViewResolver | 视图解析器接口 ViewResolver 的一个实现,它会根据所请求的文件名或请求的Accept 头来解析一个视图。更多细节请见21.5.4 内容协商视图解析器"ContentNegotiatingViewResolver"一小节。 |
咱们能够举个例子,假设这里使用的是JSP视图技术,那么咱们能够使用一个基于URL的视图解析器 UrlBasedViewResolver 。这个视图解析器会将URL解析成一个视图名,并将请求转交给请求分发器来进行视图渲染。
<bean id="viewResolver" class="org.springframework.web.servlet.view.UrlBasedViewResolver"> <property name="viewClass" value="org.springframework.web.servlet.view.JstlView"/> <property name="prefix" value="/WEB-INF/jsp/"/> <property name="suffix" value=".jsp"/> </bean>
若返回一个test
逻辑视图名,那么该视图解析器会将请求转发到 RequestDispatcher ,后者会将请求交给 /WEB-INF/jsp/test.jsp 视图去渲染。
若是须要在应用中使用多种不一样的视图技术,你能够使用 ResourceBundleViewResolver :
<bean id="viewResolver" class="org.springframework.web.servlet.view.ResourceBundleViewResolver"> <property name="basename" value="views"/> <property name="defaultParentView" value="parentView"/> </bean>
ResourceBundleViewResolver 会检索由bundle根路径下所配置的 ResourceBundle ,对于每一个视图而言,其视图类由[viewname].(class)
属性的值指定,其视图url由[viewname].url
属性的值指定。下一节将详细讲解视图技术,你能够在那里找到更多例子。你还能够看到,视图还容许有基视图,即properties文件中全部视图都“继承”的一个文件。经过继承技术,你能够为众多视图指定一个默认的视图基类。
AbstractCachingViewResolver 的子类可以缓存已经解析过的视图实例。关闭缓存特性也是能够的,只须要将
cache
属性设置为false
便可。另外,若是实在须要在运行时刷新某个视图(好比修改了Velocity模板时),你能够使用 removeFromCache(String viewName, Locale loc) 方法。`
Spring支持同时使用多个视图解析器。所以,你能够配置一个解析器链,并作更多的事好比,在特定条件下覆写一个视图等。你能够经过把多个视图解析器设置到应用上下文(application context)中的方式来串联它们。若是须要指定它们的次序,那么设置 order 属性便可。请记住,order属性的值越大,该视图解析器在链中的位置就越靠后。
在下面的代码例子中,视图解析器链中包含了两个解析器:一个是 InternalResourceViewResolver ,它老是自动被放置在解析器链的最后;另外一个是 XmlViewResolver ,它用来指定Excel视图。 InternalResourceViewResolver 不支持Excel视图。
<bean id="jspViewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="viewClass" value="org.springframework.web.servlet.view.JstlView"/> <property name="prefix" value="/WEB-INF/jsp/"/> <property name="suffix" value=".jsp"/> </bean> <bean id="excelViewResolver" class="org.springframework.web.servlet.view.XmlViewResolver"> <property name="order" value="1"/> <property name="location" value="/WEB-INF/views.xml"/> </bean> <!-- in views.xml --> <beans> <bean name="report" class="org.springframework.example.ReportExcelView"/> </beans>
若是一个视图解析器不能返回一个视图,那么Spring会继续检查上下文中其余的视图解析器。此时若是存在其余的解析器,Spring会继续调用它们,直到产生一个视图返回为止。若是最后全部视图解析器都不能返回一个视图,Spring就抛出一个 ServletException 。
视图解析器的接口清楚声明了,一个视图解析器是能够返回null值的,这表示不能找到任何合适的视图。并不是全部的视图解析器都这么作,可是也存在不得不如此的场景,即解析器确实没法检测对应的视图是否存在。好比, InternalResourceViewResolver 在内部使用了 RequestDispatcher ,而且进入分派过程是检测一个JSP视图是否存在的惟一方法,但这个过程仅可能发生惟一一次。一样的 VelocityViewResolver 和部分其余的视图解析器也存在这样的状况。具体的请查阅某个特定的视图解析器的Java文档,看它是否会report不存在的视图。所以,若是不把 InternalResourceViewResolver 放置在解析器链的最后,将可能致使解析器链没法彻底执行,由于 InternalResourceViewResolver 永远都会 返回一个视图。
如前所述,控制器一般都会返回一个逻辑视图名,而后视图解析器会把它解析到一个具体的视图技术上去渲染。对于一些能够由Servlet或JSP引擎来处理的视图技术,好比JSP等,这个解析过程一般是由 InternalResourceViewResolver和InternalResourceView 协做来完成的,而这一般会调用Servlet的 APIRequestDispatcher.forward(..) 方法或 RequestDispatcher.include(..) 方法,并发生一次内部的转发(forward)或引用(include)。而对于其余的视图技术,好比Velocity、XSLT等,视图自己的内容是直接被写回响应流中的。
有时,咱们想要在视图渲染以前,先把一个HTTP重定向请求发送回客户端。好比,当一个控制器成功地接受到了POST
过来的数据,而响应仅仅是委托另外一个控制器来处理(好比一次成功的表单提交)时,咱们但愿发生一次重定向。在这种场景下,若是只是简单地使用内部转发,那么意味着下一个控制器也能看到此次POST
请求携带的数据,这可能致使一些潜在的问题,好比可能会与其余指望的数据混淆,等。此外,另外一种在渲染视图前对请求进行重定向的需求是,防止用户屡次提交表单的数据。此时若使用重定向,则浏览器会先发送第一个POST
请求;请求被处理后浏览器会收到一个重定向响应,而后浏览器直接被重定向到一个不一样的URL,最后浏览器会使用重定向响应中携带的URL发起一次GET
请求。所以,从浏览器的角度看,当前所见的页面并非POST
请求的结果,而是一次GET
请求的结果。这就防止了用户因刷新等缘由意外地提交了屡次一样的数据。此时刷新会从新GET
一次结果页,而不是把一样的POST
数据再发送一遍。
强制重定向的一种方法是,在控制器中建立并返回一个Spring重定向视图 RedirectView 的实例。它会使得 DispatcherServlet 放弃使用通常的视图解析机制,由于你已经返回一个(重定向)视图给 DispatcherServlet 了,因此它会构造一个视图来知足渲染的需求。紧接着RedirectView
会调用 HttpServletResponse.sendRedirect() 方法,发送一个HTTP重定向响应给客户端浏览器。
若是你决定返回 RedirectView ,而且这个视图实例是由控制器内部建立出来的,那咱们更推荐在外部配置重定向URL而后注入到控制器中来,而不是写在控制器里面。这样它就能够与视图名一块儿在配置文件中配置。关于如何实现这个解耦,请参考 重定向前缀——redirect:一小节。
模 型中的全部属性默认都会考虑做为URI模板变量被添加到重定向URL中。剩下的其余属性,若是是基本类型或者基本类型的集合或数组,那它们将被自动添加到 URL的查询参数中去。若是model是专门为该重定向所准备的,那么把全部基本类型的属性添加到查询参数中多是咱们指望那个的结果。可是,在包含注解 的控制器中,model可能包含了专门做为渲染用途的属性(好比一个下拉列表的字段值等)。为了不把这样的属性也暴露在URL中, @RequestMapping 方法能够声明一个 RedirectAttributes 类型的方法参数,用它来指定专门供重定向视图 RedirectView 取用的属性。若是重定向成功发生,那么 RedirectAttributes 对象中的内容就会被使用;不然则使用模型model中的数据。
RequestMappingHandlerAdapter 提供了一个" ignoreDefaultModelOnRedirect "
标志。它被用来标记默认Model
中的属性永远不该该被用于控制器方法的重定向中。控制器方法应该声明一个 RedirectAttributes 类的参数。若是不声明,那就没有参数被传递到重定向的视图 RedirectView 中。在MVC命名空间或MVC Java编程配置方式中,为了维持向后的兼容性,这个标志都仍被保持为 false 。但若是你的应用是一个新的项目,那么咱们推荐把它的值设置成 true 。
请注意,当前请求URI中的模板变量会在填充重定向URL的时候自动对应用可见,而不须要显式地在Model
或 RedirectAttributes 中再添加属性。请看下面的例子:
@RequestMapping(path = "/files/{path}", method = RequestMethod.POST) public String upload(...) { // ... return "redirect:files/{path}"; }
另一种向重定向目标传递数据的方法是经过 闪存属性(Flash Attributes)。与其余重定向属性不一样,flash属性是存储在HTTP session中的(所以不会出如今URL中)。更多内容,请参考 21.6 使用闪存属性一节。
尽管使用 RedirectView 来作重定向能工做得很好,但若是控制器自身仍是须要建立一个 RedirectView , 那无疑控制器仍是了解重定向这么一件事情的发生。这仍是有点不尽完美,不一样范畴的耦合仍是太强。控制器其实不该该去关心响应会如何被渲染。In general it should operate only in terms of view names that have been injected into it.
一个特别的视图名前缀能完成这个解耦: redirect: 。若是返回的视图名中含有 redirect: 前缀,那么 UrlBasedViewResolver (及它的全部子类)就会接受到这个信号,意识到这里须要发生重定向。而后视图名剩下的部分会被解析成重定向URL。
这种方式与经过控制器返回一个重定向视图 RedirectView 所达到的效果是同样的,不过这样一来控制器就能够只专一于处理并返回逻辑视图名了。若是逻辑视图名是这样的形式: redirect:/myapp/some/resource ,他们重定向路径将以Servlet上下文做为相对路径进行查找,而逻辑视图名若是是这样的形式: redirect:http://myhost.com/some/arbitrary/path ,那么重定向URL使用的就是绝对路径。
注意的是,若是控制器方法注解了 @ResponseStatus ,那么注解设置的状态码值会覆盖 RedirectView 设置的响应状态码值。
对于最终会被 UrlBasedViewResolver 或其子类解析的视图名,你能够使用一个特殊的前缀: forward: 。这会致使一个 InternalResourceView 视图对象的建立(它最终会调用 RequestDispatcher.forward() 方法),后者会认为视图名剩下的部分是一个URL。所以,这个前缀在使用 InternalResourceViewResolver 和 InternalResourceView 时并无特别的做用(好比对于JSP来讲)。但当你主要使用的是其余的视图技术,而又想要强制把一个资源转发给Servlet/JSP引擎进行处理时,这个前缀可能就颇有用(或者,你也可能同时串联多个视图解析器)。
与 redirect: 前缀同样,若是控制器中的视图名使用了 forward: 前缀,控制器自己并不会发觉任何异常,它关注的仍然只是如何处理响应的问题。
ContentNegotiatingViewResolver 本身并不会解析视图,而是委托给其余的视图解析器去处理。
The ContentNegotiatingViewResolver does not resolve views itself but rather delegates to other view resolvers, selecting the view that resembles the representation requested by the client. Two strategies exist for a client to request a representation from the server:
Accept
HTTP request header to list the media types that it understands. For example, an HTTP request for <http://www.example.com/users/fred>
with an Accept
header set to application/pdf requests a PDF representation of the user fred, while <http://www.example.com/users/fred> with an Accept
header set to text/xml
requests an XML representation. This strategy is known as content negotiation.![]() |
Note |
---|---|
One issue with the Accept
header is that it is impossible to set it in a web browser within HTML. For example, in Firefox, it is fixed to:
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
For this reason it is common to see the use of a distinct URI for each representation when developing browser based web applications.
To support multiple representations of a resource, Spring provides the ContentNegotiatingViewResolver to resolve a view based on the file extension or Accept
header of the HTTP request. ContentNegotiatingViewResolver does not perform the view resolution itself but instead delegates to a list of view resolvers that you specify through the bean property ViewResolvers .
The ContentNegotiatingViewResolver selects an appropriate View
to handle the request by comparing the request media type(s) with the media type (also known as Content-Type
) supported by the View associated with each of its ViewResolvers . The first View in the list that has a compatible Content- Type
returns the representation to the client. If a compatible view cannot be supplied by the ViewResolver chain, then the list of views specified through the DefaultViews property will be consulted. This latter option is appropriate for singleton Views that can render an appropriate representation of the current resource regardless of the logical view name. The Accept
header may include wild cards, for example text/*
, in which case a View whose Content-Type was text/xml
is a compatible match.
To support custom resolution of a view based on a file extension, use a ContentNegotiationManager
: see Section 21.16.6, "Content Negotiation".
Here is an example configuration of a ContentNegotiatingViewResolver
:
<bean class="org.springframework.web.servlet.view.ContentNegotiatingViewResolver"> <property name="viewResolvers"> <list> <bean class="org.springframework.web.servlet.view.BeanNameViewResolver"/> <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="prefix" value="/WEB-INF/jsp/"/> <property name="suffix" value=".jsp"/> </bean> </list> </property> <property name="defaultViews"> <list> <bean class="org.springframework.web.servlet.view.json.MappingJackson2JsonView"/> </list> </property> </bean> <bean id="content" class="com.foo.samples.rest.SampleContentAtomView"/>
The InternalResourceViewResolver handles the translation of view names and JSP pages, while the BeanNameViewResolver returns a view based on the name of a bean. (See "[Resolving views with the ViewResolver interface](mvc.html
###mvc-viewresolver-resolver "21.5.1 Resolving views with the ViewResolver interface" )" for more details on how Spring looks up and instantiates a view.) In this example, the content
bean is a class that inherits from AbstractAtomFeedView , which returns an Atom RSS feed. For more information on creating an Atom Feed representation, see the section Atom Views.
In the above configuration, if a request is made with an .html
extension, the view resolver looks for a view that matches the text/html media type. The InternalResourceViewResolver provides the matching view for text/html . If the request is made with the file extension .atom
, the view resolver looks for a view that matches the application/atom+xml
media type. This view is provided by the BeanNameViewResolver that maps to the SampleContentAtomView if the view name returned is content
. If the request is made with the file extension .json
, the MappingJackson2JsonView instance from the DefaultViews
list will be selected regardless of the view name. Alternatively, client requests can be made without a file extension but with the Accept
header set to the preferred media-type, and the same resolution of request to views would occur.
![]() |
Note |
---|---|
If `ContentNegotiatingViewResolver's list of ViewResolvers is not configured explicitly, it automatically uses any ViewResolvers defined in the application context.
The corresponding controller code that returns an Atom RSS feed for a URI of the form <http://localhost/content.atom> or <http://localhost/content> with an Accept
header of application/atom+xml is shown below.
@Controller public class ContentController { private List<SampleContent> contentList = new ArrayList<SampleContent>(); @RequestMapping(path="/content", method=RequestMethod.GET) public ModelAndView getContent() { ModelAndView mav = new ModelAndView(); mav.setViewName("content"); mav.addObject("sampleContentList", contentList); return mav; } }
Flash属性( flash attributes )提供了一个请求为另外一个请求存储有用属性的方法。这在重定向的时候最常使用,好比常见的 POST/REDIRECT/GET 模式。Flash属性会在重定向前被暂时地保存起来(一般是保存在session中),重定向后会从新被下一个请求取用并当即从原保存地移除。
为支持flash属性,Spring MVC提供了两个抽象。 FlashMap 被用来存储 flash 属性,而用 FlashMapManager 来存储、取回、管理FlashMap
的实例。
对flash属性的支持默认是启用的,并不须要显式声明,不过没用到它时它毫不会主动地去建立HTTP会话(session)。对于每一个请求,框架都会“传进”一个FlashMap
,里面存储了从上个请求(若是有)保存下来的属性;同时,每一个请求也会“输出”一个FlashMap
,里面保存了要给下个请求使用的属性。两个FlashMap
实例在Spring MVC应用中的任何地点均可以经过 RequestContextUtils 工具类的静态方法取得。
控制器一般不须要直接接触 FlashMap 。通常是经过 @RequestMapping 方法去接受一个 RedirectAttributes 类型的参数,而后直接地往其中添加flash属性。经过 RedirectAttributes 对象添加进去的flash属性会自动被填充到请求的“输出”FlashMap
对象中去。相似地,重定向后“传进”的 FlashMap 属性也会自动被添加到服务重定向URL的控制器参数Model
中去。
匹配请求所使用的flash属性
flash 属性的概念在其余许多的Web框架中也存在,而且实践证实有时可能会致使并发上的问题。这是由于从定义上讲,flash属性保存的时间是到下个请求接收到 以前。问题在于,“下一个”请求不必定恰好就是你要重定向到的那个请求,它有多是其余的异步请求(好比polling请求或者资源请求等)。这会致使 flash属性在到达真正的目标请求前就被移除了。
为了减小这个问题发生的可能性,重定向视图 RedirectView 会自动为一个FlashMap
实例记录其目标重定向URL的路径和查询参数。而后,默认的 FlashMapManager 会在为请求查找其该“传进”的FlashMap
时,匹配这些信息。
这并不能彻底解决重定向的并发问题,但极大程度地减小了这种可能性,由于它能够从重定向URL已有的信息中来作匹配。所以,通常只有在重定向的场景下,咱们才推荐使用flash属性。
在Spring MVC中,使用了 UriComponentsBuilder 和 UriComponents 两个类来提供一种构造和加密URI的机制。
好比,你能够经过一个URI模板字符串来填充并加密一个URI:
UriComponents uriComponents = UriComponentsBuilder.fromUriString( "http://example.com/hotels/{hotel}/bookings/{booking}").build(); URI uri = uriComponents.expand("42", "21").encode().toUri();
请注意 UriComponents 是不可变对象。所以 expand() 与 encode() 操做在必要的时候会返回一个新的实例。
你也能够使用一个URI组件实例对象来实现URI的填充与加密:
UriComponents uriComponents = UriComponentsBuilder.newInstance() .scheme("http").host("example.com").path("/hotels/{hotel}/bookings/{booking}").build() .expand("42", "21") .encode();
在Servlet环境下, ServletUriComponentsBuilder 类提供了一个静态的工厂方法,能够用于从Servlet请求中获取URL信息:
HttpServletRequest request = ... // 主机名、schema, 端口号、请求路径和查询字符串都重用请求里已有的值 // 替换了其中的"accountId"查询参数 ServletUriComponentsBuilder ucb = ServletUriComponentsBuilder.fromRequest(request) .replaceQueryParam("accountId", "{id}").build() .expand("123") .encode();
或者,你也能够选择只复用请求中一部分的信息:
// 重用主机名、端口号和context path // 在路径后添加"/accounts" ServletUriComponentsBuilder ucb = ServletUriComponentsBuilder.fromContextPath(request) .path("/accounts").build()
或者,若是你的 DispatcherServlet 是经过名字(好比,/main/*
)映射请求的,you can also have the literal part of the servlet mapping included:
// Re-use host, port, context path // Append the literal part of the servlet mapping to the path // Append "/accounts" to the path ServletUriComponentsBuilder ucb = ServletUriComponentsBuilder.fromServletMapping(request) .path("/accounts").build()
Spring MVC也提供了构造指定控制器方法连接的机制。如下面代码为例子,假设咱们有这样一个控制器:
@Controller @RequestMapping("/hotels/{hotel}") public class BookingController { @RequestMapping("/bookings/{booking}") public String getBooking(@PathVariable Long booking) { // ... } }
你能够经过引用方法名字的办法来准备一个连接:
UriComponents uriComponents = MvcUriComponentsBuilder .fromMethodName(BookingController.class, "getBooking", 21).buildAndExpand(42); URI uri = uriComponents.encode().toUri();
在上面的例子中,咱们为方法参数准备了填充值:一个long型的变量值21,以用于填充路径变量并插入到URL中。另外,咱们还提供了一个值42,以用于填充其余剩余的URI变量,好比从类层级的请求映射中继承来的 hotel 变量。若是方法还有更多的参数,你能够为那些不须要参与URL构造的变量赋予null值。通常而言,只有 @PathVariable 和 @RequestParam 注解的参数才与URL的构造相关。
还有其余使用 MvcUriComponentsBuilder 的方法。好比,你能够经过相似mock掉测试对象的方法,用代理来避免直接经过名字引用一个控制器方法(如下方法假设 MvcUriComponentsBuilder.on 方法已经被静态导入):
UriComponents uriComponents = MvcUriComponentsBuilder .fromMethodCall(on(BookingController.class).getBooking(21)).buildAndExpand(42); URI uri = uriComponents.encode().toUri();
上面的代码例子中使用了 MvcUriComponentsBuilder 类的静态方法。内部实现中,它依赖于 ServletUriComponentsBuilder 来 从当前请求中抽取schema、主机名、端口号、context路径和servlet路径,并准备一个基本URL。大多数状况下它能良好工做,但有时还不 行。好比,在准备连接时,你可能在当前请求的上下文(context)以外(好比,执行一个准备连接links的批处理),或你可能须要为路径插入一个前 缀(好比一个地区性前缀,它从请求中被移除,而后又从新被插入到连接中去)。
对于上面所提的场景,你能够使用重载过的静态方法 fromXxx ,它接收一个 UriComponentsBuilder 参数,而后从中获取基本URL以便使用。或你也能够使用一个基本URL建立一个 MvcUriComponentsBuilder 对象,而后使用实例对象的 fromXxx 方法。以下面的示例:
UriComponentsBuilder base = ServletUriComponentsBuilder.fromCurrentContextPath().path("/en"); MvcUriComponentsBuilder builder = MvcUriComponentsBuilder.relativeTo(base); builder.withMethodCall(on(BookingController.class).getBooking(21)).buildAndExpand(42); URI uri = uriComponents.encode().toUri();
Spring的架构中的不少层面都提供了对国际化的支持,一样支持Spring MVC框架也能提供。 DispatcherServlet 为你提供了自动使用用户的地区信息来解析消息的能力。而这,是经过 LocaleResolver 对象来完成的。
一个请求进入处理时, DispatcherServlet 会查找一个地区解析器。若是找到,就尝试使用它来设置地区相关的信息。经过调用 RequestContext.getLocale() 都能取到地区解析器所解析到的地区信息。
此外,若是你须要自动解析地区信息,你能够在处理器映射前加一个拦截器(关于更多处理器映射拦截器的知识,请参见21.4.1 使用HandlerInterceptor拦截请求一小节),并用它来根据条件或环境不一样,好比,根据请求中某个参数值,来更改地区信息。
除了获取客户端的地区信息外,有时他们所在的时区信息也很是有用。 LocaleContextResolver 接口为 LocaleResolver 提供了拓展点,容许解析器在 LocaleContext 中提供更多的信息,这里面就能够包含时区信息。
若是用户的时区信息能被解析到,那么你总能够经过 RequestContext.getTimeZone() 方法得到。时区信息会自动被 SpringConversionService 下注册的日期/时间转换器 Converter 及格式化对象 Formatter 所使用。
AcceptHeaderLocaleResolver 解析器会检查客户端(好比,浏览器,等)所发送的请求中是否携带 accept-language 请求头。一般,该请求头字段中包含了客户端操做系统的地区信息。不过请注意,该解析器不支持时区信息的解析。
CookieLocaleResolver 解析会检查客户端是否有 Cookie ,里面可能存放了地区 Locale 或时区 TimeZone 信息。若是检查到相应的值,解析器就使用它们。经过该解析器的属性,你能够指定cookie的名称和其最大的存活时间。请见下面的例子,它展现了如何定义一个 CookieLocaleResolver :
<bean id="localeResolver" class="org.springframework.web.servlet.i18n.CookieLocaleResolver"> <property name="cookieName" value="clientlanguage"/> <!-- 单位为秒。若设置为-1,则cookie不会被持久化(客户端关闭浏览器后即被删除) --> <property name="cookieMaxAge" value="100000"> </bean>
表21.4. CookieLocaleResolver 支持的属性
属性 | 默认值 | 描述 |
---|---|---|
cookieName | classname + LOCALE | cookie名 |
cookieMaxAge | Integer.MAX_INT | cookie被保存在客户端的最长时间。若是该值为-1,那么cookie将不会被持久化,在客户端浏览器关闭以后就失效了 |
cookiePath | / | 限制了cookie仅对站点下的某些特定路径可见。若是指定了cookiePath,那么cookie将仅对该路径及其子路径下的全部站点可见 |
SessionLocaleResolver 容许你从 session 中取得可能与用户请求相关联的地区 Locale 和时区 TimeZone 信息。与 CookieLocaleResolver 不一样,这种存取策略仅将Servlet容器的 HttpSession 中相关的地区信息存取到本地。所以,这些设置仅会为该会话(session)临时保存, session 结束后,这些设置就会失效。
不过请注意,该解析器与其余外部 session 管理机制,好比 Spring的Session 项目等,并无直接联系。该 SessionLocaleResolver 仅会简单地从与当前请求 HttpServletRequest 相关的 HttpSession 对象中,取出对应的属性,并修改其值,仅此而已。
You can enable changing of locales by adding the LocaleChangeInterceptor to one of the handler mappings (see [Section 21.4, "Handler mappings"](mvc.html
###mvc-handlermapping "21.4 Handler mappings" )). It will detect a parameter in the request and change the locale. It calls setLocale()
on the LocaleResolver that also exists in the context. The following example shows that calls to all *.view
resources containing a parameter named siteLanguage will now change the locale. So, for example, a request for the following URL, <http://www.sf.net/home.view?siteLanguage=nl> will change the site language to Dutch.
你能够在处理器映射(详见21.4 处理器映射(Handler mappings)小节)前添加一个LocaleChangeInterceptor
拦截器来更改地区信息。它能检测请求中的参数,并根据其值相应地更新地区信息。它经过调用 LocaleResolver 的 setLocale() 方法来更改地区。下面的代码配置展现了如何为全部请求 *.view 路径而且携带了 siteLanguage 参数的资源请求更改地区。举个例子,一个URL为 <http://www.sf.net/home.view?siteLanguage=nl> 的请求将会将站点语言更改成荷兰语。
<bean id="localeChangeInterceptor" class="org.springframework.web.servlet.i18n.LocaleChangeInterceptor"> <property name="paramName" value="siteLanguage"/> </bean> <bean id="localeResolver" class="org.springframework.web.servlet.i18n.CookieLocaleResolver"/> <bean id="urlMapping" class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping"> <property name="interceptors"> <list> <ref bean="localeChangeInterceptor"/> </list> </property> <property name="mappings"> <value>/**/*.view=someController</value> </property> </bean>
You can apply Spring Web MVC framework themes to set the overall look-and-feel of your application, thereby enhancing user experience. A theme is a collection of static resources, typically style sheets and images, that affect the visual style of the application.
你能够使用Spring Web MVC框架提供的主题来为整站的应用设置皮肤/主题(look-and-feel),这能够提升用户体验。主题 是指一系列静态资源的集合,而且主要是样式表和图片,它们决定了你的应用的视觉风格。
要在你的应用中使用主题,你必须实现一个 org.springframework.ui.context.ThemeSource 接口。 WebApplicationContext 接口继承了 ThemeSource 接口,但主要的工做它仍是委托给接口具体的实现来完成。默认的实现是 org.springframework.ui.context.support.ResourceBundleThemeSource ,它会从classpath的根路径下去加载配置文件。若是须要定制 ThemeSource 的实现,或要配置 ResourceBundleThemeSource 的基本前缀名(base name prefix),你能够在应用上下文(application context)下注册一个名字为保留名 themeSource 的 bean ,web应用的上下文会自动检测名字为themeSource
的bean并使用它。
使用的是 ResourceBundleThemeSource 时,一个主题能够定义在一个简单的配置文件中。该配置文件会列出全部组成了该主题的资源。下面是个例子:
styleSheet=/themes/cool/style.css
background=/themes/cool/img/coolBg.jpg
属性的键(key)是主题元素在视图代码中被引用的名字。对于JSP视图来讲,通常经过 spring:theme 这个定制化的标签(tag)来作,它与 spring:message 标签很类似。如下的JSP代码即便用了上段代码片断中定义的主题,用以定制总体的皮肤:
<%@ taglib prefix="spring" uri="http://www.springframework.org/tags"%> <html> <head> <link rel="stylesheet" href="<spring:theme code=''styleSheet''/>" type="text/css"/> </head> <body style="background=<spring:theme code=''background''/>"> ... </body> </html>
默认状况下 ResourceBundleThemeSource 使用的基本名前缀(base name prefix)是空值。也便是说,配置文件是从根classpath路径下加载的。所以,你须要把主题的定义文件 cool.properties 放在classpath的根路径目录下,好比, /WEB-INF/classes。ResourceBundleThemeSource 采用了Java的标准资源 bundle 加载机制,彻底支持国际化主题。好比,你能够建立一个 /WEB-INF/classes/cool_nl.properties 配置文件,并在其中引用一副有荷兰文的背景图片。
上一小节,咱们讲了如何定义主题,定义以后,你要决定使用哪一个主题。 DispatcherServlet 会查找一个名称为 themeResolver 的 bean 以肯定使用哪一个 ThemeResolver 的实现。主题解析器的工做原理与地区解析器 LocaleResolver 的工做原理大同小异。它会检测,对于一个请求来讲,应该使用哪一个主题,同时它也能够修改一个请求所应应用的主题。Spring提供了下列的这些主题解析器:
表21.5. ThemeResolver接口的实现
类名 | 描述 |
---|---|
FixedThemeResolver |
选择一个固定的主题,这是经过设置 defaultThemeName 这个属性值实现的 |
SessionThemeResolver |
请求相关的主题保存在用户的HTTP会话(session)中。对于每一个会话来讲,它只须要被设置一次,但它不能在会话之间保存 |
CookieThemeResolver |
选中的主题被保存在客户端的cookie中 |
Spring也提供了一个主题更改拦截器 ThemeChangeInterceptor ,以支持主题的更换。这很容易作到,只须要在请求中携带一个简单的请求参数便可。
Spring内置对多路上传的支持,专门用于处理web应用中的文件上传。你能够经过注册一个可插拔的 MultipartResolver 对象来启用对文件多路上传的支持。该接口在定义于 org.springframework.web.multipart 包下。Spring为通常的文件上传提供了 MultipartResolver 接口的一个实现,为Servlet 3.0多路请求的转换提供了另外一个实现。
默 认状况下,Spring的多路上传支持是不开启的,由于有些开发者但愿由本身来处理多路请求。若是想启用Spring的多路上传支持,你须要在web应用 的上下文中添加一个多路传输解析器。每一个进来的请求,解析器都会检查是否是一个多部分请求。若发现请求是完整的,则请求按正常流程被处理;若是发现请求是 一个多路请求,则你在上下文中注册的 MultipartResolver 解析器会被用来处理该请求。以后,请求中的多路上传属性就与其余属性同样被正常对待了。【最后一句翻的很差,multipart翻译成多路仍是多部分还在斟酌中。望阅读者注意此处。】
下面的代码展现了如何使用一个通用的多路上传解析器 CommonsMultipartResolver :
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver"> <!-- 支持的其中一个属性,支持的最大文件大小,以字节为单位 --> <property name="maxUploadSize" value="100000"/> </bean>
固然,要让多路解析器正常工做,你须要在classpath路径下准备必须的jar包。若是使用的是通用的多路上传解析器 CommonsMultipartResolver ,你所须要的jar包是 commons-fileupload.jar 。
当Spring的 DispatcherServlet 检测到一个多部分请求时,它会激活你在上下文中声明的多路解析器并把请求交给它。解析器会把当前的 HttpServletRequest 请求对象包装成一个支持多路文件上传的请求对象 MultipartHttpServletRequest 。有了 MultipartHttpServletRequest 对象,你不只能够获取该多路请求中的信息,还能够在你的控制器中得到该多路请求的内容自己。
要使用基于Servlet 3.0的多路传输转换功能,你必须在web.xml
中为 DispatcherServlet 添加一个 multipart-config 元素,或者经过Servlet编程的方法使用 javax.servlet.MultipartConfigElement 进行注册,或你本身定制了本身的Servlet类,那你必须使用 javax.servlet.annotation.MultipartConfig 对其进行注解。其余诸如最大文件大小或存储位置等配置选项都必须在这个Servlet级别进行注册,由于Servlet 3.0不容许在解析器 MultipartResolver 的层级配置这些信息。
当你经过以上任一种方式启用了Servlet 3.0多路传输转换功能,你就能够把一个 StandardServletMultipartResolver 解析器添加到你的Spring配置中去了:
<bean id="multipartResolver" class="org.springframework.web.multipart.support.StandardServletMultipartResolver"> </bean>
当解析器 MultipartResolver 完成处理时,请求便会像其余请求同样被正常流程处理。首先,建立一个接受文件上传的表单将容许用于直接上传整个表单。编码属性( enctype="multipart/form-data" )能让浏览器知道如何对多路上传请求的表单进行编码(encode)。
<html> <head> <title>Upload a file please</title> </head> <body> <h1>Please upload a file</h1> <form method="post" action="/form" enctype="multipart/form-data"> <input type="text" name="name"/> <input type="file" name="file"/> <input type="submit"/> </form> </body> </html>
下一步是建立一个能处理文件上传的控制器。这里须要的控制器与通常注解了@Controller
的控制器基本同样,除了它接受的方法参数类型是 MultipartHttpServletRequest ,或 MultipartFile 。
@Controller public class FileUploadController { @RequestMapping(path = "/form", method = RequestMethod.POST) public String handleFormUpload(@RequestParam("name") String name, @RequestParam("file") MultipartFile file) { if (!file.isEmpty()) { byte[] bytes = file.getBytes(); // store the bytes somewhere return "redirect:uploadSuccess"; } return "redirect:uploadFailure"; } }
请留意 @RequestParam 注解是如何将方法参数对应到表单中的定义的输入字段的。在上面的例子中,咱们拿到了byte[]
文件数据,只是没对它作任何事。在实际应用中,你可能会将它保存到数据库、存储在文件系统上,或作其余的处理。
当使用Servlet 3.0的多路传输转换时,你也能够使用 javax.servlet.http.Part 做为方法参数:
@Controller public class FileUploadController { @RequestMapping(path = "/form", method = RequestMethod.POST) public String handleFormUpload(@RequestParam("name") String name, @RequestParam("file") Part file) { InputStream inputStream = file.getInputStream(); // store bytes from uploaded file somewhere return "redirect:uploadSuccess"; } }
在 使用了RESTful服务的场景下,非浏览器的客户端也能够直接提交多路文件请求。上一节讲述的全部例子与配置在这里也都一样适用。但与浏览器不一样的是, 提交的文件和简单的表单字段,客户端发送的数据能够更加复杂,数据能够指定为某种特定的内容类型(content type)——好比,一个多路上传请求可能第一部分是个文件,而第二部分是个JSON格式的数据:
POST /someUrl Content-Type: multipart/mixed --edt7Tfrdusa7r3lNQc79vXuhIIMlatb7PQg7Vp Content-Disposition: form-data; name="meta-data" Content-Type: application/json; charset=UTF-8 Content-Transfer-Encoding: 8bit { "name": "value" } --edt7Tfrdusa7r3lNQc79vXuhIIMlatb7PQg7Vp Content-Disposition: form-data; name="file-data"; filename="file.properties" Content-Type: text/xml Content-Transfer-Encoding: 8bit ... File Data ...
对于名称为meta-data
的部分,你能够经过控制器方法上的 @RequestParam("meta-data") String metadata 参数来得到。但对于那部分请求体中为JSON格式数据的请求,你可能更想经过接受一个对应的强类型对象,就像@RequestBody
经过 HttpMessageConverter 将通常请求的请求体转换成一个对象同样。
这是可能的,你能够使用 @RequestPart 注解来实现,而非 @RequestParam 。该注解将使得特定多路请求的请求体被传给 HttpMessageConverter ,而且在转换时考虑多路请求中不一样的内容类型参数'Content-Type'
:
@RequestMapping(path = "/someUrl", method = RequestMethod.POST) public String onSubmit(@RequestPart("meta-data") MetaData metadata, @RequestPart("file-data") MultipartFile file) { // ... }
请注意 MultipartFile 方法参数是如何可以在 @RequestParam 或 @RequestPart 注解下互用的,两种方法都能拿到数据。但,这里的方法参数 @RequestPart("meta-data") MetaData 则会由于请求中的内容类型请求头'Content-Type'
被读入成为JSON数据,而后再经过 MappingJackson2HttpMessageConverter 被转换成特定的对象。
Spring的处理器异常解析器 HandlerExceptionResolver 接口的实现负责处理各种控制器执行过程当中出现的异常。某种程度上讲, HandlerExceptionResolver 与你在web应用描述符 web.xml 文 件中能定义的异常映射(exception mapping)很相像,不过它比后者提供了更灵活的方式。好比它能提供异常被抛出时正在执行的是哪一个处理器这样的信息。而且,一个更灵活 (programmatic)的异常处理方式能够为你提供更多选择,使你在请求被直接转向到另外一个URL以前(与你使用Servlet规范的异常映射是一 样的)有更多的方式来处理异常。
实现 HandlerExceptionResolver 接口并不是实现异常处理的惟一方式,它只是提供了 resolveException(Exception, Hanlder) 方法的一个实现而已,方法会返回一个 ModelAndView 。除此以外,你还能够框架提供的 SimpleMappingExceptionResolver 或在异常处理方法上注解 @ExceptionHandler 。 SimpleMappingExceptionResolver 容许你获取可能抛出的异常类的名字,并把它映射到一个视图名上去。这与Servlet API提供的异常映射特性是功能等价的,但你也能够基于此实现粒度更精细的异常映射。而 @ExceptionHandler 注解的方法则会在异常抛出时被调用以处理该异常。这样的方法能够定义在 @Controller 注解的控制器类里,也能够定义在 @ControllerAdvice 类中,后者能够使该异常处理方法被应用到更多的 @Controller 控制器中。下一小节将提供更为详细的信息。
HandlerExceptionResolver
接口以及 SimpleMappingExceptionResolver 解析器类的实现使得你能声明式地将异常映射到特定的视图上,还能够在异常被转发(forward)到对应的视图前使用Java代码作些判断和逻辑。不过在一些场景,特别是依靠 @ResponseBody 返回响应而非依赖视图解析机制的场景下,直接设置响应的状态码并将客户端须要的错误信息直接写回响应体中,多是更方便的方法。
你也能够使用 @ExceptionHandler 方法来作到这点。若是 @ExceptionHandler 方法是在控制器内部定义的,那么它会接收并处理由控制器(或其任何子类)中的 @RequestMapping 方法抛出的异常。若是你将 @ExceptionHandler 方法定义在 @ControllerAdvice 类中,那么它会处理相关控制器中抛出的异常。下面的代码就展现了一个定义在控制器内部的 @ExceptionHandler 方法:
@Controller public class SimpleController { // @RequestMapping methods omitted ... @ExceptionHandler(IOException.class) public ResponseEntity<String> handleIOException(IOException ex) { // prepare responseEntity return responseEntity; } }
此外, @ExceptionHandler 注解还能够接受一个异常类型的数组做为参数值。若抛出了已在列表中声明的异常,那么相应的 @ExceptionHandler 方法将会被调用。若是没有给注解任何参数值,那么默认处理的异常类型将是方法参数所声明的那些异常。
与标准的控制器@RequestMapping
注解处理方法同样, @ExceptionHandler 方法的方法参数和返回值也能够很灵活。好比,在Servlet环境下方法能够接收HttpServletRequest
参数,而Portlet环境下方法能够接收PortletRequest
参数。返回值能够是String
类型——这种状况下会被解析为视图名——能够是 ModelAndView 类型的对象,也能够是ResponseEntity
。或者你还能够在方法上添加@ResponseBody
注解以使用消息转换器会转换信息为特定类型的数据,而后把它们写回到响应流中。
处理请求的过程当中,Spring MVC可能会抛出一些的异常。 SimpleMappingExceptionResolver 能够根据须要很方便地将任何异常映射到一个默认的错误视图。但,若是客户端是经过自动检测响应的方式来分发处理异常的,那么后端就须要为响应设置对应的状态码。根据抛出异常的类型不一样,可能须要设置不一样的状态码来标识是客户端错误(4xx)仍是服务器端错误(5xx)。
默认处理器异常解析器 DefaultHandlerExceptionResolver 会将Spring MVC抛出的异常转换成对应的错误状态码。该解析器在MVC命名空间配置或MVC Java配置的方式下默认已经被注册了,另外,经过DispatcherServlet
注册也是可行的(即不使用MVC命名空间或Java编程方式进行配置的时候)。下表列出了该解析器能处理的一些异常,及他们对应的状态码。
异常 | HTTP状态码 |
---|---|
BindException |
400 (无效请求) |
ConversionNotSupportedException |
500 (服务器内部错误) |
HttpMediaTypeNotAcceptableException |
406 (不接受) |
HttpMediaTypeNotSupportedException |
415 (不支持的媒体类型) |
HttpMessageNotReadableException |
400 (无效请求) |
HttpMessageNotWritableException |
500 (服务器内部错误) |
HttpRequestMethodNotSupportedException |
405 (不支持的方法) |
MethodArgumentNotValidException |
400 (无效请求) |
MissingServletRequestParameterException |
400 (无效请求) |
MissingServletRequestPartException |
400 (无效请求) |
NoHandlerFoundException |
404 (请求未找到) |
NoSuchRequestHandlingMethodException |
404 (请求未找到) |
TypeMismatchException |
400 (无效请求) |
MissingPathVariableException |
500 (服务器内部错误) |
NoHandlerFoundException |
404 (请求未找到) |
如下待翻译。
The DefaultHandlerExceptionResolver
works transparently by setting the status of the response. However, it stops short of writing any error content to the body of the response while your application may need to add developer- friendly content to every error response for example when providing a REST API. You can prepare a ModelAndView
and render error content through view resolution -- i.e. by configuring a ContentNegotiatingViewResolver
, MappingJackson2JsonView
, and so on. However, you may prefer to use @ExceptionHandler
methods instead.
If you prefer to write error content via @ExceptionHandler
methods you can extend ResponseEntityExceptionHandler
instead. This is a convenient base for @ControllerAdvice
classes providing an @ExceptionHandler
method to handle standard Spring MVC exceptions and return ResponseEntity
. That allows you to customize the response and write error content with message converters. See the ResponseEntityExceptionHandler
javadocs for more details.
业务异常能够使用 @ResponseStatus 来注解。当异常被抛出时, ResponseStatusExceptionResolver 会设置相应的响应状态码。 DispatcherServlet 会默认注册一个 ResponseStatusExceptionResolver 以供使用。
当响应的状态码被设置为错误状态码,而且响应体中没有内容时,Servlet容器一般会渲染一个HTML错误页。若须要定制容器默认提供的错误页,你能够在web.xml
中定义一个错误页面<error-page>
元素。在Servlet 3规范出来以前,该错误页元素必须被显式指定映射到一个具体的错误码或一个异常类型。从Servlet 3开始,错误页再也不须要映射到其余信息了,这意味着,你指定的位置就是对Servlet容器默认错误页的自定制了。
<error-page> <location>/error</location> </error-page>
这里错误页的位置所在能够是一个JSP页面,或者其余的一些URL,只要它指定容器里任意一个 @Controller 控制器下的处理器方法:
写回 HttpServletResponse 的错误信息和错误状态码能够在控制器中经过请求属性来获取:
@Controller public class ErrorController { @RequestMapping(path = "/error", produces = MediaType.APPLICATION_JSON_UTF8_VALUE) @ResponseBody public Map<String, Object> handle(HttpServletRequest request) { Map<String, Object> map = new HashMap<String, Object>(); map.put("status", request.getAttribute("javax.servlet.error.status_code")); map.put("reason", request.getAttribute("javax.servlet.error.message")); return map; } }
或者在JSP中这么使用:
<%@ page contentType="application/json" pageEncoding="UTF-8"%> { status:<%=request.getAttribute("javax.servlet.error.status_code") %>, reason:<%=request.getAttribute("javax.servlet.error.message") %> }
Spring Security项目为保护web应用免受恶意攻击提供了一些特性。你能够去看看参考文档的这几小节:"CSRF保护"、"安全响应头"以及"Spring MVC集成"。不过并不是应用的全部特性都须要引入Spring Security。好比,须要CSRF保护的话,你仅须要简单地在配置中添加一个过滤器 CsrfFilter 和处理器 CsrfRequestDataValueProcessor 。你能够参考Spring MVC Showcase一节,观看一个完整的展现。
另外一个选择是使用其余专一于Web安全的框架。HDIV就是这样的一个框架,而且它能与Spring MVC良好集成。
对于许多项目来讲,不打破已有的约定,对于配置等有可预测的默认值是很是适合的。如今,Spring MVC对 约定优于配置 这个实践已经有了显式的支持。这意味着什么呢?意味着若是你为项目选择了一组命名上的约定/规范,你将能减小 大量 的配置项,好比一些必要的处理器映射、视图解析器、ModelAndView
实例的配置等。这能帮你快速地创建起项目原型,此外在某种程度上(一般是好的方面)维护了整个代码库的一致性should you choose to move forward with it into production.
具体来讲,支持“约定优于配置”涉及到MVC的三个核心方面:模型、视图,和控制器。
ControllerClassNameHandlerMapping 类是 HandlerMapping 接口的一个实现,它是经过一个约定来解析请求URL及处理该请求的 @Controller 控制器实例之间的映射关系。
请看下面一个简单的控制器实现。请注意留意该类的 名称:
public class **ViewShoppingCartController** implements Controller { public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) { // 这个例子中方法的具体实现并不重要,故忽略。 } } 对应的Spring Web MVC配置文件以下所示:
<bean class="org.springframework.web.servlet.mvc.support.ControllerClassNameHandlerMapping"/> <bean id="**viewShoppingCart**" class="x.y.z.ViewShoppingCartController"> <!-- 注入须要的依赖 --> </bean>
ControllerClassNameHandlerMapping 会查找当前应用上下文中注册的全部处理器(也即控制器) bean ,并去除类名的 Controller 后缀做为决定处理器映射的依据。所以,类名 ViewShoppingCartController 会被映射到匹配 /viewshoppingcart* 的请求URL上。
让咱们多看几个例子,这样你对于核心的思想会立刻熟悉起来(注意URL中路径是全小写,而Controller
控制器类名符合驼峰命名法):
/welcome*
请求URL/home*
请求URL/index*
请求URL/register*
请求URL对于 MultiActionController 处理器类,映射规则要稍微复杂一些。请看下面的代码,假设这里的控制器都是 MultiActionController 的实现:
/admin/*
请求URL/catalog/*
请求URL只要全部控制器 Controller 实现都遵循 xxxController 这样的命名规范,那么 ControllerClassNameHandlerMapping 能把你从定义维护一个 长长长 SimpleUrlHandlerMapping 映射表的重复工做中拯救出来。
ControllerClassNameHandlerMapping 类继承自 AbstractHandlerMapping 基类。所以,你能够视它与其余 HandlerMapping 实现同样,定义你所须要的拦截器HandlerInterceptor
实例及其余全部东西。
ModelMap 类其实就是一个豪华版的 Map
,它使得你为视图展现须要所添加的对象都遵循一个显而易见的约定被命名。请看下面这个 Controller 实现,并请注意,添加到 ModelAndView 中去的对象都没有显式地指定键名。
public class DisplayShoppingCartController implements Controller { public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) { List cartItems = // 拿到一个CartItem对象的列表 User user = // 拿到当前购物的用户User ModelAndView mav = new ModelAndView("displayShoppingCart"); <-- 逻辑视图名 mav.addObject(cartItems); <-- 啊哈,直接添加的对象,没有指定名称 mav.addObject(user); <-- 啊哈再来一次 return mav; } }
ModelAndView
内部使用了一个ModelMap
类,它是Map
的一个实现,会自动为添加进来的对象生成一个键名。为添加对象生成名称的策略是,若添加对象是一个纯Java bean(a scalar object),好比User
,那么使用对象类的短类名(short class name)来做为该对象的名称。下面是一些例子,展现了为添加到ModelMap
实例中的纯Java对象所生成的名称:
x.y.User
实例,为其生成的名称为user
x.y.Registration
实例,为其生成的名称为registration
x.y.Foo
实例,为其生成的名称为foo
java.util.HashMap
实例,为其生成的名称为hashMap
。这种状况下,显式地声明一个键名可能更好,由于hashMap
的约定并不是那么符合直觉null
值将致使程序抛出一个 IllegalArgumentException 参数非法异常。若你所添加的(多个)对象有可能为null
值,那你也须要显式地指定它(们)的名字啥?键名不能自动变复数形式么?
Spring Web MVC的约定优于配置支持尚不能支持自动复数转换。这意思是,你不能指望往 ModelAndView 中添加一个Person
对象的List
列表时,框架会自动为其生成一个名称people
。
这个决定是通过许多争论后的结果,最终“最小惊喜原则”胜出并为你们所接受。
为集合Set
或列表List
生成键名所采起的策略,是先检查集合的元素类型、拿到第一个对象的短类名,而后在其后面添加List
做为名称。添加数组对象也是同理,尽管对于数组咱们就不需再检查数组内容了。下面给出的几个例子能够阐释一些东西,让集合的名称生成语义变得更加清晰:
x.y.User
元素类型的数组x.y.User[]
,为其生成的键名是userList
x.y.User
元素类型的数组x.y.Foo[]
,为其生成的键名是fooList
x.y.User
元素类型的数组java.util.ArrayList
,为其生成的键名是userList
x.y.Foo
元素类型的数组java.util.HashSet
,为其生成的键名是fooList
java.util.ArrayList
则根本不会被添加 RequestToViewNameTranslator 接口能够在逻辑视图名未被显式提供的状况下,决定一个可用的逻辑视图View
名。
DefaultRequestToViewNameTranslator 可以将请求URL映射到逻辑视图名上去,以下面代码例子所示:
public class RegistrationController implements Controller { public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) { // 处理请求…… ModelAndView mav = new ModelAndView(); // 向Model中添加须要的数据 return mav; // 请注意这里,没有设置任何View对象或逻辑视图名 } }
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" 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"> <!-- 这个众人皆知的bean将为咱们自动生成视图名 --> <bean id="viewNameTranslator" class="org.springframework.web.servlet.view.DefaultRequestToViewNameTranslator"/> <bean class="x.y.RegistrationController"> <!-- 若是须要,注入依赖 --> </bean> <!-- 请请求URL映射到控制器名 --> <bean class="org.springframework.web.servlet.mvc.support.ControllerClassNameHandlerMapping"/> <bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="prefix" value="/WEB-INF/jsp/"/> <property name="suffix" value=".jsp"/> </bean> </beans>
请注意在ha ndleRequest(...)
方法实现中,返回的ModelAndView
对象上自始至终未设置任何View
对象或逻辑视图名。这是由 DefaultRequestToViewNameTranslator 完成的,它的任务就是从请求的URL中生成一个逻辑视图名。在上面的例子中, RegistrationController 与配置的 ControllerClassNameHandlerMapping 一块儿使用的结果是,一个URL为 <http://localhost/registration.html> 的请求,会经由 DefaultRequestToViewNameTranslator 生成并对应到一个逻辑视图名 registration 上。该逻辑视图名又会由 InternalResourceViewResolverbean 解析到 /WEB-INF/jsp/registration.jsp 视图上。
你无需显式地定义一个 DefaultRequestToViewNameTranslatorbean 。若是默认的 DefaultRequestToViewNameTranslator 配置已能知足你的需求,那么你无需配置,Spring Web MVC的 DispatcherServlet 会为你实例化这样一个默认的对象。
固然,若是你须要更改默认的设置,那你就须要手动地配置本身的 DefaultRequestToViewNameTranslatorbean 。关于可配置属性的一些详细信息,你能够去咨询 DefaultRequestToViewNameTranslator 类详细的java文档。
一个好的HTTP缓存策略能够极大地提升一个web应用的性能及客户端的体验。谈到HTTP缓存,它主要是与HTTP的响应头 'Cache-Control' 相关,其次另外的一些响应头好比 'Last-Modified' 和 'ETag' 等也会起必定的做用。
HTTP的响应头 'Cache-Control' 主要帮助私有缓存(好比浏览器端缓存)和公共缓存(好比代理端缓存)了解它们应该若是缓存HTTP响应,以便后用。
ETag(实体标签)是一个HTTP响应头,可由支持HTTP/1.1的web应用服务器设置返回,主要用于标识给定的URL下的内容有无变化。能够认为它是 Last-Modified 头的一个更精细的后续版本。当服务器端返回了一个ETag头的资源表示时,客户端就能够在后续的GET请求中使用这个表示,通常是将它放在If-None-Match
请求头中。此时若内容没有变化,服务器端会直接返回 304: 内容未更改
。
这一节将讲解其余一些在Spring Web MVC应用中配置HTTP缓存的方法。
Spring MVC提供了许多方式来配置 "Cache-Control" 请求头,支持在许多场景下使用它。关于该请求头完整详尽的全部用法,你能够参考RFC 7234的第5.2.2小节,这里咱们只讲解最经常使用的几种用法。
Spring MVC的许多API中都使用了这样的惯例配置: setCachePeriod(int seconds) ,若返回值为:
-1
,则框架不会生成一个'Cache-Control'
缓存控制指令响应头0
,则指示禁止使用缓存,服务器端返回缓存控制指令'Cache-Control: no-store'
n > 0
的值,则响应会被缓存n
秒,并返回缓存控制指令'Cache-Control: max-age=n'
CacheControl
构造器类被简单的用来描述"Cache-Control"缓存控制指令,使你能更容易地建立本身的HTTP缓存策略。建立完了之后,CacheControl
类的实例就能够在Spring MVC的许多API中被传入为方法参数了。
// 缓存一小时 - "Cache-Control: max-age=3600" CacheControl ccCacheOneHour = CacheControl.maxAge(1, TimeUnit.HOURS); // 禁止缓存 - "Cache-Control: no-store" CacheControl ccNoStore = CacheControl.noStore(); // 缓存十天,对全部公共缓存和私有缓存生效 // 响应不能被公共缓存改变 // "Cache-Control: max-age=864000, public, no-transform" CacheControl ccCustom = CacheControl.maxAge(10, TimeUnit.DAYS) .noTransform().cachePublic();
为优化站点性能,静态资源应该带有恰当的 'Cache-Control' 值与其余必要的头。配置一个ResourceHttpRequestHandler
处理器服务静态资源请求不只会读取文件的元数据并填充
'Last-Modified'
头的值,正确配置时 'Cache-Control' 头也会被填充。【这段翻得还不是很清晰】
你能够设置 ResourceHttpRequestHandler 上的 cachePeriod 属性值,或使用一个 CacheControl 实例来支持更细致的指令:
@Configuration @EnableWebMvc public class WebConfig extends WebMvcConfigurerAdapter { @Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler("/resources/**") .addResourceLocations("/public-resources/") .setCacheControl(CacheControl.maxAge(1, TimeUnit.HOURS).cachePublic()); } }
XML中写法则以下:
<mvc:resources mapping="/resources/**" location="/public-resources/"> <mvc:cache-control max-age="3600" cache-public="true"/> </mvc:resources>
控制器能处理带有 'Cache-Control'、'ETag' 及/或 'If-Modified-Since' 头的请求,若是服务端在响应中设置了 'Cache-Control' 'Cache-Control' 响应头,那么咱们推荐在控制器内对这些请求头进行处理。这涉及一些工做:计算最后更改时间long
和/或请求的ETag值、与请求头的 'If-Modified-Since' 值作比较,而且在资源未更改的状况下在响应中返回一个304(资源未更改)状态码。
正如在"使用HttpEntity"一节中所讲,控制器能够经过 HttpEntity 类与请求/响应交互。返回 ResponseEntity 的控制器能够在响应中包含HTTP缓存的信息,以下代码所示:
@RequestMapping("/book/{id}") public ResponseEntity<Book> showBook(@PathVariable Long id) { Book book = findBook(id); String version = book.getVersion(); return ResponseEntity .ok() .cacheControl(CacheControl.maxAge(30, TimeUnit.DAYS)) .eTag(version) // 这里也能操做最后修改时间lastModified,只不过没有一一展现 .body(book); }
这样作不只会在响应头中设置'ETag'
及'Cache-Control'
相关的信息,同时也会 尝试将响应状态码设置为HTTP 304 Not Modified
(资源未修改)及将响应体置空——若是客户端携带的请求头信息与控制器设置的缓存信息可以匹配的话。
若是但愿在 @RequestMapping 方法上也能完成一样的事,那么你能够这样作:
@RequestMapping public String myHandleMethod(WebRequest webRequest, Model model) { long lastModified = // 1. 应用相关的方式计算获得(application-specific calculation) if (request.checkNotModified(lastModified)) { // 2. 快速退出 — 不须要更多处理了 return null; } // 3. 若资源更改了,那么再进行请求处理阶段,通常而言是准备响应内容 model.addAttribute(...); return "myViewName"; }
这里最重要的两个地方是:调用 request.checkNotModified(lastModified) 方法,以及返回 null 。前者(方法调用)在返回 true 以前会将响应状态码设为304;然后者,在检查是否更改的方法调用返回 true 的基础上直接将方法返回,这会通知Spring MVC再也不对请求作任何处理。
另外要注意的是,检查资源是否发生了更改有3种方式:
对ETag的支持是由Servlet的过滤器 ShallowEtagHeaderFilter 提供的。它是纯Servlet技术实现的过滤器,所以,它能够与任何web框架无缝集成。 ShallowEtagHeaderFilter 过 滤器会建立一个咱们称为弱ETag(与强ETag相对,后面会详述)的对象。过滤器会将渲染的JSP页面的内容(包括其余类型的内容)缓存起来,而后以此 生成一个MD5哈希值,并把这个值做为ETag头的值写回响应中。下一次客户端再次请求这个一样的资源时,它会将这个ETag的值写到If-None-Match
头中。过滤器会检测到这个请求头,而后再次把视图渲染出来并比较两个哈希值。若是比较的结果是相同的,那么服务器会返回一个304
。正如你所见,视图仍然会被渲染,所以本质上这个过滤器并不是节省任何计算资源。惟一节省的东西,是带宽,由于被渲染的响应不会被整个发送回客户端。
请注意,这个策略节省的是网络带宽,而非CPU。由于对于每一个请求,完整的响应仍然须要被整个计算出来。而其余在控制器层级实现的策略(上几节所述的)能够同时节省网络带宽及避免多余计算。
你能够在 web.xml 中配置 ShallowEtagHeaderFilter :
<filter> <filter-name>etagFilter</filter-name> <filter-class>org.springframework.web.filter.ShallowEtagHeaderFilter</filter-class> </filter> <filter-mapping> <filter-name>etagFilter</filter-name> <servlet-name>petclinic</servlet-name> </filter-mapping>
若是是在Servlet 3.0以上的环境下,能够这么作:
public class MyWebAppInitializer extends AbstractDispatcherServletInitializer { // ... @Override protected Filter[] getServletFilters() { return new Filter[] { new ShallowEtagHeaderFilter() }; } }
更多配置细节,请参考第21.15 基于代码的Servlet容器初始化一小节。
在Servlet 3.0以上的环境下,你能够经过编程的方式来配置Servlet容器了。你能够彻底放弃web.xml
,也能够两种配置方式同时使用。如下是一个注册 DispatcherServlet 的例子:
import org.springframework.web.WebApplicationInitializer; public class MyWebApplicationInitializer implements WebApplicationInitializer { @Override public void onStartup(ServletContext container) { XmlWebApplicationContext appContext = new XmlWebApplicationContext(); appContext.setConfigLocation("/WEB-INF/spring/dispatcher-config.xml"); ServletRegistration.Dynamic registration = container.addServlet("dispatcher", new DispatcherServlet(appContext)); registration.setLoadOnStartup(1); registration.addMapping("/"); } }
Spring MVC提供了一个 WebApplicationInitializer 接口,实现这个接口能保证你的配置能自动被检测到并应用于Servlet 3容器的初始化中。 WebApplicationInitializer 有一个实现,是一个抽象的基类,名字叫 AbstractDispatcherServletInitializer 。有了它,要配置 DispatcherServlet 将变得更简单,你只须要覆写相应的方法,在其中提供 servlet 映射、 DispatcherServlet 所需配置的位置便可:
public class MyWebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer { @Override protected Class<?>[] getRootConfigClasses() { return null; } @Override protected Class<?>[] getServletConfigClasses() { return new Class[] { MyWebConfig.class }; } @Override protected String[] getServletMappings() { return new String[] { "/" }; } }
以上的例子适用于使用基于Java配置的Spring应用。若是你使用的是基于XML的Spring配置方式,那么请直接继承 AbstractDispatcherServletInitializer 这个类:
public class MyWebAppInitializer extends AbstractDispatcherServletInitializer { @Override protected WebApplicationContext createRootApplicationContext() { return null; } @Override protected WebApplicationContext createServletApplicationContext() { XmlWebApplicationContext cxt = new XmlWebApplicationContext(); cxt.setConfigLocation("/WEB-INF/spring/dispatcher-config.xml"); return cxt; } @Override protected String[] getServletMappings() { return new String[] { "/" }; } }
AbstractDispatcherServletInitializer 一样也提供了便捷的方式来添加过滤器 Filter 实例并使他们自动被映射到 DispatcherServlet 下:
public class MyWebAppInitializer extends AbstractDispatcherServletInitializer { // ... @Override protected Filter[] getServletFilters() { return new Filter[] { new HiddenHttpMethodFilter(), new CharacterEncodingFilter() }; } }
每一个过滤器被添加时,默认的名称都基于其类类型决定,而且它们会被自动地映射到DispatcherServlet
下。
关于异步支持, AbstractDispatcherServletInitializer 的保护方法 isAsyncSupported 提供了一个集中的地方来开关 DispatcherServlet 上的这个配置,它会对全部映射到这个分发器上的过滤器生效。默认状况下,这个标志被设为 true 。
最后,若是你须要对 DispatcherServlet 作进一步的定制,你能够覆写 createDispatcherServlet 这个方法。
21.2.1 WebApplicationContext中特殊的bean类型小节和21.2.2 默认的DispatcherServlet配置小节解释了何谓Spring MVC的特殊bean,以及 DispatcherServlet 所使用的默认实现。在这小节中,你将了解配置Spring MVC的其余两种方式:MVC Java编程配置,以及MVC XML命名空间。
MVC Java编程配置和MVC命名空间都提供了类似的默认配置,以覆写 DispatcherServlet 的默认值。目标在于为大多数应用软件免去建立相同配置的麻烦,同时也想为配置Spring MVC提供一个更易理解的指南、一个简单的开始点,它只须要不多或不需任何关于底层配置的知识。
你 能够选用MVC Java编程配置或MVC命名空间的方式,这彻底取决于你的喜爱。若你能读完后面的小节,你会发现使用MVC Java编程配置的方式能更容易看到底层具体的配置项,而且能对建立的Spring MVC bean有更细粒度的定制空间。不过,咱们仍是从头来看起吧。
要启用MVC Java编程配置,你须要在其中一个注解了 @Configuration 的类上添加 @EnableWebMvc 注解:
@Configuration @EnableWebMvc public class WebConfig { }
要启用XML命名空间,请在你的 DispatcherServlet 上下文中(若是没有定义任何 DispatcherServlet 上下文,那么就在根上下文中)添加一个 mvc:annotation-driven 元素:
<?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/> </beans>
上面的简单的声明代码,就已经默认注册了一个R equestMappingHandlerMapping
、一个 RequestMappingHandlerAdapter ,以及一个 ExceptionHandlerExceptionResolver ,以支持对使用了 @RequestMapping、@ExceptionHandler 及其余注解的控制器方法的请求处理。
同时,上面的代码还启用了如下的特性:
下面给出了一份由 mvc:annotation-driven 注册可用的HTTP消息转换器的完整列表:
ByteArrayHttpMessageConverter
StringHttpMessageConverter
ResourceHttpMessageConverter
:org.springframework.core.io.Resource
与全部媒体类型之间的互相转换SourceHttpMessageConverter
:从(到)javax.xml.transform.Source
的转换FormHttpMessageConverter
:数据与MultiValueMap<String, String>
之间的互相转换Jaxb2RootElementHttpMessageConverter
:Java对象与XML之间的互相转换——该转换器在classpath路径下有JAXB2依赖而且没有Jackson 2 XML扩展时被注册MappingJackson2HttpMessageConverter
:从(到)JSON的转换——该转换器在classpath下有Jackson 2依赖时被注册MappingJackson2XmlHttpMessageConverter
:从(到)XML的转换——该转换器在classpath下有Jackson 2 XML扩展时被注册AtomFeedHttpMessageConverter
:Atom源的转换——该转换器在classpath路径下有Rome时被注册RssChannelHttpMessageConverter
:RSS源的转换——该转换器在classpath路径下有Rome时被注册你能够参考21.16.12 消息转换器一小节,了解如何进一步定制这些默认的转换器。
Jackson JSON和XML转换器是经过
Jackson2ObjectMapperBuilder
建立的ObjectMapper
实例建立的,目的在于提供更好的默认配置该builder会使用如下的默认属性对Jackson进行配置:
同时,若是检测到在classpath路径下存在这些模块,该builder也会自动地注册它们:
jackson-datatype-jdk7: 支持Java 7的一些类型,例如
java.nio.file.Path
jackson-datatype-joda: 支持Joda-Time类型
jackson-datatype-jsr310: 支持Java 8的Date & Time API类型
jackson-datatype-jdk8: 支持Java 8其余的一些类型,好比
Optional
等
在MVC Java编程配置方式下,若是你想对默认配置进行定制,你能够本身实现WebMvcConfigurer
接口,要么继承WebMvcConfigurerAdapter
类并覆写你须要定制的方法:
@Configuration @EnableWebMvc public class WebConfig extends WebMvcConfigurerAdapter { // Override configuration methods... }
在MVC XML命名空间下,若是你想对默认配置进行定制,请查看 <mvc:annotation-driven/> 元素支持的属性和子元素。你能够查看Spring MVC XML schema,或使用IDE的自动补全功能来查看有哪些属性和子元素是能够配置的。
数字的Number
类型和日期Date
类型的格式化是默认安装了的,包括 @NumberFormat 注解和@ DateTimeFormat
注解。若是classpath路径下存在Joda Time依赖,那么完美支持Joda Time的时间格式化库也会被安装好。若是要注册定制的格式化器或转换器,请覆写addFormatters
方法:
@Configuration @EnableWebMvc public class WebConfig extends WebMvcConfigurerAdapter { @Override public void addFormatters(FormatterRegistry registry) { // Add formatters and/or converters } }
使用MVC命名空间时, <mvc:annotation-driven> 也会进行一样的默认配置。要注册定制的格式化器和转换器,只须要提供一个转换服务 ConversionService :
<?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 conversion-service="conversionService"/> <bean id="conversionService" class="org.springframework.format.support.FormattingConversionServiceFactoryBean"> <property name="converters"> <set> <bean class="org.example.MyConverter"/> </set> </property> <property name="formatters"> <set> <bean class="org.example.MyFormatter"/> <bean class="org.example.MyAnnotationFormatterFactory"/> </set> </property> <property name="formatterRegistrars"> <set> <bean class="org.example.MyFormatterRegistrar"/> </set> </property> </bean> </beans>
关于如何使用格式化管理器 FormatterRegistrar ,请参考 8.6.4 FormatterRegistrar SPI一节,以及 FormattingConversionServiceFactoryBean 的文档。
Spring提供了一个验证器Validator接口,应用的任何一层均可以使用它来作验证。在Spring MVC中,你能够配置一个全局的 Validator 实例,用以处理全部注解了 @Valid 的元素或注解了 @Validated 的控制器方法参数、以及/或在控制器内的 @InitBinder 方法中用做局部的 Validator 。全局验证器与局部验证器实例能够结合起来使用,提供组合验证。
Spring还支持JSR-303/JSR-349的Bean验证。这是经过 LocalValidatorFactoryBean 类实现的,它为Spring的验证器接口 org.springframework.validation.Validator 到 Bean 验证的 javax.validation.Validator
接口作了适配。这个类能够插入到Spring MVC的上下文中,做为一个全局的验证器,以下所述。
若是在classpath下存在Bean验证器,诸如Hibernate Validator等,那么 @EnableWebMvc 或 <mvc:annotation-driven> 默认会自动使用 LocalValidatorFactoryBean 为Spring MVC应用提供Bean验证的支持。
有时,能将 LocalValidatorFactoryBean 直接注入到控制器或另一个类中会更方便。
Sometimes it's convenient to have a LocalValidatorFactoryBean injected into a controller or another class. The easiest way to do that is to declare your own @Bean and also mark it with @Primary in order to avoid a conflict with the one provided with the MVC Java config.
If you prefer to use the one from the MVC Java config, you'll need to override the
mvcValidator
method from WebMvcConfigurationSupport and declare the method to explicitly return LocalValidatorFactory rather than Validator . See Section 21.16.13, "Advanced Customizations with MVC Java Config" for information on how to switch to extend the provided configuration.
此外,你也能够配置你本身的全局 Validator 验证器实例:
@Configuration @EnableWebMvc public class WebConfig extends WebMvcConfigurerAdapter { @Override public Validator getValidator(); { // return "global" validator } }
XML中作法以下:
<?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 validator="globalValidator"/> </beans>
若要同时使用全局验证和局部验证,只需添加一个(或多个)局部验证器便可:
@Controller public class MyController { @InitBinder protected void initBinder(WebDataBinder binder) { binder.addValidators(new FooValidator()); } }
作完这个最少的配置以后,任什么时候候只要方法中有参数注解了 @Valid 或 @Validated ,配置的验证器就会自动对它们作验证。任何没法经过的验证都会被自动报告为错误并添加到 BindingResult 对象中去,你能够在方法参数中声明它并获取这些错误,同时这些错误也能在Spring MVC的HTML视图中被渲染。
你能够配置处理器拦截器 HandlerInterceptors 或web请求拦截器 WebRequestInterceptors 等拦截器,并配置它们拦截全部进入容器的请求,或限定到符合特定模式的URL路径。
在MVC Java编程配置下注册拦截器的方法:
@Configuration @EnableWebMvc public class WebConfig extends WebMvcConfigurerAdapter { @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(new LocaleInterceptor()); registry.addInterceptor(new ThemeInterceptor()).addPathPatterns("/**").excludePathPatterns("/admin/**"); registry.addInterceptor(new SecurityInterceptor()).addPathPatterns("/secure/*"); } }
在MVC XML命名空间下,则使用 <mvc:interceptors> 元素:
<mvc:interceptors> <bean class="org.springframework.web.servlet.i18n.LocaleChangeInterceptor"/> <mvc:interceptor> <mvc:mapping path="/**"/> <mvc:exclude-mapping path="/admin/**"/> <bean class="org.springframework.web.servlet.theme.ThemeChangeInterceptor"/> </mvc:interceptor> <mvc:interceptor> <mvc:mapping path="/secure/*"/> <bean class="org.example.SecurityInterceptor"/> </mvc:interceptor> </mvc:interceptors>
You can configure how Spring MVC determines the requested media types from the request. The available options are to check the URL path for a file extension, check the "Accept" header, a specific query parameter, or to fall back on a default content type when nothing is requested. By default the path extension in the request URI is checked first and the "Accept" header is checked second.
The MVC Java config and the MVC namespace register json
, xml
, rss
, atom
by default if corresponding dependencies are on the classpath. Additional path extension-to-media type mappings may also be registered explicitly and that also has the effect of whitelisting them as safe extensions for the purpose of RFD attack detection (see the section called "Suffix Pattern Matching and RFD" for more detail).
Below is an example of customizing content negotiation options through the MVC Java config:
_@Configuration_ _@EnableWebMvc_ public class WebConfig extends WebMvcConfigurerAdapter { _@Override_ public void configureContentNegotiation(ContentNegotiationConfigurer configurer) { configurer.mediaType("json", MediaType.APPLICATION_JSON); } }
In the MVC namespace, the <mvc:annotation-driven> element has a content- negotiation-manager attribute, which expects a ContentNegotiationManager that in turn can be created with a ContentNegotiationManagerFactoryBean :
<mvc:annotation-driven content-negotiation-manager="contentNegotiationManager"/> <bean id="contentNegotiationManager" class="org.springframework.web.accept.ContentNegotiationManagerFactoryBean"> <property name="mediaTypes"> <value> json=application/json xml=application/xml </value> </property> </bean>
If not using the MVC Java config or the MVC namespace, you'll need to create an instance of ContentNegotiationManager and use it to configure RequestMappingHandlerMapping for request mapping purposes, and RequestMappingHandlerAdapter and ExceptionHandlerExceptionResolver for content negotiation purposes.
Note that ContentNegotiatingViewResolver now can also be configured with a ContentNegotiationManager , so you can use one shared instance throughout Spring MVC.
In more advanced cases, it may be useful to configure multiple ContentNegotiationManager instances that in turn may contain custom ContentNegotiationStrategy
implementations. For example you could configure ExceptionHandlerExceptionResolver with a ContentNegotiationManager
that always resolves the requested media type to "application/json" . Or you may want to plug a custom strategy that has some logic to select a default content type (e.g. either XML or JSON) if no content types were requested.
如下的一段代码至关于定义一个 ParameterizableViewController 视图控制器的快捷方式,该控制器会当即将一个请求转发(forwards)给一个视图。请确保仅在如下情景下才使用这个类:当控制器除了将视图渲染到响应中外不须要执行任何逻辑时。
如下是一个例子,展现了如何在MVC Java编程配置方式下将全部"/"
请求直接转发给名字为"home"
的视图:
@Configuration @EnableWebMvc public class WebConfig extends WebMvcConfigurerAdapter { @Override public void addViewControllers(ViewControllerRegistry registry) { registry.addViewController("/").setViewName("home"); } }
在MVC XML命名空间下完成一样的配置,则使用 <mvc:view-controller> 元素:
<mvc:view-controller path="/" view-name="home"/>
MVC提供的配置简化了视图解析器的注册工做。
如下的代码展现了在MVC Java编程配置下,如何为内容协商配置FreeMarker HTML模板和Jackson做为JSON数据的默认视图解析:
@Configuration @EnableWebMvc public class WebConfig extends WebMvcConfigurerAdapter { @Override public void configureViewResolvers(ViewResolverRegistry registry) { registry.enableContentNegotiation(new MappingJackson2JsonView()); registry.jsp(); } }
在MVC XML命名空间下实现相同配置:
<mvc:view-resolvers> <mvc:content-negotiation> <mvc:default-views> <bean class="org.springframework.web.servlet.view.json.MappingJackson2JsonView"/> </mvc:default-views> </mvc:content-negotiation> <mvc:jsp/> </mvc:view-resolvers>
须要注意的是,使用FreeMarker, Velocity, Tiles, Groovy Markup及script模板做为视图技术时,仍须要配置一些其余选项。
MVC命名空间为每种视图都提供了相应的元素。好比下面代码是FreeMarker须要的配置:
<mvc:view-resolvers> <mvc:content-negotiation> <mvc:default-views> <bean class="org.springframework.web.servlet.view.json.MappingJackson2JsonView"/> </mvc:default-views> </mvc:content-negotiation> <mvc:freemarker cache="false"/> </mvc:view-resolvers> <mvc:freemarker-configurer> <mvc:template-loader-path location="/freemarker"/> </mvc:freemarker-configurer>
在MVC Java编程配置方式下,添加一个视图对应的“配置器” bean 便可:
@Configuration @EnableWebMvc public class WebConfig extends WebMvcConfigurerAdapter { @Override public void configureViewResolvers(ViewResolverRegistry registry) { registry.enableContentNegotiation(new MappingJackson2JsonView()); registry.freeMarker().cache(false); } @Bean public FreeMarkerConfigurer freeMarkerConfigurer() { FreeMarkerConfigurer configurer = new FreeMarkerConfigurer(); configurer.setTemplateLoaderPath("/WEB-INF/"); return configurer; } }
这些配置容许你将 DispatcherServlet 映射到"/"路径(也即覆盖了容器默认Servlet的映射),但依然保留容器默认的Servlet以处理静态资源的请求。这能够经过配置一个URL映射到"/**"的处理器 DefaultServletHttpRequestHandler 来实现,而且该处理器在其余全部URL映射关系中优先级应该是最低的。
该处理器会将全部请求转发(forward)到默认的Servlet,所以须要保证它在全部URL处理器映射 HandlerMappings 的最后。若是你是经过 <mvc:annotation-driven> 的方式进行配置,或本身定制了 HandlerMapping 实例,那么你须要确保该处理器order
属性的值比 DefaultServletHttpRequestHandler 的次序值 Integer.MAXVALUE 小。
使用默认的配置启用该特性,你能够:
@Configuration @EnableWebMvc public class WebConfig extends WebMvcConfigurerAdapter { @Override public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) { configurer.enable(); } }
XML命名空间只需一行:
<mvc:default-servlet-handler/>
不过须要注意,覆写了"/"的Servlet映射后,默认Servlet的 RequestDispatcher 就必须经过名字而非路径来取得了。 DefaultServletHttpRequestHandler 会 尝试在容器初始化的时候自动检测默认Servlet,这里它使用的是一份主流Servlet容器(包括Tomcat、Jetty、GlassFish、 JBoss、Resin、WebLogic,和WWebSphere)已知的名称列表。若是默认Servlet被配置了一个其余的名字,或者使用了一个列 表里未提供默认Servlet名称的容器,那么默认Servlet的名称必须被显式指定。正以下面代码所示:
@Configuration @EnableWebMvc public class WebConfig extends WebMvcConfigurerAdapter { @Override public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) { configurer.enable("myCustomDefaultServlet"); } }
XML命名空间的配置方式:
<mvc:default-servlet-handler default-servlet-name="myCustomDefaultServlet"/>
这些配置容许你对许多与URL映射和路径匹配有关的设置进行定制。关于全部可用的配置选项,请参考PathMatchConfigurer类的API文档。
下面是采用MVC Java编程配置的一段代码:
@Configuration @EnableWebMvc public class WebConfig extends WebMvcConfigurerAdapter { @Override public void configurePathMatch(PathMatchConfigurer configurer) { configurer .setUseSuffixPatternMatch(true) .setUseTrailingSlashMatch(false) .setUseRegisteredSuffixPatternMatch(true) .setPathMatcher(antPathMatcher()) .setUrlPathHelper(urlPathHelper()); } @Bean public UrlPathHelper urlPathHelper() { //... } @Bean public PathMatcher antPathMatcher() { //... } }
在XML命名空间下实现一样的功能,能够使用 <mvc:path-matching> 元素:
<mvc:annotation-driven> <mvc:path-matching suffix-pattern="true" trailing-slash="false" registered-suffixes-only="true" path-helper="pathHelper" path-matcher="pathMatcher"/> </mvc:annotation-driven> <bean id="pathHelper" class="org.example.app.MyPathHelper"/> <bean id="pathMatcher" class="org.example.app.MyPathMatcher"/>
使用MVC Java编程配置方式时,若是你想替换Spring MVC提供的默认转换器,彻底定制本身的 HttpMessageConverter ,这能够经过覆写configureMessageConverters()
方法来实现。若是你只是想定制一下,或者想在默认转换器以外再添加其余的转换器,那么能够经过覆写extendMessageConverters()
方法来实现。
下面是一段例子,它使用定制的 ObjectMapper 构造了新的Jackson的JSON和XML转换器,并用它们替换了默认提供的转换器:
@Configuration @EnableWebMvc public class WebConfiguration extends WebMvcConfigurerAdapter { @Override public void configureMessageConverters(List<HttpMessageConverter<?>> converters) { Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder() .indentOutput(true) .dateFormat(new SimpleDateFormat("yyyy-MM-dd")) .modulesToInstall(new ParameterNamesModule()); converters.add(new MappingJackson2HttpMessageConverter(builder.build())); converters.add(new MappingJackson2XmlHttpMessageConverter(builder.xml().build())); } }
在上面的例子中,J ackson2ObjectMapperBuilder
用于为 MappingJackson2HttpMessageConverter 和 MappingJackson2XmlHttpMessageConverter 转换器建立公共的配置,好比启用tab缩进、定制的日期格式,并注册了一个模块jackson-module-parameter-names用于获取参数名(Java 8新增的特性)
除了
jackson- dataformat-xml
,要启用Jackson XML的tab缩进支持,还须要一个woodstox-core-asl
依赖。
还有其余有用的Jackson模块能够使用:
在XML作一样的事也是可能的:
<mvc:annotation-driven> <mvc:message-converters> <bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter"> <property name="objectMapper" ref="objectMapper"/> </bean> <bean class="org.springframework.http.converter.xml.MappingJackson2XmlHttpMessageConverter"> <property name="objectMapper" ref="xmlMapper"/> </bean> </mvc:message-converters> </mvc:annotation-driven> <bean id="objectMapper" class="org.springframework.http.converter.json.Jackson2ObjectMapperFactoryBean" p:indentOutput="true" p:simpleDateFormat="yyyy-MM-dd" p:modulesToInstall="com.fasterxml.jackson.module.paramnames.ParameterNamesModule"/> <bean id="xmlMapper" parent="objectMapper" p:createXmlMapper="true"/>
从 上面许多例子你能够看到,MVC Java编程配置和MVC命名空间的方式都提供了更高抽象层级的应用配置,它不须要你对底下建立的bean有很是深刻的了解,相反,这使得你能仅专一于应 用须要的配置。不过,有时你可能但愿对应用的更精细控制,或你就是单纯但愿理解底下的配置和机制。
要作到更精细的控制,你要作的第一步就是看看底层都为你建立了哪些bean。若你使用MVC Java编程的方式进行配置,你能够看看java文档,以及 WebMvcConfigurationSupport 类的 @Bean 方法。这个类有的配置都会自动被 @EnableWebMvc 注解导入。事实上,若是你打开 @EnableWebMvc 的声明,你就会看到应用于其上的 @Import 注解。
精细控制的下一步是选择一个 WebMvcConfigurationSupport 建立的 bean ,定制它的属性,或你能够提供本身的一个实例。这确保作到如下两步:移除 @EnableWebMvc 注解以免默认配置被自动导入,而后继承 DelegatingWebMvcConfiguration 类,它是 WebMvcConfigurationSupport 的一个子类。如下是一个例子:
@Configuration public class WebConfig extends DelegatingWebMvcConfiguration { @Override public void addInterceptors(InterceptorRegistry registry){ // ... } @Override @Bean public RequestMappingHandlerAdapter requestMappingHandlerAdapter() { // 本身建立适配器,或者调用super让基类处理 // 而后在这里定制bean的一些属性 } }
应用应该只有一个继承 DelegatingWebMvcConfiguration 的配置类,或只有一个 @EnableWebMvc 注解的类,由于它们背后注册的 bean 都是相同的。
使用这个方式修改bean的属性,与这节前面展现的任何高抽象层级的配置方式并不冲突。 WebMvcConfigurerAdapter 的子类和 WebMvcConfigurer 的实现都仍是会被使用。
若是使用MVC命名空间,要在默认配置的基础上实现粒度更细的控制,则要比使用MVC Java编程配置的方式难一些。
若是你确实须要这么作,那也尽可能不要复制默认提供的配置,请尝试配置一个 BeanPostProcessor 后置处理器,用它来检测你要定制的 bean 。能够经过 bean 的类型来找,找到之后再修改须要定制的属性值。好比这样:
@Component public class MyPostProcessor implements BeanPostProcessor { public Object postProcessBeforeInitialization(Object bean, String name) throws BeansException { if (bean instanceof RequestMappingHandlerAdapter) { // 修改适配器的属性 } } }
注意, MyPostProcessor 须要被包含在 <component scan/> 的路径下,这样它才能被自动检测到;或者你也能够手动显式地用一个XML的 bean 定义来声明它。
欢迎加群JAVA编程交流群 574337670,以为有帮助的麻烦推荐一下Ok