概述 Spring MVC 的 正式名称叫做 Spring Web MVC ,SpringMVC 本质上是一个 Servlet 接口的一个实现,需要在 Servlet 配置文件,一般是 web.xml 中配置。它的核心是 DispatcherServlet,它一方面实现了 Servlet 接口,一方面依赖 Spring 进行 Bean 的寻找,处理请求,处理错误等等。
本文没有什么干货,以转载为主,主要是一些用法备忘。用的时候再查文档或者找一些最佳实践。
使用 免 XML 配置 本文都是直接使用 Spring-Boot 中的 SpringMVC 进行讲解
我这里是讲一下 Servlet 3.0+之后的 免 XML 配置的方法, 这也是 Spring-boot 中为什么不用 XML 就可以配置的原因。 在 Servlet3.0+ 容器中(Tomcat7.0+),会自动寻找实现了 WebApplicationInitializer 接口的类,对容器的初始化配置。
最简单的配置类如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 public class MyWebApplicationInitializer implements WebApplicationInitializer { @Override public void onStartup (ServletContext servletCxt) { AnnotationConfigWebApplicationContext ac = new AnnotationConfigWebApplicationContext(); DispatcherServlet servlet = new DispatcherServlet(ac); ServletRegistration.Dynamic registration = servletCxt.addServlet("app" , servlet); registration.setLoadOnStartup(1 ); registration.addMapping("/app/*" ); } }
当然,如果是实际使用,请使用 Spring-Boot ,放弃传统的复杂 XML 配置 SSM 吧。
SpringMVC 几种使用方式 常用的到一些注解: 类级: @RestController:写在类上,自动给每个添加 @ResponseBody @SessionAttributes:写在类上,规定一个共用的 Seesion 对象名
方法级: @ModelAttribute: 如果写在方法上面,会在每一个 Controller 执行前先执行,并且作为一个公共对象给各个方法传入。 @GetMapping: 处理 get 请求,还有 Post Put Delete ,可以在这里定义 解析 URI 的参数 @ResponseBody: 返回值直接输出到返回值的 Body 中,如果返回值是类,会被 Spring 寻找合适的 Converter 转换成文本,一般会转成 Json
参数级: @RequestParam: 解析 url 问号后面的参数,如果是用类接收,Spring 会寻找合适的 Converter 转成你的目标类,否则是文本。 @SessionAttribute:把 Seesion 里面的对象传入 @CookieValue : 把 Cookies 里面的对象传入 @PathVariable():解析 path 参数 (uri) @RequestBody: 输入的 RequestBody,如果是用类接收,Spring 会寻找合适的 Converter 转成你的目标类,否则是文本。 @RequestAttribute :从已存在的对象(由 ModelAttribute 创建、拦截器插入的)获取对象 @ModelAttribute: 如果把 ModelAttribute 写在参数上面,使用时它会 首先查询 @ModelAttribute 域(方法上面的) 有无绑定的该对象,若没有则查询@SessionAttributes 域上是否绑定了该对象,若没有则将 URI 中或者从 URL 参数的值按对应的名称绑定到该对象的各属性上。
RESTFul 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 @RestController public class CategoryController { @Autowired CategoryDAO categoryDAO; @GetMapping ("/category" ) public List<Category> listCategory (@RequestParam(value = "start" , defaultValue = "0" ) int start, @RequestParam (value = "size" , defaultValue = "5" ) int size) { return new ArrayList<Category>(); } @GetMapping ("/category/{id}" ) public Category getCategory (@PathVariable("id" ) int id) { return new Category(); } @PutMapping ("/category" ) public void addCategory (@RequestBody Category category) throws Exception { System.out.println("springMVC接受到浏览器以JSON格式提交的数据:" +category); } @PostMapping ("/category/{id}" ) public String updateCategory (@ModelAttribute Category c) throws Exception { return "done" ; } @DeleteMapping ("/category/{id}" ) public String deleteCategory (@ModelAttribute Category c) throws Exception { return "done" ; } }
传统模版渲染的写法 Conroller 控制的数据其实都是 Model,最后都会转成 ModelAndView ,接着 Spring 会找到 Template 文件把 model 插进去,这里展示几种不同的写法
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 @Controller public class IndexController { @ModelAttribute public Account addAccount (@RequestParam String number) { return accountManager.findAccount(number); } @GetMapping ("/index1" ) public String index1 (Model model ) { model.addAttribute("result" , "后台返回index1" ); return "result" ; } @GetMapping ("/index2" ) public ModelAndView index2 () { ModelAndView mv = new ModelAndView("result" ); mv.addObject("result" , "后台返回index2" ); return mv; } @PostMapping ("/files/{path}" ) public String upload (...) { return "redirect:files/{path}" ; } }
结合自动 Conver 获取 Seesion 和 Cookies 对象 读取 Seesion,Cookies 自动转成对象
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 @Controller @SessionAttributes ("pet" ) public class EditPetForm { @GettMapping ("redirectTest" ) public String redirectTest (Model model) { model.addAttribute("pet" ,new Pet()); return "redirect:indexView" ; } @GetMapping ("indexView" ) public String handle (@ModelAttribute Pet pet, BindingResult errors, SessionStatus status) { if (errors.hasErrors) { } System.out.println(pet); status.setComplete(); } } @RequestMapping ("/demo1" ) public String handle (@SessionAttribute("user" ) User user) { } @GetMapping ("/demo2" ) public void handle (@CookieValue("JSESSIONID" ) String cookie) { } }
错误处理 1 2 3 4 5 6 7 8 9 10 11 12 @Controller public class SimpleController { @ExceptionHandler public String handle (IOException ex) { } @ExceptionHandler ({FileSystemException.class , RemoteException .class }) public ResponseEntity <String > handle (IOException ex ) { } }
验证 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 public class PersonForm { @NotNull @Size (min=2 , max=30 ) private String name; @NotNull @Min (18 ) private Integer age; public String toString () { return "Person(Name: " + this .name + ", Age: " + this .age + ")" ; } } @Controller public class WebController { @GetMapping ("/" ) public String showForm (PersonForm personForm) { return "form" ; } @PostMapping ("/" ) public String checkPersonInfo (@Valid PersonForm personForm, BindingResult bindingResult) { if (bindingResult.hasErrors()) { return "form" ; } return "redirect:/results" ; } }
自定义 Converter 如果传入或者返回的值应该是 String,但是你却用一个对象来对应,这个时候就需要 Covert。默认情况下 ,返回值是 String (ResponseBody),传对象会自动转成 Json。
例如
1 2 3 4 5 6 7 public class Employee { private long id; private double salary; }
现在你想传入一个值做参数:?data=1,50000.00 可以自动转成上面的对象:
1 2 3 4 5 @GetMapping ("/string-to-employee" )public ResponseEntity<Object> getStringToEmployee ( @RequestParam("data" ) Employee employee) { return ResponseEntity.ok(employee); }
在 Springboot 中,你只需要添加这样一个对象,Spring 就会自动转换:
1 2 3 4 5 6 7 8 9 10 public class String2EmplyeeConverter implements Converter <String , Employee > { @Override public Employee convert (String s) { String[] data = s.split("," ); return new Employee( Long.parseLong(data[0 ]), Double.parseDouble(data[1 ])); } }
上面主要是备查,其他可查官方文档:
原理 SpringMVC 流程图 MVC 其实就是把控制器 Controller 和视图 View 分开,并且用 Model 储存实际的数据,在不同部分中流转。
收到 HTTP 请求后,DispatcherServlet 会查询 HandlerMapping 以调用相应的 Controller。
Controller 接受请求并根据使用的 GET 或 POST 等方法调用适当的服务方法。服务方法将基于定义的业务逻辑设置 Model 数据。最后 Controller 将 View 名称、Model 返回给 DispatcherServlet。
DispatcherServlet 把视图名称发给 ViewResolver,返回一个已经写好的视图(JSP 文件或者其他模版文件)
DispatcherServlet 把 Model 和 View 结合,返回给用户
拦截器流程 1 2 3 4 5 6 7 public interface HandlerInterceptor { boolean preHandle (HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception ; void postHandle (HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception ; void afterCompletion (HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception ; }
下面的 HandlerAdapter 会调用相应的 Controller 方法
DispatcherServlet 中的代码节选:
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 33 34 35 HandlerInterceptor[] interceptors = mappedHandler.getInterceptors(); if (interceptors != null ) { for (int i = 0 ; i < interceptors.length; i++) { HandlerInterceptor interceptor = interceptors[i]; if (!interceptor.preHandle(processedRequest, response, mappedHandler.getHandler())) { triggerAfterCompletion(mappedHandler, interceptorIndex, processedRequest, response, null ); return ; } interceptorIndex = i; } } mv = ha.handle(processedRequest, response, mappedHandler.getHandler()); if (mv != null && !mv.hasView()) { mv.setViewName(getDefaultViewName(request)); } if (interceptors != null ) {for (int i = interceptors.length - 1 ; i >= 0 ; i--) { HandlerInterceptor interceptor = interceptors[i]; interceptor.postHandle(processedRequest, response, mappedHandler.getHandler(), mv); } } if (mv != null && !mv.wasCleared()) {render(mv, processedRequest, response); if (errorView) { WebUtils.clearErrorRequestAttributes(request); } triggerAfterCompletion(mappedHandler, interceptorIndex, processedRequest, response, null );
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 private void triggerAfterCompletion (HandlerExecutionChain mappedHandler, int interceptorIndex, HttpServletRequest request, HttpServletResponse response, Exception ex) throws Exception { if (mappedHandler != null ) { HandlerInterceptor[] interceptors = mappedHandler.getInterceptors(); if (interceptors != null ) { for (int i = interceptorIndex; i >= 0 ; i--) { HandlerInterceptor interceptor = interceptors[i]; try { interceptor.afterCompletion(request, response, mappedHandler.getHandler(), ex); } catch (Throwable ex2) { logger.error("HandlerInterceptor.afterCompletion threw exception" , ex2); } } } } }
参考资料 http://jinnianshilongnian.iteye.com/blog/1670856 https://docs.spring.io/spring/docs/current/spring-framework-reference/web.html#spring-web