首页 技术 正文
技术 2022年11月10日
0 收藏 807 点赞 2,278 浏览 29266 个字
  1. 介绍Spring Web MVC 框架
    1. Spring Web MVC的特性
    2. 其他MVC实现的可插拔性
  2. DispatcherServlet
    1. 在WebApplicationContext中的特殊的bean types
    2. 默认的DispatcherServlet配置
    3. DispatcherServlet处理顺序
  3. 实现Controller
    1. 使用@Controller定义一个Controller
    2. 使用@RequestMapping映射requests
    3. 定义@RequestMapping handler methods
    4. 异步请求的处理
    5. 测试controllers
  4. Handler mappings
    1. 使用HandlerInterceptor拦截requests
  5. resolving views 解析视图
  6. Spring 4 官方文档学习(十一)Web MVC 框架之Flash Attributes
  7. Spring 4 官方文档学习(十一)Web MVC 框架之URI Builder
  8. Spring 4 官方文档学习(十一)Web MVC 框架之locales
  9. Spring 4 官方文档学习(十一)Web MVC 框架之themes
  10. Spring 4 官方文档学习(十一)Web MVC 框架之multipart(文件上传)支持
  11. Spring 4 官方文档学习(十一)Web MVC 框架之异常处理
  12. Spring 4 官方文档学习(十一)Web MVC 框架之约定优于配置
  13. Spring 4 官方文档学习(十一)Web MVC 框架之HTTP caching support
  14. Spring 4 官方文档学习(十一)Web MVC 框架之编码式Servlet容器初始化
  15. Spring 4 官方文档学习(十一)Web MVC 框架之配置Spring MVC

spring-projects Org on Github 里,大量的web应用都使用了这种支持。例如MvcShowcase, MvcAjax, MvcBasic, PetClinic, PetCare, 以及其他。

@Controller // 一个注解即可Controller
public class HelloWorldController { @RequestMapping("/helloWorld") //
public String helloWorld(Model model) {
model.addAttribute("message", "Hello World!");
return "helloWorld";
}
}

the section called “Method Parameters And Type Conversion” and the section called “Customizing WebDataBinder initialization”.

带正则表达式的URI Template Patterns

有时候你需要更精确的定义URI模板变量。考虑下 URL "/spring-web/spring-web-3.0.5.jar",你怎么将其拆分成多个部分?

@RequestMapping支持在URI模板变量中使用正则表达式。语法是: {varName:regex} 。如下:

@RequestMapping("/spring-web/{symbolicName:[a-z-]+}-{version:\\d\\.\\d\\.\\d}{extension:\\.[a-z]+}")
public void handle(@PathVariable String version, @PathVariable String extension) {
// ...
}

Path Patterns

除了URI模板,@RequestMapping注解和其所有变体还支持ant-style的path patterns,如 /mypath/*.do。

Path Pattern Comparison

当一个URL匹配多个patterns时,会使用一种排序来查找最佳匹配。

带有更少URI变量和通配符的pattern ,被认为更匹配。例如,/hotels/{hotel}/* /hotels/{hotel}/** 更合适,因为其他一样,通配符更少。

如果变量数量一样,更长的被认为更匹配。例如,/foo/bar* /foo/* 更匹配。

当两个patterns拥有相同数量的变量和长度时,通配符少的更匹配。例如,/hotels/{hotel}/hotels/* 更匹配。

另外,还有两个特殊规则:

  • /**  匹配度最差。

  • 带前缀的pattern,比其他所有不含双通配符的pattern,更差。例如:/public/** 比 /public/path/{a} 更差。

带有占位符的path patterns

@RequestMapping注解的patterns还支持 ${…} 占位符。

后缀pattern匹配

默认,Spring MVC会执行 “.*”的匹配,所以,当一个controller的被映射到/person的时候,实际上是隐式的被映射到/person.*。这样可以使用URL轻松的请求不同的资源表现,如/person.pdf, /person.xml。

后缀pattern匹配可以被关闭,或者被限制在一组为了内容协商目的而注册的路径扩展名中。非常建议使用最普通的请求映射来最小化请求的模糊性,因为有时候“.”不一定代表扩展名,例如/person/{id},有可能是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.

Matrix Variables

http://docs.spring.io/spring/docs/current/spring-framework-reference/html/mvc.html#mvc-ann-matrix-variables

URI specification RFC 3986定义了在path segments内包含name-value对的可行性。在Spring MVC中,它们被视为matrix Variables。

matrix variables可能出现在任意path segment中,每个matrix variable都由分号隔离。例如:"/cars;color=red;year=2012"。多个value的时候,可以使用逗号拼接,如"color=red,green,blue",也可以重复name,如"color=red;color=green;color=blue"

如果希望一个URL包含matrix variables,请求映射pattern必须使用URI模板来代表它们。

下面是提取matrix variable ”q”的例子:

// GET /pets/42;q=11;r=22@GetMapping("/pets/{petId}")
public void findPet(@PathVariable String petId, @MatrixVariable int q) { // petId == 42
// q == 11}

因为所有的path segments都可能含有matrix variables,某些情况下你需要更精确的信息来确定需要的变量:

// GET /owners/42;q=11/pets/21;q=22@GetMapping("/owners/{ownerId}/pets/{petId}")
public void findPet(
@MatrixVariable(name="q", pathVar="ownerId") int q1,
@MatrixVariable(name="q", pathVar="petId") int q2) { // q1 == 11
// q2 == 22}

一个matrix variable可以被定义为可选项,可以拥有一个指定的默认值;

// GET /pets/42@GetMapping("/pets/{petId}")
public void findPet(@MatrixVariable(required=false, defaultValue="1") int q) { // q == 1}

所有的matrix variables可以用一个Map来获取:

// GET /owners/42;q=11;r=12/pets/21;q=22;s=23@GetMapping("/owners/{ownerId}/pets/{petId}")
public void findPet(
@MatrixVariable MultiValueMap<String, String> matrixVars,
@MatrixVariable(pathVar="petId"") MultiValueMap<String, String> petMatrixVars) { // matrixVars: ["q" : [11,22], "r" : 12, "s" : 23]
// petMatrixVars: ["q" : 11, "s" : 23]}

注意:为了启用matrix variables,你必须设置RequestMappingHandlerMapping的removeSemicolonContent property为false。其默认是true。

The MVC Java config and the MVC namespace both provide options for enabling the use of matrix variables.

If you are using Java config, The Advanced Customizations with MVC Java Config section describes how the RequestMappingHandlerMapping can be customized.

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 to 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>

Consumable Media Types

通过指定一个consumable media types列表来窄化映射。只有request header中的Content-Type符合指定的媒体类型时,请求才匹配。例如:

@PostMapping(path = "/pets", consumes = "application/json")
public void addPet(@RequestBody Pet pet, Model model) {
// implementation omitted
}

注意,consumable media type表达式可以使用“!”来否定匹配的媒体类型,如使用“!text/plain”来匹配除了text/plain之外的Content-Type。建议使用MediaType中的常量,如 APPLICATION_JSON_VALUE、 APPLICATION_JSON_UTF8_VALUE

注意,虽然consumes条件支持type和method级别,但是,不同于其他条件,method级别的会覆盖type级别的类型!!!

Producible Media Types

还可以通过指定一个producible media types列表来窄化请求。仅当request header的Accept匹配时,该request才会匹配。此外,使用produces条件会确保实际的内容类型。如下:

@GetMapping(path = "/pets/{petId}", produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
@ResponseBody
public Pet getPet(@PathVariable String petId, Model model) {
// implementation omitted
}

注意produces条件指定的媒体类型,也可以选择性的指定一个字符集。例如,在上面的代码片段中,我们指定了与MappingJackson2HttpMessageConverter中默认配置的媒体类型一致的媒体类型。–是否可以认为,一种字符集就是一种媒体类型?

同consumes类似,produces也可以使用“!”。同样建议使用MediaType中的常量。

同consumes类似,方法级别的produces会覆盖类级别的媒体类型!!!

请求参数和请求头的值 Request Parameter 、Request Header values

可以通过请求参数条件来窄化请求的匹配,如:"myParam", "!myParam", or "myParam=myValue"。前两个用于测试请求参数中是否出现该参数,第三个则需要请求参数有一个特定的值。例子:

@Controller
@RequestMapping("/owners/{ownerId}")
public class RelativePathUriTemplateController { @GetMapping(path = "/pets/{petId}", params = "myParam=myValue")
public void findPet(@PathVariable String ownerId, @PathVariable String petId, Model model) {
// implementation omitted
}}

同样的情况还适合请求头:

@Controller
@RequestMapping("/owners/{ownerId}")
public class RelativePathUriTemplateController { @GetMapping(path = "/pets", headers = "myHeader=myValue")
public void findPet(@PathVariable String ownerId, @PathVariable String petId, Model model) {
// implementation omitted
}}

虽然你可以使用通配符来匹配Content-Type和Accept header values(如headers=“content-type=text/*”,可以匹配“text/plain”“text/html”),但建议使用consumes和produces。这也是它们的设计目的。

HTTP HEAD 和 HTTP OPTIONS

http://docs.spring.io/spring/docs/current/spring-framework-reference/html/mvc.html#mvc-ann-requestmapping-head-options

@RequestMapping方法映射到“GET”,同时也会隐式的映射到“HEAD”!

@RequestMapping方法内建支持HTTP OPTIONS。略。

Section 22.16.1, “Enabling the MVC Java Config or the MVC XML Namespace”

<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注解,Spring会使用配置好的Validator实例来校验该参数。当使用MVC命名空间或MVC Java config时,一个JSR-303 validator会被自定的配置 — 假如classpath中有一个JSR-303实现。

就像使用@ModelAttribute注解的参数已有,Errors参数可以用来检查errors。如果没有声明这样一个参数,会抛出一个MethodArgumentNotValidException。该异常由DefaultHandlerExceptionResolver来处理,会返回400 error。

使用@ResponseBody注解来映射response body

@ResponseBody注解类似于@RequestBody。该注解放在方法上指示返回类型会被写入HTTP response body (没有被放入Model,或被解释成view name)。 例如:

@GetMapping("/something")
@ResponseBody
public String helloWorld() {
return "Hello World";
}

上面的例子,会将字符串写入HTTP response stream。

如同@RequestBody,Spring会将返回的对象转换成一个response body — 使用一个HttpMessageConverter。

使用@RestController注解创建一个REST Controller

使用@RestController代替@ResponseBody与@Controller。它虽然是由后两者组合而成,但在将来会被赋予更多语义。

@RestController也可以配合@ControllerAdvice或@RestControllerAdvice beans。详见 the the section called “Advising controllers with @ControllerAdvice and @RestControllerAdvice” section。

使用HttpEntity

HttpEntity 类似于 @RequestBody和@ResponseBody。除了能获取request和response body之外,HttpEntity(以及其response子类:ResponseEntity)还允许获取request和response  headers,如下:

@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);
}

The above example gets the value of the MyRequestHeader request header, and reads the body as a byte array. It adds the MyResponseHeader to the response, writes Hello World to the response stream, and sets the response status code to 201 (Created).

As with @RequestBody and @ResponseBody, Spring uses HttpMessageConverter to convert from and to the request and response streams. For more information on these converters, see the previous section and Message Converters.

在方法上使用@ModelAttribute

该注解可以用在方法或方法参数上。本部分讲解用在方法上的作用,下一部分会讲解用在方法参数上的作用。

在方法上使用该注解,意味着该方法的一个目的是增加一个或多个model attribute。该方法支持的参数类型与@RequestMapping methods一样,但不能直接映射到请求。相反,同一个Controller中的@ModelAttribute methods会在@RequestMapping methods之前被调用!!!例子:

// 添加一个 attribute
// 该方法的返回值会被添加到model account中
// 你可以定义该model的名字,例如 @ModelAttribute("myAccount")@ModelAttribute
public Account addAccount(@RequestParam String number) {
return accountManager.findAccount(number);
}// 添加多个 attributes@ModelAttribute
public void populateModel(@RequestParam String number, Model model) {
model.addAttribute(accountManager.findAccount(number));
// add more ...
}

@ModelAttribute methods被用于将常用的attributes填入model。

注意两种形式的@ModelAttribute methods。第一个,是隐式的将返回值添加为attribute。第二个,接收一个Model,然后在其中增加任意数量的model attributes。

一个Controller可以拥有任意数量的@ModelAttribute methods。所有这些方法都会在同一个Controller中的@RequestMapping methods之前被调用!

@ModelAttribute methods 也可以被定义在@ControllerAdvice class内,这样的methods会被用于所有Controllers。–就是在所有Controller的所有@RequestMapping methods之前被调用!

如果没有显式指定一个model attribute name,会发生什么?这种情况下,会基于其类型赋予一个默认的名字。例如,如果方法返回了Account类型,那默认的name就是account。

@ModelAttribute注解也可以用在@RequestMapping methods上。这种情况下,方法的返回值被解释成一个model attribute,而非view name。view name会基于name惯例而获取到,更像是返回了void。 see Section 22.13.3, “The View – RequestToViewNameTranslator”

在方法参数上使用@ModelAttribute

当@ModelAttribute用于方法参数上时,代表该参数应该从该model中获取。如果model中没有,该参数会先被实例化,再被添加到model。一旦出现在model中,该参数的字段会被匹配名字的request parameters填充。这就是Spring MVC中的数据绑定(data binding),一个非常有用的机制,节省了你手动解析每个form字段的时间。

@PostMapping("/owners/{ownerId}/pets/{petId}/edit")
public String processSubmit(@ModelAttribute Pet pet) { }

上面的例子,Pet实例从哪里来?有几个选项:

  • 可能已经存在于@SessionAttributes的model中。

  • 可能已经存在于同一个Controller的@ModelAttribute method的model中。
  • 可能基于URI模板变量和类型转换器而获取(稍后详解)。
  • 可能使用其默认构造器实例化。

@ModelAttribute method是从数据库中获取attribute的一种常用方式,可能可选的存储于requests之间–通过使用@SessionAttributes。某些情况下,使用URI模板变量和类型转换器更为方便。例子:

@PutMapping("/accounts/{account}")
public String save(@ModelAttribute("account") Account account) {
// ...
}

上面的例子,model attribute的name与URI模板变量的名字一致。如果你注册了一个Converter<String, Account>,那么上面的例子就可以不必使用一个@ModelAttribute method。

下一步就是数据绑定。WebDataBinder类会匹配request parameter names — 包含query string parameters 和 form fields — 到model attribute fields,根据名字。匹配的字段会被填充–当必要的类型转换被应用了之后。Data binding and validation are covered in Chapter 9, Validation, Data Binding, and Type Conversion. Customizing the data binding process for a controller level is covered in the section called “Customizing WebDataBinder initialization”.

数据绑定的一个结果是,可能存在errors,例如缺失必须的字段或者类型转换错误。为了检查该类错误,需要在@ModelAttribute argument之后紧跟着添加一个BindingResult argument。

@PostMapping("/owners/{ownerId}/pets/{petId}/edit")
public String processSubmit(@ModelAttribute("pet") Pet pet, BindingResult result) { if (result.hasErrors()) {
return "petForm";
} // ...}

使用BindingResult,你可以检查是否有errors,可以使用Spring的<errors> form tag来在同一个form中显示错误。

注意,某些情况下,不使用数据绑定而获取model中的一个attribute很有用。这些情况下,你可以在Controller中注入Model,或者在注解上使用binding flag,如下:

@ModelAttribute
public AccountForm setUpForm() {
return new AccountForm();
}@ModelAttribute
public Account findAccount(@PathVariable String accountId) {
return accountRepository.findOne(accountId);
}@PostMapping("update")
public String update(@Valid AccountUpdateForm form, BindingResult result,
@ModelAttribute(binding=false) Account account) { // ...
}

In addition to data binding you can also invoke validation using your own custom validator passing the same BindingResult that was used to record data binding errors. That allows for data binding and validation errors to be accumulated in one place and subsequently reported back to the user:

@PostMapping("/owners/{ownerId}/pets/{petId}/edit")
public String processSubmit(@ModelAttribute("pet") Pet pet, BindingResult result) { new PetValidator().validate(pet, result);
if (result.hasErrors()) {
return "petForm";
} // ...}

— 就是根据BindingResult的结果进行自己的操作。

或者,可以使用JSR-303 @Valid注解来自动调用校验:

@PostMapping("/owners/{ownerId}/pets/{petId}/edit")
public String processSubmit(@Valid @ModelAttribute("pet") Pet pet, BindingResult result) { if (result.hasErrors()) {
return "petForm";
} // ...}

使用@SessionAttributes在requests之间的HTTP session中存储model attributes

type-level @SessionAttributes注解,声明了用于特定handler的session attributes。这会列出model attributes的names或types — 应该透明的存储于session或某conversational storage,在后续的requests中作为form-backing beans。

@Controller
@RequestMapping("/editPet.do")
@SessionAttributes("pet")
public class EditPetForm {
// ...
}

使用@SessionAttribute访问预存的全局session attributes

如果需要访问pre-existing global session attributes,就是在controller外部(例如在filter中)管理的 ,且可能或可能不会出现在method parameter上使用@SessionAttribute注解(–什么鬼)(If you need access to pre-existing session attributes that are managed globally, i.e. outside the controller (e.g. by a filter), and may or may not be present use the @SessionAttribute annotation on a method parameter)。

@RequestMapping("/")
public String handle(@SessionAttribute User user) {
// ...
}

当需要增加或移除session attributes时,可以考虑在controller method上注入 org.springframework.web.context.request.WebRequest 或 javax.servlet.http.HttpSession。

为了在session中临时存储model attributes以作为controller workflow的一部分,可以考虑使用SessionAttributes as described in the section called “Using @SessionAttributes to store model attributes in the HTTP session between requests”.

使用@RequestAttribute来获取request attributes

类似于@SessionAttribute,@RequestAttribute也可以用于获取pre-existing request attributes — 由filter或interceptor创建的。

@RequestMapping("/")
public String handle(@RequestAttribute Client client) {
// ...
}

处理application/x-www-form-urlencoded data

前面的部分描述了使用@ModelAttribute来支持来自浏览器客户端的form submission requests。@ModelAttribute注解还被推荐用于处理来自非浏览器客户端的请求。然而,当处理HTTP PUT requests时,有一个显著的不同。浏览器会通过HTTP GET或HTTP POST提交表单数据。非浏览器客户端还可以通过HTTP PUT来提交。这就有一个问题,因为Servlet specification要求 ServletRequest.getParameter*()方法仅支持HTTP POST的字段获取,而非HTTP PUT。

为了支持HTTP PUT和PATCH 请求,spring-web模块提供了过滤器:HttpPutFormContentFilter

<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>

上面的filter,会拦截content type为 application/x-www-form-urlencoded 的 HTTP PUT和PATCH请求,从其请求体中读取表单数据,封装ServletRequest以让ServletRequest.getParameter*() 能够使用表单数据。

由于HttpPutFormContentFilter会consume请求体,所以,不应为那些依赖针对 application/x-www-form-urlencoded 的转换器的PUT或PATCH URLs配置该filter。这包括@RequestBody MultiValueMap<String, String> 和 HttpEntity<MultiValueMap<String, String>>。

使用@CookieValue注解来映射cookie values

该注解允许一个方法参数绑定一个HTTP cookie的值。

假定从http request接收了如下cookie:

JSESSIONID=415A4AC178C59DACE0B2C9CA727CDD84

下面的代码演示了如何获取JSESSIONID cookie:

@RequestMapping("/displayHeaderInfo.do")
public void displayHeaderInfo(@CookieValue("JSESSIONID") String cookie) {
//...
}

如果目标方法参数的类型不是String,会自动应用类型转换。

该注解,也被在Servlet和Portlet环境下的annotated handler methods支持。

使用@RequestHeader注解来映射request header attributes

这是一个样例request header:

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 headers的值:

@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 argument时,该map会被填入所有的header values。

内建的支持允许转换一个逗号间隔的字符串,将其转成一个字符串或其他类型的数组/集合。例如,@RequestHeader(“Accept”)注解的方法参数,可能是String类型,也可能是String[]或List<String>类型。

该注解,也被在Servlet和Portlet环境下的annotated handler methods支持。

method parameters 和 type conversion

http://docs.spring.io/spring/docs/current/spring-framework-reference/html/mvc.html#mvc-ann-typeconversion

从request中提取出来的基于字符串的值,包括request parameters、path variables、request headers、还有cookie values,可能需要被转成它们要绑定的method parameter或field的类型。如果目标类型不是String,Spring会自动转成合适的类型。支持所有简单类型,如int、long、Date等等。甚至,你可以使用一个WebDataBinder来定制转换过程,或者通过在FormattingConversionService中注册Formatters。

定制WebDataBinder 初始化

通过Spring的WebDataBinder使用PropertyEditors来定制request parameter 的绑定,你可以在你的controller中使用@InitBinder methods,或者在@ControllerAdvice class中使用@InitBinder methods,或者提供一个定制的WebBindingInitializer。 See the the section called “Advising controllers with @ControllerAdvice and @RestControllerAdvice” section for more details.

使用@InitBinder 定制数据绑定

在controller方法上使用@InitBinder,允许你在controller内部配置web数据绑定。@InitBinder代表方法初始化了WebDataBinder–会被用于填充被注解的handler方法的command 和 form object arguments。

这些init-binder methods,支持@RequestMapping所支持的所有参数,除了command/form objects和相应的校验结果对象。init-binder methods必须没有返回值。所以,大多被声明为void。 典型的arguments包括WebDataBinder 结合 WebRequest或 java.util.Locale,允许代码注册特定context的editors。

下面的案例演示了使用@InitBinder来配置针对所有java.util.Date form properties的一个CustomDateEditor。

@Controller
public class MyFormController { @InitBinder
protected 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实例。

如果你在一个shared FormattingConversionService中有基于Formatter的设置,这会非常有用,只需要同样的做法来复用特定controller针对binding rules的调节。

@Controller
public class MyFormController { @InitBinder
protected void initBinder(WebDataBinder binder) {
binder.addCustomFormatter(new DateFormatter("yyyy-MM-dd"));
} // ...
}

配置一个定制的WebBindingInitializer

为了将数据绑定初始化外部化,你可以提供一个定制的WebBindingInitializer实现,然后通过提供一个定制的AnnotationMethodHandlerAdapter的bean configuration来启用它。

下面的例子示意了使用了org.springframework.samples.petclinic.web.ClinicBindingInitializer的配置,该配置配置了几个PetClinic controllers需要的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 methods也可以定义在@ControllerAdvice class内,会用于所有的controllers。效果同WebBindingInitializer。See the the section called “Advising controllers with @ControllerAdvice and @RestControllerAdvice” section for more details.

advising controllers with @ControllerAdvice and @RestControllerAdvice

@ControllerAdvice注解,是一个component annotation,允许实现类能够在classpath扫描中被自动探测到。当使用MVC namespace或MVC Java config时,自动启用。

@ControllerAdvice class,可以含有@ExceptionHandler、@InitBinder以及@ModelAttribute methods,这些methods,会被应用到所有controller中的@RequestMapping methods,而非仅仅其所声明的controller中。

@RestControllerAdvice,等同于@ExceptionHandler + @ResponseBody methods。

@ControllerAdvice和@RestControllerAdvice 都可以指定作用的controllers:

// 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文档。

Jackson Serialization View Support

It can sometimes be useful to filter contextually the object that will be serialized to the HTTP response body. 为了提供这种能力,Spring MVC提供了内建的支持,可以rendering with Jackson’s Serialization Views.

在一个@Response controller method上,或者在那些返回ResponseEntity的controller methods上,简单的添加@JsonView注解,并指定需要使用的view class或Interface即可。如下:

@RestController
public class UserController { @GetMapping("/user")
@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;
}
}

注意,尽管@JsonView允许指定多个class,但在controller method上使用时只能指定一个class!可以考虑使用复合接口,如果你需要启用多个views。

对于那些依赖view resolution的controllers,简单的将序列化view class添加到model即可:

@Controller
public class UserController extends AbstractController { @GetMapping("/user")
public String getUser(Model model) {
model.addAttribute("user", new User("eric", "7!jd#h23"));
model.addAttribute(JsonView.class.getName(), User.WithoutPasswordView.class);
return "userView";
}
}

Jackson JSONP 支持

为了启用JSONP对@ResponseBody和@ResponseEntity methods的支持,声明一个@ControllerAdvice bean  — 需要继承AbstractJsonpResponseBodyAdvice,并在其构造中指出JSONP的query parameter name(s)。如下:

@ControllerAdvice
public class JsonpAdvice extends AbstractJsonpResponseBodyAdvice { public JsonpAdvice() {
super("callback");
}
}

对于依赖view resolution的controllers,JSONP自动被启用,默认的query parameter name是 jsonp 或 callback。 可以通过其jsonpParameterNames property来定制。

http://docs.spring.io/spring/docs/current/spring-framework-reference/html/mvc.html#mvc-ann-async

Spring 3.2 引入了基于 Servlet 3 的异步请求处理。不是一直以来的让controller method返回一个值,而是,让controller method返回一个java.util.concurrent.Callable,然后从Spring MVC管理的线程中produce 返回值。同时,main Servlet container thread 会被退出和释放,以处理其他请求。Spring MVC在一个独立的线程调用Callable — 通过TaskExecutor,当Callable返回时,请求会被分派回Servlet container,从而恢复处理。例子:

@PostMapping
public Callable<String> processUpload(final MultipartFile file) { return new Callable<String>() {
public String call() throws Exception {
// ...
return "someView";
}
};}

另一个选择是,controller method返回DeferredResult。这种情况下,也可能是任意线程produce的返回值,就是说,非Spring MVC管理的线程!例如,结果可能是响应某个外部事件,如一个JMS message、一个scheduled task等等,而produce的结果。下面是一个例子:

@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 异步请求处理特性的相关知识,会很难理解这点。这里是关于底层机制的一些基本的事实:

  • 一个ServletRequest可以被放入asynchronous mode,使用request.startAsync()即可。这样做的主要效果就是,该Servlet以及所有Filters,能够退出,但response仍然保持open– 允许在后面完成处理。

  • request.startAsync() 会返回 AsyncContext,可以被用于对async处理的更进一步的控制。例如,它提供了dispatch方法,类似于Servlet API的forward — 除了它允许应用恢复在一个Servlet container thread中的请求处理。
  • ServletRequest提供了对当前DispatcherType的访问,该DispatcherType can be used to distinguish between processing the initial request, an async dispatch, a forward, and other dispatcher types.

With the above in mind, the following is the sequence of events for async request processing with a Callable:

  • Controller returns a Callable.

  • Spring MVC starts asynchronous processing and submits the Callable to a TaskExecutor for processing in a separate thread.
  • The DispatcherServlet and all Filter’s exit the Servlet container thread but the response remains open.
  • The Callable produces a result and Spring MVC dispatches the request back to the Servlet container to resume processing.
  • The DispatcherServlet is invoked again and processing resumes with the asynchronously produced result from the Callable.

The sequence for DeferredResult is very similar except it’s up to the application to produce the asynchronous result from any thread:

  • Controller returns a DeferredResult and saves it in some in-memory queue or list where it can be accessed.

  • Spring MVC starts async processing.
  • The DispatcherServlet and all configured Filter’s exit the request processing thread but the response remains open.
  • The application sets the DeferredResult from some thread and Spring MVC dispatches the request back to the Servlet container.
  • The DispatcherServlet is invoked again and processing resumes with the asynchronously produced result.

For further background on the motivation for async request processing and when or why to use it please read this blog post series.

async requests 的 Exception处理

如果,一个由controller method返回的Callable在执行时 抛出了一个Exception,会发生什么?简短的答案是与一个controller method抛出一个异常时相同。会经历常规的异常处理机制。 长的解释是,当Callable抛出一个Exception时,Spring MVC会将该Exception分派到Servlet container,将其作为结果以及恢复request processing的引导,此时request processing会处理Exception,而非controller method return value。当使用DeferredResult时,你还可以选择是否调用setResult或者setErrorResult — 传入Exception实例。

拦截async requests

一个HandlerInterceptor也可以实现AsyncHandlerInterceptor,以实现afterConcurrentHandlingStarted callback,当asynchronous processing开始时,会调用afterConcurrentHandlingStarted ,而非postHandle和afterComplete。

一个HandlerInterceptor也可以注册一个CallableProcessingInterceptor 或 一个 DeferredResultProcessingInterceptor,以更深度地集成asynchronous request的lifecycle,例如,handle 一个 timeout event。详见 AsyncHandlerInterceptor javadoc。

DeferredResult 类型,也提供了诸如 onTimeout(Runnable)、onCompletion(Runnable)之类的方法。详见javadoc。

当使用一个Callable时,你可以将其wrap进一个WebAsyncTask的实例,该实例也可以提供timeout和completion的方法注册。

HTTP Streaming

一个controller method可以使用DeferredResult和Callable来异步的produce其返回值,可被用于实现诸如long polling之类的技术 — 这样,服务器可以将一个事件尽快的push到客户端。

如果你想要在一个HTTP response上push多个事件会怎样?这就是与”Long Polling” 有关的技术,也就是HTTP Streaming。 Spring MVC通过ResponseBodyEmitter 返回值类型使其成为可能,该返回值类型可悲用于发送多个对象(而非使用@ResponseBody只发送一个 — 这种更常见),每个被发送的对象都通过一个HttpMessageConverter被写入到response 。

例子:

@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的body,以便定制response的status 和 headers。

HTTP Streaming With Server-Sent Events

http://docs.spring.io/spring/docs/current/spring-framework-reference/html/mvc.html#mvc-ann-async-sse

HTTP Streaming Directly To The OutputStream

http://docs.spring.io/spring/docs/current/spring-framework-reference/html/mvc.html#mvc-ann-async-output-stream

@RequestMapping("/download")
public StreamingResponseBody handle() {
return new StreamingResponseBody() {
@Override
public void writeTo(OutputStream outputStream) throws IOException {
// write...
}
};
}

Configuring Asynchronous Request Processing

http://docs.spring.io/spring/docs/current/spring-framework-reference/html/mvc.html#mvc-ann-async-configuration

Section 15.6, “Spring MVC Test Framework”.

http://docs.spring.io/spring/docs/current/spring-framework-reference/html/mvc.html#mvc-handlermapping-interceptor

spring的handler mapping机制包括handler interceptors,当你想要针对特定的requests应用特定的功能时,非常有用。

位于handler mapping内的interceptors,必须实现org.springframework.web.servlet.HandlerInterceptor (或者其实现/子类)。

该接口定义有三个方法preHandle(..) postHandle(..) afterHandle(..)。   见这里。(为知笔记的连接,不知道行不行,以后再说)

preHandle(..)方法会返回一个boolean值,如果false,会破坏执行链的处理过程(不再往下执行)。如果false,DispatcherServlet会认定该拦截器自身来处理请求(例如,渲染视图等),所以不会继续执行其他的拦截器和实际的handler。

拦截器可以在所有继承自AbstractHandlerMapping的类中设置,使用其interceptors属性! 如下:

<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;
}
}

该映射处理的所有请求,都会被 TimeBaseAccessInterceptor 拦截。  该拦截器的就是让你只能在办公时间访问。

注意:当使用RequestMappingHandlerMapping时,实际的handler是HandlerMethod的一个实例,该HandlerMethod会识别要被调用的特定的controller method。

我的补充:handler mapping这个过程,是将request与handler之间映射起来的过程。Spring提供的实现类,能用的也就这几个:

— 还有一个RequestMappingHandlerAdapter,不要混淆了。

如你所见,Spring的适配器类 HandlerInterceptorAdapter,让继承HandlerInterceptor更加简单。

在上面的例子中,被配置过的拦截器会被应用到所有由注解过的controller method处理的请求上。如果你想窄化一个拦截器应用的URL路径,你可以使用MVC 命名空间或者MVC Java config,或者声明MappedInterceptor类型的bean实例来完成。See Section 22.16.1, “Enabling the MVC Java Config or the MVC XML Namespace”.

注意:postHandle方法,通常不能理想地配合@ResponseBody和ResponseEntity方法。    在这种情况下,一个HttpMessageConverter会写入并提交响应 — 先于postHandle!从而导致无法修改响应,例如,添加一个header。这种情况下,可以实现ResponseBodyAdvice,然后要么将其声明为@ControllerAdvice bean,要么直接在RequestMappingHandlerAdapter中配置。

相关推荐
python开发_常用的python模块及安装方法
adodb:我们领导推荐的数据库连接组件bsddb3:BerkeleyDB的连接组件Cheetah-1.0:我比较喜欢这个版本的cheeta…
日期:2022-11-24 点赞:878 阅读:9,111
Educational Codeforces Round 11 C. Hard Process 二分
C. Hard Process题目连接:http://www.codeforces.com/contest/660/problem/CDes…
日期:2022-11-24 点赞:807 阅读:5,584
下载Ubuntn 17.04 内核源代码
zengkefu@server1:/usr/src$ uname -aLinux server1 4.10.0-19-generic #21…
日期:2022-11-24 点赞:569 阅读:6,431
可用Active Desktop Calendar V7.86 注册码序列号
可用Active Desktop Calendar V7.86 注册码序列号Name: www.greendown.cn Code: &nb…
日期:2022-11-24 点赞:733 阅读:6,203
Android调用系统相机、自定义相机、处理大图片
Android调用系统相机和自定义相机实例本博文主要是介绍了android上使用相机进行拍照并显示的两种方式,并且由于涉及到要把拍到的照片显…
日期:2022-11-24 点赞:512 阅读:7,838
Struts的使用
一、Struts2的获取  Struts的官方网站为:http://struts.apache.org/  下载完Struts2的jar包,…
日期:2022-11-24 点赞:671 阅读:4,922