• 【Spring MVC】MVC如何浏览器请求(service方法)



    背景:平时我们学习 MVC 重点关注的时DispatcherServlet 的 doDispatcher 方法,但是在 doDispatcher 方法之前 还有请求处理的前置过程,这个过程作为一个高级程序员是必须要了解的。

    1. DispatcherServlet 的 service 方法

    DispatcherServlet的继承关系如下图:

    因为是 Servlet,自然看它的 service 方法

        public void service(ServletRequest req, ServletResponse res)
            throws ServletException, IOException
        {
            HttpServletRequest  request;
            HttpServletResponse response;
            
            if (!(req instanceof HttpServletRequest &&
                    res instanceof HttpServletResponse)) {
                throw new ServletException("non-HTTP request or response");
            }
    
            request = (HttpServletRequest) req;
            response = (HttpServletResponse) res;
    
            service(request, response);
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    为了处理 patch 请求

    	protected void service(HttpServletRequest request, HttpServletResponse response)
    			throws ServletException, IOException {
    
    		HttpMethod httpMethod = HttpMethod.resolve(request.getMethod());
    		if (httpMethod == HttpMethod.PATCH || httpMethod == null) {
    			processRequest(request, response);
    		}
    		else {
    			super.service(request, response);
    		}
    	}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    各种请求类型分发:

        protected void service(HttpServletRequest req, HttpServletResponse resp)
            throws ServletException, IOException
        {
            String method = req.getMethod();
    
            if (method.equals(METHOD_GET)) {
                long lastModified = getLastModified(req);
                if (lastModified == -1) {
                    // servlet doesn't support if-modified-since, no reason
                    // to go through further expensive logic
                    doGet(req, resp);
                } else {
                    long ifModifiedSince = req.getDateHeader(HEADER_IFMODSINCE);
                    if (ifModifiedSince < lastModified) {
                        // If the servlet mod time is later, call doGet()
                        // Round down to the nearest second for a proper compare
                        // A ifModifiedSince of -1 will always be less
                        maybeSetLastModified(resp, lastModified);
                        doGet(req, resp);
                    } else {
                        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 {
                //
                // Note that this means NO servlet supports whatever
                // method was requested, anywhere on this server.
                //
    
                String errMsg = lStrings.getString("http.method_not_implemented");
                Object[] errArgs = new Object[1];
            rArgs[0] = method;
                errMsg = MessageFormat.format(errMsg, errArgs);
                
                resp.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED, errMsg);
            }
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58

    各种 doXxx 方法会流转到,以下以 doGet 为例子,其它类似。特殊的请求方法有特殊处理,我们不用管。

    	@Override
    	protected final void doGet(HttpServletRequest request, HttpServletResponse response)
    			throws ServletException, IOException {
    
    		processRequest(request, response);
    	}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    继续看 processRequest 方法

    1.1. processRequest 方法

    	protected final void processRequest(HttpServletRequest request, HttpServletResponse response)
    			throws ServletException, IOException {
    
    		long startTime = System.currentTimeMillis();
    		Throwable failureCause = null;
    
            // <1> 
    		LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();
    		LocaleContext localeContext = buildLocaleContext(request);
    
            // <2> 
    		RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes();
    		ServletRequestAttributes requestAttributes = buildRequestAttributes(request, response, previousAttributes);
    
            // <3> 
    		WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
    		asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(), new RequestBindingInterceptor());
    
    
            // <4> 
    		initContextHolders(request, localeContext, requestAttributes);
    
    		try {
                // <5> 
    			doService(request, response);
    		}
    		catch (ServletException | IOException ex) {
    			failureCause = ex;
    			throw ex;
    		}
    		catch (Throwable ex) {
    			failureCause = ex;
    			throw new NestedServletException("Request processing failed", ex);
    		}
                
        	// <6> 
    		finally {
    			resetContextHolders(request, previousLocaleContext, previousAttributes);
    			if (requestAttributes != null) {
    				requestAttributes.requestCompleted();
    			}
    			logResult(request, response, failureCause, asyncManager);
    			publishRequestHandledEvent(request, response, startTime, failureCause);
    		}
    	}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 在<1>处,获取之前的语言上下文。然后构建本次的语言上下文。LocaleContext

    语言上下文是可以继承的。把之前的保存下来,在请求处理完之后要“还原”。

    • 在<2>处,获取之前的“请求属性上下文”RequestAttributes,具体实现类是:ServletRequestAttributes

    这个上下文不能继承,必须是当次请求的。

    • 在<3>处,获取“Web 的异步管理器”

    此处与异步请求相关:RequestBindingInterceptor

    • 在<4>处,绑定 2 个上下文。把 LocaleContext 绑定到LocaleContextHolder中,把 ServletRequestAttributes 绑定到RequestContextHolder

    这个上下文非常有用,Web 的很多工具类使用到这个上下文来简化编程。不需要传参数request,直接在任意位置获取request。

    • 在<5>处,具体如何处理请求。后续会详细分析
    • 在<6>处,清尾操作。

    1、把之前的2个上下文还原。
    2、记录请求的日志(此处是否就是框架提供的打印响应日志的地方?)
    3、发布事件(请求处理完成事件ServletRequestHandledEvent)

    1.2. doService 方法

    	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);
    		}
    
    		RequestPath previousRequestPath = null;
    		if (this.parseRequestPath) {
    			previousRequestPath = (RequestPath) request.getAttribute(ServletRequestPathUtils.PATH_ATTRIBUTE);
    			ServletRequestPathUtils.parseAndCache(request);
    		}
    
    		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);
    				}
    			}
    			if (this.parseRequestPath) {
    				ServletRequestPathUtils.setParsedRequestPath(previousRequestPath, request);
    			}
    		}
    	}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53

    不详细解释了,主要是把 Spring 框架的一些组件放入到request的请求属性中。在后续可以根据 request 拿出 Spring 框架的组件。比如设置了如下的内容:

    • Web 的上下文
    • 本地语言解析器
    • 主题解析器
  • 相关阅读:
    汽车驾驶 - 四梁六柱是什么
    【Python机器学习】利用专家知识
    读高性能MySQL(第4版)笔记20_Performance Schema和其他
    C#--sugarClient使用之ColumnName
    3分钟搞懂oled透明触摸显示屏
    Day15--加入购物车-初始化vuex
    机器学习文本分类
    如何实现云上 Lakehouse 高性能
    RK3568驱动指南|第七期-设备树-第58章 实例分析:时钟
    JDBC数据库事务
  • 原文地址:https://blog.csdn.net/yuchangyuan5237/article/details/133527232