SpringMVC核心源码解析(二)

​上一章节笔者对SpringMVC的核心原理做了详细介绍,本章将结合源码的方式对SpringMVC的原理进行解析,在上个章节笔者提到了一个Spring核心类DispatcherServlet类,我们先看下这个类的继承关系

该类如笔者所说继承了我们的HttpServelt,如果我们实现HttpServelt的doGet()方法或者doPost(),那么我们根据映射关系可以将方法和url进行绑定,对于HttpServelt之上的内容,笔者不进行描述,因为这是Tomcat的知识,那么现在我们的核心关键就是回到doGet()方法或者doPost(),当我们在DispatcherServlet搜索这两个方法的时候会发现搜索不到,这时候我们回到他的父类FrameworkServlet,可以看到我们所提到的doGet和doPost方法

找到后我们打上断点,在新建一个类

@Controller
public class UserController{
​
    @RequestMapping("/home")
    public String home(){
        return "home";
    }
​
}

​然后调试代码,将代码停在processRequest(request, response);然后在进入这个方法

protected final void processRequest(HttpServletRequest request, HttpServletResponse response)
      throws ServletException, IOException {
​
    long startTime = System.currentTimeMillis();
    Throwable failureCause = null;
​
    LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();
    LocaleContext localeContext = buildLocaleContext(request);
​
    RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes();
    ServletRequestAttributes requestAttributes = buildRequestAttributes(request, response, previousAttributes);
​
    WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
    asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(), new RequestBindingInterceptor());
​
    initContextHolders(request, localeContext, requestAttributes);
​
    try {
      doService(request, response);
    }
    catch (ServletException | IOException ex) {
      failureCause = ex;
      throw ex;
    }
    catch (Throwable ex) {
      failureCause = ex;
      throw new NestedServletException("Request processing failed", ex);
    }
​
    finally {
      resetContextHolders(request, previousLocaleContext, previousAttributes);
      if (requestAttributes != null) {
        requestAttributes.requestCompleted();
      }
      logResult(request, response, failureCause, asyncManager);
      publishRequestHandledEvent(request, response, startTime, failureCause);
    }
  }
​

这些都不用太关注,直接看到 doService(request, response);点进去

FrameworkServlet在调用service方法的时候会回调进入子类的方法,

@Override
  protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
    logRequest(request);
​
    // Keep a snapshot of the request attributes in case of an include,
    // to be able to restore the original attributes after the include.
    Map<String, Object> attributesSnapshot = null;
    if (WebUtils.isIncludeRequest(request)) {
      attributesSnapshot = new HashMap<>();
      Enumeration<?> attrNames = request.getAttributeNames();
      while (attrNames.hasMoreElements()) {
        String attrName = (String) attrNames.nextElement();
        if (this.cleanupAfterInclude || attrName.startsWith(DEFAULT_STRATEGIES_PREFIX)) {
          attributesSnapshot.put(attrName, request.getAttribute(attrName));
        }
      }
    }
​
    // Make framework objects available to handlers and view objects.
    request.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE, getWebApplicationContext());
    request.setAttribute(LOCALE_RESOLVER_ATTRIBUTE, this.localeResolver);
    request.setAttribute(THEME_RESOLVER_ATTRIBUTE, this.themeResolver);
    request.setAttribute(THEME_SOURCE_ATTRIBUTE, getThemeSource());
​
    if (this.flashMapManager != null) {
      FlashMap inputFlashMap = this.flashMapManager.retrieveAndUpdate(request, response);
      if (inputFlashMap != null) {
        request.setAttribute(INPUT_FLASH_MAP_ATTRIBUTE, Collections.unmodifiableMap(inputFlashMap));
      }
      request.setAttribute(OUTPUT_FLASH_MAP_ATTRIBUTE, new FlashMap());
      request.setAttribute(FLASH_MAP_MANAGER_ATTRIBUTE, this.flashMapManager);
    }
​
    try {
    // 核心代码 
      doDispatch(request, response);
    }
    finally {
      if (!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
        // Restore the original attribute snapshot, in case of an include.
        if (attributesSnapshot != null) {
          restoreAttributesAfterInclude(request, attributesSnapshot);
        }
      }
    }
  }

前面都是一些初始化操作,找到doDispatch(request, response);跟踪进去找到mappedHandler = getHandler(processedRequest);这行代码,前面的一切操作都是判断是否异步、校验等操作,我们不需要太关注,我们进入 getHandler(processedRequest);

这里可以看到handlerMappings,如果了解SpringMVC过原理可能听到过这个属性,我们从网上复制一张网上的图

步骤一DispatcherServlet为什么进来,占什么角色笔者已经在上文说明,看到第二步获取handler,这里会有个问题,对比我们上面的截图我们看到handlerMappings是两个,这handlerMappings为什么是2,这并不是我们配置的,而是两种handlers来源,就是两种数据来源,一种来源@RequestMapping,如图

它的两个handler来源如下:

@Controller
public class WeCat {
​
    @RequestMapping("/wecat")
    public String weCat(){
        return "wecat";
    }
}
@Controller
public class UserController {
​
    @RequestMapping("/home")
    public String home(){
        System.out.println("-------------");
        return "home";
    }
​
}

第二种来源如下:

@Component("/person")
public class PersonController implements Controller {
​
    public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
        System.out.println("------new ModelAndView()-----");
        return new ModelAndView();
    }
}

我们再次调试代码:

可以看到我们已经通过BeanNameUrlHandlerMapping找到了我们配置的person,这就证明了笔者所提到的来源问题,所以可以说上面的流程图并不完全或者说不太全面,好的我们回到流程图,在获取handler后,我们要通过handler获取HandlerAdapter,看下图

可以看到我们获取handler之后确实是通过它获取HandlerAdapter,我们点进去看下

这里看到我们的HandlerAdapter有三种,分别是RequestMappingHandlerAdapter,SimpleControllerHandlerAdapterBeanNameUrlHandlerMapping,前面两种笔者已经提到,分别是@Controller和接口Controller,第三种就比较麻烦,它的方式有点类似struct框架的方式,通过配置bean然后映射url,这种方式有点回到了原来的处理机制,并不多见,所以了解即可,我们回到IDEA

通过循环比对我们是采用的那三种方式,这里我们使用的是注解方式

@Controller
public class UserController {
​
    @RequestMapping("/home")
    public String home(){
        System.out.println("-------------");
        return "home";
    }
​
}

然后成功后就返回我们的HandlerAdapter,返回类型RequestMappingHandlerAdapter,我们再次回到代码:

    //返回HandlerAdapter
        HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
​
        // Process last-modified header, if supported by the handler.
        String method = request.getMethod();
        boolean isGet = "GET".equals(method);
        if (isGet || "HEAD".equals(method)) {
          long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
          if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
            return;
          }
        }
​
        if (!mappedHandler.applyPreHandle(processedRequest, response)) {
          return;
        }
​
        // 反射执行映射方法
        mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
​

获取HandlerAdapter后做一些判断然后进入核心方法  mv = ha.handle(processedRequest, response, mappedHandler.getHandler());这里有个细节

我们的mappedHandler.getHandler()获取的其实就是Url映射的方法,通过这个方法是不是我们就可以反射执行呢?答案是肯定的,当然SpringMVC在这里还做了一个十分重要的操作,细心的读者发现了如果我们的方法是这样的

@Controller
public class UserController {
    @RequestMapping(value = "/home")
    public String home(String s){
        System.out.println("-------------");
        return "index";
    }
}

然后我们采用POSTMAN工具进行请求,

可以看到我们的参数被拿到,如果我们的参数是一个实体类对象,SpringMVC会通过对象进行绑定,这种实现读者称为数据绑定或数据转换,在后面的章节读者会单独对数据绑定做出详细描述,这也是Spring的核心功能之一,好了我们执行到我们的方法后会返回字符串,SpringMVC得到我们返回的内容后会帮我们封装成ModelAndView

  mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
​
        if (asyncManager.isConcurrentHandlingStarted()) {
          return;
        }
​
        applyDefaultViewName(processedRequest, mv);
        mappedHandler.applyPostHandle(processedRequest, response, mv);
      }
      catch (Exception ex) {
        dispatchException = ex;
      }
      catch (Throwable err) {
        // As of 4.3, we're processing Errors thrown from handler methods as well,
        // making them available for @ExceptionHandler methods and other scenarios.
        dispatchException = new NestedServletException("Handler dispatch failed", err);
      }
      processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);

 看到最后一行代码processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);这里明显就是处理我们的结果了,这里笔者就略过了,毕竟我想表达的SpringMVC流程已经表达完了,后面的几个章节会对SpringMVC初始化、数据绑定、事件以及SpringBoot如何做到内置Tomcat、如何实现不基于web.xml实现SpringMVC的初始化等进行描述。

 

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值