tags: [Spring MVC]
categories: [技术笔记]
概述
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 接口的类,对容器的初始化配置。
最简单的配置类如下:
public class MyWebApplicationInitializer implements WebApplicationInitializer {@Overridepublic void onStartup(ServletContext servletCxt) {// Load Spring web application configuration (在这里加载Spring)AnnotationConfigWebApplicationContext ac = new AnnotationConfigWebApplicationContext();// Create and register the DispatcherServletDispatcherServlet 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
@RestControllerpublic 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 插进去,这里展示几种不同的写法
@Controllerpublic class IndexController {// 为每一个 Controller 都执行一次这个,创建一个公共对象@ModelAttributepublic Account addAccount(@RequestParam String number) {return accountManager.findAccount(number);}@GetMapping("/index1")public String index1(Model model ) { // 这里也可以是 ModelMap ,差不多model.addAttribute("result", "后台返回index1");return "result"; // 根据配置找到 /template/result.xxx 进行渲染}@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 自动转成对象
@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) { // 如果有错误会传到 errors 里面// ...}System.out.println(pet);status.setComplete();// 删掉 Seesion// ...}}// 或者直接这么写,读别的地方写入的 Seesion,如果要控制 Seesion 则 入参 为 HttpSession@RequestMapping("/demo1")public String handle(@SessionAttribute("user") User user) { // 括号可选// ...}// 获取 Cookies 值@GetMapping("/demo2")public void handle(@CookieValue("JSESSIONID") String cookie) {//...}}
错误处理
@Controller
public class SimpleController {
@ExceptionHandler
public String handle(IOException ex) {
// ...
}
@ExceptionHandler({FileSystemException.class, RemoteException.class})
public ResponseEntity<String> handle(IOException ex) {
// ...
}
}
验证
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。
例如
public class Employee {
private long id;
private double salary;
// standard constructors, getters, setters
}
现在你想传入一个值做参数:?data=1,50000.00 可以自动转成上面的对象:
@GetMapping("/string-to-employee")
public ResponseEntity<Object> getStringToEmployee(
@RequestParam("data") Employee employee) {
return ResponseEntity.ok(employee);
}
在 Springboot 中,你只需要添加这样一个对象,Spring就会自动转换:
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 结合,返回给用户
拦截器流程
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 中的代码节选:
//doDispatch方法
//1、处理器拦截器的预处理(正序执行)
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())) {
//1.1、失败时触发afterCompletion的调用
triggerAfterCompletion(mappedHandler, interceptorIndex, processedRequest, response, null);
return;
}
interceptorIndex = i;//1.2、记录当前预处理成功的索引
}
}
//2、处理器适配器调用我们的处理器
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
//当我们返回null或没有返回逻辑视图名时的默认视图名翻译(详解4.15.5 RequestToViewNameTranslator)
if (mv != null && !mv.hasView()) {
mv.setViewName(getDefaultViewName(request));
}
//3、处理器拦截器的后处理(逆序)
if (interceptors != null) {
for (int i = interceptors.length - 1; i >= 0; i--) {
HandlerInterceptor interceptor = interceptors[i];
interceptor.postHandle(processedRequest, response, mappedHandler.getHandler(), mv);
}
}
//4、视图的渲染
if (mv != null && !mv.wasCleared()) {
render(mv, processedRequest, response);
if (errorView) {
WebUtils.clearErrorRequestAttributes(request);
}
//5、触发整个请求处理完毕回调方法afterCompletion
triggerAfterCompletion(mappedHandler, interceptorIndex, processedRequest, response, null);
// triggerAfterCompletion方法
private void triggerAfterCompletion(HandlerExecutionChain mappedHandler, int interceptorIndex,
HttpServletRequest request, HttpServletResponse response, Exception ex) throws Exception {
// 5、触发整个请求处理完毕回调方法afterCompletion (逆序从1.2中的预处理成功的索引处的拦截器执行)
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
