Spring Web MVC 框架是围绕DispatcherServlet设计的,所谓DispatcherServlet就是将请求分发到handler,需要有配置好的handler映射、视图解析、本地化、时区、theme解决方案、还有上传文件的支持。默认的handler是基于@Controller和@RequestMapping注解。自Spring 3.0 起,@Controller注解还能用于RESTful,需要配合@PathVariable以及其他特性。
在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的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基类是 或者,传统模式,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.1、在WebApplicationContext中的特殊的bean types Spring DispatcherServlet使用特殊的beans来处理请求并渲染视图。这些beans是Spring MVC的一部分。你可以选择使用哪个 -- 只需要在WebApplicationContext中简单的配置一些即可。 Spring MVC维护了一个默认beans列表以供使用,下一部分会讲。 现在先来看看DispatcherServlet依赖的特殊的bean types:
上面有提到,对每个特殊的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会以如下流程开始处理请求:
在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 初始化参数
Controller将用户的input解释并转成一个model,该model可以通过view描述给用户。 Spring 2.5 为MVC Controllers引入了基于注解的编程模型,使用诸如@RequestMapping、@RequestParam、@ModelAttribute之类的注解。在Servlet MVC和Portlet MVC中都可用。
@Controller // 一个注解即可Controller public class HelloWorldController { @RequestMapping("/helloWorld") // public String helloWorld(Model model) { model.addAttribute("message", "Hello World!"); return "helloWorld"; } } 3.1、使用@Controller定义一个Controller @Controller注解用于标记一个class拥有controller的角色。 注意,这种注解Controller需要开启自动探测才能使用。如下: <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www./schema/beans" xmlns:xsi="http://www./2001/XMLSchema-instance" xmlns:p="http://www./schema/p" xmlns:context="http://www./schema/context" xsi:schemaLocation=" http://www./schema/beans http://www./schema/beans/spring-beans.xsd http://www./schema/context http://www./schema/context/spring-context.xsd"> <context:component-scan base-package="org.springframework.samples.petclinic.web"/> <!-- ... --> </beans> 3.2、使用@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注解的变体,方法级别的。
@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等等)的话,你可能需要显式的配置基于类的代理。例如,将
对Spring MVC 3.1中@RequestMapping methods的新的支持类(解析类?) Spring 3.1 引入了针对RequestMapping methods的一组新的支持类,叫做:
Spring 3.1之前,type和method -level request mappings是在两个独立的阶段中检查的 -- controller先被
Spring 3.1 中新的支持类,只需要使用
这使得一些新的可能成为现实。For once a
下面这几件事情已经不再有效:
上面的特性仍由现有的支持类支持。然而,如果想使用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"; }
一个方法可以拥有任意数量的@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
@RequestMapping支持在URI模板变量中使用正则表达式。语法是: @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 ,被认为更匹配。例如, 如果变量数量一样,更长的被认为更匹配。例如, 当两个patterns拥有相同数量的变量和长度时,通配符少的更匹配。例如, 另外,还有两个特殊规则:
带有占位符的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(
默认,很多常用的路径扩展名已经在白名单中。此外,REST API的调用通常不是用作在浏览器中使用的URL。尽管如此,使用自定义HttpMessageConverter实现的应用,可以显式的注册用于内容协商目的的文件扩展名,针对这些扩展名Content-Disposition header(
Matrix Variables http://docs./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都由分号隔离。例如:
如果希望一个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。
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中的常量,如 注意,虽然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条件指定的媒体类型,也可以选择性的指定一个字符集。例如,在上面的代码片段中,我们指定了与 同consumes类似,produces也可以使用“!”。同样建议使用MediaType中的常量。 同consumes类似,方法级别的produces会覆盖类级别的媒体类型!!!
请求参数和请求头的值 Request Parameter 、Request Header values 可以通过请求参数条件来窄化请求的匹配,如: @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.3、定义@RequestMapping handler methods @RequestMapping handler methods可以有非常灵活的签名。除了BindingResult参数之外的参数可以按任意顺序排放。
支持的方法参数类型
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) { ... }
支持的方法返回类型
使用@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。 如果目标方法参数的类型不是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:
注意,如果使用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。如果没有声明这样一个参数,会抛出一个 使用@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 As with
在方法上使用@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之前被调用!
@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 @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 @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要求 为了支持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*() 能够使用表单数据。
使用@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。
该注解,也被在Servlet和Portlet环境下的annotated handler methods支持。
method parameters 和 type conversion http://docs./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来启用它。 下面的例子示意了使用了 <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; } }
对于那些依赖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/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
The sequence for
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/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:///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中配置。 |
|