Spring MVC基础

# Spring MVC

# 基本概念

  • 转发与重定向页面字符串不经过视图解析器处理,无拼串过程;其中/表示项目根路径
  • 视图对象是无状态的,因此不会有线程安全问题

# BindingAwareModelMap(“隐含模型”)

  • 控制器方法的返回值可以为ModelAndView类型,该对象既包含视图信息,又包含模型数据信息,其中模型数据信息置于request域中
    • Controller类上标注@SessionAttributes注解,可将(valuetype属性指定的)模型数据信息置于**request域以及session域**中
  • @ModelAttribute标注的方法在控制器方法前运行,与控制器方法共享同一个BindingAwareModelMap对象

# 注意事项

# Controller获取前端传入的请求参数
# GET
# POST

application/json:使用@RequestBody对象接收

x-www-form-urlencoded:使用HttpServletRequest获取请求参数,亦可直接使用POJO、@RequestBody字符串,或HttpEntity<T>对象(可以获取所有请求头)接收

# 数据格式化与数据校验
  • 在POJO字段上添加格式规则注解
  • 使用JSR 303实现类,在POJO字段上添加校验规则注解,在Controller方法入参处添加@Valid/@Validated注解(标注该注解的入参后紧跟BindingResult对象,封装校验结果)
# 异常处理
# HandlerExceptionResolver
  • ExceptionHandlerExceptionResolver
  • ResponseStatusExceptionResolver
  • DefaultHandlerExceptionResolver 处理部分预设异常
# 支持RESTful请求方法

配置HiddenHttpMethodFilter

前端携带_method参数,参数值为deleteput(在过滤器中统一转为大写)

HttpMethodRequestWrapper重写了父类HttpServletRequestgetMethod()方法,将传入的(而非request本身的)方法参数封装为新的请求方法对外返回

# 请求处理流程

# Spring MVC九大组件

  • MultipartResolver
  • LocaleResolver
  • ThemeResolver
  • HandlerMapping集合
  • HandlerAdapter集合
  • HandlerExceptionResolver集合
  • ThemeResolver
  • RequestToViewNameTranslator
  • FlashMapManager
  • ViewResolver集合

# 处理器处理请求映射及视图解析器解析页面字符串流程(Spring 4.x旧版本)

  1. 客户端(浏览器)发送请求,最终请求至 DispatcherServlet
  2. DispatcherServlet 根据请求信息调用 HandlerMapping,解析请求对应的 Handler
  3. 解析对应的 Handler,交由对应 HandlerAdapter 适配器处理
  4. HandlerAdapter#handle(...) 处理目标请求,执行业务逻辑
  5. 处理器返回 ModelAndView 对象通过适配器传递给DispaterServlet,其中Model 是返回的数据对象,View 为逻辑View
  6. ViewResolver 根据逻辑View查找实际 View
  7. DispaterServlet 将返回的 Model 传给 View(视图渲染)
  8. DispaterServletView 返回给请求者(浏览器)
participant HttpServlet
participant HttpServletBean
participant FrameworkServlet
participant DispatcherServlet

note over HttpServlet: doGet(req, res)/doPost(req, res)
note over FrameworkServlet: processRequest(req, res)
note over DispatcherServlet: doService(req, res) -> doDispatch(req, res)

# DispatcherServlet.doDispatch(req, res)
  1. checkMultipart(req),判断是否为文件上传请求

  2. getHandler(processedRequest),获取能处理当前请求的控制器/处理器(若没有符合条件的控制器/处理器,则重定向至404页面,除非用户在配置中显式要求抛出NoHandlerFoundException),其中包含请求所经过的拦截器信息interceptorList

    该方法遍历HandlerMapping[](处理器映射),获取控制器方法映射信息,构造HandlerExecutionChain对象返回

    // getHandler()
    
    // XXXHandlerMapping中保存有不同请求映射信息的(LinkedHashMap) handlerMap属性
    for(handlerMapping hm: this.handlerMappings) {
    // ...
    HandlerExecutionChain handler = hm.getHandler(req);
    // return handler
    }
    
    • (优先匹配)动态方法对应DefaultAnnotationHandlerMappingRequestMethodHandlerMapping对,handlerMap/urlMapping属性中保存有控制器方法(动态方法)请求映射信息(/xxx=XXXController@addr),需要借助<mvc:annotation-driven>实现
    • 静态资源对应SimpleUrlHandlerMapping对象,handlerMap属性中保存有(其他)请求的映射信息(/**=DefaultServletHttpRequestHandler@addr,即所有请求直接由Tomcat接管),需要借助<mvc:default-servlet-handler>实现
  3. getHandlerAdapter(mappedHandler.getHandler()),获取能执行控制器方法的适配器ha

    该方法从HandlerAdapter[]中遍历控制器方法适配器(HttpRequestHandlerAdapter/SimpleControllerHandlerAdapter/AnnotationMethodHandlerAdapter),如果适配器支持当前处理器,则返回该适配器

  4. !mappedHandler.applyPreHandle(processedRequest, response),遍历interceptorList进行preHandle()方法调用(启用多个拦截器时,不论是否有拦截器不放行请求,已放行拦截器的afterCompletion()方法都会执行)

    boolean applyPreHandle(HttpServletRequest req, HttpServletResponse res) {
      if (getInterceptors() != null) {
        for (int i = 0; i < getInterceptors().length; i++) {
          HandlerIntercoptor interceptor = getInterceptors()[i];
          if (!interceptor.preHandle(req, res, this.handler)) {
            triggerAfterCompletion(req, res, null);
            return false;
          }
          this.interceptorIndex = i; //	已有i个拦截器正常执行
        }
      }
      return true;
    }
    
  5. mv = ha.handle(processedRequest, response, mappedHandler.getHandler()),适配器ha执行目标方法,返回ModelAndView对象(自动封装可能返回的视图名字符串)

    目标控制器方法的反射执行流程:

    1. ha.handle(processedRequest, response, mappedHandler.getHandler())

      判断是否标注有SessionAttribute注解,若存在则放入SessionAttributeStore

    2. invokeHandlerMethod(processedRequest, response, mappedHandler)

      1. 获取当前处理器的ServletHandlerMethodResolver
      2. 通过方法解析器定位当前请求对应的控制器方法
      3. 创建ServletHandlerMethodInvokerBindingAwareModelMap对象
    3. HandlerMethodInvoker.invokeHandlerMethod(handlerMethod, handler, webRequest, implicitModel)

      1. 遍历所有SessionAttribute名,将SessionAttributeStore中存在的SessionAttribute放入implicitModel对象

      2. 遍历所有ModelAttributeMethod,获取ModelAttributeMethod方法的参数:

        Object[] args = resolveHandlerArguments(attributeMethodToInvoke, handler, webRequest, implicitModel);
        
        1. 解析@ModelAttribute方法的参数(参见下一节加粗部分)
        2. 根据@ModelAttribute注解中value属性值确定attrName(若未显式配置该属性则赋值为方法返回值首字母小写字符串),并放入implicitModel对象
        3. 传入args[]数组,反射执行@ModelAttribute方法(attributeMethodToInvoke
      3. 再次调用resolveHandlerArguments(attributeMethodToInvoke, handler, webRequest, implicitModel)获取目标控制器方法的参数,并真正通过反射执行目标控制器方法

        解析方法参数的流程(原始):

        1. 创建args[]数组,长度为method.getParameterTypes()长度

        2. 遍历方法参数,尝试获取参数信息:

          1. 若标有注解,则对每一类型的注解分别进行判断,返回对应不同类型注解的成员变量

          2. 若某一参数没找到任何注解信息:

            1. 判断该参数是否为Servlet原生组件对象

            2. 判断是否为ModelMap实现类,若是则将implicitModel对象赋值给args[]对应元素

            3. 判断是否为其他类型的原生apiSessionStatus/HttpEntity/Errors

            4. 判断该参数是否为简单类型(基本类型对象),若是则将paramName赋为空串

            5. 若参数为POJO类型,则将attrName赋为空串

              // 将控制器方法的入参信息传递给WebDataBinderFactory工厂对象,创建DataBinder实例
              WebDataBinder resolveModelAttribute(attrName, methodParam, implicitModel, webRequest, handler) {
              // 若attrName为空串,则赋值为参数类型首字母小写
              // 将封装的参数对象赋值给bindObject返回
              }
              

              封装POJO类型bindObject对象流程:

              1. implicitModel中寻找keyattrName的对象,若存在则赋值给bindObject返回
              2. 若该属性标注有SessionAttribute注解,则尝试从Session中获取value属性标注的对象或keyattrName的对象
              3. 否则利用反射创建名为attrName值的空POJO对象

              使用doBind(webDataBinder, webRequest, ...)方法,使用请求参数对上述对象中的属性进行一一绑定

              请求参数绑定流程

              1. DataBinder调用ConversionService对象进行数据类型转换与数据格式化,并进行入参对象的属性绑定

                自定义类型转换器:

                1. 实现Converter接口
                2. 注册自定义FormattingConversionServiceFactoryBean
                3. 可在<mvc:annotiation-driven>属性中配置自定义的ConversionService
              2. DataBinder调用多个Validator对象对已经完成属性绑定的入参对象进行数据合法性校验,数据绑定结果保存至BindingResult对象中

          3. 根据上一步返回的参数信息(标有注解时根据注解信息确定参数值,否则返回上一步中所得到的对象),将参数对象放入args[]的对应元素中

        3. 传入args[]数组,真正反射执行目标控制器方法

  6. mappedHandler.applyPostHandle(processedRequest, response, mv) 逆序执行(已完成的)拦截器的postHandle()方法

  7. processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException),转发到目标页面

    1. 若有异常传入,先处理异常

      mv = processHandlerException(req, res, handler, exception); // 遍历所有HandlerExceptionHander进行异常解析
      
    2. render(mv, req, res)

    3. View view = resolveViewName(mv.getViewName, mv.getModelInternal, locale, req)

    4. View view = viewResolver.resolveViewName(viewName, locale)

      遍历所有可用的视图解析器(默认即为InternalResourceViewResolver,优先级最低)根据视图名获取View对象(RedirectView/InternalResourceView):

      1. this.ViewCreationCache中尝试获取View对象
      2. View view = createView(viewName, locale)
      3. 进行实际的前后缀拼串过程
      4. View对象放入this.ViewCreationCache
    5. view.render(mv.getModelInternal, req, res) 转发到目标页面

      Map<String, Object> mergedModel = createMergedOutputModel(model, req, res);
      prepareResponse(req, res);
      renderMergedOutputModel(mergedModel, req, res); // 对需要在页面展示的ModelMap进行渲染
      

      renderMergedOutputModel(mergedModel, req, res)

      1. exposeModelAsRequestAttribute(model, requetsToExpose)model中的各属性放入request
      2. 获取视图地址和Servlet原生转发器(RequestDispatcher
      3. requestDispatcher.forward(requestToExpose, response)
    6. 若页面正常处理,执行afterCompletion()方法

  8. 若上述步骤出现异常,则在catch块执行afterCompletion()方法(afterCompletion()方法总会执行)

# <mvc:annotation-driven>
  • 自动注册RequestMappingHandlerMapping/RequestMappingHandlerAdaptor/ExceptionHandlerExceptionResolver三个组件
  • AnnotationDrivenBeanDefinitionParser解析<mvc:annotation-driven>标签
# HttpMessageConverter
// HttpMessageConverter
boolean canRead(Class<?>, MediaType);
boolean canWrite(Class<?>, MediaType);
list<MediaType> getSupportedMediaTypes();
T read(Class<? extends T>, HttpInputMessage); // 如@RequestBody封装对象流程
void write(T, MediaType, HttpOutputMessage); // 如@ResponseBody返回数据