SpringMVC源码解析——前置控制器DispatcherServlet

点击回顾SpringMVC请求响应流程

前置控制器类

前置控制器DispatcherServlet,间接继承自HttpServlet,本质上还是Servlet

1565316555822.png

当请求的url符合前置控制器DispatcherServlet的映射路径时,Servlet容器(Tomcat)会调用其service方法,其实现在父类FrameworkServlet中

  1. 查看FrameworkServlet的service方法(看看它怎么处理请求)
@Override
protected void service(...) throws ... {
    HttpMethod httpMethod = HttpMethod.resolve(request.getMethod());
    //增加对 PATCH 请求的处理
    if (HttpMethod.PATCH == httpMethod || httpMethod == null) {
        processRequest(request, response);
    }
    else {
        super.service(request, response);
    }
}

//重写doGet()方法
@Override
protected final void doGet(...) throws ... {
    processRequest(request, response);
}
//重写doPost()方法
@Override
protected final void doPost(...) throws ... {
    processRequest(request, response);
}

FrameworkServlet的service方法处理了Patch、Get、Post三种类型请求(通过调用processRequest方法)

  1. 查看FrameworkServlet的processRequest方法(进一步看看请求如何被处理)
protected final void processRequest(HttpServletRequest request, HttpServletResponse response)
  throws ServletException, IOException {
 ​
  long startTime = System.currentTimeMillis();
  Throwable failureCause = null;
 ​
  //获取之前的Locale上下文,同时从request中获取Locale上下文
  //Locale上下文接口用于获取对象Locale(用于构造Java国际化情景),只有一个方法——getLocale()
  LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();
  LocaleContext localeContext = buildLocaleContext(request);
 ​
  //获取之前的Request属性数据,同时从request中获取新的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() FramworkServlet没有实现,只是简单定义,交由 DispatchServlet实现
  doService(request, response);
  }
  //异常处理,采用捕获,记录然后再抛出给高层的处理策略
  catch (ServletException ex) {
  failureCause = ex;
  throw ex;
  }
  catch (IOException ex) {
  failureCause = ex;
  throw ex;
  }
  catch (Throwable ex) {
  failureCause = ex;
  throw new NestedServletException("Request processing failed", ex);
  }
 ​
  finally {
  //如果失败,恢复上一次请求的上下文 
  ...
  省略
  ...
  }
 }

可以看到FrameworkServlet的processRequest方法的职责是切换上下文(通过request设置上下文,遭遇异常会恢复原上下文),然后调用子类(DispatcherServlet)的doService方法

  1. 查看DispatcherServlet的doService方法(看看请求到底怎么被处理)

    protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
     ...
    
     //为request设置上下文及其他属性,方便其他方法需要时从request获取
     //...
     request.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE, getWebApplicationContext());
     //省略
     //...
    ​
     try {
     //将请求进行分派处理
     doDispatch(request, response);
     }
     finally {
     //...
     //省略
     //...
     }
    }
    

    DispatcherServlet的doService方法给request装填更多数据(),然后调用doDispatch方法最终将请求进行分派处理

  2. 查看DispatcherServlet的doDispatch方法(看请求如何被分派并处理)

    protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
     HttpServletRequest processedRequest = request;
     //定义Handler执行链
     HandlerExecutionChain mappedHandler = null;
     boolean multipartRequestParsed = false;
    ​
     WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
    ​
     try {
     //定义ModelAndView
     ModelAndView mv = null;
     Exception dispatchException = null;
    ​
     try {
     //检查请求是否为文件上传请求,如果是使文件上传解析器可用
     processedRequest = checkMultipart(request);
     //如果是文件上传请求multipartRequestParsed值为true
     multipartRequestParsed = (processedRequest != request);
    ​
     //获取请求对应的handler执行链
     //getHandler方法在Handler映射器环节讲解
     mappedHandler = getHandler(processedRequest);
     if (mappedHandler == null || mappedHandler.getHandler() == null) {
     noHandlerFound(processedRequest, response);
     return;
     }
    ​
     // 为Handler执行链中的Handler构建适配器
     // mappedHandler.getHandler()获取的是Handler执行链的Handler——在Handler执行链环节讲解
     HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
    ​
     // request的类型
     String method = request.getMethod();
     boolean isGet = "GET".equals(method);
     if (isGet || "HEAD".equals(method)) {
     long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
     if (logger.isDebugEnabled()) {
     logger.debug("Last-Modified value for [" + getRequestUri(request) + "] is: " + lastModified);
     }
     if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
     return;
     }
     }
    ​
     if (!mappedHandler.applyPreHandle(processedRequest, response)) {
     return;
     }
    ​
     // 适配器执行Handler执行链,返回ModelAndView
     //适配器的handle方法在Handler适配器讲解
     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) {
     dispatchException = new NestedServletException("Handler dispatch failed", err);
     }
    
     //向响应传递结果(包含异常)
     processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
     }
    
     //捕获响应传递过程中的异常
     catch (Exception ex) {
     triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
     }
     catch (Throwable err) {
     triggerAfterCompletion(processedRequest, response, mappedHandler,
     new NestedServletException("Handler processing failed", err));
     }
     finally {
     //...
     //省略
     //...
     }
    }
    

    可以看出doDispatch方法将请求分派给Handler执行链(调用Handler映射器得到Handler执行链、获取并调用Handler适配器执行Handler执行链),之后调用processDispatchResult对结果ModelAndView进行解析并装填响应结果

  3. 查看processDispatchResult方法(看看结果ModelAndView如何被装填进响应中)

    private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
     @Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv,
     @Nullable Exception exception) throws Exception {
    ​
     boolean errorView = false;
    ​
     //若之前的流程存在异常,使用异常ModelAndView填装参数(此处重构手段是:使用特例对象取代null值)
     if (exception != null) {
     if (exception instanceof ModelAndViewDefiningException) {
     logger.debug("ModelAndViewDefiningException encountered", exception);
     mv = ((ModelAndViewDefiningException) exception).getModelAndView();
     }
     else {
     Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);
     mv = processHandlerException(request, response, handler, exception);
     errorView = (mv != null);
     }
     }
    ​
     if (mv != null && !mv.wasCleared()) {
     // 通过ModelAndView渲染视图并填装至response
     render(mv, request, response);
     if (errorView) {
     WebUtils.clearErrorRequestAttributes(request);
     }
     }
     else {
     if (logger.isTraceEnabled()) {
     logger.trace("No view rendering, null ModelAndView returned.");
     }
     }
    ​
     if (WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
     // Concurrent handling started during a forward
     return;
     }
    ​
     if (mappedHandler != null) {
     mappedHandler.triggerAfterCompletion(request, response, null);
     }
    }
    ​
    protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception {
    
     // 获取请求的Locale对象并装入响应
     Locale locale =
     (this.localeResolver != null ? this.localeResolver.resolveLocale(request) : request.getLocale());
     response.setLocale(locale);
    ​
     View view;
     String viewName = mv.getViewName();
     if (viewName != null) {
     // ModelAndView指定了视图名称
     // 解析指定名称的视图
     view = resolveViewName(viewName, mv.getModelInternal(), locale, request);
     if (view == null) {
     throw new ServletException(略);
     }
     }
     else {
     // ModelAndView未指定视图名称
     // 从ModelAndView中取出View,此View由Handler(Controller)写入
     view = mv.getView();
     if (view == null) {
     throw new ServletException(略);
     }
     }
    ​
     // 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装入响应中
     view.render(mv.getModelInternal(), request, response);
     }
     catch (Exception ex) {
     if (logger.isDebugEnabled()) {
     logger.debug("Error rendering view [" + view + "]", ex);
     }
     throw ex;
     }
    }
    ​
    protected View resolveViewName(String viewName, @Nullable Map<String, Object> model,
     Locale locale, HttpServletRequest request) throws Exception {
    ​
     if (this.viewResolvers != null) {
     //调用视图解析器生成指定名称的View
     for (ViewResolver viewResolver : this.viewResolvers) {
     //视图解析器的resolveViewName方法在视图解析器环节讲解
     View view = viewResolver.resolveViewName(viewName, locale);
     if (view != null) {
     return view;
     }
     }
     }
     return null;
    }
    

    processDispatchResult方法通过调用render方法渲染视图(View)并装填响应结果(异常情况也被封装成ModelAndView进行渲染),render方法则会根据ModelAndView的数据决定是使用指定视图名加载并填充视图(resolveViewName方法)还是直接从ModelAndView取出视图,然后用视图填装结果

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,047评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,807评论 3 386
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,501评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,839评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,951评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,117评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,188评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,929评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,372评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,679评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,837评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,536评论 4 335
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,168评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,886评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,129评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,665评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,739评论 2 351