Spring Boot2 —— 请求映射源码详细分析(附源码)

17 篇文章 15 订阅
17 篇文章 0 订阅

Spring Boot参数请求映射源码详细分析(附源码)

一、Rest映射以及原理分析

1、Rest使用与原理

(1) @xxxMapping;

(2) Rest风格支持(使用HTTP请求方式动词来表示对资源的操作)

  • 以前:/getUser 获取用户 /deleteUser 删除用户 /editUser 修改用户 /saveUser 保存用户
  • 现在: /user GET-获取用户 DELETE-删除用户 PUT-修改用户 POST-保存用户
    核心Filter;HiddenHttpMethodFilter
    用法: 表单method=post,隐藏域 _method=put
  • SpringBoot在配置文件中开启
    @RequestMapping(value = "/user",method = RequestMethod.GET)
    public String getUser(){
        return "GET-张三";
    }

    @RequestMapping(value = "/user",method = RequestMethod.POST)
    public String saveUser(){
        return "POST-张三";
    }


    @RequestMapping(value = "/user",method = RequestMethod.PUT)
    public String putUser(){
        return "PUT-张三";
    }

    @RequestMapping(value = "/user",method = RequestMethod.DELETE)
    public String deleteUser(){
        return "DELETE-张三";
    }

我们都知道,form表单只能发get/post两种请求,那么springboot底层是如果实现delete和put请求呢,那就是增加隐藏域:

<form action="/user" method="get">
    <input value="REST-GET 提交" type="submit"/>
</form>
<form action="/user" method="post">
    <input value="REST-POST 提交" type="submit"/>
</form>
<form action="/user" method="post">
    <!--增加隐藏域-->
    <input name="_method" type="hidden" value="delete"/>
    <input value="REST-DELETE 提交" type="submit"/>
</form>
<form action="/user" method="post">
    <input name="_method" type="hidden" value="PUT"/>
    <input value="REST-PUT 提交" type="submit"/>
</form>
2、Rest原理

Rest原理(表单提交要使用REST的时候)

(1)表单提交会带上_method=PUT

(2)请求过来被HiddenHttpMethodFilter拦截,请求是否正常,并且是POST

  • 获取到_method的值。
  • 兼容以下请求;PUT.DELETE.PATCH
  • 原生request(post),包装模式requesWrapper重写了getMethod方法,返回的是传入的值。
  • 过滤器链放行的时候用wrapper。以后的方法调用getMethod是调用requesWrapper的
3、底层源码解析

那么底层源码如何解析的?

总体流程图:
在这里插入图片描述

SpringMVC的大都数功能都集中在SpringBoot的WebMvcAutoConfiguration类中,所以直接来到这个类看;
(1)WebMvcAutoConfiguration类中有这个方法
在这里插入图片描述在这里插入图片描述
doFilterInternal()方法对请求方法的处理,首先必须是Post方法的,并且带有name为“_method”的,获取值,如果长度不为0,说明是隐藏域的重写方法,那么就将其方法名(delete或者put)转为大写,并通过HttpMethodRequestWrapper(包装模式),再包装成(delete或者put)方法,然后返回控制器,以重包装的方法名,进行进一步的操作。在这里插入图片描述
可以看到上图中ALLOWED_METHODS中允许的方法如下:

static {
        ALLOWED_METHODS = Collections.unmodifiableList(Arrays.asList(HttpMethod.PUT.name(), HttpMethod.DELETE.name(), HttpMethod.PATCH.name()));
    }

Springboot项目启动时,就帮我们配置好了这个功能,只是默认不开启,下面第一张图中,matchIfMissing=false,若要开启,在配置文件中配置就好

spring:
  mvc:
    hiddenmethod:
      filter:
        enabled: true   #开启页面表单的Rest功能

(2)HttpMethodRequestWrapper
上面说了,最终的请求会经过 HttpMethodRequestWrapper的包装,在返回到控制器中继续流程,那就来看看HttpMethodRequestWrapper这个类。
在这里插入图片描述在这里插入图片描述
(4)Rest使用客户端工具

如PostMan直接发送Put、delete等方式请求,无需Filter。
需要注意的是:现在都是前后端分离,很少这样使用了。

二、请求映射以及原理

1、Spring MVC请求解析流程图

在这里插入图片描述
在这里插入图片描述

2、源码分析

SpringMVC功能分析都从 org.springframework.web.servlet.DispatcherServlet ->doDispatch()开始分析:
图 2-1 Dispatcher继承关系图图 2-1 Dispatcher继承关系图

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
		HttpServletRequest processedRequest = request;
		HandlerExecutionChain mappedHandler = null;
		boolean multipartRequestParsed = false;

		WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);

		try {
			ModelAndView mv = null;
			Exception dispatchException = null;

			try {
				processedRequest = checkMultipart(request);
				multipartRequestParsed = (processedRequest != request);

				// 找到当前请求使用哪个Handler(Controller的方法)处理
				mappedHandler = getHandler(processedRequest);
                
                //HandlerMapping:处理器映射。/xxx->>xxxx

在这里插入图片描述
从此图可以看出继承树,最终是来到HttpServlet的,也就是说必然会有doGetPost方法。而HttpServlet并没有,于是顺着关系找下去。

在FrameworkServlet中,我们发现了重写了doGet/doPost的方法:
在这里插入图片描述
而doGet/doPost两个方法都是调用processRequest的,进去看一眼,除了一些必要的初始化,最核心的就是doService方法了。
在这里插入图片描述
而FrameworkServlet中doService是抽象的,那么再起子类必有实现,那么来到DispatcherServlet中找到此方法的实现:

protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
        this.logRequest(request);
        Map<String, Object> attributesSnapshot = null;
        if (WebUtils.isIncludeRequest(request)) {
            attributesSnapshot = new HashMap();
            Enumeration attrNames = request.getAttributeNames();

            label95:
            while(true) {
                String attrName;
                do {
                    if (!attrNames.hasMoreElements()) {
                        break label95;
                    }

                    attrName = (String)attrNames.nextElement();
                } while(!this.cleanupAfterInclude && !attrName.startsWith("org.springframework.web.servlet"));

                attributesSnapshot.put(attrName, request.getAttribute(attrName));
            }
        }

        request.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.getWebApplicationContext());
        request.setAttribute(LOCALE_RESOLVER_ATTRIBUTE, this.localeResolver);
        request.setAttribute(THEME_RESOLVER_ATTRIBUTE, this.themeResolver);
        request.setAttribute(THEME_SOURCE_ATTRIBUTE, this.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 {
            this.doDispatch(request, response);
        } finally {
            if (!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted() && attributesSnapshot != null) {
                this.restoreAttributesAfterInclude(request, attributesSnapshot);
            }

        }

    }

在进行一大堆初始化之后,最核心的方法就是doDispatch(request, response),将请求进行转发,这样就意味着,每个请求的方法进来,都要经过这个方法,所以,SpringMVC功能分析都从 org.springframework.web.servlet.DispatcherServlet-》doDispatch()开始分析进去这个方法看一下:

 protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
        HttpServletRequest processedRequest = request;
        HandlerExecutionChain mappedHandler = null;
        boolean multipartRequestParsed = false;
        WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);

        try {
            try {
                ModelAndView mv = null;
                Object dispatchException = null;

                try {
                    processedRequest = this.checkMultipart(request);
                    multipartRequestParsed = processedRequest != request;
                    
                    // 找到当前请求使用哪个Handler(Controller的方法)处理
                    mappedHandler = this.getHandler(processedRequest);
                    if (mappedHandler == null) {
                        this.noHandlerFound(processedRequest, response);
                        return;
                    }

                    HandlerAdapter ha = this.getHandlerAdapter(mappedHandler.getHandler());
                    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());
                    if (asyncManager.isConcurrentHandlingStarted()) {
                        return;
                    }

                    this.applyDefaultViewName(processedRequest, mv);
                    mappedHandler.applyPostHandle(processedRequest, response, mv);
                } catch (Exception var20) {
                    dispatchException = var20;
                } catch (Throwable var21) {
                    dispatchException = new NestedServletException("Handler dispatch failed", var21);
                }

                this.processDispatchResult(processedRequest, response, mappedHandler, mv, (Exception)dispatchException);
            } catch (Exception var22) {
                this.triggerAfterCompletion(processedRequest, response, mappedHandler, var22);
            } catch (Throwable var23) {
                this.triggerAfterCompletion(processedRequest, response, mappedHandler, new NestedServletException("Handler processing failed", var23));
            }

        } finally {
            if (asyncManager.isConcurrentHandlingStarted()) {
                if (mappedHandler != null) {
                    mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
                }
            } else if (multipartRequestParsed) {
                this.cleanupMultipart(processedRequest);
            }

        }
    }

如何找到url对应的handler(controller)呢?就是下面这句:

  // 找到当前请求使用哪个Handler(Controller的方法)处理
  mappedHandler = this.getHandler(processedRequest);

进入getHandler方法中发现其调用了getHandlerInternal(request)方法,进行处理后获得url又调用了lookupHandlerMethod我们查看这个方法,这个方法最后找到handler返回:
在这里插入图片描述

 @Nullable
    public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
        Object handler = this.getHandlerInternal(request);
        if (handler == null) {
            handler = this.getDefaultHandler();
        }

        if (handler == null) {
            return null;
        } else {
            if (handler instanceof String) {
                String handlerName = (String)handler;
                handler = this.obtainApplicationContext().getBean(handlerName);
            }

            HandlerExecutionChain executionChain = this.getHandlerExecutionChain(handler, request);
            if (this.logger.isTraceEnabled()) {
                this.logger.trace("Mapped to " + handler);
            } else if (this.logger.isDebugEnabled() && !request.getDispatcherType().equals(DispatcherType.ASYNC)) {
                this.logger.debug("Mapped to " + executionChain.getHandler());
            }

            if (this.hasCorsConfigurationSource(handler) || CorsUtils.isPreFlightRequest(request)) {
                CorsConfiguration config = this.corsConfigurationSource != null ? this.corsConfigurationSource.getCorsConfiguration(request) : null;
                CorsConfiguration handlerConfig = this.getCorsConfiguration(handler, request);
                config = config != null ? config.combine(handlerConfig) : handlerConfig;
                executionChain = this.getCorsHandlerExecutionChain(request, executionChain, config);
            }

            return executionChain;
        }
    }

Debug到mapping.getHandler(request)时,发现调的是getHandlerInternal(request)方法,进行处理后获得url又调用了lookupHandlerMethod我们查看这个方法,这个方法最后找到handler返回。
在这里插入图片描述

@Nullable
    protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception {
        List<AbstractHandlerMethodMapping<T>.Match> matches = new ArrayList();
        List<T> directPathMatches = this.mappingRegistry.getMappingsByUrl(lookupPath);
        if (directPathMatches != null) {
            this.addMatchingMappings(directPathMatches, matches, request);
        }

        if (matches.isEmpty()) {
            this.addMatchingMappings(this.mappingRegistry.getMappings().keySet(), matches, request);
        }

        if (!matches.isEmpty()) {
            AbstractHandlerMethodMapping<T>.Match bestMatch = (AbstractHandlerMethodMapping.Match)matches.get(0);
            if (matches.size() > 1) {
                Comparator<AbstractHandlerMethodMapping<T>.Match> comparator = new AbstractHandlerMethodMapping.MatchComparator(this.getMappingComparator(request));
                matches.sort(comparator);
                bestMatch = (AbstractHandlerMethodMapping.Match)matches.get(0);
                if (this.logger.isTraceEnabled()) {
                    this.logger.trace(matches.size() + " matching mappings: " + matches);
                }

                if (CorsUtils.isPreFlightRequest(request)) {
                    return PREFLIGHT_AMBIGUOUS_MATCH;
                }

                AbstractHandlerMethodMapping<T>.Match secondBestMatch = (AbstractHandlerMethodMapping.Match)matches.get(1);
                if (comparator.compare(bestMatch, secondBestMatch) == 0) {
                    Method m1 = bestMatch.handlerMethod.getMethod();
                    Method m2 = secondBestMatch.handlerMethod.getMethod();
                    String uri = request.getRequestURI();
                    throw new IllegalStateException("Ambiguous handler methods mapped for '" + uri + "': {" + m1 + ", " + m2 + "}");
                }
            }

            request.setAttribute(BEST_MATCHING_HANDLER_ATTRIBUTE, bestMatch.handlerMethod);
            this.handleMatch(bestMatch.mapping, lookupPath, request);
            return bestMatch.handlerMethod;
        } else {
            return this.handleNoMatch(this.mappingRegistry.getMappings().keySet(), lookupPath, request);
        }
    }

本项目中,debug发现,handlerMapping中会有五个值,分别代表着5种请求对应的Handler。
在这里插入图片描述

而RequestMappingHandlerMapping:保存了所有@RequestMapping 和handler的映射规则:
在这里插入图片描述

3、静态资源匹配源码分析

我们在来看静态资源匹配(/**/webjar匹配),他是和上面不同的mapping,它是SimpleUrlHandlerMapping,会调用AbstractUrlHandlerMappinggetHandlerInternal()方法,调用lookupHandler()方法找到handler返回。

@Nullable
    protected Object lookupHandler(String urlPath, HttpServletRequest request) throws Exception {
        Object handler = this.handlerMap.get(urlPath);
        if (handler != null) {
            if (handler instanceof String) {
                String handlerName = (String)handler;
                handler = this.obtainApplicationContext().getBean(handlerName);
            }

            this.validateHandler(handler, request);
            return this.buildPathExposingHandler(handler, urlPath, urlPath, (Map)null);
        } else {
            List<String> matchingPatterns = new ArrayList();
            Iterator var5 = this.handlerMap.keySet().iterator();

            while(var5.hasNext()) {
                String registeredPattern = (String)var5.next();
                if (this.getPathMatcher().match(registeredPattern, urlPath)) {
                    matchingPatterns.add(registeredPattern);
                } else if (this.useTrailingSlashMatch() && !registeredPattern.endsWith("/") && this.getPathMatcher().match(registeredPattern + "/", urlPath)) {
                    matchingPatterns.add(registeredPattern + "/");
                }
            }

            String bestMatch = null;
            Comparator<String> patternComparator = this.getPathMatcher().getPatternComparator(urlPath);
            if (!matchingPatterns.isEmpty()) {
                matchingPatterns.sort(patternComparator);
                if (this.logger.isTraceEnabled() && matchingPatterns.size() > 1) {
                    this.logger.trace("Matching patterns " + matchingPatterns);
                }

                bestMatch = (String)matchingPatterns.get(0);
            }

            if (bestMatch != null) {
                handler = this.handlerMap.get(bestMatch);
                if (handler == null) {
                    if (bestMatch.endsWith("/")) {
                        handler = this.handlerMap.get(bestMatch.substring(0, bestMatch.length() - 1));
                    }

                    if (handler == null) {
                        throw new IllegalStateException("Could not find handler for best pattern match [" + bestMatch + "]");
                    }
                }

                String pathWithinMapping;
                if (handler instanceof String) {
                    pathWithinMapping = (String)handler;
                    handler = this.obtainApplicationContext().getBean(pathWithinMapping);
                }

                this.validateHandler(handler, request);
                pathWithinMapping = this.getPathMatcher().extractPathWithinPattern(bestMatch, urlPath);
                Map<String, String> uriTemplateVariables = new LinkedHashMap();
                Iterator var9 = matchingPatterns.iterator();

                while(var9.hasNext()) {
                    String matchingPattern = (String)var9.next();
                    if (patternComparator.compare(bestMatch, matchingPattern) == 0) {
                        Map<String, String> vars = this.getPathMatcher().extractUriTemplateVariables(matchingPattern, urlPath);
                        Map<String, String> decodedVars = this.getUrlPathHelper().decodePathVariables(request, vars);
                        uriTemplateVariables.putAll(decodedVars);
                    }
                }

                if (this.logger.isTraceEnabled() && uriTemplateVariables.size() > 0) {
                    this.logger.trace("URI variables " + uriTemplateVariables);
                }

                return this.buildPathExposingHandler(handler, bestMatch, pathWithinMapping, uriTemplateVariables);
            } else {
                return null;
            }
        }
    }
4、欢迎页源码分析

(1)SpringBoot自动配置欢迎页的 WelcomePageHandlerMapping ;访问 localhost:port/能访问到index.html;
(2)SpringBoot自动配置了默认的 RequestMappingHandlerMapping
(3) 请求进来,挨个尝试所有的HandlerMapping看是否有请求信息。

  • 如果有就找到这个请求对应的handler
  • 如果没有就是下一个 HandlerMapping

如果你啥也没传,也就是"/",那么在RequestMappingHandlerMapping中将不会找到合适的,然后他就会循环到下一个控制器:WelcomePageHandlerMapping:会调用AbstractUrlHandlerMappinggetHandlerInternal()方法,调用lookupHandler获得handler返回空(这里匹配上面那种),继续执行

protected Object getHandlerInternal(HttpServletRequest request) throws Exception {
        String lookupPath = getUrlPathHelper().getLookupPathForRequest(request);
        request.setAttribute(LOOKUP_PATH, lookupPath);
        Object handler = lookupHandler(lookupPath, request);//这里返回为空
        if (handler == null) {
            // We need to care for the default handler directly, since we need to
            // expose the PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE for it as well.
            Object rawHandler = null;
            //这里可以匹配到 / 
            if ("/".equals(lookupPath)) { 
                rawHandler = getRootHandler();
            }
            if (rawHandler == null) {
                rawHandler = getDefaultHandler();
            }
            if (rawHandler != null) {
                // Bean name or resolved handler?
                if (rawHandler instanceof String) {
                    String handlerName = (String) rawHandler;
                    rawHandler = obtainApplicationContext().getBean(handlerName);
                }
                validateHandler(rawHandler, request);
                handler = buildPathExposingHandler(rawHandler, lookupPath, lookupPath, null);
            }
        }
        return handler;
    }

而这个控制器就是专门处理"/"的,于是根据处理,转发到index.html中。
(SpringBoot自动配置欢迎页的 WelcomePageHandlerMapping ,访问 /能访问到index.html)
在这里插入图片描述

我们需要一些自定义的映射处理,我们也可以自己给容器中放HandlerMapping。自定义 HandlerMapping

	protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
		if (this.handlerMappings != null) {
			for (HandlerMapping mapping : this.handlerMappings) {
				HandlerExecutionChain handler = mapping.getHandler(request);
				if (handler != null) {
					return handler;
				}
			}
		}
		return null;
	}

三、Spring Boot请求参数解析

上节,我们只分析了doDispatch中的getHandler方法,本节继续向下看getHandlerAdapter方法和handle方法
在这里插入图片描述

1、getHandlerAdapter()

有没有想过,为何我们加了那些注解,例如@PathVariable,为什么springmvc就能将其变量确定为对应的值呢?这就是HandlerAdapter的作用,在getHandler方法确定好控制器和对应的方法后(执行链),getHandlerAdapter就会来帮我们为当前的handler找一个adapter然后我们通过该适配器,就能够将请求的链接所带的参数给适配上。
在这里插入图片描述
看一下DispatcherServlet的doService方法时序图:
在这里插入图片描述
直接进入getHandlerAdapter方法查看,debug -getHandlerAdapter方法,可以看到,会在原生的4种handlerAdapter中选择一个匹配的适配器进行返回。获取代码:

protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
        if (this.handlerAdapters != null) {
            Iterator var2 = this.handlerAdapters.iterator();

            while(var2.hasNext()) {
                HandlerAdapter adapter = (HandlerAdapter)var2.next();
                if (adapter.supports(handler)) {
                    return adapter;
                }
            }
        }

        throw new ServletException("No adapter for handler [" + handler + "]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler");
    }

在这里插入图片描述
对应的处理如下:

Handler类别对应适配器描述
ControllerSimpleControllerHandlerAdapter标准控制器,返回ModelAndView
HttpRequestHandlerHttpRequestHandlerAdapter业务自行处理请求,不需要通过ModelAndView,转到视图
ServletSimpleServletHandlerAdapter基于标准的Servlet处理
HandlerMethodRequestMappingHandlerAdapter@基于RequestMapping对应方法处理

注意:如果自己添加了Adapter就不会在加载springMVC默认的这些Adapter

getHandlerAdapter()里调用了adapter.supports(handler)

  • 通过supports方法来确定adapter,我们进入supports方法,发现不同的adapter有不同的判断方法,我们还是先以requestMapping请求的到的handler为例
  • 可以发现他的判断方式很简单,就是判断handler是不是一个HandlerMethod(在上面匹配的时候会根据不同的情况获得不同的handler)
public class AbstractHandlerMethodAdapter{
    public final boolean supports(Object handler) {
        return (handler instanceof HandlerMethod && supportsInternal((HandlerMethod) handler));
    }
}

2、handler

(1)执行

mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

(2)来到

在这里插入图片描述
进一步,进入到该抽象类的实现类RequestMappingHandlerAdapter中(为何是RequestMappingHandlerAdapter),因为我的方法使用了@RequestMapping,所以就返回这个Adapter),对一个请求方法的所有操作都会在这里进行。RequestMappingHandlerAdapter 部分源码如下:可以看到,handleInternal执行后,会返回一个ModelAndView

 protected ModelAndView handleInternal(HttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
        this.checkRequest(request);
        ModelAndView mav;
        if (this.synchronizeOnSession) {
            HttpSession session = request.getSession(false);
            if (session != null) {
                Object mutex = WebUtils.getSessionMutex(session);
                synchronized(mutex) {
                    mav = this.invokeHandlerMethod(request, response, handlerMethod);
                }
            } else {
                mav = this.invokeHandlerMethod(request, response, handlerMethod);
            }
        } else {
            //最终来到invokeHandlerMethod这里,才是真正的执行handler方法
            mav = this.invokeHandlerMethod(request, response, handlerMethod);
        }

        if (!response.containsHeader("Cache-Control")) {
            if (this.getSessionAttributesHandler(handlerMethod).hasSessionAttributes()) {
                this.applyCacheSeconds(response, this.cacheSecondsForSessionAttributeHandlers);
            } else {
                this.prepareResponse(response);
            }
        }
        return mav;
    }

invokeHandlerMethod方法源码:

@Nullable
    protected ModelAndView invokeHandlerMethod(HttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
        ServletWebRequest webRequest = new ServletWebRequest(request, response);
        Object result;
        try {
            WebDataBinderFactory binderFactory = this.getDataBinderFactory(handlerMethod);
            ModelFactory modelFactory = this.getModelFactory(handlerMethod, binderFactory);
            ServletInvocableHandlerMethod invocableMethod = this.createInvocableHandlerMethod(handlerMethod);
            if (this.argumentResolvers != null) {
                invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
            }

            if (this.returnValueHandlers != null) {
                invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);
            }

            invocableMethod.setDataBinderFactory(binderFactory);
            invocableMethod.setParameterNameDiscoverer(this.parameterNameDiscoverer);
            ModelAndViewContainer mavContainer = new ModelAndViewContainer();
            mavContainer.addAllAttributes(RequestContextUtils.getInputFlashMap(request));
            modelFactory.initModel(webRequest, mavContainer, invocableMethod);
            mavContainer.setIgnoreDefaultModelOnRedirect(this.ignoreDefaultModelOnRedirect);
            AsyncWebRequest asyncWebRequest = WebAsyncUtils.createAsyncWebRequest(request, response);
            asyncWebRequest.setTimeout(this.asyncRequestTimeout);
            WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
            asyncManager.setTaskExecutor(this.taskExecutor);
            asyncManager.setAsyncWebRequest(asyncWebRequest);
            asyncManager.registerCallableInterceptors(this.callableInterceptors);
            asyncManager.registerDeferredResultInterceptors(this.deferredResultInterceptors);
            if (asyncManager.hasConcurrentResult()) {
                result = asyncManager.getConcurrentResult();
                mavContainer = (ModelAndViewContainer)asyncManager.getConcurrentResultContext()[0];
                asyncManager.clearConcurrentResult();
                LogFormatUtils.traceDebug(this.logger, (traceOn) -> {
                    String formatted = LogFormatUtils.formatValue(result, !traceOn);
                    return "Resume with async result [" + formatted + "]";
                });
                invocableMethod = invocableMethod.wrapConcurrentResult(result);
            }

            invocableMethod.invokeAndHandle(webRequest, mavContainer, new Object[0]);
            if (!asyncManager.isConcurrentHandlingStarted()) {
                ModelAndView var15 = this.getModelAndView(mavContainer, modelFactory, webRequest);
                return var15;
            }

            result = null;
        } finally {
            webRequest.requestCompleted();
        }

        return (ModelAndView)result;
    }

其中有两个变量值得我们研究:argumentResolvers(参数解析器 26种)和returnValueHandlers(返回值处理器 15种),这两个东西就是这篇文章的主题:参数解析的核心

(3) 参数解析器

返回值解析器(ArgumentResolvers),其底层是一个接口。HandlerMethodArgumentResolver是其父接口

  public interface HandlerMethodArgumentResolver {
            /** * 解析器是否支持当前参数 * * @param var1 需要被解析的Controller参数 * @return */
            boolean supportsParameter(MethodParameter var1);
            // 将request中的请求参数解析到当前Controller参数上,在这里进行类型转换 
            Object resolveArgument(MethodParameter var1, ModelAndViewContainer var2, NativeWebRequest var3, WebDataBinderFactory var4) throws Exception;
        }

该接口作用:当前解析器是否支持解析这种参数,支持就调用 resolveArgument解析最终确定将要执行的目标方法的每一个参数的值是什么SpringMVC目标方法能写多少种参数类型。取决于参数解析器,默认26种:
在这里插入图片描述

(4) 返回值解析器

决定了目标方法到底能写多少种类型的返回值,默认15种
在这里插入图片描述
有一个值得注意的处理器就是RequestResponseBodyMethodProcessor,就是我们使用@ResponseBody时,使用的处理器,底层如下:
在这里插入图片描述
在将参数解析器和返回值处理器设置好后,进一步调用了invokeAndHandle方法,跟踪该方法,我们来到:ServletInvocableHandlerMethod类中的invokeAndHandle方法
部分源码:

public class ServletInvocableHandlerMethod extends InvocableHandlerMethod {
   public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception {
        Object returnValue = this.invokeForRequest(webRequest, mavContainer, providedArgs);
        this.setResponseStatus(webRequest);
        if (returnValue == null) {
            if (this.isRequestNotModified(webRequest) || this.getResponseStatus() != null || mavContainer.isRequestHandled()) {
                this.disableContentCachingIfNecessary(webRequest);
                mavContainer.setRequestHandled(true);
                return;
            }
        } else if (StringUtils.hasText(this.getResponseStatusReason())) {
            mavContainer.setRequestHandled(true);
            return;
        }
 }

跟踪invokeForRequest,来到InvocableHandlerMethod类, invokeForRequestgetMethodArgumentValues(开始解析参数了)源码

public class InvocableHandlerMethod extends HandlerMethod {
  @Nullable
    public Object invokeForRequest(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception {
        Object[] args = this.getMethodArgumentValues(request, mavContainer, providedArgs);
        if (logger.isTraceEnabled()) {
            logger.trace("Arguments: " + Arrays.toString(args));
        }

        return this.doInvoke(args);
    }


    //核心方法,获取参数值最底层的方法
    protected Object[] getMethodArgumentValues(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception {
        //获取到方法的所有参数声明(例如注解,索引,类型)
        MethodParameter[] parameters = this.getMethodParameters();
        //判断参数是否为空,为空直接返回,无须确定任何值
        if (ObjectUtils.isEmpty(parameters)) {
            return EMPTY_ARGS;
        } else {
            Object[] args = new Object[parameters.length];
            //挨个遍历参数取值
            for(int i = 0; i < parameters.length; ++i) {
                MethodParameter parameter = parameters[i];
                //确定参数名字
               parameter.initParameterNameDiscovery(this.parameterNameDiscoverer);
                args[i] = findProvidedArgument(parameter, providedArgs);
                if (args[i] == null) {
                //先判断当前解析器是否支持这种类型,不支持便对解析器遍历,直到找到支持的解析器
                //具体调用链supportsParameter->HandlerMethodArgumentResolverComposite.getArgumentResolver-> 
                    if (!this.resolvers.supportsParameter(parameter)) {
                        throw new IllegalStateException(formatArgumentError(parameter, "No suitable resolver"));
                    }

                    try {
                    //真正的核心
                        args[i] = this.resolvers.resolveArgument(parameter, mavContainer, request, this.dataBinderFactory);
                    } catch (Exception var10) {
                        if (logger.isDebugEnabled()) {
                            String exMsg = var10.getMessage();
                            if (exMsg != null && !exMsg.contains(parameter.getExecutable().toGenericString())) {
                                logger.debug(formatArgumentError(parameter, exMsg));
                            }
                        }

                        throw var10;
                    }
                }
            }

            return args;
        }
    }
}

获取到的参数声明:
在这里插入图片描述
HandlerMethodArgumentResolverComposite.getArgumentResolver
源码:
这里可以看到对我们上面提到的那26种解析器的遍历,最后会完全缓存在springboot的本地缓存中
在这里插入图片描述
拿到参数解析器后,我们就可以来获取参数的值了
HandlerMethodArgumentResolverComposite.ArgumentResolver
源码:

@Nullable
    public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer, NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
        HandlerMethodArgumentResolver resolver = this.getArgumentResolver(parameter);
        if (resolver == null) {
            throw new IllegalArgumentException("Unsupported parameter type [" + parameter.getParameterType().getName() + "]. supportsParameter should be called first.");
        } else {
            //获取参数值并返回
            return resolver.resolveArgument(parameter, mavContainer, webRequest, binderFactory);
        }
    }

resolveArgument最终会调用AbstractNamedValueMethodArgumentResolver的各种实现类如下:
在这里插入图片描述
再配合UrlPathHelper(会将url中的变量解析出来,放在request的请求域中),最终得到变量值。

3、Servlet API参数处理
 @GetMapping("/goto")
    public String goToPage(HttpServletRequest request){

        request.setAttribute("msg","成功了...");
        request.setAttribute("code",200);
        return "forward:/success";  //转发到  /success请求
    }

WebRequest、ServletRequest、MultipartRequest、 HttpSession、javax.servlet.http.PushBuilder、Principal、InputStream、Reader、HttpMethod、Locale、TimeZone、ZoneId这些都能找到对应的resolver进行解析。以ServletRequestMethodArgumentResolver为例,它能解析以下的参数,总之,就是进行到resolvers.supportsParameter(parameter)这个方法后,遍历那26个参数解析器,拿到对应的解析器去解析就好了,原理都是一样的。

@Override
    public boolean supportsParameter(MethodParameter parameter) {
        Class<?> paramType = parameter.getParameterType();
        return (WebRequest.class.isAssignableFrom(paramType) ||
                ServletRequest.class.isAssignableFrom(paramType) ||
                MultipartRequest.class.isAssignableFrom(paramType) ||
                HttpSession.class.isAssignableFrom(paramType) ||
                (pushBuilder != null && pushBuilder.isAssignableFrom(paramType)) ||
                Principal.class.isAssignableFrom(paramType) ||
                InputStream.class.isAssignableFrom(paramType) ||
                Reader.class.isAssignableFrom(paramType) ||
                HttpMethod.class == paramType ||
                Locale.class == paramType ||
                TimeZone.class == paramType ||
                ZoneId.class == paramType);
    }

4、复杂参数的处理

Map、Model(map、model里面的数据会被放在request的请求域 request.setAttribute)、Errors/BindingResultRedirectAttributes( 重定向携带数据)、ServletResponse(response)SessionStatusUriComponentsBuilderServletUriComponentsBuilder

接下里进行断点调试:

http://localhost:8080/params

在这里插入图片描述
首先,解析参数都是跟之前调试的步骤是一样的,就是进行到resolvers.supportsParameter(parameter)这个方法后,遍历那26个参数解析器,拿到对应的解析器去解析,而对于map这类型的数据来说,就是对应的MapMethodProcessor。
在这里插入图片描述
拿到解析器后,就得找对应的参数进行影射了,对于上面的使用@requestmapping注解的方法,它会去url缓存中获取参数值,那么map类型的呢?debug后我们发现,它来到了mavContainer.getModel()这个方法,准备获取模型数据。

@Nullable
    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();
    }

而getModel()这个方法他会返回一个ModeMap类型的数据,源码如下:
在这里插入图片描述
最终,他是返回一个ModelMap的子类BindingAwareModelMap,BindingAwareModelMap 是Model 也是Map
在这里插入图片描述
继承树如下:
在这里插入图片描述
Model参数类型就调用另一个解析器:
在这里插入图片描述
debug后发现,居然跟解析Map类型调用的是一样的方法,也是来到了mavContainer.getModel()这个方法,准备获取模型数据。我们可以发现,两者返回的是同一个BindingAwareModelMap。同时,直接放心让request,和response对象也解析好。
在这里插入图片描述
然后我们放行方法,执行完invokeForRequest方法,此时,我们知道,对于请求的处理已经完成了,接下来就是视图解析了,这里先不讨论视图解析的流程,就研究forward的时候,spring是如何将数据(model)放在请求域中给转发出去的。
在这里插入图片描述
跟踪进去,我们发现在处理返回结果的时候,也把mavContainer传进去了:
在这里插入图片描述
mavContainer此时如下:
在这里插入图片描述
handleReturnValue方法:

public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {
        HandlerMethodReturnValueHandler handler = this.selectHandler(returnValue, returnType);
        if (handler == null) {
            throw new IllegalArgumentException("Unknown return value type: " + returnType.getParameterType().getName());
        } else {
            handler.handleReturnValue(returnValue, returnType, mavContainer, webRequest);
        }
    }

如果你放回的类型是个字符串,就把字符串设置成viewName

public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {
        if (returnValue instanceof CharSequence) {
            String viewName = returnValue.toString();
            mavContainer.setViewName(viewName);
            if (this.isRedirectViewName(viewName)) {
                mavContainer.setRedirectModelScenario(true);
            }
        } else if (returnValue != null) {
            throw new UnsupportedOperationException("Unexpected return type: " + returnType.getParameterType().getName() + " in method: " + returnType.getMethod());
        }

    }

此时的mavContainer(view已经为“”forward:/success)
在这里插入图片描述

至此可以得出一个结论:方法执行完成后,springmvc会所有的数据都放在 ModelAndViewContainer;包含要去的页面地址View。还包含Model数据。然后进一步对这些数据进行处理(渲染),会执行以下:

return getModelAndVieww(mavContainer,modelFactory,webRequest);

继续跟踪
在这里插入图片描述
仍然可以看到,还是围绕着处理mavContainer展开,ModelFactory里有一个
updateBindingResult方法,这是关键,它会遍历所有model的值,并根据绑定策略对数据进行封装
在这里插入图片描述
然后在执行:ModelAndView mav=new ModelAndView(....);这一句,即把遍历到的model数据生成一个ModelAndView。然后再根据是不是重定向,转发,或者普通处理,再进一步对数据进行处理

此时,DispatchServlet的:

mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

执行完成,开始执行DispatchServlet的另一个方法:

 //完成业务处理后的后置处理
 mappedHandler.applyPostHandle(processedRequest, response, mv);

涉及两个主要方法:

//处理派发结果
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
//渲染合并输出模型(最关键的核心)
renderMergedOutputModel(mergedModel, getRequestToExpose(request), response);

@Override
    protected void renderMergedOutputModel(
            Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception {
        // Expose the model object as request attributes.
        exposeModelAsRequestAttributes(model, request);
        // Expose helpers as request attributes, if any.
        exposeHelpers(request);
        // Determine the path for the request dispatcher.
        String dispatcherPath = prepareForRendering(request, response);
        // Obtain a RequestDispatcher for the target resource (typically a JSP).
        RequestDispatcher rd = getRequestDispatcher(request, dispatcherPath);
        if (rd == null) {
            throw new ServletException("Could not get RequestDispatcher for [" + getUrl() +
                    "]: Check that the corresponding file exists within your web application archive!");
        }
        // If already included or response already committed, perform include, else forward.
        if (useInclude(request, response)) {
            response.setContentType(getContentType());
            if (logger.isDebugEnabled()) {
                logger.debug("Including [" + getUrl() + "]");
            }
            rd.include(request, response);
        }
        else {
            // Note: The forwarded resource is supposed to determine the content type itself.
            if (logger.isDebugEnabled()) {
                logger.debug("Forwarding to [" + getUrl() + "]");
            }
            rd.forward(request, response);
        }
    }

暴露模型作为请求域属性
// Expose the model object as request attributes.
        exposeModelAsRequestAttributes(model, request);

//该方法可以看出,底层最终就是通过最普通的遍历,将model数据重新放入请求域中
protected void exposeModelAsRequestAttributes(Map<String, Object> model,
            HttpServletRequest request) throws Exception {
    //model中的所有数据遍历挨个放在请求域中
        model.forEach((name, value) -> {
            if (value != null) {
                request.setAttribute(name, value);
            }
            else {
                request.removeAttribute(name);
            }
        });
    }

四、自定义POJO类型参数的处理

跟上面一样,来到resolvers.supportsParameter(parameter),处理POJO类型的有两个参数解析器,都是叫:ServletModelAttributeMethodProcessor,但是一个是处理带注解的bean,一个是处理不带注解的bean。判断时,先判断参数是不是简单类型。
在这里插入图片描述

public static boolean isSimpleValueType(Class<?> type) {
        return Void.class != type && Void.TYPE != type && (ClassUtils.isPrimitiveOrWrapper(type) || Enum.class.isAssignableFrom(type) || CharSequence.class.isAssignableFrom(type) || Number.class.isAssignableFrom(type) || Date.class.isAssignableFrom(type) || Temporal.class.isAssignableFrom(type) || URI.class == type || URL.class == type || Locale.class == type || Class.class == type);
    }

而自定义对象,自然就不是简单类型,然后便开始执行resolveArgument方法。

@Nullable
    public final Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer, NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
        Assert.state(mavContainer != null, "ModelAttributeMethodProcessor requires ModelAndViewContainer");
        Assert.state(binderFactory != null, "ModelAttributeMethodProcessor requires WebDataBinderFactory");
        String name = ModelFactory.getNameForParameter(parameter);
        ModelAttribute ann = (ModelAttribute)parameter.getParameterAnnotation(ModelAttribute.class);
        if (ann != null) {
            mavContainer.setBinding(name, ann.binding());
        }

        Object attribute = null;
        BindingResult bindingResult = null;
        if (mavContainer.containsAttribute(name)) {
            attribute = mavContainer.getModel().get(name);
        } else {
            try {
                attribute = this.createAttribute(name, parameter, binderFactory, webRequest);
            } catch (BindException var10) {
                if (this.isBindExceptionRequired(parameter)) {
                    throw var10;
                }

                if (parameter.getParameterType() == Optional.class) {
                    attribute = Optional.empty();
                }

                bindingResult = var10.getBindingResult();
            }
        }

        if (bindingResult == null) {
            WebDataBinder binder = binderFactory.createBinder(webRequest, attribute, name);
            if (binder.getTarget() != null) {
                if (!mavContainer.isBindingDisabled(name)) {
                    this.bindRequestParameters(binder, webRequest);
                }

                this.validateIfApplicable(binder, parameter);
                if (binder.getBindingResult().hasErrors() && this.isBindExceptionRequired(binder, parameter)) {
                    throw new BindException(binder.getBindingResult());
                }
            }

            if (!parameter.getParameterType().isInstance(attribute)) {
                attribute = binder.convertIfNecessary(binder.getTarget(), parameter.getParameterType(), parameter);
            }

            bindingResult = binder.getBindingResult();
        }

        Map<String, Object> bindingResultModel = bindingResult.getModel();
        mavContainer.removeAttributes(bindingResultModel);
        mavContainer.addAllAttributes(bindingResultModel);
        return attribute;
    }

(1)先看model中有没有(getModel().containsAttribute(name);),没有就看get域中有没有HandlerMapping.uriTemplateVariables的map里面有没有,然后看域中有没有,没有就反射创建
(2)反射创建一个没有初始化的bean
(3)然后通过WebDatabinder来bind属性。

  • WebDataBinder binder = binderFactory.createBinder(webRequest, attribute, name);核心方法,将请求参数的值绑定到指定的JavaBean里面,WebDataBinder 利用它里面的
    Converters将请求数据转成指定的数据类型。再次封装到JavaBean中。
  • Converters :底层默认有124个,如下:
    在这里插入图片描述
    在这里插入图片描述
    我们也可以自定义自己的Converters:
@FunctionalInterfacepublic interface Converter<S, T>
    //1、WebMvcConfigurer定制化SpringMVC的功能
    @Bean
    public WebMvcConfigurer webMvcConfigurer(){
        return new WebMvcConfigurer() {
            @Override
            public void configurePathMatch(PathMatchConfigurer configurer) {
                UrlPathHelper urlPathHelper = new UrlPathHelper();
                // 不移除;后面的内容。矩阵变量功能就可以生效
                urlPathHelper.setRemoveSemicolonContent(false);
                configurer.setUrlPathHelper(urlPathHelper);
            }

            @Override
            public void addFormatters(FormatterRegistry registry) {
                registry.addConverter(new Converter<String, Pet>() {

                    @Override
                    public Pet convert(String source) {
                        // 啊猫,3
                        if(!StringUtils.isEmpty(source)){
                            Pet pet = new Pet();
                            String[] split = source.split(",");
                            pet.setName(split[0]);
                            pet.setAge(Integer.parseInt(split[1]));
                            return pet;
                        }
                        return null;
                    }
                });
            }
        };
    }
自定义Converter

(1)后端POJO如下:

/**
 *     姓名: <input name="userName"/> <br/>
 *     年龄: <input name="age"/> <br/>
 *     生日: <input name="birth"/> <br/>
 *     宠物姓名:<input name="pet.name"/><br/>
 *     宠物年龄:<input name="pet.age"/>
 */
@Data
public class Person {

    private String userName;
    private Integer age;
    private Date birth;
    private Pet pet;

}

(2)在前端不使用级联属性,直接用逗号隔开配置
在这里插入图片描述
(3)如果不自定义Converter就会报错
在这里插入图片描述
(4)使用自定义的convert进行解析

这就牵扯到了对springMVC的定制,而springMVC的定制,我们通过webConfigurer就可以实现了。

    //1、WebMvcConfigurer定制化SpringMVC的功能
    @Bean
    public WebMvcConfigurer webMvcConfigurer(){
        return new WebMvcConfigurer() {
            @Override
            public void configurePathMatch(PathMatchConfigurer configurer) {
                UrlPathHelper urlPathHelper = new UrlPathHelper();
                // 不移除;后面的内容。矩阵变量功能就可以生效
                urlPathHelper.setRemoveSemicolonContent(false);
                configurer.setUrlPathHelper(urlPathHelper);
            }

            @Override
            public void addFormatters(FormatterRegistry registry) {
                registry.addConverter(new Converter<String, Pet>() {

                    @Override
                    public Pet convert(String source) {
                        // 啊猫,3
                        if(!StringUtils.isEmpty(source)){
                            Pet pet = new Pet();
                            String[] split = source.split(",");
                            pet.setName(split[0]);
                            pet.setAge(Integer.parseInt(split[1]));
                            return pet;
                        }
                        return null;
                    }
                });
            }
        };
    }

添加了自定义的convert之后,debug会发现原来默认124个convert就会编程125个,多的就是自定义的

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值