Spring下的一次Http请求全过程

11 篇文章 0 订阅
5 篇文章 0 订阅

1.引言

在之前的文章中使用拦截器解析HttpServletRequest请求头参数构件Http上下文实现了优雅获取请求头参数,在学习记录的过程中,对Http请求的底层处理流程上不是很熟悉,在此通过对Servlet规范学习和Spring对Servlet的代码实现代码追踪加深一下自己的了解,同时也为后续过滤器和拦截器作在http调用链中的作用节点和区别的学习做下铺垫。

2.Servlet

  • 为什么要了解Servelt?

​ Spring的Web框架就是基于Servlet实现的。所以要分析Spring MVC,首先应追根溯源,弄懂Servlet。

  • Servlet是什么?

​ Servlet是J2EE规范的一部分,是一种运行在Web服务器端的小型java程序。Servlet是按照Servlet规范编写的一个java类,用于交互 式地浏览和修改数据,生成web动态内容。在遵守Servlet规范的前提下,我们可讲Web应用部署在Servlet容器下。

  • 使用Servlet的好处?

    开发者可以聚焦业务逻辑,而不用关心Http协议方面的事情。我们给予Servlet规范实现Web应用的话,HTTP协议的处理过程就不需要我们参与了。这些工作交给Servlet容器(tomcat和jetty)去做就行了,我们只需要关系业务逻辑怎么实现。

3 Servlet规范的实现

a.交互过程

img

在Servlet完成初始化后,针对外部对Servlet的每次请求,Servlet容器就可以使用它处理客户端请求了。

  • 客户端请求由ServletRequest类型的对象请求对象表示。
  • Servlet封装响应并返回给请求的客户端,该响应由ServletResponse类型的对象表示
  • 这两个对象是由容器通过参数传递到Servlet接口的Service方法

在HTTP请求的场景下,容器提供的请求和响应对象具体类型分别是HttpServletRequest和HttpServletResponse。

b. 容器对Servlet规范的实现

tomcat的javax.servlet-api包中的HttpServlet,找到其中的service方法

  • 完成对请求和跟返回对象的验证,确保是按照Servlet规范来的。

    //javax.servlet.http.HttpServlet.java
    @Override
    public void service(ServletRequest req, ServletResponse res)
        throws ServletException, IOException
    {
        HttpServletRequest  request;
        HttpServletResponse response;
        //验证请求跟返回信息封装对象的类型是不是servlet规范中定义的类型的
        //否则抛异常
        if (!(req instanceof HttpServletRequest &&
                res instanceof HttpServletResponse)) {
            throw new ServletException("non-HTTP request or response");
        }
        //转为HttpServletRequest跟HttpServletResponse类型
        request = (HttpServletRequest) req;
        response = (HttpServletResponse) res;
    
        service(request, response);
    }
    
  • 对请求中的请求类型进行判断,来选择后面的逻辑

    protected void service(HttpServletRequest req, HttpServletResponse resp)
            throws ServletException, IOException {
        //获取请求的方法值
        String method = req.getMethod();
        //判断是不是get请求
        if (method.equals(METHOD_GET)) {
            //判断之前这个请求对象的最后修改时间是否修改过
            long lastModified = getLastModified(req);
            //如果值为-1,表示当前的servlet对象不支持请求头中的if-modified-since,这时候就需要调用后面的代码逻辑,代价高
            if (lastModified == -1) {
                doGet(req, resp);
            } else {
                //如果不是-1,表示支持,然后获取请求头中的if-modified-since参数
                long ifModifiedSince = req.getDateHeader(HEADER_IFMODSINCE);
                //如果请求头中的时间小于上次的请求时间,则表示页面需要进行刷新
                if (ifModifiedSince < lastModified) {
                    //设置返回体中的Last-Modified参数
                    maybeSetLastModified(resp, lastModified);
                    //进行后面的逻辑
                    doGet(req, resp);
                } else {
                    //如果两个时间相等则设置返回体的请求状态为304
                    resp.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
                }
            }
        } else if (method.equals(METHOD_HEAD)) {
            //获取上次的修改时间
            long lastModified = getLastModified(req);
            //设置修改时间
            maybeSetLastModified(resp, lastModified);
            //进行后面逻辑
            doHead(req, resp);
        } else if (method.equals(METHOD_POST)) {
            doPost(req, resp);
        } else if (method.equals(METHOD_PUT)) {
            doPut(req, resp);
        } else if (method.equals(METHOD_DELETE)) {
            doDelete(req, resp);
        } else if (method.equals(METHOD_OPTIONS)) {
            doOptions(req,resp);
       } else if (method.equals(METHOD_TRACE)) {
            doTrace(req,resp);
        } else {
            String errMsg = lStrings.getString("http.method_not_implemented");
            Object[] errArgs = new Object[1];
            errArgs[0] = method;
            errMsg = MessageFormat.format(errMsg, errArgs);
            resp.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED, errMsg);
        }
    }
    

c. Spring对Servlet规范的实现

  • Spring中对Servlet规范的实现类是FrameworkServlet类。继承了HttpServletBean类,对get、post等方法进行实现。

    //HttpServletBean继承了HttpServlet类
    public abstract class FrameworkServlet extends HttpServletBean implements ApplicationContextAware {
        //省略...
        @Override
        protected final void doGet(HttpServletRequest request, HttpServletResponse response)
                throws ServletException, IOException {
            //进行请求的处理
            processRequest(request, response);
        }
    
        @Override
        protected final void doPost(HttpServletRequest request, HttpServletResponse response)
                throws ServletException, IOException {
            //进行请求的处理          
            processRequest(request, response);
        }
        //省略...
    }
    
  • 主要处理逻辑在processRequest方法中,主要处理逻辑是准备请求的上下文和参数。

    //FrameworkServlet.java
    protected final void processRequest(HttpServletRequest request, HttpServletResponse response)
                throws ServletException, IOException {
        //获取当前系统时间,用来计算这个步骤的处理耗时
        long startTime = System.currentTimeMillis();
        Throwable failureCause = null;
        //获取当前服务运行所在地区,在RequestContextFilter中进行处理设值
        LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();
        //创建相关地区的对象的LocalContext,
        LocaleContext localeContext = buildLocaleContext(request);
        //获取请求的属性,请求相关属性会在RequestContextHolder中,它是用ThreadLocal实现的
        RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes();
        //创建requestAttributes,ServletRequestAttributes是RequestAttributes子类
        ServletRequestAttributes requestAttributes = buildRequestAttributes(request, response, previousAttributes);
        //从request获取WebAsyncManager,在filter阶段会创建WebAsyncManager,表示是不是异步相应的请求
        WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
        //将FrameworkServlet的内部类RequestBindingInterceptor设置到asyncManager中,用于在异步中初始化跟重新设置FrameworkServlet
        asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(), new RequestBindingInterceptor());
        //将localeContext跟requestAttributes设置到LocaleContextHolder跟RequestContextHolder中
        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 {
            //移除ThreadLocal中的请求相关信息,attribute跟context信息
            resetContextHolders(request, previousLocaleContext, previousAttributes);
            if (requestAttributes != null) {
                requestAttributes.requestCompleted();
            }
            //打印结果
            logResult(request, response, failureCause, asyncManager);
            //发布请求处理完成事件
            publishRequestHandledEvent(request, response, startTime, failureCause);
        }
    }
    
    
  • processRequest中调用了负责业务处理的doService方法,它是一个抽象方法,必须由子类实现。FrameWorkServlet只有一个子类DispatchServlet,其中doService方法实现如下:

    //DispatchServlet.java
    protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
            logRequest(request);
    
        //如果是一个include请求,<jsp:incluede page="xxx.jsp"/> 这种
        //可能在一个请求中嵌套了另外的一个请求,因此需要备份当前请求
        Map<String, Object> attributesSnapshot = null;
        //是否是一个include请求,通过request中的javax.servlet.include.request_uri属性判断
        //JSP在运行期间是会被编译成相应的Servlet类来运行的,
        if (WebUtils.isIncludeRequest(request)) {
            attributesSnapshot = new HashMap<>();
            Enumeration<?> attrNames = request.getAttributeNames();
            while (attrNames.hasMoreElements()) {
                String attrName = (String) attrNames.nextElement();
                //获取包含的请求中的获取A请求的内部B请求设定的spring的策略
                if (this.cleanupAfterInclude || attrName.startsWith(DEFAULT_STRATEGIES_PREFIX)) {
                    attributesSnapshot.put(attrName, request.getAttribute(attrName));
                }
            }
        }
        //设置web应用上下文到请求中,
        request.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE, getWebApplicationContext());
        //设置本地解析器
        request.setAttribute(LOCALE_RESOLVER_ATTRIBUTE, this.localeResolver);
        //设置主题解析器
        request.setAttribute(THEME_RESOLVER_ATTRIBUTE, this.themeResolver);
        //设置主题,如果没有设置则为null,默认的为WebApplicationContext
        request.setAttribute(THEME_SOURCE_ATTRIBUTE, getThemeSource());
        //将request跟response保存到一个FlashMap中,FlashMap用来将一个请求跟
        //另外一个请求关联起来,通常在redirect的时候有用
        if (this.flashMapManager != null) {
            //如果当前请求的FlashMap在之前的请求中保存过了,则取出来,并去除对应的缓存
            FlashMap inputFlashMap = this.flashMapManager.retrieveAndUpdate(request, response);
            //保存FlashMap到属性中
            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);
                }
            }
        }
    }
    
  • doService实现中的核心是doDispath方法

    //DispatchServlet.java
    protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
        HttpServletRequest processedRequest = request;
        HandlerExecutionChain mappedHandler = null;
        boolean multipartRequestParsed = false;
        //从request中获取WebAsyncManager
        WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
        try {
            ModelAndView mv = null;
            Exception dispatchException = null;
            try {
                //检查是不是multipart请求并将请求转化为MultipartHttpServletRequest类型的
                processedRequest = checkMultipart(request);
                //如果请求不是原来的request请求,则表示是multipart请求并且解析过的
                multipartRequestParsed = (processedRequest != request);
                //根据request中的url从hanlderMapping中获取
                //封装了HandlerInterceptor的HandlerExecutionChain
                mappedHandler = getHandler(processedRequest);
                //如果不存在对应的处理链则返回
                if (mappedHandler == null) {
                    noHandlerFound(processedRequest, response);
                    return;
                }
                //从HandlerExecutionChain中获取Handler然后寻找合适的HandlerAdapter
                HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
                //获取请求方式
                String method = request.getMethod();
                boolean isGet = "GET".equals(method);
                //检查是不是GET请求
                if (isGet || "HEAD".equals(method)) {
                    //检查当前的get类型的请求的最后修改时间是不是存在的
                    //这个参数用来减少数据传输用
                    long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
                    //如果浏览器请求中的最后请求时间跟服务器的最后修改时间一致
                    //并且是get类型请求则直接返回。
                    if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
                        return;
                    }
                }
                //调用applyPreHandle方法,执行全部的前置拦截器
                if (!mappedHandler.applyPreHandle(processedRequest, response)) {
                    return;
                }
                //执行对应的HandlerAdapter的Handler方法,拿到对应的视图
                mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
                //检查当前的请求是否正在异步处理,如果是的则直接放弃并返回
                if (asyncManager.isConcurrentHandlingStarted()) {
                    return;
                }
                //如果处理的结果返回的视图是空的则使用默认的视图,不为空则用处理的结果
                applyDefaultViewName(processedRequest, mv);
                //调用applyPostHandle方法执行后置拦截器
                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 {
            //检查当前的请求是否正在异步处理
            if (asyncManager.isConcurrentHandlingStarted()) {
                //如果mappedHandler不是null,则调用对应的mappedHandler中的AsyncHandlerInterceptor
                if (mappedHandler != null) {
                    mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
                }
            }
            else {
                //对流类型的请求,做后置的处理
                if (multipartRequestParsed) {
                    cleanupMultipart(processedRequest);
                }
            }
        }
    }
    

    img

    • 检查当前请求类型是否为multipart/form-data类型,是的话转换为MultipartHttpServletRequest类型的request
    • 解析url,从handleMapping对象中找到对应的封装了HandlerInterceptor的HandlerExecutionChain对象,没有找到就返回404
    • 从HandlerExecutionChain中获取Handler,然后寻找合适的HandlerAdapter
    • 检查是不是get请求,然后检查lastModified属性判断继续处理还是返回
    • 调用HandlerAdapter的applyPreHandle方法,取到所有拦截器后,遍历执行它们的preHandle方法
    • 调用前面选择的HandlerAdapter的handle方法,进行逻辑的处理,返回ModelAndView对象
    • 检查当前请求是否在异步处理,如果是直接返回
    • 检查当前视图是否为空,不是则返回,否则返回默认的ModelAndView对象
    • 调用HandlerAdapter的applyPostHandle方法,取到所有拦截器后,遍历执行它们的postHandle方法
    • 对正常返回的或是发生异常时生成的视图处理
    • 对异步请求或者multipart/form-data类型请求进行后续处理

    d. Controller对接Servlet

    • Controller是怎么被调用和做出响应的?

      Spring容器启动时会加载这些Controller类,并解析出URL对应的处理函数,封装成Handler对象,存储到HandlerMapping中。当有请求到来时,DispatchServlet从HandlerMapping中查找URL对应的Handler,然后调用Handler对应的函数代码,最后将执行结果返回给客户端。

    • 为什么要将Controller封装成Handler对象?

      controller的定义方式有多种:

      //方式一:通过@Controller、@RequestMapping来定义
      @Controller
      public class DemoController {
          @RequestMapping("/getUserName")
          public ModelAndView getUserName() {
              ModelAndView model = new ModelAndView("Greeting");
              model.addObject("message", "TOM");
              return model;
          }
      }
      //方式二:实现Controller接口 + xml配置文件:配置DemoController与URL的对应关系
      public class DemoControllerImpl implements Controller {
          @Override
          public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
              ModelAndView model = new ModelAndView("HelloWorld");
              model.addObject("message", "HelloWorld");
              return model;
          }
      }
      //方式三:继承HttpServlet抽象类 + xml配置文件:配置DemoExtendServletController类与URL的对应关系
      public class DemoExtendServletController extends HttpServlet {
          @Override
          protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
              this.doPost(req, resp);
          }
      
          @Override
          protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
              resp.getWriter().write("Hello World!");
          }
      }
      

      可以看出函数的定义很随意,如果不做适配,DIspatchServlet没有办法做统一调用,而且在有接口新增时,要在DispatchServlet类代码中增加判断逻辑,不符合开闭原则。Spring利用适配器模式对代码进行统一改造,定义了统一接口的HandlerAdapter,并对每种类型的Controller定义了对应的适配类。这样在DispatchServlet类中,就不需要区分对待不同的Controller对象了,统一调用HandlerAdapter的handle对象就可以了。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值