[SpringMVC源码学习]请求如何处理(七)

100 阅读4分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第16天,点击查看活动详情

由于我这里Controller是这么写的

@RequestMapping("/j1")
public String j1(@RequestParam String name)

是有参数的,如果没有参数在getMethodArgumentValues方法下面的

if (ObjectUtils.isEmpty(parameters)) {
return EMPTY_ARGS;
}

这个判断直接return,没有后面的事情了

resolver.resolveArgument->

public final Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
      NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
​
   NamedValueInfo namedValueInfo = getNamedValueInfo(parameter);
   MethodParameter nestedParameter = parameter.nestedIfOptional();
​
   Object resolvedName = resolveEmbeddedValuesAndExpressions(namedValueInfo.name);
   if (resolvedName == null) {
      throw new IllegalArgumentException(
            "Specified name must not resolve to null: [" + namedValueInfo.name + "]");
   }
​
   Object arg = resolveName(resolvedName.toString(), nestedParameter, webRequest);
   if (arg == null) {
      if (namedValueInfo.defaultValue != null) {
         arg = resolveEmbeddedValuesAndExpressions(namedValueInfo.defaultValue);
      }
      else if (namedValueInfo.required && !nestedParameter.isOptional()) {
         handleMissingValue(namedValueInfo.name, nestedParameter, webRequest);
      }
      arg = handleNullValue(namedValueInfo.name, arg, nestedParameter.getNestedParameterType());
   }
   else if ("".equals(arg) && namedValueInfo.defaultValue != null) {
      arg = resolveEmbeddedValuesAndExpressions(namedValueInfo.defaultValue);
   }
​
   if (binderFactory != null) {
      WebDataBinder binder = binderFactory.createBinder(webRequest, null, namedValueInfo.name);
      try {
         arg = binder.convertIfNecessary(arg, parameter.getParameterType(), parameter);
      }
      catch (ConversionNotSupportedException ex) {
         throw new MethodArgumentConversionNotSupportedException(arg, ex.getRequiredType(),
               namedValueInfo.name, parameter, ex.getCause());
      }
      catch (TypeMismatchException ex) {
         throw new MethodArgumentTypeMismatchException(arg, ex.getRequiredType(),
               namedValueInfo.name, parameter, ex.getCause());
      }
      // Check for null value after conversion of incoming argument value
      if (arg == null && namedValueInfo.defaultValue == null &&
            namedValueInfo.required && !nestedParameter.isOptional()) {
         handleMissingValueAfterConversion(namedValueInfo.name, nestedParameter, webRequest);
      }
   }
​
   handleResolvedValue(arg, namedValueInfo.name, parameter, mavContainer, webRequest);
​
   return arg;
}

这是AbstractNamedValueMethodArgumentResolver类实现的

首先就是去拿到参数名字,拿到名字之后,resolveName去解析。

protected Object resolveName(String name, MethodParameter parameter, NativeWebRequest request) throws Exception {
   HttpServletRequest servletRequest = request.getNativeRequest(HttpServletRequest.class);
​
   if (servletRequest != null) {
      Object mpArg = MultipartResolutionDelegate.resolveMultipartArgument(name, parameter, servletRequest);
      if (mpArg != MultipartResolutionDelegate.UNRESOLVABLE) {
         return mpArg;
      }
   }
​
   Object arg = null;
   MultipartRequest multipartRequest = request.getNativeRequest(MultipartRequest.class);
   if (multipartRequest != null) {
      List<MultipartFile> files = multipartRequest.getFiles(name);
      if (!files.isEmpty()) {
         arg = (files.size() == 1 ? files.get(0) : files);
      }
   }
   if (arg == null) {
      String[] paramValues = request.getParameterValues(name); // <--- 直接执行这里
      if (paramValues != null) {
         arg = (paramValues.length == 1 ? paramValues[0] : paramValues);
      }
   }
   return arg;
}

我们这里不是上传请求,所以直接到getParameterValues方法。

public String[] getParameterValues(String paramName) {
   return getRequest().getParameterValues(paramName);
}

这里比较熟悉了,request.getParameterValues()方法获取参数值。我们拿到了参数名字,自然能获取到参数值。获取到之后,判断是不是只有一个值,如果是返回数组第一个位置,否则直接返回数组。返回到resolveArgument当中。经过这样的处理,我们就匹配到了参数,得到了参数数组,返回到invokeForRequest方法中,最后doInvoke反射执行处理方法。

这里又有点不一样,如果我是用Model的,如:

@RequestMapping("/hello")
public String sayHello(Model model){
 //向模型中添加属性msg与值,可以在JSP页面中取出并渲染
 model.addAttribute("msg","hello,SpringMVC");
 //web-inf/jsp/hello.jsp
 return "hello";
}

则,resolveArgument是这样的ModelMethodProcessor类实现的

来看到return后面的方法

public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
      NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
​
   Assert.state(mavContainer != null, "ModelAndViewContainer is required for model exposure");
   return mavContainer.getModel();
}

->

public ModelMap getModel() {
   if (useDefaultModel()) {
      return this.defaultModel;
   }
   else {
      if (this.redirectModel == null) {
         this.redirectModel = new ModelMap();
      }
      return this.redirectModel;
   }
}

这里要返回我们的视图,也就是model.addAttribute("msg","hello,SpringMVC");中的键值对,然后返回给args[i],拿到注解里面的参数后,回到invokeForRequest方法,最后doInvoke反射执行处理方法。到这其实大体流程就结束了。

但还有个视图解析并渲染到页面的方法要执行

processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);

如果是传递的json,mv就是null,基本里面的方法都不执行。如果不是,这里面既可以处理exception,也可以处理modelandview。

如果处理视图

// 处理程序是否返回要渲染的视图?
if (mv != null && !mv.wasCleared()) {
   render(mv, request, response);
   if (errorView) {
      WebUtils.clearErrorRequestAttributes(request);
   }
}

进入render渲染

protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception {
   // 配置中文语言环境
   Locale locale =
         (this.localeResolver != null ? this.localeResolver.resolveLocale(request) : request.getLocale());
   response.setLocale(locale);
​
   View view;
   String viewName = mv.getViewName();
   if (viewName != null) {
      // 我们需要解析视图名称
      view = resolveViewName(viewName, mv.getModelInternal(), locale, request);
      if (view == null) {
         throw new ServletException("Could not resolve view with name '" + mv.getViewName() +
               "' in servlet with name '" + getServletName() + "'");
      }
   }
   else {
      // No need to lookup: the ModelAndView object contains the actual View object.
      view = mv.getView();
      if (view == null) {
         throw new ServletException("ModelAndView [" + mv + "] neither contains a view name nor a " +
               "View object in servlet with name '" + getServletName() + "'");
      }
   }
​
   // Delegate to the View object for rendering.
   if (logger.isTraceEnabled()) {
      logger.trace("Rendering view [" + view + "] ");
   }
   try {
      if (mv.getStatus() != null) {
         response.setStatus(mv.getStatus().value());
      }
      view.render(mv.getModelInternal(), request, response);
   }
   catch (Exception ex) {
      if (logger.isDebugEnabled()) {
         logger.debug("Error rendering view [" + view + "]", ex);
      }
      throw ex;
   }
}

在对我们视图的语言环境进行配置过后,需要解析视图名称,进入resolveViewName方法

protected View resolveViewName(String viewName, @Nullable Map<String, Object> model,
      Locale locale, HttpServletRequest request) throws Exception {
​
   if (this.viewResolvers != null) {
      for (ViewResolver viewResolver : this.viewResolvers) {
         View view = viewResolver.resolveViewName(viewName, locale);
         if (view != null) {
            return view;
         }
      }
   }
   return null;
}

this.viewResolvers就是我们自己配置的视图解析器,再在for循环中遍历所有视图解析器,一般就我们配置的一个,然后再单独处理视图进入resolveViewName获取视图名称

public View resolveViewName(String viewName, Locale locale) throws Exception {
   if (!isCache()) {
      return createView(viewName, locale);
   }
   else {
      Object cacheKey = getCacheKey(viewName, locale);
      View view = this.viewAccessCache.get(cacheKey);
      if (view == null) {
         synchronized (this.viewCreationCache) {
            view = this.viewCreationCache.get(cacheKey);
            if (view == null) {
               // Ask the subclass to create the View object.
               view = createView(viewName, locale);
               if (view == null && this.cacheUnresolved) {
                  view = UNRESOLVED_VIEW;
               }
               if (view != null && this.cacheFilter.filter(view, viewName, locale)) {
                  this.viewAccessCache.put(cacheKey, view);
                  this.viewCreationCache.put(cacheKey, view);
               }
            }
         }
      }
      else {
         if (logger.isTraceEnabled()) {
            logger.trace(formatKey(cacheKey) + "served from cache");
         }
      }
      return (view != UNRESOLVED_VIEW ? view : null);
   }
}

先判断是否开启缓存,不开启直接创建你view,开启的话优先从缓存中查找,然后可以看到我们先从map的缓存中取,没有就向下执行取到我们的视图后就返回,然后缓存里面就有了,下次来就可以直接拿了。

得到了视图,我们回到render方法里面,调用 Viewrender 方法渲染视图

view.render(mv.getModelInternal(), request, response);

进入里面

public void render(@Nullable Map<String, ?> model, HttpServletRequest request,
      HttpServletResponse response) throws Exception {
​
   if (logger.isDebugEnabled()) {
      logger.debug("View " + formatViewName() +
            ", model " + (model != null ? model : Collections.emptyMap()) +
            (this.staticAttributes.isEmpty() ? "" : ", static attributes " + this.staticAttributes));
   }
​
   Map<String, Object> mergedModel = createMergedOutputModel(model, request, response);
   prepareResponse(request, response);
   renderMergedOutputModel(mergedModel, getRequestToExpose(request), response);
}

里面进行的视图拼接的操作,不怎么重要,就不解释了,基本大致重要的流程就在这了。

总结:

  1. tomcat的Servlet调起Spring容器启动,Spring容器启动完,事件通知到SpringMVC的DispatcherServlet。

  2. 这时会扫描所有的bean,将注解了@Controller和@RequestMapping的解析出来。

  3. 前端请求发过来,DispatcherServlet接收到(因为它是个servlet,配置在web.xml的),根据上一步处理好的映射关系,找到对应的方法来处理。

    如:通过/test能找到test方法

    @RequestMapping("/test")
    public String test(String name, HttpServletRequest request,Model model){
        System.out.println("name");
        model.addAttribute("date",new Date());
        return "success";
    }
    
  4. 找到对应的方法后,反射调用

    method.invoke(Object obj,Object... args)
    
  5. 组装modelAndView渲染视图到前端