0xCAFEBABE

talk is cheap show me the code

0%

springmvc官翻笔记

spring mvc是建立在servlet api 之上的web mvc框架,与spring深度整合, 本章大部分内容取自spring官方文档, 可以把它当成一个翻译版 (受英语水平限制, 可能有些地方没理解透彻, 不足的地方欢迎指正哈), 一切还以官方文档为准,毕竟技术是在不断地更新, 说不定哪天就out了

DispatcherServlet

spring mvc核心前端啊控制器 负责请求分发, 视图解析,异常处理等, 依赖于web ioc容器

存在父子容器的概念, 一个web app 中可以有一个根ioc容器, 每个DispatcherServlet也有自己的子容器, 子容器可访问父容器中的组件

通常根容器主要用于存储基本的业务逻辑组件, 子容器主要存储请求处理相关的组件

基础组件

DispatcherServlet通过一些基础组件的配合 完成请求的处理

  • HandlerMapping 请求与处理器映射, 将请求映射到相应的处理器bean
  • HandlerAdapter 处理器适配器, 负责具体的处理器调用
  • HandlerExceptionResolver 异常处理器
  • ViewResolver 视图解析器, 将字符串视图名解析到实际的视图
  • LocaleResolver 区域解析器, 负责解析请求中的区域信息, 用于国际化
  • MultipartResolver 文件上传解析器, 用于解析关于文件上传的请求
  • FlashMapManager 存储输入与输出, 用于请求间的参数传递

Servlet配置

可通过web.xml或在Servlet 3.0+中使用SPI模式编程性地进行DispatcherServlet和ioc容器的配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class MyWebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
@Override
protected Class<?>[] getRootConfigClasses() {
// 配置根容器
return new Class[]{RootConfig.class};
}

@Override
protected Class<?>[] getServletConfigClasses() {
// 配置子容器
return new Class[]{WebConfig.class};
}
// servlet映射路径
@Override
protected String[] getServletMappings() {
return new String[]{"/"};
}
}

拦截器

在请求到来到之前或处理之后进行的处理逻辑, 通常用于权限验证等, 自定义拦截器需要实现HandlerInterceptor接口 并实现如下方法

  • preHandle 实际的处理器调用之前执行
  • postHanlde 处理器调用之后执行
  • afterCompletion 请求完成后调用

对于preHandle入股返回false, 则会打断接下来的拦截器, 目标处理器不会调用 直接进入afterCompletion

对于@ResponseBody或返回了ResponseEntity 的处理器方法, postHanlde无效

1
2
3
4
5
6
7
8
9
10
11
12
public class MyInterceptor implements HandlerInterceptor {
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
}
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
return true;
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
}
}

异常处理

如果异常发生在请求映射或从处理器中抛出DispatcherServlet会执行异常处理链来处理异常

spring mvc提供了以下错误处理器

  • SimpleMappingExceptionResolver 错误类型到视图名的映射, 可用于在浏览器端渲染错误页面
  • DefaultHandlerExceptionResolver 异常映射到状态码
  • ResponseStatusExceptionResolver 处理带@ResponseStatus 注解的异常并映射到状态码
  • ExceptionHandlerExceptionResolver 通过调用处理器中带@ExceptionHandler 注解的方法或带@ControllerAdvice 注解的类中的方法处理异常
  • 处理器链

    可通声明多个异常处理器形成处理器链, 并通过order属性进行排序

    异常处理器可返回

    • ModelAndView 指向一个错误视图
    • 空ModelAndView 异常在一个处理器中被处理
    • null 异常没有被处理, 传递给下一个处理器, 如果异常最后没能处理,将会传递给servlet 容器
  • 错误页面

    如果异常处理器链没能处理一个异常且状态码被设置成了4xx或5xx, 可在web.xml中定制化错误页面

    1
    2
    3
    <error-page>
    <location>/error</location>
    </error-page>

视图解析

spring mvc 提供多种视图解析策略,可通过下实现

ViewResolver完成自定义视图解析, View接口可在将数据将给指定的视图技术前完成数据的准备

springmvc提供了以下视图解析器

  • AbstractCachingViewResolver 可缓存的视图解析器
  • XmlViewResolver xml视图解析器, 可从xml文件读取视图解析配置
  • ResourceBundleViewResolver properties文件中获取视图配置
  • UrlBasedViewResolver url到视图的映射
  • InternalResourceViewResolver 内部资源视图解析器, 可指定视图的名前缀和后缀
  • FreeMarkerViewResolver freemaker视图解析器
  • ContentNegotiatingViewResolver 可解析基于请求文件名或Accept请求头的视图
  • 处理

    可通过配置多个视图解析器形成解析器链并通过order属性指定解析器执行顺序, 视图解析器可以返回null表示视图不存在

    对于jsp和InternalResourceViewResolver 确定一个jsp是否存在的方法是通过RequestDispatcher进行请求转发

  • 重定向

    视图名前加redirect:前缀表示重定向, 如果无@ResponseBody注解的控制器方法返回的字符串视图名前带有此前缀 则最终返回的是RedirectView

    如果控制器方法带有@ResponseStatus注解, 则注解的属性比RedirectView设置的属性优先级高

  • 转发

    forward:视图名前缀执行转发操作, 最终会被UrlBasedViewResolver 和其子类解析,并创建一个InternalResourceView,调用RequestDispatcher.forward()方法, 因此, 这个前缀对于InternalResourceViewResolver和使用jsp的InternalResourceView是无效的, 但如果使用另一个模板技术时需要强制转发到jsp时是有帮助的

  • Content Negotiation

    ContentNegotiatingViewResolve并不解析视图, 而是委托给其他的视图解析器并选择与请求相匹配的视图,可通过Accept请求头或请求参数进行匹配, 通过比较被与视图解析器关联的视图支持的请求的媒体信息(Content-Type)来选择合适的视图来处理这个请求, Accept请求头可接受通配符

Locale

DispatcherServlet可通过LocaleResolver根据请求中的Locale信息进行消息国际化, 还可以通过拦截器来改变Locale信息

  • 时区

    通过LocaleContextResolver可获取到更多的关于Locale的信息,其中包括

    时区信息, 时区信息可自动被时间日期转换器与格式化器使用

  • 请求头解析器(Header Resolver)

    LocaleResolver可检查请求头中的accept-language属性, 通常包含Locale信息, 但此解析器并不支持时区信息

  • Cookie Resolver

    此解析器可解析cookie中的区域信息, 可指定cookie名和有效时间

  • Seesion Resolver

    通过SessionLocaleResolver 可从session中获取Locale和TimeZone信息

  • Locale Interceptor

    LocaleChangeInterceptor通过拦截器方式获取和修改Locale信息

文件上传

spring mvc 通过配置MultipartResolver支持文件上传请求,

主要支持两种方式的解析文件上传请求

  • Apache Commons FileUpload

    配置bean名称为multipartResolverCommonsMultipartResolver, 需要引入commons-fileupload依赖

  • Servlet3.0

    • Servlet中注册MultipartConfigElement

      1
      2
      3
      4
      5
      6
      7
      public class MyWebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
      @Override
      protected void customizeRegistration(ServletRegistration.Dynamic registration) {
      MultipartConfigElement configElement = new MultipartConfigElement("/tep", 1024*1024, 1024*1024, 1024*1024);
      registration.setMultipartConfig(configElement);
      }
      }
    • web.xml中添加<multipart-config>

    • bean配置文件中添加名为multipartResolverStandardServletMultipartResolver

日志

可通过Servlet注册的方式修改开始请求日志

1
2
3
4
5
6
public class MyWebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
@Override
protected void customizeRegistration(ServletRegistration.Dynamic registration) {
registration.setInitParameter("enableLoggingRequestDetails", "true");
}
}

过滤器

spring mvc 提供了一些列过滤器

表单

通过FormContentFilter 可拦截内容类型为application/x-www-form-urlencoded的请求并读取请求表单数据并包装该请求使得可以通过ServletRequest.getParameter*()读取数据

转发头

ForwardedHeaderFilter可在有代理的情况下改变请求头Forwarded以提供原始的请求信息, 因为使用请求包装的方式, 所以优先级要比其他过滤器高,处于安全的考虑,应该使用removeOnly=true属性来移除不受信任的转发头

Shallow ETag

ShallowEtagHeaderFilter提供内容缓存机制, 通过计算内容的MD5值来判断是否发生了更改, 如果相同则返回304状态码, 为了支持异步, 需要在过滤器注册时需要属性DispatcherType.ASYNC属性

注解控制器

springmvc使用基于注解的控制器来处理请求, 控制器类方法可以有灵活的方法签名而并不需要扩展基类

定义

可通过@Controller@RestController注解定义控制器并可被ioc容器扫描

1
2
3
@Controller
public class UserController {
}
  • AOP代理

    如果需要再控制器上使用代理, 推荐使用基于类的代理

    以声明式事务为例,可通过bean配置文件上加<tx:annotation-driven proxy-target-class="true"/>或配置类上加@EnableTransactionManagement(proxyTargetClass = true)

请求映射

通过在控制器方法上添加@RequestMapping注解可进行请求路径,请求参数, 请求头的匹配,加在控制器类上表示所有方法共享,根据请求方法 spring提供了如下注解作为@RequestMapping的捷径

  • @GetMapping
  • @PostMapping
  • @PutMapping
  • @DeleteMapping
  • @PatchMapping

URL匹配

  • 通配符

    • ? 匹配一个字符
    • * 在一个路径内匹配零个或多个字符
    • **在多个路径中匹配零个或多个字符
  • @PathVariable

    路径变量, 数据类型可自动转换, 如果转换失败则抛出TypeMismatchException,还可通过自定义转换器完成数据转换(例如时间日期格式转换)

    1
    2
    3
    @GetMapping("/owners/{ownerId}/pets/{petId}")
    public Pet findPet(@PathVariable Long ownerId, @PathVariable Long petId) {
    }

    混合正则表达式

    1
    2
    3
    @GetMapping("/{name:[a-z-]+}-{version:\\d\\.\\d\\.\\d}{ext:\\.[a-z]+}")
    public void handle(@PathVariable String version, @PathVariable String ext) {
    }

匹配比较

如果多个匹配规则匹配了相同的url, 遵循精确优先的原则,

路径变量较多者优先

后缀匹配

文件名后缀匹配 .*被证明是有问题的, 可能会与路径变量和请求参数产生歧义, 为例禁用文件后缀名匹配,需要

  • PathMatchConfigurer中使用useSuffixPatternMatching(false)
  • ContentNegotiationConfigurer中使用favorPathExtension(false)

匹配内容类型

可通过注解的consumes属性来指定控制器方法接受那些内容类型

如果在控制器类上使用该属性,则所有的控制器方法都生效, 但控制器方法可以覆盖

1
2
3
@PostMapping(path = "/pets", consumes = "application/json") 
public void addPet(@RequestBody Pet pet) {
}

限定相应类型

可通过produces限定相应类型匹配请求头中的Accept属性

1
2
3
4
5
@ResponseBody
@GetMapping(value = "/index", produces = {"application/json;charset=utf-8"})
public String index(){
return "hello";
}

限定请求参数和请求头

  • params

    限定请求必须带有指定的参数

    1
    2
    3
    4
    5
    @ResponseBody
    @GetMapping(value = "/test", params = {"id=1"})
    public String test(){
    return "Hello";
    }
  • headers

    限定必须带有指定请求头

    1
    2
    3
    @GetMapping(path = "/pets", headers = "myHeader=myValue") 
    public void findPet(@PathVariable String petId) {
    }

处理器方法

通过@RequestMapping注解的方法支持灵活的方法参数和返回类型,并支持参数的类型转换和格式化

支持的参数类型点我查看

支持的返回类型点我查看

键值对参数

springmvc支持形如/cars;color=red,green;year=2012键值对方式的请求参数, 可通过@MatrixVariable参数注解进行匹配

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

通过pathVar区分参数来自哪个路径片段

1
2
3
4
5
6
7
// 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) {
}

支持通过requireddefaultValue设置参数的必须性和默认值

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

可使用@MultiValueMap获取所有键值对参数

1
2
3
4
5
6
// 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) {
}

请求参数

可通过@RequestParam注解绑定请求参数到指定的处理器方法参数

非String类型的参数可自动转换为目标类型, 如果该将注解加在Map<String,String>MultiValueMap<String, String>且没有指定参数名的情况下将填充所有参数

1
2
3
4
5
6
7
// GET /pets?petId=1
@ResponseBody
@GetMapping("/pets")
// required 是否必须 defaultValue 默认值 name 指定参数名 默认为方法参数名
public String pets(@RequestParam(required = false, defaultValue = "1") int petId){
return "done";
}

请求头

@RequestHeader注解在方法参数上课可获取请求头信息

如果注解的参数类型为Map MultiValueMap, 或 HttpHeaders 会将所有的请求头参数填充

以逗号分隔的多个值可以被转换为数组或集合

1
2
3
4
5
@GetMapping("/demo")
public void handle(
@RequestHeader("Accept-Encoding") String encoding,
@RequestHeader("Keep-Alive") long keepAlive) {
}

@CookieValue用于获取cookie中的信息

1
2
3
@GetMapping("/demo")
public void handle(@CookieValue("JSESSIONID") String cookie) {
}

各种Attribute

@ModelAttribute

在控制器参数上添加@ModelAttribute注解可从Model中获取或实例化一个实体类对象, 并可使用请求参数为其属性赋值(通过自定义转换器或WebDataBinder为其属性赋值)

1
2
3
4
5
6
7
8
9
10
11
/*
首先再Model中寻找
然后再@SessionAttribute中查找
最后如果没找到,如果存在转换器则使用转换器,如果不存在则实例化并使用WebDataBinder为其属性赋值
*/
@ResponseBody
@GetMapping("/user")
public String user(@ModelAttribute User user){
System.out.println(user);
return "done";
}

还可再方法上加此注解表示参数不需要经过数据绑定, 可以像处理器方法那样拥有任意多的参数,可获取请求信息, 并且在处理器方法之前调用

1
2
3
4
5
// 需要再参数注解上声明binding=false
@ModelAttribute
public User getUser(){
return new User();
}
@SessionAttribute

加在控制器类上让指定的参数在存入Model的同时也放入session, 直到session被清空(使用SessionStatus方法参数)

1
2
3
@Controller
@SessionAttributes("pet")
public class EditPetForm {}

在方法参数加此注解以访问预先存在的session参数

1
2
3
@RequestMapping("/")
public String handle(@SessionAttribute User user) {
}
@RequestAttribute

通过此注解访问已经存在于请求中参数

1
2
@GetMapping("/")
public String handle(@RequestAttribute Client client) { }

重定向

redirect:视图名前缀表示一个重定向视图, 在处理器方法上添加RedirectAttributes类型的参数可向重定向视图RedirectView传递参数, RequestMappingHandlerAdapter提供一个名为ignoreDefaultModelOnRedirect的属性来指定是否当前Model的内容在重定向视图中可用

1
2
3
4
5
@PostMapping("/files/{path}")
public String upload(...) {
// 路径变量可自动传递到重定向视图
return "redirect:files/{path}";
}

闪现消息

通常用于请求间的参数传递(重定向), 实际存储在session中 通过FlashMapFlashMapManager可以管理, 实际上只需在控制器方法上添加RedirectView类型的从参数即可完成闪现消息的传递, FlashMap在一些异步请求上可能会出现并发问题, 为了减少这个问题RedirectView会自动使用重定向目标URL的路径和请求参数为FlashMap打上’’印章’’

文件上传

如果启用了MultipartResolver, 内容类型为multipart/form-data的POST请求将会被解析并可通过MultipartFile的处理器参数类型访问指定请求参数名的上传文件, 如果参数类型为List<MultipartFile>,则可获取多个文件, 如果存在参数类型为Map<String, MultipartFile>MultiValueMap<String, MultipartFile>且没有参数名被定义,将被填充所有的上传文件

1
2
3
4
5
6
7
8
9
10
@PostMapping("/form")
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";
}

@RequestBody

在处理器方法参数上使用此注解可将请求体(通常为json)反序列化对象, 还可以使用数据校验, 当校验不成功时,返回400状态码或通过BindingResult处理校验错误

1
2
3
@PostMapping("/accounts")
public void handle(@Valid @RequestBody Account account, BindingResult result) {
}

@HttpEntity

@RequestBody类似, 但可以获取更多的请求信息

1
2
3
@PostMapping("/accounts")
public void handle(HttpEntity<Account> entity) {
}

@ResponseBody

处理器方法上加此注解,表示方法返回值为响应体, 可通过HttpMessageConverter消息转换(通常是java对象->json)

1
2
3
4
@GetMapping("/accounts/{id}")
@ResponseBody
public Account handle() {
}

ResponseEntity

当处理器方法返回此类型时,表示返回一个完整的响应(包括响应头, 状态码…)

1
2
3
4
5
6
@GetMapping("/something")
public ResponseEntity<String> handle() {
String body = ... ;
String etag = ... ;
return ResponseEntity.ok().eTag(etag).build(body);
}

Jackson JSON

spring mvc提供对jackson json的内建序列化支持, 除了@ResponseBodyResponseEntity,还可使用@JsonView对响应内容进行序列化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
@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;
}
}

数据绑定

控制器类的@InitBinder方法可以定制化WebDataBinder, 并通过它可以自定义

  • 请求参数绑定
  • 字符串值转换
  • 格式化实体类渲染html表单

@InitBinder方法可像处理器方法那样拥有任意的参数并访问请求信息

1
2
3
4
5
6
7
8
9
@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@ControllerAdvice中的@ExceptionHandler可匹配并处理由处理器方法抛出的异常

1
2
@ExceptionHandler
public ResponseEntity<String> handle(IOException ex) {}

声明具体异常类型列表, 如果为空将应用于参数上的异常类型

1
2
3
@ExceptionHandler({FileSystemException.class, RemoteException.class})
public ResponseEntity<String> handle(IOException ex) {
}

当存在多个@ControllerAdvice类时, @ExceptionHandler方法可以通过将异常抛出传递给下一个@ExceptionHandler, 方法支持的参数类型和返回类型详见官方文档

@ControllerAdvice

可通过在@ControllerAdvice@RestControllerAdvice类中定义全局的@ExceptionHandler, @InitBinder, @ModelAttribute方法, @Controller类中的@ExceptionHandler方法优先于全局@ExceptionHandler执行, 全局的@InitBinder@ModelAttribute的方法优先于@Controller类中的方法执行, 默认@ControllerAdvice将应用于每个请求,可通过参数进行限定

1
2
3
4
5
6
7
8
9
// 目标注解
@ControllerAdvice(annotations = RestController.class)
public class ExampleAdvice1 {}
// 目标包名
@ControllerAdvice("org.example.controllers")
public class ExampleAdvice2 {}
// 目标接口
@ControllerAdvice(assignableTypes = {ControllerInterface.class, AbstractController.class})
public class ExampleAdvice3 {}

异步

spring mvc支持基于Servlet3.0的异步请求

具体的处理过程详见官方文档

DeferredResult

异步响应结果,

1
2
3
4
5
6
7
8
9
10
11
12
13
@GetMapping("/deferred")
public DeferredResult<String> deferred(){
DeferredResult<String> deferredResult = new DeferredResult<>(5000L); // 超时时间
new Thread(() ->{
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
deferredResult.setResult("hello");
}).start();
return deferredResult;
}

Callable

通过返回Callable开启异步响应处理

1
2
3
4
5
6
7
@GetMapping("/call")
public Callable<String> callable(){
return ()-> {
Thread.sleep(3000);
return "done";
};
}

Stream

spring mvc 支持异步方式发送流数据

ResponseBodyEmitter

生产对象流, 经过序列化候写入响应

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@GetMapping("/emit")
public ResponseBodyEmitter responseBodyEmitter(){
ResponseBodyEmitter emitter = new ResponseBodyEmitter(5000L);
emitter.onError(Throwable::printStackTrace);

new Thread(()->{
try {
Thread.sleep(3000);
emitter.send("Hello", MediaType.TEXT_HTML);
emitter.complete(); // 完成响应
} catch (Exception e) {
e.printStackTrace();
}
}).start();
return emitter;
}

SSE

基于W3C SSE specificatio的服务器事件发送机制

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@GetMapping(path="/sse")
public SseEmitter handle() {
SseEmitter emitter = new SseEmitter(5000L);
new Thread(()->{
try {
emitter.send("<h1>Hello</h1>", MediaType.TEXT_HTML);
Thread.sleep(1000);
emitter.send("<h2>world</h2>", MediaType.TEXT_HTML);
emitter.complete();
} catch (Exception e) {
e.printStackTrace();
}
}).start();

return emitter;
}

StreamingResponseBody

异步发送原始数据

1
2
3
4
5
6
7
@GetMapping("/download")
public StreamingResponseBody download() {
return (outputStream)-> {
outputStream.write("Hello world".getBytes());
outputStream.flush();
};
}

跨域

@CrossOrigin可在控制器类或方法上启用跨域, 默认允许所有的请来源

1
2
3
4
5
@GetMapping("/cors")
@CrossOrigin
public String cors(){
return "done";
}

MVC配置

可通过@EnableWebMvcWebMvcConfigurer对spring mvc进行定制化

1
2
3
4
@Configuration
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {
}

@EnableWebMvc导入了DelegatingWebMvcConfiguration

  • 提供默认配置
  • 检查和委托WebMvcConfigurer是实现类进行自定义配置

类型转换

配置类型转换器和格式化器将请求参数转换为指定类型或日期格式化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@Override
public void addFormatters(FormatterRegistry registry) {
registry.addFormatter(new Formatter<Date>() {
@Override
public String print(Date object, Locale locale) {
return object.toString();
}
@Override
public Date parse(String text, Locale locale) throws ParseException {
return new SimpleDateFormat("yyyy-MM-dd").parse(text);
}
});
registry.addConverter(new Converter<String, User>() {
@Override
public User convert(String source) {
String username = source.substring(0, source.indexOf("@"));
String password = source.substring(source.lastIndexOf("@")+1);
User user = new User();
user.setUname(username);
user.setPsw(password);
return user;
}
});
}

数据校验

默认情况下,如果数据校验器出现在了类路径下(例如Hibernate Validator)将被自动注册为全局校验器

还可通过MVC配置定制化校验器

1
2
3
@Override
public Validator getValidator() {
}

拦截器

MVC配置添加拦截器

1
2
3
4
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new MyInterceptor());
}

请求内容解析

1
2
3
4
5
@Override
public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
configurer.mediaType("json", MediaType.APPLICATION_JSON);
configurer.mediaType("xml", MediaType.APPLICATION_XML);
}

消息转换

spring mvc默认使用jackson作为消息转换器, 如果jackson依赖包存在类路径,将会被自动注册

可使用一下MVC配置覆盖spring mvc的默认配置

1
2
3
4
5
6
7
8
@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder()
.indentOutput(true)
.serializationInclusion(JsonInclude.Include.NON_NULL);
converters.add(new MappingJackson2HttpMessageConverter(builder.build()));
converters.add(new MappingJackson2XmlHttpMessageConverter(builder.createXmlMapper(true).build()));
}

视图控制器

路径到视图的直接映射

1
2
3
4
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/").setViewName("home");
}

视图解析器

1
2
3
4
5
@Override
public void configureViewResolvers(ViewResolverRegistry registry) {
registry.enableContentNegotiation(new MappingJackson2JsonView());
registry.jsp();
}

静态资源

配置静态资源的访问

1
2
3
4
5
6
7
8
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
// 以resources开头的路径匹配
registry.addResourceHandler("/resources/**")
// 资源的具体路径
.addResourceLocations("/public", "classpath:/static/")
.setCachePeriod(31556926); // 设置缓存时间
}

默认Servlet

如果将DispatcherServlet的拦截路径设为/将使得默认Servlet失效, 可通过配置方式启动默认Servlet实现静态资源的访问,将配置一个DefaultServletHttpRequestHandler 并处在所有的HandlerMappings的顺序之后

1
2
3
4
@Override
public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
configurer.enable();
}

路径匹配

与路径匹配相关的配置

1
2
3
4
5
6
@Override
public void configurePathMatch(PathMatchConfigurer configurer) {
configurer
.setUseTrailingSlashMatch(false)
.setUseRegisteredSuffixPatternMatch(true);
}