相关文章:
- Spring参考手册 1 Spring Framework简介和典型的Web应用程序
- Spring参考手册 2 核心技术
- Spring参考手册 3 校验,数据绑定和类型转换
- Spring参考手册 4 面向切面编程
- Spring参考手册 5 数据访问
一、Web MVC framework
1.1 Spring Web MVC framework简介
Spring Web model-view-controller (MVC) framework的核心是一个DispatcherServlet
,这个调度器负责分配请求到处理器,可实现配置处理器映射、视图解析、本地化、时区和主题解析还有上传文件的支持。默认的处理器基于@Controller
和@RequestMapping
注解,提供了一个很灵活的处理方法。在Spring 3.0的简介可了解到,@Controller
机制还允许你通过@PathVariable
注解和其他特性创建RESTful网址和应用程序。
1.2 DispatcherServlet
Spring web MVC framework与其他许多MCV框架类似,请求驱动,围绕一个中心Servlet,由它分配请求到控制器并提供其他功能来促进web应用程序开发。Spring的DispatcherServlet
然而做的要更多,它已经完全与Spring IoC容器整合,这样就允许你使用Spring的其他特性。
Spring Web MVC DispatcherServlet
的请求处理工作流通过下面的图示来说明。懂设计模式的读者会认出DispatcherServlet
使用的是一种前端控制器设计模式。
DispatcherServlet
是一个实实在在的Servlet
,这样它可以被声明在web.xml
里。你需要映射你希望DispatcherServlet
处理的请求。这是一个标准的Java EE Servlet配置:
<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>
所有以/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/*");
}
}
在初始化一个DispatcherServlet
时,Spring MVC会查找在WEB-INF文件夹下的一个名为[servlet-name]-servlet.xml的文件并在创建bean,重写全局范围内相同名字的bean。
假设有如下DispatcherServlet
配置:
<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>
你需要一个文件/WEB-INF/golfing-servlet.xml
,它将会包含所有Spring Web MVC特定的组件(beans)。也可以通过一个Servlet初始化参数来改变这个位置。
针对单一DispatcherServlet场景也可以只有一个根上下文:
<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>
1.2.1 DispatcherServlet处理顺序
在你设置一个DispatcherServlet
后,当一个请求进入DispatcherServlet
会开始处理请求:
WebApplicationContext
被搜索并且绑定请求作为一个属性这样controller和其他元素在处理过程中可以使用它。默认它被绑定在DispatcherServlet.WEB_APPLICATION_CONTEXT_ATTRIBUTE
key下。本地化解析器被绑定到这个请求。如果你不需要本地化解析那你就不需要它。
主题解析器被绑定到这个请求。如果你不使用主题你可以忽略它。
如果你指定了一个multipart文件解析器,这个请求将会被检查;如果找到了multipart这个请求会被包装成一个
MultipartHttpServletRequest
来进行进一步处理。(比如上传文件时)一个适当的处理器被搜索。如果找到了就链接到处理器来准备一个model或者渲染。
一个model被返回,视图被渲染
1.3 实现控制器
在Spring 2.5里已经介绍了一种基于注解的编程模型,使用像@RequestMapping
, @RequestParam
, @ModelAttribute
这样的注解。通过这种形式实现控制器不需要继承指定的父类或者实现指定的接口。此外它们通常也不直接依赖Servlet APIs。
@Controller
public class HelloWorldController {
@RequestMapping("/helloWorld")
public String helloWorld(Model model) {
model.addAttribute("message", "Hello World!");
return "helloWorld";
}
}
如你所见,@Controller
和 @RequestMapping
注解允许灵活的方法名。@Controller
和 @RequestMapping
和许多其他注解构成了Spring MVC实现的基础。
1.3.1 使用@Controller定义一个控制器
@Controller
注解标明一个指定的类扮演控制器的角色。Spring不要求你继承任何父控制器类或者引用Servlet API。但是如果需要你仍然可以引用Servlet的特性。
你可以在DispatcherServlet上下文中使用标准的Spring bean定义规则明确的定义一个控制器bean。但是@Controller
也允许自动检测,自动注册为bean。
为了自动检测被注解的控制器,你需要在配置文件添加组件扫描:
<?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>
1.3.2 使用@RequestMapping映射请求
你可以使用@RequestMapping
注解来映射URL就像这个/appointments
,既可以在类上也可以在指定方法上。
下面这个例子展示了Spring MVC中使用注解的控制器:
@Controller
@RequestMapping("/appointments")
public class AppointmentsController {
private final AppointmentBook appointmentBook;
@Autowired
public AppointmentsController(AppointmentBook appointmentBook) {
this.appointmentBook = appointmentBook;
}
@RequestMapping(method = RequestMethod.GET)
public Map<String, Appointment> get() {
return appointmentBook.getAppointmentsForToday();
}
@RequestMapping(value="/{day}", method = RequestMethod.GET)
public Map<String, Appointment> getForDay(@PathVariable @DateTimeFormat(iso=ISO.DATE) Date day, Model model) {
return appointmentBook.getAppointmentsForDay(day);
}
@RequestMapping(value="/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()
方法将请求映射更加细化:它只接收GET请求,HTTP GET /appointments
会调用这个方法。getNewForm()
联合了HTTP方法定义和路径,那么GET请求appointments/new
将会被这个方法处理。
getForDay()
方法展示了@RequestMapping
的另一种用法:URI 模版。(详见下一章)
@RequestMapping
定义在类级别并不是必须的。没有了它所有路径都是绝对的不是相对的。下面的例子展示了一个多处理活动的控制器:
@Controller
public class ClinicController {
private final Clinic clinic;
@Autowired
public ClinicController(Clinic clinic) {
this.clinic = clinic;
}
@RequestMapping("/")
public void welcomeHandler() {
}
@RequestMapping("/vets")
public ModelMap vetsHandler() {
return new ModelMap(this.clinic.getVets());
}
}
上面的例子并没有指定GET,PUT,POST等等,因为@RequestMapping
默认会映射所有HTTP方法。使用@RequestMapping(method = RequestMethod.POST)
缩小了映射。
URI Template模式
URI templates 可以被用于在@RequestMapping
方法中方便的访问URL中被选中的一部分。
一个URI Template是一个类似URI的字符串,包含了一个或多个变量的名字。当你替换了这些变量模版就会变成一个URI。例如http://www.example.com/users/{userId}包含变量userId。指定fred为变量的值就变成http://www.example.com/users/fred
在Spring MVC你可以用@PathVariable
注解来绑定一个URI template 的变量:
@RequestMapping(value="/owners/{ownerId}", method=RequestMethod.GET)
public String findOwner(@PathVariable String ownerId, Model model) {
Owner owner = ownerService.findOwner(ownerId);
model.addAttribute("owner", owner);
return "displayOwner";
}
当一个请求/owners/fred
到来后,String ownerId的值就是fred。
你还可以指定URI template变量的名字:
@RequestMapping(value="/owners/{ownerId}", method=RequestMethod.GET)
public String findOwner(@PathVariable("ownerId") String theOwner, Model model) {
// implementation omitted
}
当URI template变量的名字与方法参数的名字相同时可以省略@PathVariable
注解中的值。
一个方法可以有任意数量的@PathVariable
注解:
@RequestMapping(value="/owners/{ownerId}/pets/{petId}", method=RequestMethod.GET)
public String findPet(@PathVariable String ownerId, @PathVariable String petId, Model model) {
Owner owner = ownerService.findOwner(ownerId);
Pet pet = owner.getPet(petId);
model.addAttribute("pet", pet);
return "displayPet";
}
当一个@PathVariable
注解被用在一个Map<String, String>
参数,这个map会被所有URI template变量填充。
路径模式
除了URI template外,@RequestMapping
注解还支持Ant-style路径,例如/myPath/*.do
。也支持URI template变量和Ant-style联合使用,例如:/owners/*/pets/{petId}
。
消费媒体类型
你可以通过指定一系列的消费媒体类型缩窄主映射范围。只有请求头的Content-Type
与指定的媒体类型匹配时请求才会被接受,例如:
@Controller
@RequestMapping(value = "/pets", method = RequestMethod.POST, consumes="application/json")
public void addPet(@RequestBody Pet pet, Model model) {
// implementation omitted
}
只有当请求头的Content-Type
包含application/json
时这个请求才会被接受并处理。消费类型表达式可以是否定的,例如!text/plain
,这将会匹配所有Content-Type
不包含text/plain
的请求。
生产媒体类型
你可以通过指定一系列的生成媒体类型缩窄主映射范围。只有请求头的Accept
与其中一个媒体类型匹配时请求才会被接受,例如:
@Controller
@RequestMapping(value = "/pets/{petId}", method = RequestMethod.GET, produces="application/json")
@ResponseBody
public Pet getPet(@PathVariable String petId, Model model) {
// implementation omitted
}
与消费媒体类型类似,生产类型表达式可以是否定的,例如!text/plain
,这将会匹配所有Accept
不包含text/plain
的请求。
请求参数和请求头
你可以通过请求参数条件缩窄请求映射范围,例如"myParam", "!myParam"或者"myParam=myValue"。前两个是测试参数是否存在的,第三个是指定一个参数的值。下面是一个例子:
@Controller
@RequestMapping("/owners/{ownerId}")
public class RelativePathUriTemplateController {
@RequestMapping(value = "/pets/{petId}", method = RequestMethod.GET, params="myParam=myValue")
public void findPet(@PathVariable String ownerId, @PathVariable String petId, Model model) {
// implementation omitted
}
}
这个例子里,只有请求的参数里有myParam
并且它的值为myParam时才会被这个方法处理。同样的方法可以用来测试请求头:
@Controller
@RequestMapping("/owners/{ownerId}")
public class RelativePathUriTemplateController {
@RequestMapping(value = "/pets", method = RequestMethod.GET, headers="myHeader=myValue")
public void findPet(@PathVariable String ownerId, @PathVariable String petId, Model model) {
// implementation omitted
}
}
1.3.3 定义@RequestMapping处理方法
一个@RequestMapping
处理方法可以拥有一个非常灵活的签名。
支持的参数类型
下面是支持的方法参数:
- Request 或者 response对象 (Servlet API)
- Session 对象 (Servlet API),这个参数永远不会是
null
-
org.springframework.web.context.request.WebRequest
或者org.springframework.web.context.request.NativeWebRequest
。允许通用的请求参数访问,就像request/session属性访问一样,但是没有被束缚于Servlet/Portlet API -
java.util.Locale
当前请求的本地化 - 用来访问请求内容的
java.io.InputStream
/java.io.Reader
- 用来生产响应内容的
java.io.OutputStream
/java.io.Writer
-
org.springframework.http.HttpMethod
HTTP请求方法 -
java.security.Principal
当前已验证的用户 -
@PathVariable
注解的参数 -
@MatrixVariable
注解的参数 -
@RequestParam
注解的参数 -
@RequestHeader
注解的参数,用来访问HTTP请求头。 -
@RequestBody
注解的参数,用来访问HTTP请求体 -
@RequestPart
注解的参数,用来访问一个请求的multipart/form-data部分 -
HttpEntity<?>
参数用来访问HTTP请求头和内容。 -
java.util.Map
/org.springframework.ui.Model
/org.springframework.ui.ModelMap
传递值给web视图 -
org.springframework.web.servlet.mvc.support.RedirectAttributes
在请求重定向时传递属性,如果一个方发返回一个"redirect:"前缀的视图或者RedirectView
-
@ModelAttribute
注解,用来绑定请求的参数到一个实体类,例如一个User类 -
org.springframework.validation.Errors
/org.springframework.validation.BindingResult
上面实体类的校验结果 -
org.springframework.web.bind.support.SessionStatus
标记表单处理状态为完成 -
org.springframework.web.util.UriComponentsBuilder
构建当前请求的host, port, scheme, context path
需要注意的是Errors
或者 BindingResult
需要紧跟着实体类。这个例子将不会正常工作:
@RequestMapping(method = RequestMethod.POST)
public String processSubmit(@ModelAttribute("pet") Pet pet, Model model, BindingResult result) { ... }
下面这个例子才是正确的:
@RequestMapping(method = RequestMethod.POST)
public String processSubmit(@ModelAttribute("pet") Pet pet, BindingResult result, Model model) { ... }
支持的返回类型
下面是支持的返回类型:
- 一个
ModelAndView
对象 - 一个
Model
对象 - 一个
Map
对象 - 一个
View
对象 - 一个
String
对象,它可以被理解为一个本地视图名称 -
void
,如果方法自己处理响应 - 如果一个方法上有
@ResponseBody
注解,返回类型将被写到响应体内,如果配置了JSON转换器,返回的内容还会被转换成JSON字符串。 - 一个
HttpEntity<?>
或者ResponseEntity<?>
对象,可以用来访问HTTP响应头和内容 - 一个
HttpHeaders
对象,返回一个没有响应体的response - 一个
Callable<?>
可以被返回当应用程序想在一个Spring MVC管理的线程里生产异步的返回值 - 一个
DeferredResult<?>
或者ListenableFuture<?>
可以被返回当应用程序想在一个它自己选择的线程里生产异步的返回值 - 任何其他的返回值都会被当做一个model的属性暴露给视图
使用@RequestParam绑定请求参数
在controller里使用@RequestParam
注解可以绑定一个请求参数到一个方法的参数。
下面的展示了使用方法:
@Controller
@RequestMapping("/pets")
@SessionAttributes("pet")
public class EditPetForm {
// ...
@RequestMapping(method = RequestMethod.GET)
public String setupForm(@RequestParam("petId") int petId, ModelMap model) {
Pet pet = this.clinic.loadPet(petId);
model.addAttribute("pet", pet);
return "petForm";
}
// ...
}
这个注解的参数默认是必须存在的,但是你可以指定它为可选的,例如:@RequestParam(value="petId", required=false)
如果方法参数的类型不是String
,那么将会自动进行类型转换。
如果@RequestParam
注解使用在一个Map<String, String>
或者 MultiValueMap<String, String>
参数上,请求里所有的参数都会被填充进来。
使用@RequestBody注解映射请求体
@RequestBody
注解声明一个方法的参数被绑定了HTTP请求体的值。
@RequestMapping(value = "/something", method = RequestMethod.PUT)
public void handle(@RequestBody String body, Writer writer) throws IOException {
writer.write(body);
}
HttpMessageConverter
负责转换HTTP请求信息到一个对象也可以把一个对象转换为HTTP响应体。
下面是几个默认的HttpMessageConverter
:
-
ByteArrayHttpMessageConverter
转换 byte 数组 -
StringHttpMessageConverter
转换 字符串 -
FormHttpMessageConverter
转换表单数据 到/从 一个MultiValueMap<String, String>
-
SourceHttpMessageConverter
转换 到/从javax.xml.transform.Source
JSON转换器(译者增加):
-
FastJsonHttpMessageConverter
使用FastJson转换
相关配置:
<bean id="fastJsonHttpMessageConverter"
class="com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter">
<property name="supportedMediaTypes">
<list>
<value>text/html;charset=UTF-8</value>
<value>application/json</value>
</list>
</property>
<property name="features">
<list>
<value>WriteMapNullValue</value>
<value>QuoteFieldNames</value>
</list>
</property>
</bean>
<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter">
<property name="messageConverters">
<list >
<ref bean="fastJsonHttpMessageConverter" />
</list>
</property>
</bean>
使用@ResponseBody注解映射响应体
与@RequestBody
相似。这个注解表明返回类型要被直接写入响应体中:
@RequestMapping(value = "/something", method = RequestMethod.PUT)
@ResponseBody
public String helloWorld() {
return "Hello World";
}
当然Spring会使用HttpMessageConverter
转换对象,然后才写入相应体,如果配置的是FastJsonHttpMessageConverter
那么对象将会被转换成JSON字符串写入响应体。
使用@RestController注解创建REST Controllers
简单的讲就是将一个Controller所有的@RequestMapping
方法自动加上@ResponseBody
。
使用@RequestHeader注解映射请求头
@RequestHeader
注解允许一个方法参数绑定一个请求头。
@RequestMapping("/displayHeaderInfo.do")
public void displayHeaderInfo(@RequestHeader("Accept-Encoding") String encoding,
@RequestHeader("Keep-Alive") long keepAlive) {
//...
}
当@RequestHeader
注解在一个Map<String, String>
, MultiValueMap<String, String>
, 或者 HttpHeaders
时会填充所有请求头。
使用@ControllerAdvice注解为controller增加全局处理
@ControllerAdvice
注解是一个组件注解,允许实现的类被自动发现。当使用MVC命名空间时它会自动启用。它注解的
类里可以包含@ExceptionHandler
, @InitBinder
和@ModelAttribute
注解的方法,这些方法将会应用到相应的@RequestMapping
方法上。
@ControllerAdvice
注解还允许匹配controller的一部分子集:
// 匹配所有@RestController注解的Controller
@ControllerAdvice(annotations = RestController.class)
public class AnnotationAdvice {}
// 匹配指定包下的Controller
@ControllerAdvice("org.example.controllers")
public class BasePackageAdvice {}
// 匹配指定Controller类
@ControllerAdvice(assignableTypes = {ControllerInterface.class, AbstractController.class})
public class AssignableTypesAdvice {}
1.4 解析视图
所有的MVC框架针对于web应用程序都提供了一种方法来处理视图。Spring提供了视图解析器,允许你在浏览器中渲染模型而不用你尝试使用一个特别的视图技术。Spring允许你使用JSP, Velocity templates and XSLT等视图。
有两个接口对Spring处理视图很重要,它们是ViewResolver
和View
。ViewResolver
在视图名称和真实视图之间提供了一个映射。View
接口引导传递请求到一个视图。
1.4.1 视图解析和ViewResolver接口
Spring提供了不少视图解析器,这个表格列出了它们的大部分;一些例子紧随其后。
ViewResolver | 描述 |
---|---|
AbstractCachingViewResolver |
抽象视图解析缓存视图。通常视图需要准备后才能使用;拓展这个视图解析器可以提供缓存 |
XmlViewResolver |
允许将配置写在XML文件里,默认的配置文件是/WEB-INF/views.xml
|
ResourceBundleViewResolver |
使用一个properties文件配置视图。默认的文件名views.properties
|
UrlBasedViewResolver |
根据逻辑名称匹配视图资源 |
InternalResourceViewResolver |
UrlBasedViewResolver 的子类,支持InternalResourceView
|
VelocityViewResolver / FreeMarkerViewResolver
|
支持返回模板引擎的视图 |
ContentNegotiatingViewResolver |
根据请求文件名或者Accept 头解析视图 |
例如,如果使用JSP作为视图,你可以使用UrlBasedViewResolver
。这个视图解析器将一个视图名转化为一个URL,传递请求到RequestDispatcher
来渲染视图。
<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
当你在web应用程序中联合不同的视图技术时,可以使用ResourceBundleViewResolver
:
<bean id="viewResolver"
class="org.springframework.web.servlet.view.ResourceBundleViewResolver">
<property name="basename" value="views"/>
<property name="defaultParentView" value="parentView"/>
</bean>
ResourceBundleViewResolver
根据basename属性指定的值检查ResourceBundle
。它会使用[viewname].(class)
属性作为视图类,使用[viewname].url
属性作为视图url。views.properties
默认路径在WEB-INF/classes
welcome.(class)=org.springframework.web.servlet.view.JstlView
welcome.url=/WEB-INF/jsp/welcome.jsp
productList.(class)=org.springframework.web.servlet.view.velocity.VelocityView
productList.url=/WEB-INF/template/test2.vm
还有一个种方式来使用多视图技术,Spring支持多个视图解析器,它会按照配置的顺序依次匹配合适的视图。通过order
属性可以指定顺序,order
值越大顺序越靠后,下面是个例子:
<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>
如果到最后Spring一个视图解析都没有匹配上就会抛出一个ServletException
。
1.4.2 重定向视图
特殊的redirect:
前缀允许你完成重定向操作。如果你返回的一个视图名字拥有redirect:
前缀,UrlBasedViewResolver
(及其子类)会识别出这个前缀,剩下的视图名字会被当做重定向的URL。一个逻辑视图名像这样的:redirect:/myapp/some/resource
会被重定向到当前的Servlet上下文中,而redirect:http://myhost.com/some/arbitrary/path
这个会重定向到指定的URL。
1.4.3 转发视图
几乎与重定向一样,只不过使用forward:
前缀。
1.5 使用flash attributes
Flash attributes提供了一种方式,让一个请求存储属性来让另一个请求使用。通常用于重定向时,例如Post/Redirect/Get模式。Flash attributes在重定向前被临时存储,当重定向后被置为可用并被立即移除。
Spring MVC有两个主要的抽象支持flash attributes。FlashMap
被用来承载flash attributes,FlashMapManager
被用来存储,取回和管理FlashMap
实例。
使用注解的controllers通常不需要直接使用FlashMap
。一个@RequestMapping
方法可以接收一个RedirectAttributes
类型的参数,在重定向场景使用它添加flash attributes。
1.6 构建URIs
Spring MVC提供了一种机制用来构建并且编码一个URI使用UriComponentsBuilder
和UriComponents
。
例如你可以拓展和编码一个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 components:
UriComponents uriComponents = UriComponentsBuilder.newInstance()
.scheme("http").host("example.com").path("/hotels/{hotel}/bookings/{booking}").build()
.expand("42", "21")
.encode();
在一个Servlet环境ServletUriComponentsBuilder
子类提供了一个静态工厂方法从一个Servlet requests中复制可用的URL信息:
HttpServletRequest request = ...
// 复用 host, scheme, port, path and query string
// 替换 the "accountId" 查询参数
ServletUriComponentsBuilder ucb = ServletUriComponentsBuilder.fromRequest(request)
.replaceQueryParam("accountId", "{id}").build()
.expand("123")
.encode();
1.7 Spring 文件上传支持
Spring内置的multipart支持在web应用程序处理文件上传。Spring针对使用Apache Commons FileUpload的用户提供了一个MultipartResolver
实现,另一个实现是针对使用Servlet 3.0 multipart request parsing的用户。
默认的,Spring不处理文件上传,因为一些开发者想自己处理文件上传。你可以通过添加一个multipart解析器到web应用程序的上下文中来启用Spring文件上传处理。每一个请求都会被检查是否包含文件上传的部分。如果没有找到文件上传的部分,请求将会继续。如果在一个请求中找到了文件上传部分,MultipartResolver
将会被使用。经过处理后请求里的文件上传的属性像其他别的属性被对待。
1.7.1 MultipartResolver与Commons FileUpload
下面的例子展示了如何配置CommonsMultipartResolver
:
<bean id="multipartResolver"
class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<!-- 最大的文件尺寸(bytes) -->
<property name="maxUploadSize" value="100000"/>
</bean>
你还需要添加Commons FileUpload的依赖:
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.3.1</version>
</dependency>
当Spring DispatcherServlet
发现一个上传文件的请求,它会激活配置的MultipartResolver
并把请求传递过去。解析器会包装当前的HttpServletRequest
成为一个MultipartHttpServletRequest
来支持文件上传。使用MultipartHttpServletRequest
你可以获得文件上传数据的相关信息也可以访问到文件上传的数据。
1.7.2 MultipartResolver与Servlet 3.0
为了使用基于Servlet 3.0的上传文件处理你需要在web.xml
中使用"multipart-config"
标记DispatcherServlet
。还有编程式、使用javax.servlet.annotation.MultipartConfig
注解自定义Servlet。
一旦配置好后你需要添加StandardServletMultipartResolver
到你的Spring配置:
<bean id="multipartResolver"
class="org.springframework.web.multipart.support.StandardServletMultipartResolver">
</bean>
(只要不是关于Spring的配置一般都没有例子,感兴趣可以自行搜索)
1.7.3 处理从表单上传的文件
首先创建一个表单来允许用户上传文件:
<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处理文件上传。
@Controller
public class FileUploadController {
@RequestMapping(value = "/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";
}
}
当使用Servlet 3.0文件上传解析时你还可以使用javax.servlet.http.Part
作为方法参数:
@Controller
public class FileUploadController {
@RequestMapping(value = "/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";
}
}
1.8 处理异常
@ExceptionHandler
注解可以在一个controller里或者一个有@ControllerAdvice
注解的类里处理异常。定义在一个controller里只对那个controller里@RequestMapping
方法产生的异常有效;定义在@ControllerAdvice
类时对所有的controller生效。下面这个例子只对该controller本地生效:
@Controller
public class SimpleController {
// @RequestMapping 方法省略 ...
@ExceptionHandler(IOException.class)
public ResponseEntity<String> handleIOException(IOException ex) {
// prepare responseEntity
return responseEntity;
}
}
下面是在@ControllerAdvice
注解的类中使用:
@ControllerAdvice
public class SimpleExceptionHandler {
@ExceptionHandler(Exception.class)
public ResponseEntity<String> handleIOException(Exception ex) {
// prepare responseEntity
return responseEntity;
}
}
所有Controller产生的异常都会被捕获。
自定义默认Servlet容器错误页面
你可以在web.xml
声明一个<error-page>
元素:
<error-page>
<location>/error</location>
</error-page>
或者指定一个HTTP状态码:
<error-page>
<error-code>404</error-code>
<location>/error</location>
</error-page>
注意:实际的地址可以是一个JSP页面或者容器内其他URL包括一个@Controller
方法。
@Controller
public class ErrorController {
@RequestMapping(value="/error", produces="application/json")
@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;
}
}
1.9 配置Spring MVC
1.9.1 启用MVC
Java config方式
@Configuration
@EnableWebMvc
public class WebConfig {
}
MVC XML Namespace方式
<?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>
1.9.2 自定义默认配置
为了使用Java自定义默认配置你仅仅需要实现WebMvcConfigurer
接口或者继承WebMvcConfigurerAdapter
类并重写你需要的方法。
@Configuration
@EnableWebMvc
public class WebConfig extends WebMvcConfigurerAdapter {
@Override
protected void addFormatters(FormatterRegistry registry) {
// Add formatters and/or converters
}
@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
// Configure the list of HttpMessageConverters to use
}
}
自定义<mvc:annotation-driven />
默认配置:
<mvc:annotation-driven conversion-service="conversionService">
<mvc:message-converters>
<bean class="org.example.MyHttpMessageConverter"/>
<bean class="org.example.MyOtherHttpMessageConverter"/>
</mvc:message-converters>
</mvc:annotation-driven>
<bean id="conversionService" class="org.springframework.format.support.FormattingConversionServiceFactoryBean">
<property name="formatters">
<list>
<bean class="org.example.MyFormatter"/>
<bean class="org.example.MyOtherFormatter"/>
</list>
</property>
</bean>
1.9.3 拦截器
你可以配置HandlerInterceptors
或者WebRequestInterceptors
应用在所有即将到来的请求,或者只匹配特殊的URL路径。
一个用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/*");
}
}
或者在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>
1.9.4 视图控制器
这个一个快捷方式定义一个ParameterizableViewController
当执行时立即跳转到一个视图。使用它在静态视图的场景,当在视图生成相应之前没有Java controller逻辑执行。
一个Java配置例子,使/
请求跳转到一个叫做home
的视图:
@Configuration
@EnableWebMvc
public class WebConfig extends WebMvcConfigurerAdapter {
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/").setViewName("home");
}
}
同样的配置,在XML中使用<mvc:view-controller>
元素:
<mvc:view-controller path="/" view-name="home"/>
1.9.5 视图解析器
配置一个velocity模版引擎视图解析器:
<bean id="velocityViewResolver"
class="org.springframework.web.servlet.view.velocity.VelocityViewResolver">
<property name="suffix" value=".vm" />
<property name="contentType" value="text/html;charset=utf-8" />
<property name="exposeSessionAttributes" value="true" />
<property name="allowSessionOverride" value="true" />
<property name="toolboxConfigLocation" value="/WEB-INF/Toolbox.xml" />
</bean>
配置一个JSP视图解析器:
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"
p:prefix="/WEB-INF/view/"
p:suffix=".jsp"/>
1.9.6 静态Resources服务
这个选项允许在一个特殊的URL路径下请求静态资源,由ResourceHttpRequestHandler
服务于任何列表中的Resource
位置。这提供了一种方便的方法从位置服务静态资源而不是从web应用程序根目录,包括classpath的位置。处理器还会正确的计算Last-Modified
头,这样对于那些已经缓存在客户端的资源一个304
状态码将会被返回,避免不必要的间接开销。
例如使用/resources/**
URL模式来服务静态资源,静态资源文件目录是web应用程序根目录下的public-resources
文件夹,使用Java配置:
@Configuration
@EnableWebMvc
public class WebConfig extends WebMvcConfigurerAdapter {
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/resources/**").addResourceLocations("/public-resources/");
}
}
用XML配置:
<mvc:resources mapping="/resources/**" location="/public-resources/"/>
设置一个1年的有效时间:
@Configuration
@EnableWebMvc
public class WebConfig extends WebMvcConfigurerAdapter {
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/resources/**").addResourceLocations("/public-resources/").setCachePeriod(31556926);
}
}
XML配置:
<mvc:resources mapping="/resources/**" location="/public-resources/" cache-period="31556926"/>
mapping
属性必须是一个可以被SimpleUrlHandlerMapping
使用的Ant模式,location
必须指定一个或多个有效的资源文件夹地址。多个资源位置可以使用逗号分割的一列值。例如使用web应用程序的根目录和/META-INF/public-web-resources/
:
@EnableWebMvc
@Configuration
public class WebConfig extends WebMvcConfigurerAdapter {
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/resources/**")
.addResourceLocations("/", "classpath:/META-INF/public-web-resources/");
}
}
使用XML配置:
<mvc:resources mapping="/resources/**" location="/, classpath:/META-INF/public-web-resources/"/>
当一个新版本应用程序被部署时静态资源可能改变,推荐你合并一个版本字符串到映射模式,强制客户端请求新版本的资源。版本化的URL已经被内置在框架里,可以通过在资源处理器里配置一个资源链来启用。
内置的VersionResourceResolver
可以被配置为不同的策略。例如,一个FixedVersionStrategy
可以使用一个属性、一个日期或者其他的东西来作为版本。一个ContentVersionStrategy
使用一个由资源的内容生产的MD5字符串作为版本。
ContentVersionStrategy
是一个不错的默认的选择,除了有些请求不能使用(例如,与JavaScript module loaders)。你可以为不同的URL模式配置不同的版本策略。请牢记根据内容来计算版本的做法是消耗昂贵的,因此资源链缓存在产品环境一定要开启。
Java配置示例:
@Configuration
@EnableWebMvc
public class WebConfig extends WebMvcConfigurerAdapter {
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/resources/")
.addResourceLocations("/public-resources/")
.resourceChain(true).addResolver(
new VersionResourceResolver().addContentVersionStrategy("/"));
}
}
XML示例:
<mvc:resources mapping="/resources/" location="/public-resources/">
<mvc:resource-chain>
<mvc:resource-cache />
<mvc:resolvers>
<mvc:version-resolver>
<mvc:content-version-strategy patterns="/"/>
</mvc:version-resolver>
</mvc:resolvers>
</mvc:resource-chain>
</mvc:resources>
为了使上面配置生效,应用程序必须使用版本渲染URL。最简单的方式是配置ResourceUrlEncodingFilter
包装response重写encodeURL
方法。这个对JSP, FreeMarker, Velocity, 和其他任何调用response encodeURL
方法的视图技术都有效。
Copies of this document may be made for your own use and for distribution to others, provided that you do not charge any fee for such copies and further provided that each copy contains this Copyright Notice, whether distributed in print or electronically.