SpringWebMVC框架是围绕DispatcherServlet设计的,所谓DispatcherServlet就是将请求分发到handler,需要有配置好的handler映射、视图解析、本地化、时区、theme解决方案、还有上传文件的支持。默认的handler是基于@Controller和@RequestMapping注解。自Spring3.0起,@Controller注解还能用于RESTful,需要配合@PathVariable以及其他特性。
SpringWebMVC和Spring的设计准则是“对扩展开放,对修改关闭”--可以扩展,不能修改。
SpringWebMVC中核心类的一些方法被标记为final。开发者不能覆盖这些方法以提供自己的行为。这不是任性,而是遵从设计准则。
在SpringMVC中,你不能为final方法添加advice。
在SpringWebMVC中,你可以使用任何对象作为命令对象或者form-backing对象;你不需要实现框架的特定接口或者基类。Spring的数据绑定是高度弹性的:例如,它将类型错误匹配视为校验错误,而非系统错误,从而可被应用evaluate。
Spring的viewresolution是非常弹性的。Controller负责准备一个modelMap,其中带有数据,还负责挑选一个viewname--也可以直接写到响应流而完成一个请求。viewnameresolution是高度可定制的,使用文件扩展名或者Acceptheadercontenttypenegotiation,通过beannames、properties文件、或者,甚至一个自定义的ViewResolver实现。model(MVC中的M)是一个Map接口,允许view技术的完全抽象。你可以直接集成基于模板的rendering技术,例如JSP、Velocity和Freemarker、或者直接生成XML、JSON、Atom、以及很多其他内容类型。modelMap会被简单的变成合适的格式,如JSP请求属性、如Velocity模板模型。
1.1、SpringWebMVC的特性
SpringWebFlow
目标是成为管理web应用页面流程的最佳解决方案。
SWF集成了现有的框架,如SpringMVC和JSF,在Servlet和Portlet环境中。如果你有一个业务process(或多个)想从conversationalmodel中受益(与纯requestmodel相比),那么SWF就是这种解决方案。
SWF允许你捕获logicalpageflows,将其作为self-containedmodules,可以在不同环境下复用,因此,这是构建web应用模块的理想方式,能够引导用户--使用驱动业务processes的controllednavigations。
Spring的web模块包含许多独特的web支持特性:
1.2、其他MVC实现的可插拔性
对于某些项目来说,非Spring的MVC实现更适合。很多团队希望借用已有的投资(囧,真抽象),例如,使用JSF。
如果你不想使用SpringWebMVC,而想使用Spring提供的其他解决方案,你可以将你选择的webMVC框架集成到Spring中,这很简单。通过Spring的ContextLoaderListener启动Spring的rootapplicationcontext,可以在任意actionobject中通过它的ServletContext属性来获取它--也可以使用Spring的相应帮助方法来获取。
2、DispatcherServlet
像很多其他webMVC框架一样,SpringMVC框架也是请求驱动的,围绕一个中心Servlet来设计,该中心Servlet可以分发请求到Controllers,并提供其他功能。然而Spring的DispatcherServlet做的更多。它被彻底地与SpringIoC容器集成到了一起,所以,你可以使用Spring的所有特性。
SpringMVCDispatcherServlet处理请求的工作流程如下图所示。聪明的读者会认出DispatcherServlet是FrontController设计模式的一种实现。
DispatcherServlet是一个具体的Servlet(继承自HttpServlet基类),所以你需要使用一个URLmapping来映射请求--就是你想让DispatcherServlet处理的请求。这是一个标准JavaEEServlet配置,在Servlet3.0+环境下可以这样注册该Servlet:
publicclassMyWebApplicationInitializerimplementsWebApplicationInitializer{//这个接口,或者其抽象实现类@OverridepublicvoidonStartup(ServletContextcontainer){ServletRegistration.Dynamicregistration=container.addServlet("example",newDispatcherServlet());registration.setLoadOnStartup(1);registration.addMapping("/example/*");}}上面的例子中,所有以/example开头的请求都会由名字为example的DispatcherServlet实例来处理。
WebApplicationInitializer是SpringMVC提供的接口,可以确保基于代码的配置被探测到、且被自动用于初始化Servlet3容器。该接口的一个abstract基类是AbstractAnnotationConfigDispatcherServletInitializer,该abstract基类注册DispatcherServlet更简便,只需要指定映射、列出配置类即可--这是设置SpringMVC项目的一种推荐的方式(javaconfig)。
或者,传统模式,web.xml中的设置方式:
在前面曾讲过,在Spring中,ApplicationContext实例可以被scoped(就是有scope)。
在DispatcherServlet初始化过程中,SpringMVC会在web应用的/WEB-INF文件夹下查找名字为[servlet-name]-servlet.xml的文件,创建其中定义的bean,并会重写在全局scope中已经定义的任何beans的定义。
看一下下面的DispatcherServlet配置:
根据上面的Servlet配置,还需要一个文件:/WEB-INF/golfing-servlet.xml。该文件会包含所有的SpringMVC特定的组件(beans)。当然,你可以更改该路径,通过特定的Servlet初始化参数(详见下面)。
对于单DispatcherServlet情景来说,也可以只有一个rootcontext。
这可以通过设置一个空的contextConfigLocationservletinit参数来配置,如下:
WebApplicationContext是简单的ApplicationContext的一个扩展,针对web应用拥有一些额外的特性。
它不同于normalApplicationContext的地方是它能够resolvethemes,它还知道关联哪个Servlet(通过到ServletContext的连接)。
WebApplicationContext被束缚在ServletContext中,通过使用RequestContextUtils类的静态方法,你可以随时查找WebApplicationContext。
注意,我们可以使用基于Java的配置达到同样的目的:
2.1、在WebApplicationContext中的特殊的beantypes
SpringDispatcherServlet使用特殊的beans来处理请求并渲染视图。这些beans是SpringMVC的一部分。你可以选择使用哪个--只需要在WebApplicationContext中简单的配置一些即可。
SpringMVC维护了一个默认beans列表以供使用,下一部分会讲。
现在先来看看DispatcherServlet依赖的特殊的beantypes:
2.2、默认的DispatcherServlet配置
上面有提到,对每个特殊的bean,DispatcherServlet默认都维护了一个实现列表以供使用。这个信息保存在DispatcherServlet.properties中,在org.springframework.web.servlet包下。
所有的特殊的beans都有其合理的默认(bean还是设置?)。或早或晚,你总会需要定制这些beans提供的一个或多个properties。例如,配置InternalResourceViewResolver的prefixproperty。
这里最需要理解的概念就是:一旦你在你的WebApplicationContext中配置了一个特殊的bean,如InternalResourceViewResolver,你就完全重写了某个类型的默认的实现列表。例如,如果你配置了InternalResourceViewResolver,那默认的ViewResolver实现列表会被忽略!
2.3、DispatcherServlet处理顺序
当你setup了一个DispatcherServlet,然后一个由其负责的request进来了,这时该DispatcherServlet会以如下流程开始处理请求:
SpringDispatcherServlet也支持返回last-modification-date(最后修改日期),如同ServletAPI中指定的一样。决定特定request的lastmodificationdate的处理是简单粗暴的:DispatcherServlet会查找合适的handlermapping,并测试找到的handler是否实现了LastModified接口。如果实现了,longgetLastModified(request)方法的值会被返回。
DispatcherServlet初始化参数
3.、实现Controller
Controller将用户的input解释并转成一个model,该model可以通过view描述给用户。
Spring2.5为MVCControllers引入了基于注解的编程模型,使用诸如@RequestMapping、@RequestParam、@ModelAttribute之类的注解。在ServletMVC和PortletMVC中都可用。
@Controller//一个注解即可ControllerpublicclassHelloWorldController{@RequestMapping("/helloWorld")//publicStringhelloWorld(Modelmodel){model.addAttribute("message","HelloWorld!");return"helloWorld";}}3.1、使用@Controller定义一个Controller
@Controller注解用于标记一个class拥有controller的角色。
注意,这种注解Controller需要开启自动探测才能使用。如下:
3.2、使用@RequestMapping映射requests
使用@RequestMapping注解将URLs如/app映射到一个类或者特定的handler方法。示例:
@Controller@RequestMapping("/appointments")publicclassAppointmentsController{privatefinalAppointmentBookappointmentBook;@AutowiredpublicAppointmentsController(AppointmentBookappointmentBook){this.appointmentBook=appointmentBook;}@RequestMapping(method=RequestMethod.GET)publicMap
@ControllerpublicclassClinicController{privatefinalClinicclinic;@AutowiredpublicClinicController(Clinicclinic){this.clinic=clinic;}@RequestMapping("/")publicvoidwelcomeHandler(){}@RequestMapping("/vets")publicModelMapvetsHandler(){returnnewModelMap(this.clinic.getVets());}}@RequestMapping变体
Spring4.3引入了以下@RequestMapping注解的变体,方法级别的。
@GetMapping
@PostMapping
@PutMapping
@DeleteMapping
@PatchMapping
@Controller@RequestMapping("/appointments")publicclassAppointmentsController{privatefinalAppointmentBookappointmentBook;@AutowiredpublicAppointmentsController(AppointmentBookappointmentBook){this.appointmentBook=appointmentBook;}@GetMappingpublicMap
一些情况下,一个controller可能需要在运行时被AOP代理装饰。一个例子是在controller上使用@Transactional。这时,对这些controllers,我们建议使用基于类的代理。这也是controller的默认选项。
然而,如果一个controller必须实现一个非SpringContext回调的接口(如InitializingBean、*Aware等等)的话,你可能需要显式的配置基于类的代理。例如,将
对SpringMVC3.1中@RequestMappingmethods的新的支持类(解析类?)
Spring3.1引入了针对RequestMappingmethods的一组新的支持类,叫做:RequestMappingHandlerMapping、RequestMappingHandlerAdapter。推荐使用它们,甚至需要利用SpringMVC3.1及以后的一些新特性。这些新的支持类默认就由MVC命名空间和MVCJavaconfig启用,其他情况必须显式的配置。本部分会描述一下新旧支持类之间的一些重要区别。
Spring3.1之前,type和method-levelrequestmappings是在两个独立的阶段中检查的--controller先被DefaultAnnotationHandlerMapping选中,具体的方法调用则由AnnotationMethodHandlerAdapter负责。
Spring3.1中新的支持类,只需要使用RequestMappingHandlerMapping。不妨将controller的那些方法看作一个集合,其内容是带有映射的各种端点。
这使得一些新的可能成为现实。ForonceaHandlerInterceptororaHandlerExceptionResolvercannowexpecttheObject-basedhandlertobeaHandlerMethod,whichallowsthemtoexaminetheexactmethod,itsparametersandassociatedannotations.TheprocessingforaURLnolongerneedstobesplitacrossdifferentcontrollers.
下面这几件事情已经不再有效:
上面的特性仍由现有的支持类支持。然而,如果想使用Spring3.1的新特性,你需要使用新的支持类!
URITemplatePatterns
URITemplate是一个类似URI的字符串,包含一个或多个变量名字。当你使用值将其中的变量全部替换之后,该模板会变成一个URI。
在SpringMVC中,你可以在方法的一个参数上使用@PathVariable注解,就可以将实参绑定到URI模板变量的值。
@GetMapping("/owners/{ownerId}")publicStringfindOwner(@PathVariableStringownerId,Modelmodel){Ownerowner=ownerService.findOwner(ownerId);model.addAttribute("owner",owner);return"displayOwner";}注意,如果方法的参数名与URI模板变量的名字不符,那需要显式的指定;否则可以省略。如下:
@GetMapping("/owners/{ownerId}/pets/{petId}")publicStringfindPet(@PathVariableStringownerId,@PathVariableStringpetId,Modelmodel){Ownerowner=ownerService.findOwner(ownerId);Petpet=owner.getPet(petId);model.addAttribute("pet",pet);return"displayPet";}当在Map
URI模板可以从type和method级别的@RequestMapping注解中组装。
带正则表达式的URITemplatePatterns
有时候你需要更精确的定义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]+}")publicvoidhandle(@PathVariableStringversion,@PathVariableStringextension){//...}PathPatterns
除了URI模板,@RequestMapping注解和其所有变体还支持ant-style的pathpatterns,如/mypath/*.do。
PathPatternComparison
当一个URL匹配多个patterns时,会使用一种排序来查找最佳匹配。
带有更少URI变量和通配符的pattern,被认为更匹配。例如,/hotels/{hotel}/*比/hotels/{hotel}/**更合适,因为其他一样,通配符更少。
如果变量数量一样,更长的被认为更匹配。例如,/foo/bar*比/foo/*更匹配。
当两个patterns拥有相同数量的变量和长度时,通配符少的更匹配。例如,/hotels/{hotel}比/hotels/*更匹配。
另外,还有两个特殊规则:
带有占位符的pathpatterns
@RequestMapping注解的patterns还支持${...}占位符。
后缀pattern匹配
默认,SpringMVC会执行“.*”的匹配,所以,当一个controller的被映射到/person的时候,实际上是隐式的被映射到/person.*。这样可以使用URL轻松的请求不同的资源表现,如/person.pdf,/person.xml。
后缀pattern匹配可以被关闭,或者被限制在一组为了内容协商目的而注册的路径扩展名中。非常建议使用最普通的请求映射来最小化请求的模糊性,因为有时候“.”不一定代表扩展名,例如/person/{id},有可能是/person/joe@xx.com。
后缀pattern匹配和RFD
Reflectedfiledownload(RFD)攻击,最早由Trustwave在2014年指出。这种攻击类似XSS,都依赖可以反射到响应的输入。然而,不是将js插入到HTML中,RFD攻击依赖于浏览器的下载,并依赖于浏览器将响应视为一个可执行脚本(双击能运行的那种)。
在SpringMVC中,@ResponseBody和@ResponseEntity方法同样具备风险,因为它们可以渲染不同内容类型--客户端可能通过URL路径扩展名来请求的类型。需要注意,无论是单独的禁用后缀pattern匹配还是单独的禁用用于内容协商目的的路径扩展名,都不能有效的组织RFD攻击。
为了有效的防护RFD,Spring在渲染响应体之前添加了一个header(Content-Disposition:inline;filename=f.txt)以建议一个固定和安全的下载文件名。这只有在URL路径包含的扩展名既不在白名单中,也不是因内容协商目的而显式注册的文件扩展名时才有效。然而,这样做也有其副作用,有时候可能会直接显示在浏览器中!
默认,很多常用的路径扩展名已经在白名单中。此外,RESTAPI的调用通常不是用作在浏览器中使用的URL。尽管如此,使用自定义HttpMessageConverter实现的应用,可以显式的注册用于内容协商目的的文件扩展名,针对这些扩展名Content-Dispositionheader(Content-Disposition:inline;filename=f.txt)不会被添加。
MatrixVariables
matrixvariables可能出现在任意pathsegment中,每个matrixvariable都由分号隔离。例如:"/cars;color=red;year=2012"。多个value的时候,可以使用逗号拼接,如"color=red,green,blue",也可以重复name,如"color=red;color=green;color=blue"。
如果希望一个URL包含matrixvariables,请求映射pattern必须使用URI模板来代表它们。
下面是提取matrixvariable”q”的例子:
//GET/pets/42;q=11;r=22@GetMapping("/pets/{petId}")publicvoidfindPet(@PathVariableStringpetId,@MatrixVariableintq){//petId==42//q==11}因为所有的pathsegments都可能含有matrixvariables,某些情况下你需要更精确的信息来确定需要的变量:
//GET/owners/42;q=11/pets/21;q=22@GetMapping("/owners/{ownerId}/pets/{petId}")publicvoidfindPet(@MatrixVariable(name="q",pathVar="ownerId")intq1,@MatrixVariable(name="q",pathVar="petId")intq2){//q1==11//q2==22}一个matrixvariable可以被定义为可选项,可以拥有一个指定的默认值;
//GET/owners/42;q=11;r=12/pets/21;q=22;s=23@GetMapping("/owners/{ownerId}/pets/{petId}")publicvoidfindPet(@MatrixVariableMultiValueMap
TheMVCJavaconfigandtheMVCnamespacebothprovideoptionsforenablingtheuseofmatrixvariables.
IntheMVCnamespace,the
通过指定一个consumablemediatypes列表来窄化映射。只有requestheader中的Content-Type符合指定的媒体类型时,请求才匹配。例如:
@PostMapping(path="/pets",consumes="application/json")publicvoidaddPet(@RequestBodyPetpet,Modelmodel){//implementationomitted}注意,consumablemediatype表达式可以使用“!”来否定匹配的媒体类型,如使用“!text/plain”来匹配除了text/plain之外的Content-Type。建议使用MediaType中的常量,如APPLICATION_JSON_VALUE、APPLICATION_JSON_UTF8_VALUE。
注意,虽然consumes条件支持type和method级别,但是,不同于其他条件,method级别的会覆盖type级别的类型!!!
ProducibleMediaTypes
还可以通过指定一个produciblemediatypes列表来窄化请求。仅当requestheader的Accept匹配时,该request才会匹配。此外,使用produces条件会确保实际的内容类型。如下:
@GetMapping(path="/pets/{petId}",produces=MediaType.APPLICATION_JSON_UTF8_VALUE)@ResponseBodypublicPetgetPet(@PathVariableStringpetId,Modelmodel){//implementationomitted}注意produces条件指定的媒体类型,也可以选择性的指定一个字符集。例如,在上面的代码片段中,我们指定了与MappingJackson2HttpMessageConverter中默认配置的媒体类型一致的媒体类型。--是否可以认为,一种字符集就是一种媒体类型?
同consumes类似,produces也可以使用“!”。同样建议使用MediaType中的常量。
同consumes类似,方法级别的produces会覆盖类级别的媒体类型!!!
请求参数和请求头的值RequestParameter、RequestHeadervalues
可以通过请求参数条件来窄化请求的匹配,如:"myParam","!myParam",or"myParam=myValue"。前两个用于测试请求参数中是否出现该参数,第三个则需要请求参数有一个特定的值。例子:
@Controller@RequestMapping("/owners/{ownerId}")publicclassRelativePathUriTemplateController{@GetMapping(path="/pets",headers="myHeader=myValue")publicvoidfindPet(@PathVariableStringownerId,@PathVariableStringpetId,Modelmodel){//implementationomitted}}虽然你可以使用通配符来匹配Content-Type和Acceptheadervalues(如headers="content-type=text/*",可以匹配"text/plain"和"text/html"),但建议使用consumes和produces。这也是它们的设计目的。
HTTPHEAD和HTTPOPTIONS
@RequestMapping方法映射到“GET”,同时也会隐式的映射到“HEAD”!
@RequestMapping方法内建支持HTTPOPTIONS。略。
3.3、定义@RequestMappinghandlermethods
@RequestMappinghandlermethods可以有非常灵活的签名。除了BindingResult参数之外的参数可以按任意顺序排放。
Spring3.1为@RequestMappingmethods引入了一组新的支持类,分别是:RequestMappingHandlerMappingandRequestMappingHandlerAdapter。建议使用它们,而且,应该使用Spring3.1及以后的新特性。这些新的支持类默认由MVC命名空间启用,如果是Javaconfig,必须显式的配置--否则无法使用。
支持的方法参数类型
session的访问可能不是线程安全的,特别是在一个Servlet环境中。如果需要多个请求并发访问一个session时,可以考虑将RequestMappingHandlerAdapter的synchronizeOnSession设置为true。
Errors和BindingResult参数,必须跟在modelobject后面,因为可能有多个modelobject,Spring会为每个modelobject创建一个单独的BindingResult,所以,下面的例子不会执行:
@PostMappingpublicStringprocessSubmit(@ModelAttribute("pet")Petpet,BindingResultresult,Modelmodel){...}支持JDK1.8的java.util.Optional作为方法参数类型,与带有requiredattribute的注解一起使用(如@RequestParam、@RequestHeader等等)。这些情况下,java.util.Optional相当于required=false。
支持的方法返回类型
使用@RequestParameter将请求参数绑定到方法参数
@Controller@RequestMapping("/pets")@SessionAttributes("pet")publicclassEditPetForm{//...@GetMappingpublicStringsetupForm(@RequestParam("petId")intpetId,ModelMapmodel){Petpet=this.clinic.loadPet(petId);model.addAttribute("pet",pet);return"petForm";}//...}默认,@RequestParam的requiredattribute是true,可以设置为false。@RequestParam(name="id",required=false)
如果目标方法参数的类型不是String,会自动使用类型转换。
当在Map
使用RequestBody注解来映射requestbody
方法参数的@RequestBody注解,标识了方法参数应该绑成HTTPrequestbody的值。例如:
@PutMapping("/something")publicvoidhandle(@RequestBodyStringbody,Writerwriter)throwsIOException{writer.write(body);}可以通过使用一个HttpMessageConverter将requestbody转成methodargument。HttpMessageConverter负责将HTTPrequestmsg转成一个对象以及将对象转成HTTPresponsebody。RequestMappingHandlerAdapter支持@RequestBody的默认HttpMessageConverters:
注意,如果使用MVC命名空间或者使用MVCJavaconfig,默认会注册更多的messageconverters。
@RequestBody注解的方法参数还可以使用@Valid注解,Spring会使用配置好的Validator实例来校验该参数。当使用MVC命名空间或MVCJavaconfig时,一个JSR-303validator会被自定的配置--假如classpath中有一个JSR-303实现。
使用@ResponseBody注解来映射responsebody
@ResponseBody注解类似于@RequestBody。该注解放在方法上指示返回类型会被写入HTTPresponsebody(没有被放入Model,或被解释成viewname)。例如:
@GetMapping("/something")@ResponseBodypublicStringhelloWorld(){return"HelloWorld";}上面的例子,会将字符串写入HTTPresponsestream。
如同@RequestBody,Spring会将返回的对象转换成一个responsebody--使用一个HttpMessageConverter。
使用@RestController注解创建一个RESTController
使用@RestController代替@ResponseBody与@Controller。它虽然是由后两者组合而成,但在将来会被赋予更多语义。
使用HttpEntity
HttpEntity类似于@RequestBody和@ResponseBody。除了能获取request和responsebody之外,HttpEntity(以及其response子类:ResponseEntity)还允许获取request和responseheaders,如下:
TheaboveexamplegetsthevalueoftheMyRequestHeaderrequestheader,andreadsthebodyasabytearray.ItaddstheMyResponseHeadertotheresponse,writesHelloWorldtotheresponsestream,andsetstheresponsestatuscodeto201(Created).
在方法上使用@ModelAttribute
该注解可以用在方法或方法参数上。本部分讲解用在方法上的作用,下一部分会讲解用在方法参数上的作用。
在方法上使用该注解,意味着该方法的一个目的是增加一个或多个modelattribute。该方法支持的参数类型与@RequestMappingmethods一样,但不能直接映射到请求。相反,同一个Controller中的@ModelAttributemethods会在@RequestMappingmethods之前被调用!!!例子:
//添加一个attribute//该方法的返回值会被添加到modelaccount中//你可以定义该model的名字,例如@ModelAttribute("myAccount")@ModelAttributepublicAccountaddAccount(@RequestParamStringnumber){returnaccountManager.findAccount(number);}//添加多个attributes@ModelAttributepublicvoidpopulateModel(@RequestParamStringnumber,Modelmodel){model.addAttribute(accountManager.findAccount(number));//addmore...}@ModelAttributemethods被用于将常用的attributes填入model。
注意两种形式的@ModelAttributemethods。第一个,是隐式的将返回值添加为attribute。第二个,接收一个Model,然后在其中增加任意数量的modelattributes。
一个Controller可以拥有任意数量的@ModelAttributemethods。所有这些方法都会在同一个Controller中的@RequestMappingmethods之前被调用!
@ModelAttributemethods也可以被定义在@ControllerAdviceclass内,这样的methods会被用于所有Controllers。--就是在所有Controller的所有@RequestMappingmethods之前被调用!
如果没有显式指定一个modelattributename,会发生什么?这种情况下,会基于其类型赋予一个默认的名字。例如,如果方法返回了Account类型,那默认的name就是account。
在方法参数上使用@ModelAttribute
@PutMapping("/accounts/{account}")publicStringsave(@ModelAttribute("account")Accountaccount){//...}上面的例子,modelattribute的name与URI模板变量的名字一致。如果你注册了一个Converter
数据绑定的一个结果是,可能存在errors,例如缺失必须的字段或者类型转换错误。为了检查该类错误,需要在@ModelAttributeargument之后紧跟着添加一个BindingResultargument。
@PostMapping("/owners/{ownerId}/pets/{petId}/edit")publicStringprocessSubmit(@ModelAttribute("pet")Petpet,BindingResultresult){if(result.hasErrors()){return"petForm";}//...}使用BindingResult,你可以检查是否有errors,可以使用Spring的
注意,某些情况下,不使用数据绑定而获取model中的一个attribute很有用。这些情况下,你可以在Controller中注入Model,或者在注解上使用bindingflag,如下:
@ModelAttributepublicAccountFormsetUpForm(){returnnewAccountForm();}@ModelAttributepublicAccountfindAccount(@PathVariableStringaccountId){returnaccountRepository.findOne(accountId);}@PostMapping("update")publicStringupdate(@ValidAccountUpdateFormform,BindingResultresult,@ModelAttribute(binding=false)Accountaccount){//...}InadditiontodatabindingyoucanalsoinvokevalidationusingyourowncustomvalidatorpassingthesameBindingResultthatwasusedtorecorddatabindingerrors.Thatallowsfordatabindingandvalidationerrorstobeaccumulatedinoneplaceandsubsequentlyreportedbacktotheuser:
@PostMapping("/owners/{ownerId}/pets/{petId}/edit")publicStringprocessSubmit(@ModelAttribute("pet")Petpet,BindingResultresult){newPetValidator().validate(pet,result);if(result.hasErrors()){return"petForm";}//...}--就是根据BindingResult的结果进行自己的操作。
或者,可以使用JSR-303@Valid注解来自动调用校验:
@PostMapping("/owners/{ownerId}/pets/{petId}/edit")publicStringprocessSubmit(@Valid@ModelAttribute("pet")Petpet,BindingResultresult){if(result.hasErrors()){return"petForm";}//...}使用@SessionAttributes在requests之间的HTTPsession中存储modelattributes
使用@RequestAttribute来获取requestattributes
类似于@SessionAttribute,@RequestAttribute也可以用于获取pre-existingrequestattributes--由filter或interceptor创建的。
由于HttpPutFormContentFilter会consume请求体,所以,不应为那些依赖针对application/x-www-form-urlencoded的转换器的PUT或PATCHURLs配置该filter。这包括@RequestBodyMultiValueMap
使用@CookieValue注解来映射cookievalues
该注解允许一个方法参数绑定一个HTTPcookie的值。
JSESSIONID=415A4AC178C59DACE0B2C9CA727CDD84下面的代码演示了如何获取JSESSIONIDcookie:
@RequestMapping("/displayHeaderInfo.do")publicvoiddisplayHeaderInfo(@CookieValue("JSESSIONID")Stringcookie){//...}如果目标方法参数的类型不是String,会自动应用类型转换。
该注解,也被在Servlet和Portlet环境下的annotatedhandlermethods支持。
使用@RequestHeader注解来映射requestheaderattributes
这是一个样例requestheader:
Hostlocalhost:8080Accepttext/html,application/xhtml+xml,application/xml;q=0.9Accept-Languagefr,en-gb;q=0.7,en;q=0.3Accept-Encodinggzip,deflateAccept-CharsetISO-8859-1,utf-8;q=0.7,*;q=0.7Keep-Alive300下面的代码演示了如何获取Accept-Encoding和Keep-Aliveheaders的值:
@RequestMapping("/displayHeaderInfo.do")publicvoiddisplayHeaderInfo(@RequestHeader("Accept-Encoding")Stringencoding,@RequestHeader("Keep-Alive")longkeepAlive){//...}如果目标方法参数的类型不是String,会自动应用类型转换。
当@RequestHeader注解用于一个Map
内建的支持允许转换一个逗号间隔的字符串,将其转成一个字符串或其他类型的数组/集合。例如,@RequestHeader(“Accept”)注解的方法参数,可能是String类型,也可能是String[]或List
methodparameters和typeconversion
从request中提取出来的基于字符串的值,包括requestparameters、pathvariables、requestheaders、还有cookievalues,可能需要被转成它们要绑定的methodparameter或field的类型。如果目标类型不是String,Spring会自动转成合适的类型。支持所有简单类型,如int、long、Date等等。甚至,你可以使用一个WebDataBinder来定制转换过程,或者通过在FormattingConversionService中注册Formatters。
定制WebDataBinder初始化
使用@InitBinder定制数据绑定
在controller方法上使用@InitBinder,允许你在controller内部配置web数据绑定。@InitBinder代表方法初始化了WebDataBinder--会被用于填充被注解的handler方法的command和formobjectarguments。
下面的案例演示了使用@InitBinder来配置针对所有java.util.Dateformproperties的一个CustomDateEditor。
@ControllerpublicclassMyFormController{@InitBinderprotectedvoidinitBinder(WebDataBinderbinder){SimpleDateFormatdateFormat=newSimpleDateFormat("yyyy-MM-dd");dateFormat.setLenient(false);binder.registerCustomEditor(Date.class,newCustomDateEditor(dateFormat,false));}//...}或者,自Spring4.2起,可以考虑使用addCustomFormatter来指定Formatter实现,取代PropertyEditor实例。
如果你在一个sharedFormattingConversionService中有基于Formatter的设置,这会非常有用,只需要同样的做法来复用特定controller针对bindingrules的调节。
@ControllerpublicclassMyFormController{@InitBinderprotectedvoidinitBinder(WebDataBinderbinder){binder.addCustomFormatter(newDateFormatter("yyyy-MM-dd"));}//...}配置一个定制的WebBindingInitializer
为了将数据绑定初始化外部化,你可以提供一个定制的WebBindingInitializer实现,然后通过提供一个定制的AnnotationMethodHandlerAdapter的beanconfiguration来启用它。
下面的例子示意了使用了org.springframework.samples.petclinic.web.ClinicBindingInitializer的配置,该配置配置了几个PetCliniccontrollers需要的PropertyEditors。
advisingcontrollerswith@ControllerAdviceand@RestControllerAdvice
@ControllerAdvice注解,是一个componentannotation,允许实现类能够在classpath扫描中被自动探测到。当使用MVCnamespace或MVCJavaconfig时,自动启用。
@RestControllerAdvice,等同于@ExceptionHandler+@ResponseBodymethods。
@ControllerAdvice和@RestControllerAdvice都可以指定作用的controllers:
//TargetallControllersannotatedwith@RestController@ControllerAdvice(annotations=RestController.class)publicclassAnnotationAdvice{}//TargetallControllerswithinspecificpackages@ControllerAdvice("org.example.controllers")publicclassBasePackageAdvice{}//TargetallControllersassignabletospecificclasses@ControllerAdvice(assignableTypes={ControllerInterface.class,AbstractController.class})publicclassAssignableTypesAdvice{}详见@ControllerAdvice文档。
JacksonSerializationViewSupport
在一个@Responsecontrollermethod上,或者在那些返回ResponseEntity的controllermethods上,简单的添加@JsonView注解,并指定需要使用的viewclass或Interface即可。如下:
@RestControllerpublicclassUserController{@GetMapping("/user")@JsonView(User.WithoutPasswordView.class)publicUsergetUser(){returnnewUser("eric","7!jd#h23");}}publicclassUser{publicinterfaceWithoutPasswordView{};publicinterfaceWithPasswordViewextendsWithoutPasswordView{};privateStringusername;privateStringpassword;publicUser(){}publicUser(Stringusername,Stringpassword){this.username=username;this.password=password;}@JsonView(WithoutPasswordView.class)publicStringgetUsername(){returnthis.username;}@JsonView(WithPasswordView.class)publicStringgetPassword(){returnthis.password;}}注意,尽管@JsonView允许指定多个class,但在controllermethod上使用时只能指定一个class!可以考虑使用复合接口,如果你需要启用多个views。
对于那些依赖viewresolution的controllers,简单的将序列化viewclass添加到model即可:
@ControllerpublicclassUserControllerextendsAbstractController{@GetMapping("/user")publicStringgetUser(Modelmodel){model.addAttribute("user",newUser("eric","7!jd#h23"));model.addAttribute(JsonView.class.getName(),User.WithoutPasswordView.class);return"userView";}}
JacksonJSONP支持
@ControllerAdvicepublicclassJsonpAdviceextendsAbstractJsonpResponseBodyAdvice{publicJsonpAdvice(){super("callback");}}对于依赖viewresolution的controllers,JSONP自动被启用,默认的queryparametername是jsonp或callback。可以通过其jsonpParameterNamesproperty来定制。
3.4、异步请求的处理
Spring3.2引入了基于Servlet3的异步请求处理。不是一直以来的让controllermethod返回一个值,而是,让controllermethod返回一个java.util.concurrent.Callable,然后从SpringMVC管理的线程中produce返回值。同时,mainServletcontainerthread会被退出和释放,以处理其他请求。SpringMVC在一个独立的线程调用Callable--通过TaskExecutor,当Callable返回时,请求会被分派回Servletcontainer,从而恢复处理。例子:
另一个选择是,controllermethod返回DeferredResult。这种情况下,也可能是任意线程produce的返回值,就是说,非SpringMVC管理的线程!例如,结果可能是响应某个外部事件,如一个JMSmessage、一个scheduledtask等等,而produce的结果。下面是一个例子:
Withtheaboveinmind,thefollowingisthesequenceofeventsforasyncrequestprocessingwithaCallable:
ThesequenceforDeferredResultisverysimilarexceptit’suptotheapplicationtoproducetheasynchronousresultfromanythread:
asyncrequests的Exception处理
如果,一个由controllermethod返回的Callable在执行时抛出了一个Exception,会发生什么?简短的答案是与一个controllermethod抛出一个异常时相同。会经历常规的异常处理机制。长的解释是,当Callable抛出一个Exception时,SpringMVC会将该Exception分派到Servletcontainer,将其作为结果以及恢复requestprocessing的引导,此时requestprocessing会处理Exception,而非controllermethodreturnvalue。当使用DeferredResult时,你还可以选择是否调用setResult或者setErrorResult--传入Exception实例。
拦截asyncrequests
一个HandlerInterceptor也可以实现AsyncHandlerInterceptor,以实现afterConcurrentHandlingStartedcallback,当asynchronousprocessing开始时,会调用afterConcurrentHandlingStarted,而非postHandle和afterComplete。
一个HandlerInterceptor也可以注册一个CallableProcessingInterceptor或一个DeferredResultProcessingInterceptor,以更深度地集成asynchronousrequest的lifecycle,例如,handle一个timeoutevent。详见AsyncHandlerInterceptorjavadoc。
DeferredResult类型,也提供了诸如onTimeout(Runnable)、onCompletion(Runnable)之类的方法。详见javadoc。
当使用一个Callable时,你可以将其wrap进一个WebAsyncTask的实例,该实例也可以提供timeout和completion的方法注册。
HTTPStreaming
如果你想要在一个HTTPresponse上push多个事件会怎样?这就是与”LongPolling”有关的技术,也就是HTTPStreaming。SpringMVC通过ResponseBodyEmitter返回值类型使其成为可能,该返回值类型可悲用于发送多个对象(而非使用@ResponseBody只发送一个--这种更常见),每个被发送的对象都通过一个HttpMessageConverter被写入到response。
例子:
@RequestMapping("/events")publicResponseBodyEmitterhandle(){ResponseBodyEmitteremitter=newResponseBodyEmitter();//Savetheemittersomewhere..returnemitter;}//Insomeotherthreademitter.send("Helloonce");//andagainlateronemitter.send("Helloagain");//anddoneatsomepointemitter.complete();注意,ResponseBodyEmitter也可被用做ResponseEntity的body,以便定制response的status和headers。
HTTPStreamingWithServer-SentEvents
HTTPStreamingDirectlyToTheOutputStream
@RequestMapping("/download")publicStreamingResponseBodyhandle(){returnnewStreamingResponseBody(){@OverridepublicvoidwriteTo(OutputStreamoutputStream)throwsIOException{//write...}};}
ConfiguringAsynchronousRequestProcessing
3.5、测试controllers
4、Handlermappings
在之前的Spring版本中,用户必须要在web应用上下文中定义一个或者多个HandlerMappingbeans以将incomingwebrequests映射到合适的handlers。随着annotatedcontrollers的引入,现在一般可以不必那样做了,因为RequestMappingHandlerMapping会自动在所有@Controllerbeans中查找@RequestMapping注解。然而,务必记住,所有的继承自AbstractHandlerMapping的HandlerMapping类,都有以下properties--你可以用来定制它们的行为:
interceptors,使用的拦截器列表。
defaultHandler,默认使用的handler--当handlermapping没有一个匹配的handler时。
order,基于该property的值(Ordered接口),Spring将所有可用的handlermappings进行排序,并应用第一匹配的handler。
alwaysUseFullPath,如果设为true,Spring会在当前Servletcontext中使用全路径来查找合适的handler。如果false(默认就是),会使用当前Servletmapping内的路径。例如,如果一个Servlet被映射到/testing/*,当设为true时,使用/testing/viewPage.html,否则,/viewPage.html。
urlDecode,默认true,自Spring2.5起。如果你倾向于对比encodedpaths,需要设为false。然而,HttpServletRequest总是以decoded形式暴露Servletpath。注意,当与encodedpath对比时,Servletpath不会匹配。
配置拦截器的例子:
4.1使用HandlerInterceptor拦截requests
spring的handlermapping机制包括handlerinterceptors,当你想要针对特定的requests应用特定的功能时,非常有用。
位于handlermapping内的interceptors,必须实现org.springframework.web.servlet.HandlerInterceptor(或者其实现/子类)。
该接口定义有三个方法preHandle(..)postHandle(..)afterHandle(..)。见这里。(为知笔记的连接,不知道行不行,以后再说)
preHandle(..)方法会返回一个boolean值,如果false,会破坏执行链的处理过程(不再往下执行)。如果false,DispatcherServlet会认定该拦截器自身来处理请求(例如,渲染视图等),所以不会继续执行其他的拦截器和实际的handler。
拦截器可以在所有继承自AbstractHandlerMapping的类中设置,使用其interceptors属性!如下:
注意:当使用RequestMappingHandlerMapping时,实际的handler是HandlerMethod的一个实例,该HandlerMethod会识别要被调用的特定的controllermethod。
我的补充:handlermapping这个过程,是将request与handler之间映射起来的过程。Spring提供的实现类,能用的也就这几个:
---还有一个RequestMappingHandlerAdapter,不要混淆了。
如你所见,Spring的适配器类HandlerInterceptorAdapter,让继承HandlerInterceptor更加简单。