转载

Spring MVC 学习笔记

  1. 表现层:view 试图 + controller + 数据模型(V 和 C 之间的数据传递)
  2. 业务层:service
  3. Dao 层:访问数据库

MVC 模式(代码的组织方式)

  1. M 模型:数据模型(POJO, VO, PO)+ 业务模型(业务逻辑)
  2. V 视图:即 jsp 和 html
  3. C 控制器:即 servlet

Spring MVC 属于表现层

位于 Spring 架构图中的 Web 模块,本质上是对 servlet 的封装

工作流程

  1. 用户发送请求到前端控制器
  2. 前端控制器到处理器映射器查询请求对应的 Handler
  3. 处理器映射器返回处理器执行链
  4. 前端控制器向处理器适配器请求执行 Handler
  5. 处理器适配器请求处理器执行 Handler
  6. 处理器返回 ModelAndView
  7. 处理器适配器返回 ModelAndView
  8. 前端控制器向视图解析器请求解析视图
  9. 视图解析器返回 View 对象
  10. 前端控制器渲染视图
  11. 前端控制器响应用户

九大组件

  1. HandlerMapping 处理器映射器
  2. HandlerAdapter 处理器适配器
  3. HandlerExceptionResolver 根据异常设置 ModelAndView
  4. ViewResolver 视图解析器,用于将视图名解析为视图
  5. RequestToViewNameTranslator 用于查找视图名
  6. LocaleResolver 用于国际化
  7. ThemeResolver 用于解析主题
  8. MultipartResolver 用于上传请求
  9. FlashMapManager 用于不想把参数写进 URL

请求参数绑定

Servlet API

DemoController.java

/**
 * 对原生servlet api的支持
 * url:/demo/handle02?id=1
 * 如果要在SpringMVC中使用servlet原生对象,直接在Handler方法形参中声明使用即可
 */
@RequestMapping("/handle02")
public ModelAndView handle02(HttpServletRequest request, HttpServletResponse response, HttpSession session) {
    String id = request.getParameter("id");
    Date date = new Date();
    ModelAndView modelAndView = new ModelAndView();
    modelAndView.addObject("date", date);
    modelAndView.setViewName("success");
    return modelAndView;
}
复制代码

success.jsp

<%@ page contentType="text/html; charset=utf-8" language="java" %>

<html>
<head>
    <title>Success</title>
</head>
<body>
跳转成功!服务器时间:${date}
</body>
</html>
复制代码

springmvc.xml

<!--配置springmvc的视图解析器-->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
    <property name="prefix" value="/WEB-INF/jsp/"/>
    <property name="suffix" value=".jsp"/>
</bean>
复制代码

绑定简单类型参数

/*
 * SpringMVC 接收简单数据类型参数
 * url:/demo/handle03?id=1
 * 注意:接收简单数据类型参数,直接在handler方法的形参中声明即可,框架会取出参数值然后绑定到对应参数上
 * 要求:传递的参数名和声明的形参名称保持一致
 */
@RequestMapping("/handle03")
public ModelAndView handle03(@RequestParam("ids") Integer id, Boolean flag) {
    Date date = new Date();
    ModelAndView modelAndView = new ModelAndView();
    modelAndView.addObject("date", date);
    modelAndView.setViewName("success");
    return modelAndView;
}
复制代码

绑定 POJO 类型参数

/*
 * SpringMVC接收pojo类型参数
 * url:/demo/handle04?id=1&username=zhangsan
 * 接收pojo类型参数,直接形参声明即可,类型就是Pojo的类型,形参名无所谓
 * 但是要求传递的参数名必须和Pojo的属性名保持一致
 */
@RequestMapping("/handle04")
public ModelAndView handle04(User user) {
    Date date = new Date();
    ModelAndView modelAndView = new ModelAndView();
    modelAndView.addObject("date", date);
    modelAndView.setViewName("success");
    return modelAndView;
}
复制代码

绑定 POJO 包装类型参数

/**
 * SpringMVC接收pojo包装类型参数
 * url:/demo/handle05?user.id=1&user.username=zhangsan
 * 不管包装Pojo与否,它首先是一个pojo,那么就可以按照上述pojo的要求来
 * 1、绑定时候直接形参声明即可
 * 2、传参参数名和pojo属性保持一致,如果不能够定位数据项,那么通过属性名 + "." 的方式进一步锁定数据
 */
@RequestMapping("/handle05")
public ModelAndView handle05(QueryVo queryVo) {
    Date date = new Date();
    ModelAndView modelAndView = new ModelAndView();
    modelAndView.addObject("date", date);
    modelAndView.setViewName("success");
    return modelAndView;
}
复制代码

绑定日期类型参数

需要配置自定义类型转换器,实现Converter接口

springmvc.xml

<!--自动注册最合适的处理器映射器,处理器适配器(调用handler方法)-->
<mvc:annotation-driven conversion-service="conversionServiceBean"/>


<!--注册自定义类型转换器-->
<bean id="conversionServiceBean" class="org.springframework.format.support.FormattingConversionServiceFactoryBean">
    <property name="converters">
        <set>
            <bean class="com.lagou.edu.converter.DateConverter"></bean>
        </set>
    </property>
</bean>
复制代码

DemoController.java

/**
 * 绑定日期类型参数
 * 定义一个 SpringMVC 的类型转换器接口,扩展实现接口接口,注册你的实现
 */
@RequestMapping("/handle06")
public ModelAndView handle06(Date birthday) {
    Date date = new Date();
    ModelAndView modelAndView = new ModelAndView();
    modelAndView.addObject("date", date);
    modelAndView.setViewName("success");
    return modelAndView;
}
复制代码

REST 风格支持

什么是 REST 风格?

之前

http://localhost:8080/user/queryUserById_action?id=3

之后

http://localhost:8080/user/3

get(查询),post(增加),put(更新),delete(删除)

先锁定资源,再根据请求方式不同决定操作

直观体验:传递参数方式的变化,参数可以在 uri 中

@PathVariable 注解

/*
 * restful get   
 * url: /demo/handle/15
 */
@RequestMapping(value = "/handle/{id}", method = {RequestMethod.GET})
public ModelAndView handleGet(@PathVariable("id") Integer id) {
    Date date = new Date();
    ModelAndView modelAndView = new ModelAndView();
    modelAndView.addObject("date", date);
    modelAndView.setViewName("success");
    return modelAndView;
}

/*
 * restful post
 * url: /demo/handle
 */
@RequestMapping(value = "/handle", method = {RequestMethod.POST})
public ModelAndView handlePost(String username) {
    Date date = new Date();
    ModelAndView modelAndView = new ModelAndView();
    modelAndView.addObject("date", date);
    modelAndView.setViewName("success");
    return modelAndView;
}

/*
 * restful put
 * url: /demo/handle/15/lisi
 */
@RequestMapping(value = "/handle/{id}/{name}", method = {RequestMethod.PUT})
public ModelAndView handlePut(@PathVariable("id") Integer id, @PathVariable("name") String username) {
    Date date = new Date();
    ModelAndView modelAndView = new ModelAndView();
    modelAndView.addObject("date", date);
    modelAndView.setViewName("success");
    return modelAndView;
}

/*
 * restful delete
 * url: /demo/handle/15
 */
@RequestMapping(value = "/handle/{id}", method = {RequestMethod.DELETE})
public ModelAndView handleDelete(@PathVariable("id") Integer id) {

    Date date = new Date();
    ModelAndView modelAndView = new ModelAndView();
    modelAndView.addObject("date", date);
    modelAndView.setViewName("success");
    return modelAndView;
}
复制代码

AJAX 支持

引入 Jackson 依赖

@RequestBody 注解

@ResponseBody 注解

将 Handler 方法返回的对象转换为指定的格式之后,写入到 response 对象的 body 区,通常用来返回 JSON 数据或者是XML数据。在使用此注解之后不会再走视图处理器,而是直接将数据写入到输出流中,他的效果等同于通过 response 对象输出指定格式的数据。

@RequestMapping("/handle07")
// 添加@ResponseBody之后,不再走视图解析器那个流程,而是等同于response直接输出数据
@ResponseBody
public User handle07(@RequestBody User user) {
    // 业务逻辑处理,修改name为张三丰
    user.setName("张三丰");
    return user;
}
复制代码

高级技术

拦截器

单个拦截器

方法 操作
CustomInterceptor::preHandle 拦截,若返回 true 则放行
HandlerAdaptor::handle 处理器适配器处理请求
CustomInterceptor::postHandle 拦截
DispatcherServlet::render 前端控制器渲染视图
CustomInterceptor::afterCompletion 拦截

多个拦截器

方法 操作
Interceptor1::preHandle 拦截器1进行拦截
Interceptor2::preHandle 拦截器2进行拦截
HandlerAdaptor::handle 处理器适配器处理请求
Interceptor2::postHandle 拦截器2进行拦截
Interceptor1::postHandle 拦截器1进行拦截
DispatcherServlet::render 前端控制器渲染视图
Interceptor2::afterCompletion 拦截器2进行拦截
Interceptor1::afterCompletion 拦截器1进行拦截

自定义拦截器

实现 HandlerInterceptor 接口

public class LoginInterceptor implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // 权限校验
    }
}
复制代码

处理 Multipart 形式的数据

配置MultipartResolver,id固定为multipartResolver

<!--多元素解析器,id固定为multipartResolver-->
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
    <!--设置上传文件大小上限,单位是字节,-1代表没有限制也是默认的-->
    <property name="maxUploadSize" value="5000000"/>
</bean>
复制代码

异常处理

@ControllerAdvice

@ExceptionHandler

// 可以让我们优雅的捕获所有Controller对象handler方法抛出的异常
@ControllerAdvice
public class GlobalExceptionResolver {

    @ExceptionHandler(ArithmeticException.class)
    public ModelAndView handle(ArithmeticException exception, HttpServletResponse response) {
        ModelAndView modelAndView = new ModelAndView();
        modelAndView.addObject("msg", exception.getMessage());
        modelAndView.setViewName("error");
        return modelAndView;
    }
}
复制代码

手写框架

实现思路

  1. Tomcat 加载 web.xml
  2. 前端控制器加载 spring.xml 配置文件,开启扫描
  3. 包扫描,注解扫描
  4. IOC 容器进行 bean 的初始化和依赖处理
  5. Spring MVC 相关组件初始化,建立 url 和 method 之间的映射
  6. HandlerMapping 等待请求进来,处理请求

源码剖析

前端控制器 DispatcherServlet 的代码结构

DispatcherServelt extends FrameworkServlet extends HttpServletBean extends HttpServlet

FrameworkServlet 的 doGet 和 doPost 调用 doService 抽象方法

DispatcherServlet 的 doService 调用 doDispatch 方法

核心方法为 doDispatch

重要时机点分析

Handler方法的执行时机

页面渲染时机(打断点并观察调用栈)

核心步骤

​ SpringMVC处理请求的流程即为 DispatcherServlet::doDispatch 方法的执行过程

1. 调用getHandler()获取到能够处理当前请求的执行链 HandlerExecutionChain(Handler + 拦截器)
2. 调用getHandlerAdapter();获取能够执行1 中 Handler 的适配器
3. 适配器调用 Handler 执行 ha.handle(总会返回一个ModelAndView对象)
4. 调用 processDispatchResult 方法完成视图渲染跳转
复制代码
原文  https://juejin.im/post/5ef6c702e51d45348255088a
正文到此结束
Loading...