为了方便,SpringMVC简称为S。
S是基于ServletAPI的,从一开始就在Spring框架中。在S5.0中引入了WebFlux,本篇只关注S
DispatcherServlet,简称D,和普通的servlet一样,需要声明和配置,可以在web.xml中或者通过Java代码配置来完成。D通过spring的配置来找到它能识别的代理组件用来进行消息映射,view的处理,以及异常处理等等。下面是一个D的代码配置案例:
除了使用ServletContextAPI外,还可以继承AbstractAnnotationConfigDispatcherServletInitializer来进行相关配置
public class MyWebApplicationInitializer implements WebApplicationInitializer {
@Override
public void onStartup(ServletContext servletCxt) {
// Load Spring web application configuration
//创建Spring的配置环境
AnnotationConfigWebApplicationContext ac = new AnnotationConfigWebApplicationContext();
ac.register(AppConfig.class);
ac.refresh();
// Create and register the DispatcherServlet
//通过代码的方式添加Servlet
DispatcherServlet servlet = new DispatcherServlet(ac);
ServletRegistration.Dynamic registration = servletCxt.addServlet("app", servlet);
registration.setLoadOnStartup(1);
registration.addMapping("/app/*");
}
}
也可以在web.xml中进行配置:
<web-app>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListenerlistener-class>
listener>
<context-param>
<param-name>contextConfigLocationparam-name>
<param-value>/WEB-INF/app-context.xmlparam-value>
context-param>
<servlet>
<servlet-name>appservlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServletservlet-class>
<init-param>
<param-name>contextConfigLocationparam-name>
<param-value>param-value>
init-param>
<load-on-startup>1load-on-startup>
servlet>
<servlet-mapping>
<servlet-name>appservlet-name>
<url-pattern>/app/*url-pattern>
servlet-mapping>
web-app>
D依赖WebApplicationContext,简称W,而W关联了ServletContext以及附带的Servlet;并且W将自己绑定在了ServletContext中,它以下面的字符串作为key,保存在了ServletContext中,通过Servlet.getAttribute可以拿到该对象。该操作可以通过RequestContextUtils来完成。
String ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE = WebApplicationContext.class.getName() + ".ROOT";
对于大多数的应用来说有一个W足够了,但是也可以有一个根的W,然后每个D有自己的子的W。根W包含了基础的bean,比如数据访问和业务上的bean,这些类可以在Servlet中共享,然后可以在每个S中重写。子W一般只包含针对给定S的bean
下面是一个有层级的W的配置
public class MyWebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
@Override
protected Class<?>[] getRootConfigClasses() {
return new Class<?>[] { RootConfig.class };
}
//如果没有层级,这里的可以返回null
@Override
protected Class<?>[] getServletConfigClasses() {
return new Class<?>[] { App1Config.class };
}
@Override
protected String[] getServletMappings() {
return new String[] { "/app1/*" };
}
}
对应的xml的配置
<web-app>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListenerlistener-class>
listener>
<context-param>
<param-name>contextConfigLocationparam-name>
<param-value>/WEB-INF/root-context.xmlparam-value>
context-param>
<servlet>
<servlet-name>app1servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServletservlet-class>
<init-param>
<param-name>contextConfigLocationparam-name>
<param-value>/WEB-INF/app1-context.xmlparam-value>
init-param>
<load-on-startup>1load-on-startup>
servlet>
<servlet-mapping>
<servlet-name>app1servlet-name>
<url-pattern>/app1/*url-pattern>
servlet-mapping>
web-app>
D通过一些特定的bean来处理请求以及返回响应,这些bean是spring管理的,以下就是这些bean
bean的类型 | 解释 |
---|---|
HandlerMapping | 将request映射到一个handler以及附带的一些列pre和post的interceptors。这些映射都是HandlerMapping的实现类完成的。常见的实现类有两个:RequestMappingHandlerMapping:支持@RequestMapping的注解以及SimpleUrlHandlerMapping,用来显式的注册URI对应的handlers |
HandlerAdapter | 帮助D用来执行上面映射的handler |
HandlerExceptionResolver | 处理异常 |
ViewResolver | 处理handler返回的Stirng类型的视图 |
LocaleResolver, LocaleContextResolver | 处理国际化 |
ThemeResolver | 处理应用的主题 |
MultipartResolver | 处理multi-part的请求 |
FlashMapManager | 存储以及获取输入输出的FlashMap,该Map用来保存redirect参数 |
你可以配置上面的这些特殊的bean,D会检查W中是否有这些bean,如果没有配置,会使用默认的类型
在S3.0+的环境,你可以通过编程来配置Servlet,或者通过xml的方式,如下所示:
public class MyWebApplicationInitializer implements WebApplicationInitializer {
@Override
public void onStartup(ServletContext container) {
XmlWebApplicationContext appContext = new XmlWebApplicationContext();
appContext.setConfigLocation("/WEB-INF/spring/dispatcher-config.xml");
ServletRegistration.Dynamic registration = container.addServlet("dispatcher", new DispatcherServlet(appContext));
registration.setLoadOnStartup(1);
registration.addMapping("/");
}
}
WebApplicationInitializer是一个接口,它确保你的实现能够在Servlet3环境中自动被发现,可以直接使用,该接口有一个子类AbstractDispatcherServletInitializer,可以更加简化配置。
这里推荐你使用java代码的方式来配置:
public class MyWebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
@Override
protected Class<?>[] getRootConfigClasses() {
return null;
}
@Override
protected Class<?>[] getServletConfigClasses() {
return new Class<?>[] { MyWebConfig.class };
}
@Override
protected String[] getServletMappings() {
return new String[] { "/" };
}
}
如果你是基于xml的方式,可以直接继承AbstractDispatcherServletInitializer:
public class MyWebAppInitializer extends AbstractDispatcherServletInitializer {
@Override
protected WebApplicationContext createRootApplicationContext() {
return null;
}
@Override
protected WebApplicationContext createServletApplicationContext() {
XmlWebApplicationContext cxt = new XmlWebApplicationContext();
cxt.setConfigLocation("/WEB-INF/spring/dispatcher-config.xml");
return cxt;
}
@Override
protected String[] getServletMappings() {
return new String[] { "/" };
}
}
AbstractDispatcherServletInitializer也提供了添加Filter的方法:
每个Filter的默认名字是实现类的名称,它们会被自动映射到D上
public class MyWebAppInitializer extends AbstractDispatcherServletInitializer {
// ...
@Override
protected Filter[] getServletFilters() {
return new Filter[] {
new HiddenHttpMethodFilter(), new CharacterEncodingFilter() };
}
}
AbstractDispatcherServletInitializer提供了isAsyncSupported方法来使能是否支持异步,默认是支持的
request的处理过程如下:
WebApplicationContext绑定在了request中一个属性中,控制器可以获取它使用
本地化解析器绑定reqeust中来处理国际化问题
主题解析器绑定request用来及决定view使用什么主题
如果制定了multipart file resolver,request将会检查是否有multiparts,如果是,request将会被包装成MultipartHttpServletRequest,进行后续处理
寻找一个匹配的handler:如果找到,该handler关联的执行链(preprocessors, postprocessors, controllers)将会被执行,生成model或者渲染
如果一个model返回,view将会被渲染,如果没有model返回,不会渲染任何视图
D同时支持返回last-modification-date,返回的逻辑:D查找一个合适的handler,然后检查handler是否实现了LastModified接口,如果是,该接口的long getLastModified(request)方法的结果将会被返回给客户端
D初始化参数
参数 | 解释 |
---|---|
contextClass | 实现了WebApplicationContext的类, 默认是XmlWebApplicationContext |
contextConfigLocation | 用来指示WebApplicationContext的配置文件,可以配置多个,用逗号隔开,对于多个配置文件,为了防止bean的重复定义,排在前面的文件优先级最高 |
namespace | WebApplicationContext的命名空间,默认是[servlet-name]-servlet . 也就是默认自动识别该前缀的配置文件 |
拦截器要实现HandlerInterceptor接口,该接口有三个方法实现:
preHandle(..):返回boolean,可以用返回值做一些流程控制,返回true表示执行链会继续执行,返回false则不会继续执行
postHandle(..):需要注意的是,对于@ResponseBody和ResponseEntity方法上,postHandle没什么用,因为返回值会在HandlerAdapter写入和渲染,这是在postHandle方法之前做的,因此再做一些后续处理,比如添加消息头已经为时已晚,此时,可以实现ResponseBodyAdvice类或者声明为Controller Advice类
afterCompletion(..)
当处理出现异常的时候,D为通过HandlerExceptionResolver的执行链来处理异常,HandlerExceptionResolver 的实现类如下:
HandlerExceptionResolver | 描述 |
---|---|
SimpleMappingExceptionResolver | 将异常类名和错误页面的名称映射,一般用来转到错误页面去 |
DefaultHandlerExceptionResolver | 映射到HTTP错误码,一般用来REST API中 |
ResponseStatusExceptionResolver | 处理@ResponseStatus注解,基于该注解的值,将异常映射到HTTP错误码 |
ExceptionHandlerExceptionResolver | 处理@ExceptionHandler注解,执行@Controller和@ControllerAdvice中对应的有该注解的方法 |
异常执行链:
通过声明多个HandlerExceptionResolver可以形成执行链,同时可以设置它们的优先级(order)
HandlerExceptionResolver返回值的规则
ModelAndView:指向一个错误页面
空的ModelAndView:表示该异常已经在处理器中处理了
null:异常没有处理,给后续的处理器去处理,如果最后都未处理,交给Servlet容器去处理
配置容器级的错误页面:针对异常没有处理或者返回的是异常的HTTP状态码
在web.xm中配置
注意,这种配置只能在xml中实现,不能通过Java代码配置
<error-page>
<location>/errorlocation>
error-page>
这时,我们可以配置控制器,来对请求响应进一步处理,比如返回Json格式的响应:
@RestController
public class ErrorController {
@RequestMapping(path = "/error")
public Map<String, Object> handle(HttpServletRequest request) {
Map<String, Object> map = new HashMap<String, Object>();
map.put("status", request.getAttribute("javax.servlet.error.status_code"));
map.put("reason", request.getAttribute("javax.servlet.error.message"));
return map;
}
}
S定义了ViewResolver以及View接口能够让你直接提价models给浏览器,不需要绑定指定的视图技术。ViewResolver将view的名称和实际的view进行映射。View用来给特定的视图技术准备好数据来显示
ViewResolver的实现类:
ViewResolver | 描述 |
---|---|
AbstractCachingViewResolver | AbstractCachingViewResolver的子类,用来处理缓存的view实例 |
XmlViewResolver | 通过配置文件来处理,默认的配置是/WEB-INF/views.xml |
ResourceBundleViewResolver | 通过ResourceBundle中的配置文件来处理 |
UrlBasedViewResolver | 直接将视图名称解析成url |
InternalResourceViewResolver | UrlBasedViewResolver的子类,用来支持InternalResourceView的解析 |
FreeMarkerViewResolver | UrlBasedViewResolver的子类,支持FreeMarkerView的解析 |
ContentNegotiatingViewResolver | 通过请求文件的名称和Accept请求头来解析 |
处理过程:
可以配置多个视图解析器,同时也支持设置顺序,优先级越高,越靠后处理
ViewResolver返回null表示视图未被发现,但是对于JSP和InternalResourceViewResolver来说,JSP是否存在是在RequestDispatcher分发的时候才知道,因此InternalResourceViewResolver一定要配置到最后
重定向:
视图名称中的redirect:表示重定向,UrlBasedViewResolver会识别该前缀,效果和控制器直接返回RedirectView一样,另外redirect:/myapp/some/resource会重定向到当前Servlet context相对的路径,而redirect:http://myhost.com/some/arbitrary/path 会重定向到绝对的URL
Forwarding转发
也可以使用forward:前缀来进行请求转发,该前缀同样能够被UrlBasedViewResolver识别,这会创建InternalResourceView并执行RequestDispatcher.forward()
内容协商(Content negotiation)
ContentNegotiatingViewResolver本身不处理视图,它会交给其他的处理器处理,然后从中选择合适的视图,同时通过Accept或者url中的参数来决定最后的视图
通过LocaleResolver来解析处理国际化的问题,D会查找国际化的resolver,如果找到就会使用;通过RequestContext.getLocale()方法也能获取到国际化
也可以关联一个interceptor来改变locale,locale解析器和拦截器在org.springframework.web.servlet.i18n目录下定义了(class文件),常见的国际化解析器:
时区:LocaleContextResolver提供时区的获取:RequestContext.getTimeZone()
Header解析器:通过检查accept-language消息头来确定,此解析器不支持时区
Cookie解析器:CookieLocaleResolver会检查Cookie,查看是否指定Locale或者TimeZone,可以通过配置文件指定cookie的名称,如下代码所示
Session解析器:SessionLocaleResolver会在session中解析Locale和TimeZone,和CookieLocaleResolver不同的是,当session终结的时候,就无法解析了
Locale拦截器:可以添加LocaleChangeInterceptor来改变locale,它会根据request的参数来改变locale
配置CookieLocaleResolver:
<bean id="localeResolver" class="org.springframework.web.servlet.i18n.CookieLocaleResolver">
<property name="cookieName" value="clientlanguage"/>
<!-- in seconds. If set to -1, the cookie is not persisted (deleted when browser shuts down) -->
<property name="cookieMaxAge" value="100000"/>
</bean>
可指定的参数和默认名称:
参数 | 默认 | 描述 |
---|---|---|
cookieName | classname + LOCALE | cookie的名称 |
cookieMaxAge | 容器默认的 | 生存时间 |
cookiePath | / |
配置LocaleChangeInterceptor:
当URL为http://www.sf.net/home.view?siteLanguage=nl](https://www.sf.net/home.view?siteLanguage=nl时会切语言到Dutch
<bean id="localeChangeInterceptor"
class="org.springframework.web.servlet.i18n.LocaleChangeInterceptor">
<property name="paramName" value="siteLanguage"/>
</bean>
<bean id="localeResolver"
class="org.springframework.web.servlet.i18n.CookieLocaleResolver"/>
<bean id="urlMapping"
class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
<property name="interceptors">
<list>
<ref bean="localeChangeInterceptor"/>
</list>
</property>
<property name="mappings">
<value>/**/*.view=someController</value>
</property>
</bean>
应用中使用主题,必须设置ThemeSource,ResourceBundleThemeSource就是用来处理主题的设置的实现类,它会从classpath中加载配置文件。要自定义ThemeSource或者配置ResourceBundleThemeSource中基础名称的前缀,你需要配置一个名称为themeSource的bean。
主题的配置文件,放在classpath的根目录:
该配置文件支持国家化,如 cool_nl.properties, 其中nl是国际化标识
styleSheet=/themes/cool/style.css
background=/themes/cool/img/coolBg.jpg
JSP中的使用:
<%@ taglib prefix="spring" uri="http://www.springframework.org/tags"%>
<html>
<head>
<link rel="stylesheet" href=" " type="text/css"/>
</head>
<body style="background= ">
...
</body>
</html>
解析主题:
DispatcherServlet会查找名称为themeResolver的bean来发现ThemeResolver的实现类作为解析器,常见的实现类:
Class | 描述 |
---|---|
FixedThemeResolver | 通过设置defaultThemeName属性选择固定的主题 |
SessionThemeResolver | 主题在HTTP session管理,每个session只需要设置一次,但是只在一个seesion生命周期中有效 |
CookieThemeResolver | 解析保存在cookie中主题 |
MultipartResolver用来解析multipart请求,包括上传。有一个实现是基于Commons FileUpload的还有一个是基于Servlet 3.0 multipart请求解析
为了使用multipart解析,需要在spring配置名称为multipartResolver的解析器:
对于Apache FileUpload解析器来说,配置的类为CommonsMultipartResolver
对于Servlet 3.0的解析器来说,需要通过Servlet容器来配置:如果使用的是Java代码配置,需要配置MultipartConfigElement,如果是xml,则添加"< multipart-config >"到servlet声明中
public class AppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
// ...
@Override
protected void customizeRegistration(ServletRegistration.Dynamic registration) {
// Optionally also set maxFileSize, maxRequestSize, fileSizeThreshold
registration.setMultipartConfig(new MultipartConfigElement("/tmp"));
}
}
SpringMVC提供基于注解的编程模型,通过@Controller和@RestController组件来表达请求映射,请求输入,异常处理等等,下面是一个案例:
@Controller
public class HelloController {
@GetMapping("/hello")
public String handle(Model model) {
model.addAttribute("message", "Hello World!");
return "index";
}
}
通过@Controller来定义控制器,注意该注解包含了@Component。为了让spring能够自动扫描注册这些类,通过添加ComponentScan来配置扫描的包路径
@Configuration
@ComponentScan("org.example.web")
public class WebConfig {
// ...
}
对应xml的配置
<?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.example.web"/>
<!-- ... -->
</beans>
@RestController注解是一个复合注解,它包括了@Controller和@ResponseBody,用来表明一个控制器的每个方法都是@ResponseBody注解的,该主机会直接写返回体,而不会进行视图解析或者渲染HTML模板
关于控制器中使用AOP
推荐基于类的代理,而不是基于接口的代理,如果控制器必须要继承一些接口,在配置的时候,要做如下改变:
改成
@RequestMapping注解用来映射请求到控制器的方法,可匹配属性有多种,像URL,请求方法等,能够在类或者方法上使用。SpringMVC也提供了针对特定方法的映射注解:
@GetMapping
@PostMapping
@PutMapping
@DeleteMapping
@PatchMapping
下面是一个案例:
@RestController
@RequestMapping("/persons")
class PersonController {
@GetMapping("/{id}")
public Person getPerson(@PathVariable Long id) {
// ...
}
@PostMapping
@ResponseStatus(HttpStatus.CREATED)
public void add(@RequestBody Person person) {
// ...
}
}
URI模式:
可以在匹配条件中加入通配符来匹配不同的URI模式
? 匹配一个字符
* 单个路径中匹配0个或者更多的字符
** 多个路径中匹配0个或多个字符
可以声明URI变量,然后在方法中用@PathVariable来获取:
@GetMapping("/owners/{ownerId}/pets/{petId}")
public Pet findPet(@PathVariable Long ownerId, @PathVariable Long petId) {
// ...
}
URI变量既可以是类级别的又可以是方法级别的
@Controller
@RequestMapping("/owners/{ownerId}")
public class OwnerController {
@GetMapping("/pets/{petId}")
public Pet findPet(@PathVariable Long ownerId, @PathVariable Long petId) {
// ...
}
}
URI变量可以自动转为合适的类型,如果转换失败,会抛出TypeMismatchException异常,int,long,Date是默认支持的类型,你也可以注册对其他类型的支持。
@PathVariable可以显示地说明是哪个参数,如:@PathVariable(“customId”),如果名称一致的话,就不用显式说明,但这需要开启-parameters编译选项。
如果参数是变化的,也可以通过正则来提取参数的值:
当URL是/spring-web-3.0.5 .jar时,通过下面的方式可以提取名称,版本号以及文件扩展名
@GetMapping("/{name:[a-z-]+}-{version:\\d\\.\\d\\.\\d}{ext:\\.[a-z]+}")
public void handle(@PathVariable String version, @PathVariable String ext) {
// ...
}
URI路径也可以加入${}占位符,将会在启动的时候从PropertyPlaceHolderConfigurer中解析配置的值,解析的来源包括本地的,系统的,环境的等等配置文件。
Spring MVC使用的是PathMatcher的规则,实现类是AntPathMatcher,详细的匹配规则可以查看该类
可消费的medial type匹配
也匹配指定的Content-Type,并且支持否定表达式,像!text/plain,表示除了text/plain之外的Content-Type,
该配置可以在类上配置,从而各个方法可以共享,不像其他的属性,方法上的同样的属性配置会覆盖类上的配置,consumes会拓展类级别的配置
@PostMapping(path = "/pets", consumes = "application/json")
public void addPet(@RequestBody Pet pet) {
// ...
}
可生成的media type匹配
针对Accept请求头的参数,我们也可以如下匹配,同样也支持否定表达:
@GetMapping(path = "/pets/{petId}", produces = "application/json;charset=UTF-8")
@ResponseBody
public Pet getPet(@PathVariable String petId) {
// ...
}
参数以及消息头匹配
针对请求参数和消息头,我们也可以指定匹配条件:
验证参数存在:(“myParam”)
验证参数不存在:(“!myParam”)
验证参数为某个特定的值:(“myParam=myValue”)
@GetMapping(path = "/pets/{petId}", params = "myParam=myValue")
public void findPet(@PathVariable String petId) {
// ...
}
对header也可以如此匹配
@GetMapping(path = "/pets", headers = "myHeader=myValue")
public void findPet(@PathVariable String petId) {
// ...
}
关于HEAD请求
@GetMapping或者@RequestMapping(method=HttpMethod.GET)隐式的支持HEAD请求
控制器方法参数类型如下表所示,需要注意的是JDK8的Optional也可以作为参数,不过此时关联的注解如果有required属性,则 为false
Controller method argument | Description |
---|---|
WebRequest , NativeWebRequest |
Generic access to request parameters, request & session attributes, without direct use of the Servlet API. |
javax.servlet.ServletRequest , javax.servlet.ServletResponse |
Choose any specific request or response type — e.g. ServletRequest , HttpServletRequest , or Spring’s MultipartRequest , MultipartHttpServletRequest . |
javax.servlet.http.HttpSession |
Enforces the presence of a session. As a consequence, such an argument is never null . Note: Session access is not thread-safe. Consider setting theRequestMappingHandlerAdapter ’s “synchronizeOnSession” flag to “true” if multiple requests are allowed to access a session concurrently. |
javax.servlet.http.PushBuilder |
Servlet 4.0 push builder API for programmatic HTTP/2 resource pushes. Note that per Servlet spec, the injected PushBuilder instance can be null if the client does not support that HTTP/2 feature. |
java.security.Principal |
Currently authenticated user; possibly a specific Principal implementation class if known. |
HttpMethod |
The HTTP method of the request. |
java.util.Locale |
The current request locale, determined by the most specific LocaleResolver available, in effect, the configured LocaleResolver /LocaleContextResolver . |
java.util.TimeZone + java.time.ZoneId |
The time zone associated with the current request, as determined by a LocaleContextResolver . |
java.io.InputStream , java.io.Reader |
For access to the raw request body as exposed by the Servlet API. |
java.io.OutputStream , java.io.Writer |
For access to the raw response body as exposed by the Servlet API. |
@PathVariable |
For access to URI template variables. See URI patterns. |
@MatrixVariable |
For access to name-value pairs in URI path segments. See Matrix variables. |
@RequestParam |
For access to Servlet request parameters. Parameter values are converted to the declared method argument type. See @RequestParam.Note that use of @RequestParam is optional, e.g. to set its attributes. See “Any other argument” further below in this table. |
@RequestHeader |
For access to request headers. Header values are converted to the declared method argument type. See @RequestHeader. |
@CookieValue |
For access to cookies. Cookies values are converted to the declared method argument type. See @CookieValue. |
@RequestBody |
For access to the HTTP request body. Body content is converted to the declared method argument type using HttpMessageConverter s. See @RequestBody. |
HttpEntity |
For access to request headers and body. The body is converted with HttpMessageConverter s. See HttpEntity. |
@RequestPart |
For access to a part in a “multipart/form-data” request. See Multipart. |
java.util.Map , org.springframework.ui.Model , org.springframework.ui.ModelMap |
For access to the model that is used in HTML controllers and exposed to templates as part of view rendering. |
RedirectAttributes |
Specify attributes to use in case of a redirect — i.e. to be appended to the query string, and/or flash attributes to be stored temporarily until the request after redirect. See Redirect attributes and Flash attributes. |
@ModelAttribute |
For access to an existing attribute in the model (instantiated if not present) with data binding and validation applied. See @ModelAttribute as well as Model and DataBinder.Note that use of @ModelAttribute is optional, e.g. to set its attributes. See “Any other argument” further below in this table. |
Errors , BindingResult |
For access to errors from validation and data binding for a command object (i.e. @ModelAttribute argument), or errors from the validation of an @RequestBody or @RequestPart arguments; an Errors , or BindingResult argument must be declared immediately after the validated method argument. |
SessionStatus + class-level @SessionAttributes |
For marking form processing complete which triggers cleanup of session attributes declared through a class-level @SessionAttributes annotation. See@SessionAttributes for more details. |
UriComponentsBuilder |
For preparing a URL relative to the current request’s host, port, scheme, context path, and the literal part of the servlet mapping also taking into account Forwarded and X-Forwarded-* headers. See URI Links. |
@SessionAttribute |
For access to any session attribute; in contrast to model attributes stored in the session as a result of a class-level @SessionAttributes declaration. See@SessionAttribute for more details. |
@RequestAttribute |
For access to request attributes. See @RequestAttribute for more details. |
Any other argument | If a method argument is not matched to any of the above, by default it is resolved as an @RequestParam if it is a simple type, as determined byBeanUtils#isSimpleProperty, or as an @ModelAttribute otherwise. |
返回值的类型如下表所示:
Controller method return value | Description |
---|---|
@ResponseBody |
The return value is converted through HttpMessageConverter s and written to the response. See @ResponseBody. |
HttpEntity , ResponseEntity |
The return value specifies the full response including HTTP headers and body be converted through HttpMessageConverter s and written to the response. See ResponseEntity. |
HttpHeaders |
For returning a response with headers and no body. |
String |
A view name to be resolved with ViewResolver 's and used together with the implicit model — determined through command objects and @ModelAttribute methods. The handler method may also programmatically enrich the model by declaring a Model argument (see above). |
View |
A View instance to use for rendering together with the implicit model — determined through command objects and @ModelAttribute methods. The handler method may also programmatically enrich the model by declaring a Model argument (see above). |
java.util.Map , org.springframework.ui.Model |
Attributes to be added to the implicit model with the view name implicitly determined through a RequestToViewNameTranslator . |
@ModelAttribute |
An attribute to be added to the model with the view name implicitly determined through a RequestToViewNameTranslator .Note that @ModelAttribute is optional. See “Any other return value” further below in this table. |
ModelAndView object |
The view and model attributes to use, and optionally a response status. |
void |
A method with a void return type (or null return value) is considered to have fully handled the response if it also has a ServletResponse , or an OutputStream argument, or an @ResponseStatus annotation. The same is true also if the controller has made a positive ETag or lastModified timestamp check (see @Controller caching for details).If none of the above is true, a void return type may also indicate “no response body” for REST controllers, or default view name selection for HTML controllers. |
DeferredResult |
Produce any of the above return values asynchronously from any thread — e.g. possibly as a result of some event or callback. See Async Requests and DeferredResult . |
Callable |
Produce any of the above return values asynchronously in a Spring MVC managed thread. See Async Requests and Callable . |
ListenableFuture ,CompletionStage CompletableFuture |
Alternative to DeferredResult as a convenience for example when an underlying service returns one of those. |
ResponseBodyEmitter , SseEmitter |
Emit a stream of objects asynchronously to be written to the response with HttpMessageConverter 's; also supported as the body of a ResponseEntity . See Async Requests and HTTP Streaming. |
StreamingResponseBody |
Write to the response OutputStream asynchronously; also supported as the body of a ResponseEntity . See Async Requests and HTTP Streaming. |
Reactive types — Reactor, RxJava, or others via ReactiveAdapterRegistry |
Alternative to ``DeferredResultwith multi-value streams (e.g. Flux, Observable) collected to a List.For streaming scenarios — e.g. text/event-stream, application/json+stream— SseEmitterand ResponseBodyEmitterare used instead, where ServletOutputStream`blocking I/O is performed on a Spring MVC managed thread and back pressure applied against the completion of each write.See Async Requests and Reactive types. |
Any other return value | If a return value is not matched to any of the above, by default it is treated as a view name, if it is String or void (default view name selection via RequestToViewNameTranslator applies); or as a model attribute to be added to the model, unless it is a simple type, as determined by BeanUtils#isSimpleProperty in which case it remains unresolved. |
类型转换
像@RequestParam,@RequestHeader,@PathVariable,@MatrixVariable和@CookieValue注解是作用在String参数上,但实际参数可能是其他的类型,这就需要类型转换。默认会支持一些简单类型的转换,像int,long,Date等可以自动转为String。你也可以通过WebDataBinder自定义类型转换器,或者使用FormattingConversionService注册Formatters
矩阵变量
所谓矩阵变量就是请求路径中会出现以分隔开的变量,每个变量的值也可以有多个,以逗号隔开,像:
/cars;color=red,green;year=2012 多个参数值也可以用重复的名称分开写:
color=red;color=green;color=blue
当URL包含矩阵变量时,必须要使用URI变量来表示器内容,这样能够保证正确的匹配,以及使与变量顺序无关:
// GET /pets/42;q=11;r=22
@GetMapping("/pets/{petId}")
public void findPet(@PathVariable String petId, @MatrixVariable int q) {
// petId == 42
// q == 11
}
当多个路径片段都有矩阵变量时,需要做一定的区分:
// 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
}
变量也可以是可选择,并且可是这默认值:
// GET /pets/42
@GetMapping("/pets/{petId}")
public void findPet(@MatrixVariable(required=false, defaultValue="1") int q) {
// q == 1
}
也可以通过MultiValueMap拿到所有的变量:
// 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" : 22, "s" : 23]
}
使用矩阵变量需要显式地进行配置,使用Java配置话,需要设置UrlPathHelper的removeSemicolonContent=false,使用xml配置如下:
@RequestParam
该注解用来绑定Servlet请求参数,有也就是查询参数或者表单数据:
@Controller
@RequestMapping("/pets")
public class EditPetForm {
// ...
@GetMapping
public String setupForm(@RequestParam("petId") int petId, Model model) {
Pet pet = this.clinic.loadPet(petId);
model.addAttribute("pet", pet);
return "petForm";
}
// ...
}
使用该注解,默认参数是必须的,也可以设置required为false,或者使用Java8的Optional包装器。同样,类型转换的规则和前面说的一致。
当@RequestParam作用在Map
如果一个参数没有被其他参数解析器解析,并且是一些简单类型,则默认是@RequestParam注解的
@RequestHeader
用来绑定消息头,同样参数如果是String默认的转换器将会应用,当@RequestHeader作用在Map
@GetMapping("/demo")
public void handle(
@RequestHeader("Accept-Encoding") String encoding,
@RequestHeader("Keep-Alive") long keepAlive) {
//...
}
对于逗号隔开的多个值的请求头,支持转为String[]或者List< String >
@CookieValue
用来绑定cookie,比如一个cookie是:
JSESSIONID=415A4AC178C59DACE0B2C9CA727CDD84
可以通过如下代码来绑定:
同样支持默认转换
@GetMapping("/demo")
public void handle(@CookieValue("JSESSIONID") String cookie) {
//...
}
@ModelAttribute
一篇详细使用的博客
使用该注解作用在方法参数上,可以获取model中的数据,如果参数不存在,会给我们初始化。model中的参数可以被request中的参数覆盖,只要名称相同。
@PostMapping("/owners/{ownerId}/pets/{petId}/edit")
public String processSubmit(@ModelAttribute Pet pet) { }
对于上面的使用@ModelAttribute作用的注解,pet值的解析过程:
如果Model中有直接从model中拿
通过@SessionAttributes从session中拿
通过Converter从URI路径参数中拿(路径参数只能传字符串)
调用默认构造方法初始化
调用有参的构造方法,参数要和请求参数匹配上,匹配的参数通过@ConstructorProperties或者运行时保存的字节码形式的参数名称
当注解的model属性实例初始化后,将会进行数据绑定。WebDataBinder类将请求参数匹配到目标类的属性名称中,必要的时候将会使用转化器来初始化属性。数据绑定可能会导致错误从而生成BindException异常,通过在@ModelAttribute旁边添加BindingResult参数我们可以判断是否有异常:
@PostMapping("/owners/{ownerId}/pets/{petId}/edit")
public String processSubmit(@ModelAttribute("pet") Pet pet, BindingResult result) {
if (result.hasErrors()) {
return "petForm";
}
// ...
}
有些情况下,你想访问的model是不经过数据绑定的,这些情况下,你可以直接将Model注入到控制器,然后直接访问,或者使用@ModelAttribute(binding=false)来说明不绑定数据:
@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) {
// ...
}
另外可以通过javax.validation.Valid或者Spring的@Validated注解来添加校验:
@PostMapping("/owners/{ownerId}/pets/{petId}/edit")
public String processSubmit(@Valid @ModelAttribute("pet") Pet pet, BindingResult result) {
if (result.hasErrors()) {
return "petForm";
}
// ...
}
注意@ModelAttribute的属性设置不是必须的,默认情况下,如果参数不是一个简单类型,并且没有被其他的参数解析器处理,默认是认为添加了@ModelAttribute注解。
@SessionAttributes
该注解是在HTTP Sesson中存储不同的request可以访问的model属性,这是个类级别的注解,可以在控制器上声明session属性,该注解会列出一些model属性的名称或者类型,这些声明的属性会在session中存储,从而让后面的请求访问,例如:
@Controller
@SessionAttributes("pet")
public class EditPetForm {
// ...
}
上面的例子中,在第一次请求中,一个名称为pet的属性会被添加到model中,然后存储在Session中,知道另个一个控制器方法调用SessionStatus的方法来清除缓存,如下所示:
@Controller
@SessionAttributes("pet")
public class EditPetForm {
// ...
@PostMapping("/pets/{id}")
public String handle(Pet pet, BindingResult errors, SessionStatus status) {
if (errors.hasErrors) {
// ...
}
status.setComplete();
// ...
}
}
}
@SessionAttribute
如果你想访问全局存在的session属性,比如来自于其他控制器(像filter),可以使用@SessionAttribute注解该参数
@RequestMapping("/")
public String handle(@SessionAttribute User user) {
// ...
}
为了方便添加和移除属性session属性,可以注入WebRequest和HttpSession类。在session中短时间存储模型属性优先考虑使用@SessionAttributes
RequestAttribute
和上面两个注解类似,该注解能够访问已经存在的请求属性
@GetMapping("/")
public String handle(@RequestAttribute Client client) {
// ...
}
重定向属性:
默认所有的模型属性会展现在重定向URL的URL模板参数中,对于剩下的基本类型参数或者基本类型的集合将会以查询参数的形式拼接。
将原始类型属性拼接为查询参数对于模型本来就用来重定向使用的场景来说是没问题的,然而对于注解的控制器模型,可能会包括其他的为了渲染而产生的参数,为了避免这些参数呈现在URL中。使用@RequestMapping注解能够声明一个RedirectAttributes类型的参数,并用它来提取属性给RedirectView。如果方法确实用来重定向,RedirectAttributes中的内容将会被使用,否则model中的参数将会被使用(互斥的)
RequestMappingHandlerAdapter提供一个ignoreDefaultModelOnRedirect标志,可以使用它来指定默认Model中的参数是否能够在重定向时使用(默认是不使用)
需要注意的是,URI模板参数可以在重定向URL中直接使用,比如下面的案例:
@PostMapping("/files/{path}")
public String upload(...) {
// ...
return "redirect:files/{path}";
}
另一个中传递重定向参数的方法是Flash属性,不想其他的重定向属性,flash属性保存在session中,不会在url中呈现
Flash属性
Flash属性提供了一个请求中存储属性,然后另一个请求使用的能力,重定向就需要这种能力,Flash属性会在重定向之前保存属性,重定向后可以使用并自动移除。
S对flash属性的支持由两种抽象,FlashMap用来保存flash属性,而FlashMapManager用来存储,取回以及管理FlashMap实例。Flash属性默认是开启的,不需要显示enable,它也不会导致HTTP session的创建。对于每个请求,有一个input FlashMap用来传递上一个请求的属性,以及一个output FlashMap用来保存一个请求可用的属性。可以在任何地方使用RequestContextUtils来访问FlashMap实例。
注解的控制器不需要直接使用FlashMap,@RequestMapping方法可以接受一个RedirectAttributes类型的参数,然后添加属性。通过RedirectAttributes添加的Flash属性,会自动放入output FlashMap中。同样,重定向后,属性从input FlashMap中会自动添加到Model里面。
Multipart
当MultipartResolver enable后,当携带multipart/form-data的post请求会被该解析器解析,使参数能够像正常请求一样访问,下面是一个form请求和上传请求的例子:
@Controller
public class FileUploadController {
@PostMapping("/form")
//servlet 3.0后,可以是使用javax.servlet.http.Part作为参数
public String handleFormUpload(@RequestParam("name") String name,
@RequestParam("file") MultipartFile file) {
if (!file.isEmpty()) {
byte[] bytes = file.getBytes();
// store the bytes somewhere
return "redirect:uploadSuccess";
}
return "redirect:uploadFailure";
}
}
Multipart内容可以被用来绑定在一个command对象上,例如下面的上面的form field和file能够成为一个对象的属性:
class MyForm {
private String name;
private MultipartFile file;
// ...
}
@Controller
public class FileUploadController {
@PostMapping("/form")
public String handleFormUpload(MyForm form, BindingResult errors) {
if (!form.getFile().isEmpty()) {
byte[] bytes = form.getFile().getBytes();
// store the bytes somewhere
return "redirect:uploadSuccess";
}
return "redirect:uploadFailure";
}
}
也可以通过非浏览器发送Multipart请求,比如RESTful服务场景,下面是两种请求:
POST /someUrl
Content-Type: multipart/mixed
--edt7Tfrdusa7r3lNQc79vXuhIIMlatb7PQg7Vp
Content-Disposition: form-data; name="meta-data"
Content-Type: application/json; charset=UTF-8
Content-Transfer-Encoding: 8bit
{
"name": "value"
}
--edt7Tfrdusa7r3lNQc79vXuhIIMlatb7PQg7Vp
Content-Disposition: form-data; name="file-data"; filename="file.properties"
Content-Type: text/xml
Content-Transfer-Encoding: 8bit
... File Data ...
可以通过@RequestParam将meta-data部分作为String解析,如果想将其序列化为JSON,使用@RequestPart注解来访问HttpMessageConverter转换后的内容
@PostMapping("/")
public String handle(@RequestPart("meta-data") MetaData metadata,
@RequestPart("file-data") MultipartFile file) {
// ...
}
@RequestPart可以和@Valid和@Validated注解配合使用,校验如果异常,会抛出MethodArgumentNotValidException,此时会返回400错误,通过Errors或者BindingResult参数来进行异常的处理:
@PostMapping("/")
public String handle(@Valid @RequestPart("meta-data") MetaData metadata,
BindingResult result) {
// ...
}
@RequestBody
使用@RequestBody注解可以通过HttpMessageConverter让请求体反序列化为对象:
@PostMapping("/accounts")
public void handle(@RequestBody Account account) {
// ...
}
也可以使用Message Converters自定义消息转换方式;同样该注解可以和@Valid和@Validated注解配合使用记性标准bean校验:
@PostMapping("/accounts")
public void handle(@Valid @RequestBody Account account, BindingResult result) {
// ...
}
HttpEntity
HttpEntity某种程度上是和@RequestBody一样,但是是基于包含请求头和请求体的容器对象:
@PostMapping("/accounts")
public void handle(HttpEntity<Account> entity) {
// ...
}
@ResponseBody
可以在方法上使用@ResponseBody注解,能够让返回值通过HttpMessageConverter序列化为返回体
@GetMapping("/accounts/{id}")
@ResponseBody
public Account handle() {
// ...
}
该注解也可以作用在类级别上,能够被其所有的控制器方法继承,同样你可以直接使用@RestController注解,它同时包含了@Controller和@ResponseBody
ResponseEntity
ResponseEntity某种程度上和上面的@ResponseBody的注解一样:
@PostMapping("/something")
public ResponseEntity<String> handle() {
// ...
URI location = ... ;
return ResponseEntity.created(location).build();
}
S提供内置的Jackson序列化视图的支持,它允许渲染对象的一部分属性。可以在@ResponseBody或者ResponseEntity的方法上,使用@JsonView注解来激活序列化视图类:
@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;
}
}
为了开启对@ResponseBody和ResponseEntity方法的JSONP支持,可以声明一个@ControllerAdvice注解的类,继承AbstractJsonpResponseBodyAdvice,通过构造参数表示JSNONP查询参数:
@ControllerAdvice
public class JsonpAdvice extends AbstractJsonpResponseBodyAdvice {
public JsonpAdvice() {
super("callback");
}
}
@ModelAttribute可以用来:
作用在@RequestMapping方法的参数上,用来创建或者从model中访问对象,通过WebDataBinder将其绑定在request上
作为一个方法注解,作用在@Controller或者@ControllerAdvice类中,能帮助在@RequestMapping方法之前初始化model
作用在@RequestMapping方法上来将其返回值作为model属性
这里讨论第二种用法,一个控制器可以有多个@ModelAttribute方法,所有的这些方法都是在@RequestMapping的方法调用之前先执行,通过@ControllerAdvice注解可以在多个控制器中共享@ModelAttribute方法。使用案例:
@ModelAttribute
public void populateModel(@RequestParam String number, Model model) {
model.addAttribute(accountRepository.findAccount(number));
// add more ...
}
也可以只添加一个属性:
@ModelAttribute
public Account addAccount(@RequestParam String number) {
return accountRepository.findAccount(number);
}
当属性名称没有指定的时候,会使用默认的名称,默认名称的规则示例:
com.myapp.Product --> "product"
com.myapp.MyProduct --> "myProduct"
com.myapp.UKProduct --> "UKProduct"
具体规则可以查看Conventions.getVariableName的方法。你也可以添加的时候显式指定名称,或者给@ModelAttribute指定value。
@RequestMapping注解的方法上也可以用该注解,这时返回这会作为model的属性。
@RequestMapping支持很多在@RequestMapping中使用的参数,除了@ModelAttribute自己或者和request body相关联的参数
@GetMapping("/accounts/{id}")
@ModelAttribute("myAccount")
public Account handle() {
// ...
return account;
}
@Controller或者@ControllerAdvice类上可以添加@InitBinder注解的方法来初始化WebDataBinder实例,从而可以:
绑定请求参数到model中
将基于String的请求参数,比如request中的参数,路径参数,消息头,cookies等等转换为其他类型的参数
当渲染HTML表单的时候格式化model对象的值为string类型的值
@InitBinder的方法中可以注册指定控制器的PropertyEditor或者Spring Converter和Formatter组件。@InitBinder注解的方法支持许多@RequestMapping方法中使用的参数,除了@ModelAttribute注解的参数。典型的案例如下:
@Controller
public class FormController {
@InitBinder
public void initBinder(WebDataBinder binder) {
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
dateFormat.setLenient(false);
binder.registerCustomEditor(Date.class, new CustomDateEditor(dateFormat, false));
}
// ...
}
@Controller
public class FormController {
@InitBinder
protected void initBinder(WebDataBinder binder) {
binder.addCustomFormatter(new DateFormatter("yyyy-MM-dd"));
}
// ...
}
@Controller和@ControllerAdvice类可以使用@ExceptionHandler方法来处理控制器中方法的异常:
@Controller
public class SimpleController {
// ...
@ExceptionHandler
public ResponseEntity<String> handle(IOException ex) {
// ...
}
}
该注解可以列出匹配的异常类型,或者在方法参数中直接指定目标异常,当多个异常匹配的时候,根异常的优先级最高,ExceptionDepthComparator可以用来定义异常的优先级。
当有多个@ControllerAdvice注解的类是,确保跟异常在优先级最高的类里面,优先级越高的@ControllerAdvice,里面的方法匹配异常的优先级也越高。
@ExceptionHandler支持的参数如下:
异常返回值如下
Return value | Description |
---|---|
@ResponseBody |
The return value is converted through HttpMessageConverter s and written to the response. See @ResponseBody. |
HttpEntity , ResponseEntity |
The return value specifies the full response including HTTP headers and body be converted through HttpMessageConverter s and written to the response. See ResponseEntity. |
String |
A view name to be resolved with ViewResolver 's and used together with the implicit model — determined through command objects and @ModelAttribute methods. The handler method may also programmatically enrich the model by declaring a Model argument (see above). |
View |
A View instance to use for rendering together with the implicit model — determined through command objects and @ModelAttribute methods. The handler method may also programmatically enrich the model by declaring a Model argument (see above). |
java.util.Map , org.springframework.ui.Model |
Attributes to be added to the implicit model with the view name implicitly determined through a RequestToViewNameTranslator . |
@ModelAttribute |
An attribute to be added to the model with the view name implicitly determined through a RequestToViewNameTranslator .Note that @ModelAttribute is optional. See “Any other return value” further below in this table. |
ModelAndView object |
The view and model attributes to use, and optionally a response status. |
void |
A method with a void return type (or null return value) is considered to have fully handled the response if it also has a ServletResponse , or an OutputStream argument, or an @ResponseStatus annotation. The same is true also if the controller has made a positive ETag or lastModified timestamp check (see @Controller caching for details).If none of the above is true, a void return type may also indicate “no response body” for REST controllers, or default view name selection for HTML controllers. |
Any other return value | If a return value is not matched to any of the above, by default it is treated as a model attribute to be added to the model, unless it is a simple type, as determined by BeanUtils#isSimpleProperty in which case it remains unresolved. |
REST API 异常
在REST API中,异常信息通常是在响应中的,正常情况下S不会自动给我们封装异常响应,因为针对不同应用,封装的对象是不一样的。但是我们可以在@RestController中的@ExceptionHandler方法上,使用ResponseEntity返回值来封装状态和消息体。这些方法也可以在@ControllerAdvice中定义。
如果应用有全局的处理返回的异常详情的能力,可以考虑继承ResponseEntityExceptionHandler类,该类提供了处理S的异常的能力,并提供了hook来自定义异常,一般同时配合@ControllerAdvice使用。
正常情况下,@ExceptionHandler,@InitBinder以及@ModelAttribute方法都是使用在@Controller类中的,如果你想将这些方法作为全局的方法,应该将其放在@ControllerAdvice或者@RestControllerAdvice的类中。
当@ControllerAdvice和@Component一起注解的时候,通过S的自动扫描来注册@ControllerAdvice类。需要注意的是全局的@ExceptionHandler方法优先级要低于控制器里面的本地@ExceptionHandler处理方法,而全局的@ModelAttribute和@InitBinder优先级高于本地的。
@ControllerAdvice可以配置参数缩小作用的控制器范围:
// Target all Controllers annotated with @RestController
@ControllerAdvice(annotations = RestController.class)
public class ExampleAdvice1 {}
// Target all Controllers within specific packages
@ControllerAdvice("org.example.controllers")
public class ExampleAdvice2 {}
// Target all Controllers assignable to specific classes
@ControllerAdvice(assignableTypes = {ControllerInterface.class, AbstractController.class})
public class ExampleAdvice3 {}
需要注意的是,上面的判断时运行时的,因此如果配置角度会降低性能。
MVC java配置,以及MVC xml配置是S提供的默认配置,也可以使用配置API来自定相关的配置
使用@EnableWebMvc注解:
@Configuration
@EnableWebMvc
public class WebConfig {
}
xml中:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc.xsd">
<mvc:annotation-driven/>
</beans>
需要继承WebMvcConfigurer:
@Configuration
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {
// Implement configuration methods...
}
默认情况下会配置Number和Date类型的转换,以及支持@NumberFormat和@DateTimeFormat注解,使用java注册formatter和converter:
@Configuration
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {
@Override
public void addFormatters(FormatterRegistry registry) {
// ...
}
}
对应XML配置:
<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 conversion-service="conversionService"/>
<bean id="conversionService"
class="org.springframework.format.support.FormattingConversionServiceFactoryBean">
<property name="converters">
<set>
<bean class="org.example.MyConverter"/>
set>
property>
<property name="formatters">
<set>
<bean class="org.example.MyFormatter"/>
<bean class="org.example.MyAnnotationFormatterFactory"/>
set>
property>
<property name="formatterRegistrars">
<set>
<bean class="org.example.MyFormatterRegistrar"/>
set>
property>
bean>
beans>
当使用@Valid和Validated注解时,配置使用的是全局的LocalValidatorFactoryBean。
使用Java配置Validator实例:
@Configuration
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {
@Override
public Validator getValidator(); {
// ...
}
}
对应的xml:
<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 validator="globalValidator"/>
beans>
也可以注册本地的校验器(控制器内部):
@Controller
public class MyController {
@InitBinder
protected void initBinder(WebDataBinder binder) {
binder.addValidators(new FooValidator());
}
}
如果你想将LocalValidatorFactoryBean注入到某个类,为了避免和MVC Config中同样的类冲突,使用@Primary注解作用在创建的类上。
java配置:
@Configuration
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LocaleInterceptor());
registry.addInterceptor(new ThemeInterceptor()).addPathPatterns("/**").excludePathPatterns("/admin/**");
registry.addInterceptor(new SecurityInterceptor()).addPathPatterns("/secure/*");
}
}
xml的配置:
<mvc:interceptors>
<bean class="org.springframework.web.servlet.i18n.LocaleChangeInterceptor"/>
<mvc:interceptor>
<mvc:mapping path="/**"/>
<mvc:exclude-mapping path="/admin/**"/>
<bean class="org.springframework.web.servlet.theme.ThemeChangeInterceptor"/>
</mvc:interceptor>
<mvc:interceptor>
<mvc:mapping path="/secure/*"/>
<bean class="org.example.SecurityInterceptor"/>
</mvc:interceptor>
</mvc:interceptors>
可以配置S怎么去确定请求的media types,比如从Accept消息头,URL路径扩展,以及查询参数中解析。
默认URL路径扩展名是最先检查的,比如json,xml等。
使用Java配置自定义请求的content types节解析:
补充博客:Content Negotiation
@Configuration
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {
@Override
public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
configurer.mediaType("json", MediaType.APPLICATION_JSON);
}
}
xml
<mvc:annotation-driven content-negotiation-manager="contentNegotiationManager"/>
<bean id="contentNegotiationManager" class="org.springframework.web.accept.ContentNegotiationManagerFactoryBean">
<property name="mediaTypes">
<value>
json=application/json
xml=application/xml
value>
property>
bean>
可以通过重写WebMvcConfigurer.configureMessageConverters()方法来自定义自己的HttpMessageConverter,或者通过 extendMessageConverters()方法添加另外的转换器:
下面是配置JSON和XML转换器的案例
Jackson2ObjectMapperBuilder用来创建MappingJackson2HttpMessageConverter和MappingJackson2XmlHttpMessageConverter的配置,这里开启了indentation等配置
@Configuration
@EnableWebMvc
public class WebConfiguration implements WebMvcConfigurer {
@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder()
.indentOutput(true)
.dateFormat(new SimpleDateFormat("yyyy-MM-dd"))
.modulesToInstall(new ParameterNamesModule());
converters.add(new MappingJackson2HttpMessageConverter(builder.build()));
converters.add(new MappingJackson2XmlHttpMessageConverter(builder.xml().build()));
}
}
xml:
下面是Java配置的案例:将/请求foward到一个view,view的名称是home
@Configuration
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/").setViewName("home");
}
}
xml:
<mvc:view-controller path="/" view-name="home"/>
下面是使用java代码配置内容协商视图解析器的案例,默认的JSON渲染视图是JSP和Jackson
@Configuration
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {
@Override
public void configureViewResolvers(ViewResolverRegistry registry) {
registry.enableContentNegotiation(new MappingJackson2JsonView());
registry.jsp();
}
}
<mvc:view-resolvers>
<mvc:content-negotiation>
<mvc:default-views>
<bean class="org.springframework.web.servlet.view.json.MappingJackson2JsonView"/>
mvc:default-views>
mvc:content-negotiation>
<mvc:jsp/>
mvc:view-resolvers>
注意对于其他的像FreeMarker等等标记语言同样需要配置:
<mvc:view-resolvers>
<mvc:content-negotiation>
<mvc:default-views>
<bean class="org.springframework.web.servlet.view.json.MappingJackson2JsonView"/>
</mvc:default-views>
</mvc:content-negotiation>
<mvc:freemarker cache="false"/>
</mvc:view-resolvers>
<mvc:freemarker-configurer>
<mvc:template-loader-path location="/freemarker"/>
</mvc:freemarker-configurer>
使用Java代码只需要添加各自的配置类即可:
@Configuration
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {
@Override
public void configureViewResolvers(ViewResolverRegistry registry) {
registry.enableContentNegotiation(new MappingJackson2JsonView());
registry.freeMarker().cache(false);
}
@Bean
public FreeMarkerConfigurer freeMarkerConfigurer() {
FreeMarkerConfigurer configurer = new FreeMarkerConfigurer();
configurer.setTemplateLoaderPath("/WEB-INF/");
return configurer;
}
}
静态资源方便从资源路径中提供静态资源。在下面的例子中,假设请求以/resources开头,则后面的相对路径用来发现web项目根路径或者其中/static目录下的静态资源,这些资源有一年的有效期,从而最大程度利用浏览器缓存:
@Configuration
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/resources/**")
.addResourceLocations("/public", "classpath:/static/")
.setCachePeriod(31556926);
}
}
<mvc:resources mapping="/resources/**"
location="/public, classpath:/static/"
cache-period="31556926" />
如果请求的URL带有不同的版本号,可以使用VersionResourceResolver处理:
@Configuration
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/resources/**")
.addResourceLocations("/public/")
.resourceChain(true)
.addResolver(new VersionResourceResolver().addContentVersionStrategy("/**"));
}
}
<mvc:resources mapping="/resources/**" location="/public/">
<mvc:resource-chain>
<mvc:resource-cache/>
<mvc:resolvers>
<mvc:version-resolver>
<mvc:content-version-strategy patterns="/**"/>
mvc:version-resolver>
mvc:resolvers>
mvc:resource-chain>
mvc:resources>
通过默认Servlet可以将DispatcherServlet映射到"/"(覆盖容器的默认Servlet),但仍然能够让容器的默认Servlet处理static requests的请求。该配置为创建DefaultServletHttpRequestHandler处理/**的请求,需要放在所有的HandlerMappings中的最后,也就是优先级最高。
@Configuration
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {
@Override
public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
configurer.enable();
}
}
对应xml:
<mvc:default-servlet-handler/>
DefaultServletHttpRequestHandler会自动发现容器的默认Servlet,也可以中指定名称:
@Configuration
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {
@Override
public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
configurer.enable("myCustomDefaultServlet");
}
}
<mvc:default-servlet-handler default-servlet-name="myCustomDefaultServlet"/>
创建WebSocket服务器,只需要继承WebSocketHandler,或者TextWebSocketHandler和BinaryWebSocketHandler:
import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.TextMessage;
public class MyHandler extends TextWebSocketHandler {
@Override
public void handleTextMessage(WebSocketSession session, TextMessage message) {
// ...
}
}
也可以指定URL:
import org.springframework.web.socket.config.annotation.EnableWebSocket;
import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;
@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {
@Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
registry.addHandler(myHandler(), "/myHandler");
}
@Bean
public WebSocketHandler myHandler() {
return new MyHandler();
}
}
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:websocket="http://www.springframework.org/schema/websocket"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/websocket
http://www.springframework.org/schema/websocket/spring-websocket.xsd">
<websocket:handlers>
<websocket:mapping path="/myHandler" handler="myHandler"/>
websocket:handlers>
<bean id="myHandler" class="org.springframework.samples.MyHandler"/>
beans>
也可以通过HandshakeInterceptor拦截handshake请求:
@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {
@Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
registry.addHandler(new MyHandler(), "/myHandler")
.addInterceptors(new HttpSessionHandshakeInterceptor());
}
}
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:websocket="http://www.springframework.org/schema/websocket"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/websocket
http://www.springframework.org/schema/websocket/spring-websocket.xsd">
<websocket:handlers>
<websocket:mapping path="/myHandler" handler="myHandler"/>
<websocket:handshake-interceptors>
<bean class="org.springframework.web.socket.server.support.HttpSessionHandshakeInterceptor"/>
websocket:handshake-interceptors>
websocket:handlers>
<bean id="myHandler" class="org.springframework.samples.MyHandler"/>
beans>
更高级的选择是继承DefaultHandshakeHandler,详细定制实现细节