Spring Web MVC 框架是围绕DispatcherServlet设计的,所谓DispatcherServlet就是将请求分发到handler,须要有配置好的handler映射、视图解析、本地化、时区、theme解决方案、还有上传文件的支持。默认的handler是基于@Controller和@RequestMapping注解。自Spring 3.0 起,@Controller注解还能用于RESTful,须要配合@PathVariable以及其余特性。java
Spring Web MVC 和 Spring的设计准则是“对扩展开放,对修改关闭”--能够扩展,不能修改。git
Spring Web MVC中核心类的一些方法被标记为final。开发者不能覆盖这些方法以提供本身的行为。这不是任性,而是听从设计准则。github
在Spring MVC中,你不能为final方法添加advice。web
在Spring Web MVC中,你可使用任何对象做为命令对象或者form-backing对象;你不须要实现框架的特定接口或者基类。Spring的数据绑定是高度弹性的:例如,它将类型错误匹配视为校验错误,而非系统错误,从而可被应用evaluate。正则表达式
Spring的view resolution是很是弹性的。Controller负责准备一个model Map,其中带有数据,还负责挑选一个view name -- 也能够直接写到响应流而完成一个请求。view name resolution是高度可定制的,使用文件扩展名或者Accept header content type negotiation,经过bean names、properties文件、或者,甚至一个自定义的ViewResolver实现。model(MVC中的M)是一个Map接口,容许view技术的彻底抽象。你能够直接集成基于模板的rendering技术,例如JSP、Velocity和Freemarker、或者直接生成XML、JSON、Atom、以及不少其余内容类型。model Map会被简单的变成合适的格式,如JSP请求属性、如Velocity模板模型。spring
Spring Web Flow编程
目标是成为管理web应用页面流程的最佳解决方案。json
SWF集成了现有的框架,如Spring MVC和JSF,在Servlet和Portlet环境中。若是你有一个业务process(或多个)想从conversational model中受益(与纯request model相比),那么SWF就是这种解决方案。
SWF容许你捕获logical page flows,将其做为self-contained modules,能够在不一样环境下复用,所以,这是构建web应用模块的理想方式,可以引导用户-- 使用驱动业务processes的controlled navigations。
Spring的web模块包含许多独特的web支持特性:
对于某些项目来讲,非Spring的MVC实现更适合。不少团队但愿借用已有的投资(囧,真抽象),例如,使用JSF。
若是你不想使用Spring Web MVC,而想使用Spring提供的其余解决方案,你能够将 你选择的web MVC框架 集成到Spring中,这很简单。经过Spring的ContextLoaderListener启动Spring的root application context,能够在任意action object中经过它的ServletContext属性来获取它 -- 也可使用Spring的相应帮助方法来获取。
你注册的beans和Spring的服务随时可用 -- 哪怕没有Spring MVC。这种情景下,Spring不会同其余web框架竞争。它只是简单的致力于纯web MVC框架没有关注的地方,从bean配置到数据访问和事务处理。因此,哪怕你只是想配合JDBC或Hibernate来使用Spring的事务抽象,仍然能够将Spring中间件 和/或 数据访问中间件做为你应用的一部分。
像不少其余web MVC框架同样,Spring MVC框架也是请求驱动的,围绕一个中心Servlet来设计,该中心Servlet能够分发请求到Controllers,并提供其余功能。然而Spring的DispatcherServlet作的更多。它被完全地与Spring IoC容器集成到了一块儿,因此,你可使用Spring的全部特性。
Spring MVC DispatcherServlet处理请求的工做流程以下图所示。聪明的读者会认出DispatcherServlet是Front Controller设计模式的一种实现。
DispatcherServlet是一个具体的Servlet(继承自HttpServlet基类),因此你须要使用一个URL mapping来映射请求 -- 就是你想让DispatcherServlet处理的请求。这是一个标准Java EE Servlet配置,在Servlet 3.0+ 环境下能够这样注册该Servlet:
public class MyWebApplicationInitializer implements WebApplicationInitializer { // 这个接口,或者其抽象实现类 @Override public void onStartup(ServletContext container) { ServletRegistration.Dynamic registration = container.addServlet("example", new DispatcherServlet()); registration.setLoadOnStartup(1); registration.addMapping("/example/*"); } }
上面的例子中,全部以 /example开头的请求 都会由名字为example的DispatcherServlet实例来处理。
WebApplicationInitializer是Spring MVC提供的接口,能够确保基于代码的配置被探测到、且被自动用于初始化Servlet 3 容器。该接口的一个abstract基类是AbstractAnnotationConfigDispatcherServletInitializer
,该abstract基类注册DispatcherServlet更简便,只须要指定映射、列出配置类便可 -- 这是设置Spring MVC项目的一种推荐的方式(java config)。
或者,传统模式,web.xml中的设置方式:
<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>
在前面曾讲过,在Spring中,ApplicationContext实例能够被scoped (就是有scope)。
而在Spring MVC框架中,每一个DispatcherServlet都有其自有的WebApplicationContext,它会继承在root WebApplicationContext中定义的beans。 root WebApplicationContext应该包含全部基础beans,以被其余contexts 和 Servlet实例分享。被继承的beans能够被特定Servlet scope重写,你能够定义针对给定Servlet实例(其scope)的beans。
在DispatcherServlet初始化过程当中,Spring MVC会在web应用的/WEB-INF文件夹下查找名字为 [servlet-name]-servlet.xml 的文件,建立其中定义的bean,并会重写在全局scope中已经定义的任何beans的定义。
看一下下面的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>
根据上面的Servlet配置,还须要一个文件:/WEB-INF/golfing-servlet.xml。该文件会包含全部的Spring MVC特定的组件(beans)。固然,你能够更改该路径,经过特定的Servlet初始化参数(详见下面)。
对于单DispatcherServlet情景来讲,也能够只有一个root context。
这能够经过设置一个空的contextConfigLocation servlet init 参数来配置,以下:
<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应用拥有一些额外的特性。
它不一样于normal ApplicationContext的地方是它可以resolve themes,它还知道关联哪一个Servlet(经过到ServletContext的链接)。
WebApplicationContext被束缚在ServletContext中,经过使用RequestContextUtils类的静态方法,你能够随时查找WebApplicationContext。
注意,咱们可使用基于Java的配置达到一样的目的:
public class GolfingWebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer { // @Override protected Class<?>[] getRootConfigClasses() { // GolfingAppConfig defines beans that would be in root-context.xml return new Class[] { GolfingAppConfig.class }; } @Override protected Class<?>[] getServletConfigClasses() { // GolfingWebConfig defines beans that would be in golfing-servlet.xml return new Class[] { GolfingWebConfig.class }; } @Override protected String[] getServletMappings() { return new String[] { "/golfing/*" }; } }
2.一、在WebApplicationContext中的特殊的bean types
Spring DispatcherServlet使用特殊的beans来处理请求并渲染视图。这些beans是Spring MVC的一部分。你能够选择使用哪一个 -- 只须要在WebApplicationContext中简单的配置一些便可。
Spring MVC维护了一个默认beans列表以供使用,下一部分会讲。
如今先来看看DispatcherServlet依赖的特殊的bean types:
bean type | 解释 |
HandlerMapping | 基于criteria(不一样的HandlerMapping实现有不一样的criteria)将incoming requests映射到handlers以及一个pre和post-processors(handler interceptors)。最流行的实现支持注解controllers。 |
HandlerAdapter | 帮助DispatcherServlet调用映射到一个request的handler,不管handler是否实际被调用了。例如,调用一个注解Controller须要处理不一样的注解。因此,HandlerAdapter的主要目的是让DispatcherServlet远离这些细节。 |
HandlerExceptionResolver | 将exceptions映射到views,也容许更复杂的exception处理代码。 |
ViewResolver | 处理基于字符串的逻辑视图的名字,将其转成实际的View 类型。 |
LocaleResolver & LocaleContextResolver | Resolves the locale a client is using and possibly their time zone, in order to be able to offer internationalized views |
ThemeResolver | Resolves themes your web application can use, for example, to offer personalized layouts |
MultipartResolver | 解析multi-part请求,例如,支持处理来自HTML forms的文件上传。 |
FlashMapManager | 存储和获取input 和 output的FlashMap -- 可用于从一个request将attributes传递至另外一个,一般跨域一个redirect。 |
上面有提到,对每一个特殊的bean,DispatcherServlet默认都维护了一个实现列表以供使用。这个信息保存在 DispatcherServlet.properties 中,在org.springframework.web.servlet包下。
全部的特殊的beans都有其合理的默认(bean仍是设置?)。或早或晚,你总会须要定制这些beans提供的一个或多个properties。例如,配置InternalResourceViewResolver的prefix property 。
这里最须要理解的概念就是:一旦你在你的WebApplicationContext中配置了一个特殊的bean,如InternalResourceViewResolver,你就彻底重写了某个类型的默认的实现列表。例如,若是你配置了InternalResourceViewResolver,那默认的ViewResolver实现列表会被忽略!
当你set up了一个DispatcherServlet,而后一个由其负责的request进来了,这时该DispatcherServlet会以以下流程开始处理请求:
DispatcherServlet.WEB_APPLICATION_CONTEXT_ATTRIBUTE
。
在WebApplicationContext中声明的handler exception resolvers会pick up 处理request过程当中抛出的exceptions。使用这些exception resolvers容许你定义本身的行为来处理exceptions。
SpringDispatcherServlet也支持返回 last-modification-date(最后修改日期),如同Servlet API中指定的同样。决定特定request的last modification date的处理是简单粗暴的:DispatcherServlet会查找合适的handler mapping,并测试找到的handler是否实现了LastModified接口。若是实现了,long getLastModified(request)方法的值会被返回。
你能够定制本身的DispatcherServlet实例,只要在web.xml的Servlet声明中添加Servlet初始化参数(init-param elements)便可。下面列出了支持的参数。
DispatcherServlet 初始化参数
参数 | 解释 |
contextClass | 实现了WebApplicationContext的类,实例化了该Servlet使用的context。默认,使用XmlWebApplicationContext。 |
contextConfigLocation | 传递给context实例(由contextClass指定)的字符串,用于指出在什么地方找到context config。若有多个,以逗号间隔。若是多个,且beans有重复定义,以最后一个为准。 |
namespace | WebApplicationContext的命名空间。默认是[servlet-name]-servlet。 |
Controller将用户的input解释并转成一个model,该model能够经过view描述给用户。
Spring 2.5 为MVC Controllers引入了基于注解的编程模型,使用诸如@RequestMapping、@RequestParam、@ModelAttribute之类的注解。在Servlet MVC和Portlet 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"; } }
3.一、使用@Controller定义一个Controller
@Controller注解用于标记一个class拥有controller的角色。
注意,这种注解Controller须要开启自动探测才能使用。以下:
<?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>
3.二、使用@RequestMapping映射requests
使用@RequestMapping注解将URLs如/app映射到一个类或者特定的handler方法。示例:
@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"; } }
class级别上的@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()); } }
@RequestMapping 变体
Spring 4.3 引入了如下@RequestMapping注解的变体,方法级别的。
@GetMapping
@PostMapping
@PutMapping
@DeleteMapping
@PatchMapping
@Controller @RequestMapping("/appointments") public class AppointmentsController { private final AppointmentBook appointmentBook; @Autowired public AppointmentsController(AppointmentBook appointmentBook) { this.appointmentBook = appointmentBook; } @GetMapping public Map<String, Appointment> get() { return appointmentBook.getAppointmentsForToday(); } @GetMapping("/{day}") public Map<String, Appointment> getForDay(@PathVariable @DateTimeFormat(iso=ISO.DATE) Date day, Model model) { return appointmentBook.getAppointmentsForDay(day); } @GetMapping("/new") public AppointmentForm getNewForm() { return new AppointmentForm(); } @PostMapping public String add(@Valid AppointmentForm appointment, BindingResult result) { if (result.hasErrors()) { return "appointments/new"; } appointmentBook.addAppointment(appointment); return "redirect:/appointments"; } }
@Controller 和 AOP 代理
一些状况下,一个controller可能须要在运行时被AOP代理装饰。一个例子是在controller上使用@Transactional。这时,对这些controllers,咱们建议使用基于类的代理。这也是controller的默认选项。
然而,若是一个controller必须实现一个非Spring Context回调的接口(如InitializingBean、*Aware等等)的话,你可能须要显式的配置基于类的代理。例如,将 <tx:annotation-driven/>
改为 <tx:annotation-driven proxy-target-class="true"/>
。
对Spring MVC 3.1中@RequestMapping methods的新的支持类(解析类?)
Spring 3.1 引入了针对RequestMapping methods的一组新的支持类,叫作:RequestMappingHandlerMapping
、RequestMappingHandlerAdapter
。推荐使用它们,甚至须要利用Spring MVC 3.1及之后的一些新特性。这些新的支持类默认就由MVC命名空间和MVC Java config启用,其余状况必须显式的配置。本部分会描述一下新旧支持类之间的一些重要区别。
Spring 3.1以前,type和method -level request mappings是在两个独立的阶段中检查的 -- controller先被DefaultAnnotationHandlerMapping
选中,具体的方法调用则由AnnotationMethodHandlerAdapter
负责。
Spring 3.1 中新的支持类,只须要使用RequestMappingHandlerMapping
。不妨将controller的那些方法看做一个集合,其内容是带有映射的各类端点。
这使得一些新的可能成为现实。For once a HandlerInterceptor
or a HandlerExceptionResolver
can now expect the Object-based handler to be a HandlerMethod
, 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.
下面这几件事情已经再也不有效:
SimpleUrlHandlerMapping
or BeanNameUrlHandlerMapping
and then narrow the method based on @RequestMapping
annotations. -- 先选择一个controller,再根据其@RequestMapping注解窄化请求?为何不行了???几个意思? @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. --再也不支持基于方法名来区分没有显式指定映射URL的@RequestMapping方法。必须显式的指定映射URL。 上面的特性仍由现有的支持类支持。然而,若是想使用Spring 3.1的新特性,你须要使用新的支持类!
URI Template Patterns
URI Template是一个相似URI的字符串,包含一个或多个变量名字。当你使用值将其中的变量所有替换以后,该模板会变成一个URI。
在Spring MVC中,你能够在方法的一个参数上使用@PathVariable注解,就能够将实参绑定到URI模板变量的值。
@GetMapping("/owners/{ownerId}") public String findOwner(@PathVariable String ownerId, Model model) { Owner owner = ownerService.findOwner(ownerId); model.addAttribute("owner", owner); return "displayOwner"; }
注意,若是方法的参数名与URI模板变量的名字不符,那须要显式的指定;不然能够省略。以下:
@GetMapping("/owners/{ownerId}") public String findOwner(@PathVariable("ownerId") String theOwner, Model model) { // implementation omitted }
一个方法能够拥有任意数量的@PathVariable注解:
@GetMapping("/owners/{ownerId}/pets/{petId}") public String findPet(@PathVariable String ownerId, @PathVariable String petId, Model model) { Owner owner = ownerService.findOwner(ownerId); Pet pet = owner.getPet(petId); model.addAttribute("pet", pet); return "displayPet"; }
当在Map<String, String>实参上使用@PathVariable时,map会被全部的URI模板变量填满。
URI模板能够从type和method级别的@RequestMapping注解中组装。
@Controller @RequestMapping("/owners/{ownerId}") // 这里,类中的方法可使用 public class RelativePathUriTemplateController { @RequestMapping("/pets/{petId}") public void findPet(@PathVariable String ownerId, @PathVariable String petId, Model model) { // implementation omitted } }
@PathVariable实参能够是任何简单类型,如int、long、Date等。Spring会自动转换到合适的类型,若是失败会抛出TypeMishmatchException。-- 也能够注册其余数据类型: 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”.
带正则表达式的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/*
更匹配。
另外,还有两个特殊规则:
带有占位符的path patterns
@RequestMapping注解的patterns还支持 ${...} 占位符。
后缀pattern匹配
默认,Spring MVC会执行 “.*”的匹配,因此,当一个controller的被映射到/person的时候,其实是隐式的被映射到/person.*。这样可使用URL轻松的请求不一样的资源表现,如/person.pdf, /person.xml。
后缀pattern匹配能够被关闭,或者被限制在一组为了内容协商目的而注册的路径扩展名中。很是建议使用最普通的请求映射来最小化请求的模糊性,由于有时候“.”不必定表明扩展名,例如/person/{id},有多是/person/joe@xx.com。
后缀pattern匹配和RFD
Reflected file download (RFD) 攻击,最先由Trustwave在2014年指出。这种攻击相似XSS,都依赖能够反射到响应的输入。然而,不是将js插入到HTML中,RFD攻击依赖于浏览器的下载,并依赖于浏览器将响应视为一个可执行脚本(双击能运行的那种)。
在Spring MVC中,@ResponseBody 和 @ResponseEntity方法一样具有风险,由于它们能够渲染不一样内容类型--客户端可能经过URL路径扩展名来请求的类型。须要注意,不管是单独的禁用后缀pattern匹配仍是单独的禁用用于内容协商目的的路径扩展名,都不能有效的组织RFD攻击。
为了有效的防御RFD,Spring在渲染响应体以前添加了一个header(Content-Disposition:inline;filename=f.txt
)以建议一个固定和安全的下载文件名。这只有在URL路径包含的扩展名既不在白名单中,也不是因内容协商目的而显式注册的文件扩展名时才有效。然而,这样作也有其反作用,有时候可能会直接显示在浏览器中!
默认,不少经常使用的路径扩展名已经在白名单中。此外,REST API的调用一般不是用做在浏览器中使用的URL。尽管如此,使用自定义HttpMessageConverter实现的应用,能够显式的注册用于内容协商目的的文件扩展名,针对这些扩展名Content-Disposition header(Content-Disposition:inline;filename=f.txt
)不会被添加。
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.
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 anenable-matrix-variables
attribute that should be set totrue
. By default it is set tofalse
.<?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
@RequestMapping方法映射到“GET”,同时也会隐式的映射到“HEAD”!
@RequestMapping方法内建支持HTTP OPTIONS。略。
3.三、定义@RequestMapping handler methods
@RequestMapping handler methods能够有很是灵活的签名。除了BindingResult参数以外的参数能够按任意顺序排放。
Spring 3.1 为 @RequestMapping methods引入了一组新的支持类,分别是:
RequestMappingHandlerMapping
andRequestMappingHandlerAdapter
。建议使用它们,并且,应该使用Spring 3.1及之后的新特性。这些新的支持类默认由MVC命名空间启用,若是是Java config,必须显式的配置--不然没法使用。
支持的方法参数类型
session的访问可能不是线程安全的,特别是在一个Servlet环境中。若是须要多个请求并发访问一个session时,能够考虑将RequestMappingHandlerAdapter的synchronizeOnSession设置为true。
org.springframework.web.context.request.WebRequest
或者 org.springframework.web.context.request.NativeWebRequest
。容许泛型的请求参数访问,以及请求/会话属性访问,不须要绑定到native的Servlet/Portlet API。 java.util.Map
/ org.springframework.ui.Model
/ org.springframework.ui.ModelMap
,略。 org.springframework.web.servlet.mvc.support.RedirectAttributes
,用于指定在重定向中使用的一组具体的attributes,也能够用于添加flash attributes(attributes暂存在服务器侧,以在重定向后可用。)。 org.springframework.validation.Errors
/ org.springframework.validation.BindingResult
校验结果,用于其前面的第一个command 或者 form object。 org.springframework.web.bind.support.SessionStatus
,状态处理,用于标记form处理已完成,该标记会致使session attributes被清理 -- 那些由@SessionAttributes注解在type上指定的session attributes! org.springframework.web.util.UriComponentsBuilder
,一个builder,用于预处理一个URL,关联到当前请求的host、port、scheme、context path、以及servlet mapping的字面值部分。Errors和BindingResult参数,必须跟在model object后面,由于可能有多个model object,Spring会为每一个model object建立一个单独的BindingResult,因此,下面的例子不会执行:
@PostMapping // Invalid ordering of BindingResult and @ModelAttribute. public String processSubmit(@ModelAttribute("pet") Pet pet, Model model, BindingResult result) { ... }
注意,在Pet和BindingResult之间有一个Model参数。必须以下排序才能工做:
@PostMapping public String processSubmit(@ModelAttribute("pet") Pet pet, BindingResult result, Model model) { ... }
支持JDK 1.8 的 java.util.Optional 做为方法参数类型,与带有required attribute的注解一块儿使用(如@RequestParam、@RequestHeader等等)。这些状况下,java.util.Optional 至关于 required = false 。
支持的方法返回类型
@ModelAttribute
annotated reference data accessor methods. RequestToViewNameTranslator
and the model implicitly enriched with command objects and the results of @ModelAttribute
annotated reference data accessor methods. RequestToViewNameTranslator
and the model implicitly enriched with command objects and the results of @ModelAttribute
annotated reference data accessor methods. @ModelAttribute
annotated reference data accessor methods. The handler method may also programmatically enrich the model by declaring a Model
argument (see above). @ModelAttribute
annotated reference data accessor methods. The handler method may also programmatically enrich the model by declaring a Model
argument (see above). RequestToViewNameTranslator
(not declaring a response argument in the handler method signature). @ModelAttribute
annotated reference data accessor methods.
使用@RequestParameter将请求参数绑定到方法参数
@Controller @RequestMapping("/pets") @SessionAttributes("pet") public class EditPetForm { // ... @GetMapping public String setupForm(@RequestParam("petId") int petId, ModelMap model) { Pet pet = this.clinic.loadPet(petId); model.addAttribute("pet", pet); return "petForm"; } // ... }
默认,@RequestParam的required attribute是true,能够设置为false。@RequestParam(name="id", required=false)
若是目标方法参数的类型不是String,会自动使用类型转换。
当在Map<String, String> 或 MultiValueMap<String, String>实参上使用@RequestParam注解时,会被填入全部的request parameters。
使用RequestBody注解来映射request body
方法参数的@RequestBody注解,标识了方法参数应该绑成HTTP request body的值。例如:
@PutMapping("/something") public void handle(@RequestBody String body, Writer writer) throws IOException { writer.write(body); }
能够经过使用一个HttpMessageConverter将request body转成method argument。HttpMessageConverter负责将HTTP request msg转成一个对象以及将对象转成HTTP response body。RequestMappingHandlerAdapter支持@RequestBody的默认HttpMessageConverters:
ByteArrayHttpMessageConverter
converts byte arrays. StringHttpMessageConverter
converts strings. FormHttpMessageConverter
converts form data to/from a MultiValueMap<String, String>. SourceHttpMessageConverter
converts to/from a javax.xml.transform.Source. 注意,若是使用MVC 命名空间或者使用MVC Java config,默认会注册更多的message converters。
若是你打算读写XML,你会须要配置一个MarshallingHttpMessageConverter -- 使用org.springframework.oxm包中的特定的Marshaller和Unmarshaller实现。下面的例子演示了如何直接读写XML -- 可是,若是你的应用是经过MVC命名空间或MVC Java config来配置的,见 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实例从哪里来?有几个选项:
@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
从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 异步请求处理特性的相关知识,会很难理解这点。这里是关于底层机制的一些基本的事实:
With the above in mind, the following is the sequence of events for async request processing with a Callable
:
Callable
. Callable
to a TaskExecutor
for processing in a separate thread. DispatcherServlet
and all Filter’s exit the Servlet container thread but the response remains open. Callable
produces a result and Spring MVC dispatches the request back to the Servlet container to resume processing. 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:
DeferredResult
and saves it in some in-memory queue or list where it can be accessed. DispatcherServlet
and all configured Filter’s exit the request processing thread but the response remains open. DeferredResult
from some thread and Spring MVC dispatches the request back to the Servlet container. 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
@RequestMapping("/download") public StreamingResponseBody handle() { return new StreamingResponseBody() { @Override public void writeTo(OutputStream outputStream) throws IOException { // write... } }; }
Configuring Asynchronous Request Processing
spring-test模块提供了第一等级的针对注解controller的测试支持。See Section 15.6, “Spring MVC Test Framework”.
在以前的Spring版本中,用户必需要在web应用上下文中定义一个或者多个HandlerMapping beans 以将incoming web requests映射到合适的handlers。 随着annotated controllers的引入,如今通常能够没必要那样作了,由于RequestMappingHandlerMapping 会自动在全部@Controller beans中查找@RequestMapping注解。然而,务必记住,全部的继承自AbstractHandlerMapping的HandlerMapping类,都有如下properties -- 你能够用来定制它们的行为:
interceptors,使用的拦截器列表。
defaultHandler,默认使用的handler -- 当handler mapping 没有一个匹配的handler时。
order,基于该property的值(Ordered接口),Spring将全部可用的handler mappings进行排序,并应用第一匹配的handler。
alwaysUseFullPath,若是设为true,Spring会在当前Servlet context中使用全路径来查找合适的handler。若是false(默认就是),会使用当前Servlet mapping内的路径。例如,若是一个Servlet被映射到/testing/*,当设为true时,使用/testing/viewPage.html,不然,/viewPage.html。
urlDecode,默认true,自Spring 2.5起。若是你倾向于对比encoded paths,须要设为false。然而,HttpServletRequest老是以decoded形式暴露Servlet path。注意,当与encoded path对比时,Servlet path不会匹配。
配置拦截器的例子:
<beans> <bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping"> <property name="interceptors"> <bean class="example.MyInterceptor"/> </property> </bean> <beans>
4.1 使用HandlerInterceptor拦截requests
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中配置。