10、请求参数处理

文章目录


【尚硅谷】SpringBoot2零基础入门教程-讲师:雷丰阳
笔记

路还在继续,梦还在期许

1、请求映射

编写controller类,类上写 @Controller注解,在类中每一个方法上写 @RequestMapping注解,声明当前方法可以处理的请求,这个声明过程称为请求映射

请求映射中常用的方式是@RequestMapping注解。

开发常使用REST风格编写请求映射。

1.1、REST使用与原理

1、介绍

以前:路径

/getUser:获取用户
/deleteUser:删除用户
/editUser:修改用户
/saveUser:保存用户

现在:路径+请求方式(使用HTTP请求方式动词来表示对资源的操作)

/user:GET-获取用户
/user:DELETE-删除用户
/user:PUT-修改用户
/user:POST-保存用户

2、SpringMVC使用

浏览器只能发送GET或POST请求,需要后端配合使用过滤器,对请求进行包装。

在web.xml文件中配置过滤器(HiddenHttpMethodFilter)。

使用POST方式提交表单,表单中写隐藏域 _method=请求方式

3、Spring Boot使用

spring boot 已经为用户自动配置 HiddenHttpMethodFilter

位置:org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration

@Bean
// 容器中没有HiddenHttpMethodFilter才自动配置
@ConditionalOnMissingBean(HiddenHttpMethodFilter.class)
// 获取配置文件中配置属性,确定开启REST风格(配置文件中获取不到属性就不开启)
@ConditionalOnProperty(prefix = "spring.mvc.hiddenmethod.filter", name = "enabled", matchIfMissing = false)
public OrderedHiddenHttpMethodFilter hiddenHttpMethodFilter() {
    
    
	return new OrderedHiddenHttpMethodFilter();
}

在页面使用表单提交,需要表单以POST方式提交,表单中写隐藏域 _method=请求方式

在配置文件中开启REST风格(用于接收页面表单提交数据)

spring:
  mvc:
    hiddenmethod:
      filter:
        enabled: true #开启REST风格

4、测试REST

测试页面

测试REST风格
<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 type="hidden" name="_method" value="delete">
    <input value="REST-DELETE 提交" type="submit"/>
</form>
<form action="/user" method="post">
    <input type="hidden" name="_method" value="put">
    <input value="REST-PUT 提交" type="submit"/>
</form>

controller

@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-张三";
}

5、REST原理(表单提交使用REST的时候)

  • 表单提交会带上_method=PUT
  • 请求过来被HiddenHttpMethodFilter拦截
    • 请求是POST,且无异常抛出
    • 获取到_method的值
    • 兼容以下请求:PUT、DELETE、PATCH
    • 将原生 POST 方式 提交的 request() 请求 ,经过包装模式 requesWrapper ,重写了getMethod方法,更改提交方式
    • 过滤器链放行的时候用wrapper,以后的方法调用 getMethod 是调用 requesWrapper 包装过的请求方式

位置:org.springframework.web.filter.HiddenHttpMethodFilter

@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
		throws ServletException, IOException {
    
    

	HttpServletRequest requestToUse = request;
	// POST请求方式且程序无异常,如果发送PUT或DELETE直接放行
	if ("POST".equals(request.getMethod()) && request.getAttribute(WebUtils.ERROR_EXCEPTION_ATTRIBUTE) == null) {
    
    
		String paramValue = request.getParameter(this.methodParam);
		if (StringUtils.hasLength(paramValue)) {
    
    
			String method = paramValue.toUpperCase(Locale.ENGLISH);
			if (ALLOWED_METHODS.contains(method)) {
    
    
				requestToUse = new HttpMethodRequestWrapper(request, method);
			}
		}
	}

	filterChain.doFilter(requestToUse, response);
}

Rest使用客户端工具直接发送,就和以上做表单的无关了。

比如 安卓 可以直接发送PUT、DELETE。

或 Postman 直接发送PUT、DELETE等方式请求,无需Filter。

所以 spring boot 中的 REST风格功能是选择性开启。

6、扩展:如何把_method 这个名字换成自定义名字。

//在spring boot 配置类中 自定义 hiddenHttpMethodFilter
@Bean
public HiddenHttpMethodFilter hiddenHttpMethodFilter(){
    
    
    HiddenHttpMethodFilter methodFilter = new HiddenHttpMethodFilter();
    // 改成自定义名字
    methodFilter.setMethodParam("_m");
    return methodFilter;
}

1.2、请求映射原理

1、DispatcherServlet 的继承关系与功能的实现

梦回 SpringMVC ,重新讲了一遍 SpringMVC 中 DispatcherServlet 的继承关系与功能的实现。

在这里插入图片描述
SpringMVC 功能分析都从 org.springframework.web.servlet.DispatcherServlet 内 doDispatch()方法开始。

2、分析 doDispatch 方法

位置:org.springframework.web.servlet.DispatcherServlet(IDEA查询文件快捷键:Ctrl+Shift+N)

// 请求作派发
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 mv = null;
			Exception dispatchException = null;

			try {
    
    
				// 检查是否是文件上传请求
				processedRequest = checkMultipart(request);
				multipartRequestParsed = (processedRequest != request);

				// Determine handler for the current request.
				// 找到当前请求使用哪个Handler(Controller的方法)处理
				mappedHandler = getHandler(processedRequest);     

3、分析 getHandler 方法

位置:org.springframework.web.servlet.DispatcherServlet(IDEA查询文件快捷键:Ctrl+Shift+N)

进入 getHandler 方法

/**
 * Return the HandlerExecutionChain for this request.
 * <p>Tries all handler mappings in order.
 * @param request current HTTP request
 * @return the HandlerExecutionChain, or {@code null} if no handler could be found
 */
@Nullable
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
    
    
	// 获取到所有的 HandlerMapping(处理器映射器)
	// 处理器映射规则:/请求--->>交给controller中方法处理
	if (this.handlerMappings != null) {
    
    
		for (HandlerMapping mapping : this.handlerMappings) {
    
    
HandlerExecutionChain handler = mapping.getHandler(request);
			// 获取处理器
			HandlerExecutionChain handler = mapping.getHandler(request);
			if (handler != null) {
    
    
				return handler;
			}
		}
	}
	return null;
}

获取到的五个默认的HandlerMapping

在这里插入图片描述

WelcomePageHandIerMapping:保存了首页访问规则

4、RequestMappingHandlerMapping 处理器映射器

作用:保存了所有@RequestMapping 和handler的映射规则。

在这里插入图片描述

5、获取处理器

根据RequestMappingHandlerMapping 处理器映射器中的映射规则获取处理器

位置:org.springframework.web.servlet.handler.AbstractHandlerMapping

/**
 * Look up a handler for the given request, falling back to the default
 * handler if no specific one is found.
 * @param request current HTTP request
 * @return the corresponding handler instance, or the default handler
 * @see #getHandlerInternal
 */
@Override
@Nullable
public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
    
    
	// 获取处理器
	Object handler = getHandlerInternal(request);
	if (handler == null) {
    
    
		handler = getDefaultHandler();
	}
	if (handler == null) {
    
    
		return null;
	}
	// Bean name or resolved handler?
	if (handler instanceof String) {
    
    
		String handlerName = (String) handler;
		handler = obtainApplicationContext().getBean(handlerName);
	}

	HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request);

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

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

	return executionChain;
}

6、分析 getHandlerInternal

位置:org.springframework.web.servlet.mvc.method.RequestMappingInfoHandlerMapping

@Override
protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {
    
    
	request.removeAttribute(PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE);
	try {
    
    
		return super.getHandlerInternal(request);
	}
	finally {
    
    
		ProducesRequestCondition.clearMediaTypesAttribute(request);
	}
}

7、分析 getHandlerInternal

位置:org.springframework.web.servlet.handler.AbstractHandlerMethodMapping

// Handler method lookup

/**
 * Look up a handler method for the given request.
 */
@Override
protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {
    
    
	// 拿到访问路径
	String lookupPath = getUrlPathHelper().getLookupPathForRequest(request);
	request.setAttribute(LOOKUP_PATH, lookupPath);
	// 拿到一把锁,防止并发问题
	this.mappingRegistry.acquireReadLock();
	try {
    
    
		HandlerMethod handlerMethod = lookupHandlerMethod(lookupPath, request);
		return (handlerMethod != null ? handlerMethod.createWithResolvedBean() : null);
	}
	finally {
    
    
		this.mappingRegistry.releaseReadLock();
	}
}

8、分析 lookupHandlerMethod

位置:org.springframework.web.servlet.handler.AbstractHandlerMethodMapping

/**
 * Look up the best-matching handler method for the current request.
 * If multiple matches are found, the best match is selected.
 * @param lookupPath mapping lookup path within the current servlet mapping
 * @param request the current request
 * @return the best-matching handler method, or {@code null} if no match
 * @see #handleMatch(Object, String, HttpServletRequest)
 * @see #handleNoMatch(Set, String, HttpServletRequest)
 */
@Nullable
// lookupPath:当前要查询的路径  request:原生请求
protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception {
    
    
	List<Match> matches = new ArrayList<>();
	// 通过路径查询那个控制器方法可以处理
	List<T> directPathMatches = this.mappingRegistry.getMappingsByUrl(lookupPath);
	// 查询到4个
	if (directPathMatches != null) {
    
    
		// 添加到匹配的集合中
		addMatchingMappings(directPathMatches, matches, request);
	}
	// 找不到就填写一些空的东西
	if (matches.isEmpty()) {
    
    
		// No choice but to go through all mappings...
		addMatchingMappings(this.mappingRegistry.getMappings().keySet(), matches, request);
	}
	// 找到不为空
	if (!matches.isEmpty()) {
    
    
		// 获取找到的第一个,并认为第一个是最佳匹配项
		Match bestMatch = matches.get(0);
		// matches.size() > 1:同时找到多个
		if (matches.size() > 1) {
    
    
			Comparator<Match> comparator = new MatchComparator(getMappingComparator(request));
			matches.sort(comparator);
			bestMatch = matches.get(0);
			if (logger.isTraceEnabled()) {
    
    
				logger.trace(matches.size() + " matching mappings: " + matches);
			}
			if (CorsUtils.isPreFlightRequest(request)) {
    
    
				return PREFLIGHT_AMBIGUOUS_MATCH;
			}
			Match secondBestMatch = 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);
		handleMatch(bestMatch.mapping, lookupPath, request);
		return bestMatch.handlerMethod;
	}
	else {
    
    
		return handleNoMatch(this.mappingRegistry.getMappings().keySet(), lookupPath, request);
	}
}

9、总结

  • SpringBoot 自动配置欢迎页的 WelcomePageHandlerMapping,访问 /能访问到index.html

  • SpringBoot 自动配置了默认 的 RequestMappingHandlerMapping

  • 请求进来,挨个尝试所有的 HandlerMapping 看是否有请求信息

    • 如果有就找到这个请求对应的handler
    • 如果没有就是下一个 HandlerMapping
  • 我们需要一些自定义的映射处理,我们也可以自己给容器中放HandlerMapping

2、普通参数与基本注解

重新讲了 SpringMVC 获取的请求参数,加入 springboot 矩阵模型和 request域对象共享。

2.1、注解

1、介绍

@PathVariable(路径变量与控制器方法的形参绑定,配合REST风格使用)

如果是表单POST提交,需要手动开启

spring:
  mvc:
    hiddenmethod:
      filter:
        enabled: true

@RequestHeader(请求头信息与控制器方法的形参绑定)

@RequestParam(设置与形参绑定的请求参数的名称)

@CookieValue(cookie数据和控制器方法的形参绑定)

@RequestBody(让控制器方法返回字符串,配合JSON使用)

@RequestAttribute(获取request域属性)

@MatrixVariable(矩阵变量)

spring boot默认禁用了矩阵变量,需要手动开启

手动开启:对于路径的处理,使用UrlPathHelper进行解析(原理)

位置:org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration.WebMvcAutoConfigurationAdapter

// 配置路径映射
@Override
@SuppressWarnings("deprecation")
public void configurePathMatch(PathMatchConfigurer configurer) {
    
    
	configurer.setUseSuffixPatternMatch(this.mvcProperties.getPathmatch().isUseSuffixPattern());
	configurer.setUseRegisteredSuffixPatternMatch(
			this.mvcProperties.getPathmatch().isUseRegisteredSuffixPattern());
	this.dispatcherServletPath.ifAvailable((dispatcherPath) -> {
    
    
		String servletUrlMapping = dispatcherPath.getServletUrlMapping();
		if (servletUrlMapping.equals("/") && singleDispatcherServlet()) {
    
    
			// URL的路径帮助器
			UrlPathHelper urlPathHelper = new UrlPathHelper();
			urlPathHelper.setAlwaysUseFullPath(true);
			configurer.setUrlPathHelper(urlPathHelper);
		}
	});
}

位置:org.springframework.web.util.UrlPathHelper

public class UrlPathHelper {
    
    

	private boolean removeSemicolonContent = true;
	
	/**
	 * Set if ";" (semicolon) content should be stripped from the request URI.
	 * <p>Default is "true".
	 */
	public void setRemoveSemicolonContent(boolean removeSemicolonContent) {
    
    
		checkReadOnly();
		this.removeSemicolonContent = removeSemicolonContent;
	}
}

UrlPathHelper中removeSemicolonContent(移除分号内容)属性,默认是true,不支持矩阵变量的方式,矩阵变量必须有url路径变量分号后内容才能被解析,将这个属性通过配置文件改为false。

配置类

@Configuration
public class WebConfig {
    
    

    /**
     * 方式一
     * 添加 WebMvcConfigurer 组件
     */
    @Bean
    public WebMvcConfigurer webMvcConfigurer() {
    
    
        return new WebMvcConfigurer() {
    
    
            @Override
            public void configurePathMatch(PathMatchConfigurer configurer) {
    
    
                UrlPathHelper urlPathHelper = new UrlPathHelper();
                // 不移出路径变量中;后的内容(矩阵变量就可以使用了)
                urlPathHelper.setRemoveSemicolonContent(false);
                configurer.setUrlPathHelper(urlPathHelper);
            }
        };
    }

    /**
     * 方式二
     * 实现WebMvcConfigurer接口,jdk8接口有默认实现,只需要重写configurePathMatch即可
     * 重写内容与方式一相同
     */
}

2、练习

页面

<h2>测试基本注解:</h2>
<ul>
    <li><a href="/car/3/owner/lisi">@PathVariable(路径变量)</a></li>
    <li><a href="/headers">@RequestHeader(获取请求头)</a></li>
    <li><a href="/param?age=18&inters=basketball&inters=game">@RequestParam(获取请求参数)</a></li>
    <li><a href="/setcookie">向浏览器响应一个JSESSIONID的Cookie</a></li>
    <li><a href="cookies">@CookieValue(获取cookie值)</a></li>
    <li><a href="">@RequestBody(获取请求体的值,在下面表单中测试)</a></li>
    <li><a href="">@RequestAttribute(获取request域属性)</a></li>
    <li><a href="">@MatrixVariable(矩阵变量)</a></li>
</ul>
<h2>矩阵变量:</h2>
<h3>页面开发,cookie禁用了,session里面的内容怎么使用?</h3>
<p>cookie未被禁用的使用</p><br/>
<p>服务端向session域中传值:session.set(a,b),将 JSESSIONID 封装进入 cookie ,通过浏览器每次访问携带cookie,实现session的会话共享 </p><br/>
<p>cookie被禁用的使用</p><br/>
<p>使用矩阵变量的方式进行传递,url:/abc;JSESSIONID=xxx</p>
<ul>
    <li><a href="/cars/sell;low=34;brand=byd,audi,yd">@matrixvariab1e(矩阵变量)</a></li>
    <li><a href="/cars/sell;low=34;brand=byd;brand=audi;brand=yd">@MatrixVariab1e(矩阵变量)</a></li>
    <li><a href="/boss/1;age=20/2;age=10">@Matrixvariab1e(矩阵变量)/boss/{bossId}/{empId}</a></li>
</ul>

<h2>获取请求体的值</h2>
<form action="/save" method="post">
    测试@RequestBody获取数据<br/>
    用户名:<input name="userName"/> <br>
    邮箱:<input name="email"/>
    <input type="submit" value="提交"/>
</form>
<ol>
    <li> 矩阵变量需要在 SpringBoot 中手动开启</li>
    <li> 根据 RFC3986 的规范,矩阵变量应当绑定在路径变量中!</li>
    <li> 若是有多个矩阵变量,应当使用英文苻号; 进行分隔。</li>
    <li> 若是一个矩阵变量有多个值,应当使用英文符号,进行分隔,或之命名多个重复的key即可。</li>
</ol>

ParameterTestController

@RestController
public class ParameterTestController {
    
    

    /**
     * @PathVariable 获取路径中的变量
     * 获取单个属性 @PathVariable("id") Integer id
     * 获取全部属性 @PathVariable Map<String, String> pv
     */
    @GetMapping("/car/{id}/owner/{username}")
    public Map<String, Object> testPathVariable(@PathVariable("id") Integer id,
                                                @PathVariable("username") String name,
                                                @PathVariable Map<String, String> pv) {
    
    
        Map<String, Object> map = new HashMap<>();
        map.put("id", id);
        map.put("name", name);
        map.put("pv", pv);
        return map;
    }

    /**
     * @RequestHeader 获取浏览器请求头信息
     * 获取单个请求头信息 @RequestHeader("host") String host
     * 获取全部请求头信息 @RequestHeader Map<String, String> headers
     */

    @GetMapping("/headers")
    public Map<String, Object> testRequestHeader(@RequestHeader("host") String host,
                                                 @RequestHeader Map<String, String> headers) {
    
    
        Map<String, Object> map = new HashMap<>();
        map.put("单独获取的:host", host);
        map.put("headers", headers);
        return map;
    }

    /**
     * @RequestParam 获取请求参数
     * 获取单个请请求参数 @RequestParam("age") Integer age
     * 获取全部请请求参数 @RequestParam Map<String, String> params
     */

    @GetMapping("/param")
    public Map<String, Object> testRequestParam(@RequestParam("age") Integer age,
                                                @RequestParam("inters") List<String> inters,
                                                @RequestParam Map<String, String> params) {
    
    
        Map<String, Object> map = new HashMap<>();
        map.put("age", age);
        map.put("inters", inters);
        map.put("Params", params);
        return map;
    }

    /**
     * 向浏览器响应一个JSESSIONID的Cookie
     */
    @GetMapping("/setcookie")
    public String testSession(HttpServletRequest request) {
    
    
        // 向浏览器响应一个JSESSIONID的Cookie
        HttpSession session = request.getSession();
        return "成功";
    }

    /**
     * @CookieValue 获取cookie值
     * 获取指定Cookie值:@CookieValue("JSESSIONID") String jsessionId
     * 获取指定Cookie对象:@CookieValue("JSESSIONID") Cookie cookie
     */

    @GetMapping("/cookies")
    public Map<String, Object> testCookieValue(@CookieValue("JSESSIONID") String jsessionId,
                                               @CookieValue("JSESSIONID") Cookie cookie) {
    
    
        Map<String, Object> map = new HashMap<>();
        map.put("JSESSIONID", jsessionId);
        System.out.println(cookie.getName() + ":" + cookie.getValue());
        return map;
    }

    /**
     * @RequestBody 获取请求体的值
     * 获取 @RequestBody String content
     */
    @PostMapping("/save")
    public Map<String, Object> testRequestBody(@RequestParam("userName") String userName,
                                               @RequestParam("email") String email,
                                               @RequestBody String content) {
    
    
        Map<String, Object> map = new HashMap<>();
        map.put("userName", userName);
        map.put("email", email);
        map.put("content", content);
        return map;
    }

    /**
     * @MatrixVariable 访问路径:/cars/sell;low=34;brand=byd,audi,yd
     * 获取单个属性:@MatrixVariable("low") Integer low
     * 获取多个属性:@MatrixVariable("brand") List<String> brand
     */
    @GetMapping("/cars/{path}")
    public Map<String, Object> testMatrixVariable(@MatrixVariable("low") Integer low,
                                                  @MatrixVariable("brand") List<String> brand,
                                                  @PathVariable("path") String path) {
    
    
        Map<String, Object> map = new HashMap<>();
        map.put("path", path);
        map.put("low", low);
        map.put("brand", brand);
        return map;
    }

    /**
     * 矩阵变量中属性名相同
     */
    @GetMapping("/boss/{bossId}/{empId}")
    public Map<String, Object> testBoos(@MatrixVariable(value = "age", pathVar = "bossId") Integer bossAge,
                                        @MatrixVariable(value = "age", pathVar = "empId") Integer empAge,
                                        @PathVariable("bossId") Integer bossId,
                                        @PathVariable("empId") Integer empId) {
    
    
        Map<String, Object> map = new HashMap<>();
        map.put("bossAge", bossAge);
        map.put("empAge", empAge);
        map.put("bossId", bossId);
        map.put("empId", empId);
        return map;
    }
}

RequestController

@Controller
public class RequestController {
    
    

    /**
     * 向request域存属性
     */
    @GetMapping("/goto")
    public String goToPage(HttpServletRequest request) {
    
    
        request.setAttribute("msg", "成功");
        request.setAttribute("code", "200");
        return "forward:/success";
    }

    /**
     * @RequestAttribute 获取request域属性
     * 获取指定值 @RequestAttribute("msg") String msg
     */
    @ResponseBody
    @GetMapping("/success")
    public Map<String, Object> testRequestAttribute(@RequestAttribute("msg") String msg,
                                                    @RequestAttribute("code") Integer code) {
    
    
        Map<String, Object> map = new HashMap<>();
        map.put("msg", msg);
        map.put("code", code);
        return map;
    }
}

2.2、Servlet API

WebRequest、ServletRequest、MultipartRequest、 HttpSession、javax.servlet.http.PushBuilder、Principal、InputStream、Reader、HttpMethod、Locale、TimeZone、ZoneId

1、缓存参数解析器

在执行 handler 前,缓存参数解析器

方法参数传入 ServletRequest。

循环26个参数解析器,找到处理Servlet的解析器。

位置:org.springframework.web.method.support.HandlerMethodArgumentResolverComposite

HandlerMethodArgumentResolver 方法

/**
 * Find a registered {@link HandlerMethodArgumentResolver} that supports
 * the given method parameter.
 */
@Nullable
private HandlerMethodArgumentResolver getArgumentResolver(MethodParameter parameter) {
    
    
	HandlerMethodArgumentResolver result = this.argumentResolverCache.get(parameter);
	if (result == null) {
    
    
		for (HandlerMethodArgumentResolver resolver : this.argumentResolvers) {
    
    
			// 循环26个参数解析器,并调用参数解析器匹配规则
			if (resolver.supportsParameter(parameter)) {
    
    
				result = resolver;
				// 匹配成功后,把参数解析器缓存起来
				this.argumentResolverCache.put(parameter, result);
				break;
			}
		}
	}
	return result;
}

2、匹配规则

挨个调用参数解析器内部匹配规则,这里查看的是 ServletRequestMethodArgumentResolver 类的匹配规则。

位置:org.springframework.web.servlet.mvc.method.annotation.ServletRequestMethodArgumentResolver

supportsParameter 方法

// 拿到参数的类型,看是否支持(一下参数类型中的任意一种)
@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);
}

3、参数处理器处理细节

位置:org.springframework.web.servlet.mvc.method.annotation.ServletRequestMethodArgumentResolver

resolveArgument 方法

// webRequest:将原生的Request和Response包装起来
@Override
public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
		NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
    
    

	// 拿到传入参数对象类型(控制器方法形参写的是:HttpServletRequest)
	Class<?> paramType = parameter.getParameterType();

	// 判断下面对象类型
	// WebRequest / NativeWebRequest / ServletWebRequest
	if (WebRequest.class.isAssignableFrom(paramType)) {
    
    
		if (!paramType.isInstance(webRequest)) {
    
    
			throw new IllegalStateException(
					"Current request is not of type [" + paramType.getName() + "]: " + webRequest);
		}
		return webRequest;
	}

	// 判断下面对象类型,判断成功
	// ServletRequest / HttpServletRequest / MultipartRequest / MultipartHttpServletRequest
	if (ServletRequest.class.isAssignableFrom(paramType) || MultipartRequest.class.isAssignableFrom(paramType)) {
    
    
		// 进入 resolveNativeRequest 方法
		return resolveNativeRequest(webRequest, paramType);
	}

	// HttpServletRequest required for all further argument types
	return resolveArgument(paramType, resolveNativeRequest(webRequest, HttpServletRequest.class));
}

4、resolveNativeRequest 方法

位置:org.springframework.web.servlet.mvc.method.annotation.ServletRequestMethodArgumentResolver

resolveNativeRequest 方法

private <T> T resolveNativeRequest(NativeWebRequest webRequest, Class<T> requiredType) {
    
    
	// 拿到原生的Request请求
	T nativeRequest = webRequest.getNativeRequest(requiredType);
	if (nativeRequest == null) {
    
    
		throw new IllegalStateException(
				"Current request is not of type [" + requiredType.getName() + "]: " + webRequest);
	}
	// 返回原生Request请求
	return nativeRequest;
}

2.3、复杂参数

Map、Model、Errors/BindingResult、RedirectAttributes( 重定向携带数据)、ServletResponse(response)、SessionStatus、UriComponentsBuilder、ServletUriComponentsBuilder

1、Map、Model、HttpServletRequest、HttpServletResponse

map、model里面的数据会被放在request的请求域 request.setAttribute。

准备控制器

@GetMapping("/params")
public String testParam(Map<String, Object> map,
                        Model model,
                        HttpServletRequest request,
                        HttpServletResponse response) {
    
    
    map.put("hello", "world666");
    model.addAttribute("world", "hello666");
    request.setAttribute("message", "HelloWorld");

    Cookie cookie = new Cookie("c1", "v1");
    response.addCookie(cookie);
    return "forward:/success";
}

@ResponseBody
@GetMapping("/success")
public Map<String, Object> testRequestAttribute(@RequestAttribute(value = "msg",required = false) String msg,
                                                @RequestAttribute(value = "code",required = false) Integer code,
                                                HttpServletRequest request) {
    
    
    Map<String, Object> map = new HashMap<>();
    Object hello = request.getAttribute("hello");
    Object world = request.getAttribute("world");
    Object message = request.getAttribute("message");

    map.put("hello", hello);
    map.put("world", world);
    map.put("message", message);
    return map;
}

2、RedirectAttributes

3、ServletResponse

2.4、源码解析

1、获取参数解析器

开始循环判断可以执行的参数解析器

位置:org.springframework.web.method.support.HandlerMethodArgumentResolverComposite

HandlerMethodArgumentResolver 方法

/**
 * Find a registered {@link HandlerMethodArgumentResolver} that supports
 * the given method parameter.
 */
@Nullable
private HandlerMethodArgumentResolver getArgumentResolver(MethodParameter parameter) {
    
    
	HandlerMethodArgumentResolver result = this.argumentResolverCache.get(parameter);
	if (result == null) {
    
    
		for (HandlerMethodArgumentResolver resolver : this.argumentResolvers) {
    
    
			// 判断到 MapMethodProcessor
			if (resolver.supportsParameter(parameter)) {
    
    
				result = resolver;
				// 参数解析器存入缓存
				// Map参数:MapMethodProcessor
				// Model参数:ModelMethodProcessor
				// Request参数:ServletRequestMethodArgumentResolver
				// Response参数:ServletResponseMethodArgumentResolver
				this.argumentResolverCache.put(parameter, result);
				break;
			}
		}
	}
	return result;
}

2、MapMethodProcessor

从缓存中获取对应的参数解析器,进入解析器内部方法查看如何解析的。

位置:org.springframework.web.method.annotation.MapMethodProcessor

resolveArgument 方法

@Override
@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");
	// 如果是Map类型的参数,会返回 mavContainer.getModel();---> BindingAwareModelMap();--->是Model也是Map
	return mavContainer.getModel();
}

进入 getModel 方法

位置:org.springframework.web.method.support.ModelAndViewContainer

/**
 * Return the model to use -- either the "default" or the "redirect" model.
 * The default model is used if {@code redirectModelScenario=false} or
 * there is no redirect model (i.e. RedirectAttributes was not declared as
 * a method argument) and {@code ignoreDefaultModelOnRedirect=false}.
 */
public ModelMap getModel() {
    
    
	if (useDefaultModel()) {
    
    
		return this.defaultModel;// private final ModelMap defaultModel = new BindingAwareModelMap();
	}
	else {
    
    
		if (this.redirectModel == null) {
    
    
			this.redirectModel = new ModelMap();
		}
		return this.redirectModel;
	}
}

3、ModelMethodProcessor

位置:org.springframework.web.method.annotation.ModelMethodProcessor

resolveArgument 方法

@Override
@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");
	// 调用与Map类型的参数解析器相同方法,会返回 mavContainer.getModel();---> BindingAwareModelMap();内存地址都相同
	return mavContainer.getModel();
}

所以证明 Map 和 Model 处理都是相同的
在这里插入图片描述

4、数据放入请求域中

执行目标方法

在这里插入图片描述
目标方法执行完

位置:org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod

invokeAndHandle 方法

最终目标方法的返回值 “forward:/success”

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

2.5、自定义对象参数

可以自动类型转换与格式化,可以级联封装。

bean

/**
* 姓名:<input name="userName" value="zhangsan"/> <br/>
* 年龄:<input name="age" value="18"/> <br/>
* 生日:<input name="birth" value="2019/12/10"/> <br/>
* 宠物姓名:<input name="pet.name" value="阿猫"/> <br/>
* 宠物年龄:<input name="pet.age" value="5"/> <br/>
* <input type="submit" value="保存">
*/

@Data
public class Person {
    
    
	private String userName;
	private Integer age;
	private Date birth;
	private Pet pet;
}

@Data
public class Pet {
    
    
	private String name;
	private String age;
}

controller

@RestController
public class ParameterTestController {
    
    

    @PostMapping("/saveuser")
    public Person saveuser(Person person) {
    
    
        return person;
    }
}

3、POJO封装过程

数据绑定:页面提交的请求数据(GET、POST)都可以和对象属性进行绑定

自定义类型参数是由 ServletModelAttributeMethodProcessor(参数解析器)解析的

在这里插入图片描述

4、参数原理

用户只需要给方法参数位置标记注解,SpringMVC就可以自动在调用目标方法的时候为参数赋值。

分析自动赋值的过程以及源码。

4.1、思路

  • 在 HandlerMapping(处理器映射器)中找到能处理请求的 Handler(处理器)
  • 为当前 Handler 找一个HandlerAdapter (适配器),实际的适配器 RequestMappingHandlerAdapter
  • 适配器执行目标方法并确定方法参数的每一个值,返回结果

4.2、进入 DispatcherServlet 类

这个类是SpringMVC处理请求的入口,doDispatch方法是核心,将断点打带doDispatch方法中。

位置:org.springframework.web.servlet.DispatcherServlet

doDispatch 方法

/**
 * Process the actual dispatching to the handler.
 * <p>The handler will be obtained by applying the servlet's HandlerMappings in order.
 * The HandlerAdapter will be obtained by querying the servlet's installed HandlerAdapters
 * to find the first that supports the handler class.
 * <p>All HTTP methods are handled by this method. It's up to HandlerAdapters or handlers
 * themselves to decide which methods are acceptable.
 * @param request current HTTP request
 * @param response current HTTP response
 * @throws Exception in case of any kind of processing failure
 */
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);

			// Determine handler for the current request.
			// 通过 HandlerMapping(处理器映射器)获取 Handler(处理器:封装了Controller.method的详细信息)
			mappedHandler = getHandler(processedRequest);

4.3、进入 getHandler 方法

位置:org.springframework.web.servlet.DispatcherServlet

getHandler 方法

@Nullable
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
    
    
	if (this.handlerMappings != null) {
    
     // 获取到5个 HandlerMapping(处理器映射器)
		for (HandlerMapping mapping : this.handlerMappings) {
    
    
			HandlerExecutionChain handler = mapping.getHandler(request);
			if (handler != null) {
    
    
				return handler;
			}
		}
	}
	return null;
}

在这里插入图片描述
在RequestMappingHandleMapping注解中映射中心有的映射关系。

在这里插入图片描述
继续 doDispatch 方法

位置:org.springframework.web.servlet.DispatcherServlet

			if (mappedHandler == null) {
    
    
				noHandlerFound(processedRequest, response);
				return;
			}
			// Determine handler adapter for the current request.
			// 获取 HandlerAdapter(处理器适配器:完成控制器方法的调用,参数的传递)
			HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

4.4、进入 getHandlerAdapter 方法

为当前的 Handler(处理器)找到 HandlerAdapter(处理器适配器)

位置:org.springframework.web.servlet.DispatcherServlet

/**
 * Return the HandlerAdapter for this handler object.
 * @param handler the handler object to find an adapter for
 * @throws ServletException if no HandlerAdapter can be found for the handler. This is a fatal error.
 */
// 传入一个 handler
protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
    
    
	if (this.handlerAdapters != null) {
    
    
		// 循环4种处理器适配器
		for (HandlerAdapter adapter : this.handlerAdapters) {
    
    
			// 判断适配器能否支持handler,handler被封装成{HandlerMethod@5332}
			if (adapter.supports(handler)) {
    
    

找到4种处理器适配器,判断并使用其中一种。

在这里插入图片描述
0 - 支持方法上标注@RequestMapping
1 - 支持函数式编程的
(其它的就不在描述)…

1、进入 supports 方法

位置:org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter

/**
 * This implementation expects the handler to be an {@link HandlerMethod}.
 * @param handler the handler instance to check
 * @return whether or not this adapter can adapt the given handler
 */
@Override
public final boolean supports(Object handler) {
    
    
	// 判断handler 类型与 HandlerMethod 是否相同 结果:相同
	return (handler instanceof HandlerMethod && supportsInternal((HandlerMethod) handler));
}

2、回到 getHandlerAdapter 方法

位置:org.springframework.web.servlet.DispatcherServlet

// 判断结果相同,返回第一个处理器适配器:{RequestMappingHandlerAdapter@6352}
				return adapter;
			}
		}
	}
	throw new ServletException("No adapter for handler [" + handler +
			"]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler");
}

3、回到 doDispatch 方法

位置:org.springframework.web.servlet.DispatcherServlet

			// Process last-modified header, if supported by the handler.
			// 判断请求是不是GET方法
			String method = request.getMethod();
			boolean isGet = "GET".equals(method);
			// 判断是不是HEAD
			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;
			}

			// Actually invoke the handler.
			// HandlerAdapter(处理器适配器) 调用 handle(处理器) 处理目标方法
			mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

4.5、调用执行目标方法

1、进入 handle 方法

位置:org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter

handle 方法

/**
 * This implementation expects the handler to be an {@link HandlerMethod}.
 */
@Override
@Nullable
public final ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
		throws Exception {
    
    

	return handleInternal(request, response, (HandlerMethod) handler);
}

2、接口

位置:org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter

handleInternal 方法

@Nullable
protected abstract ModelAndView handleInternal(HttpServletRequest request,
		HttpServletResponse response, HandlerMethod handlerMethod) throws Exception;

3、进入 handleInternal 方法

位置:org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter

handleInternal 方法

@Override
protected ModelAndView handleInternal(HttpServletRequest request,
		HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
    
    

	ModelAndView mav;
	checkRequest(request);

	// Execute invokeHandlerMethod in synchronized block if required.
	if (this.synchronizeOnSession) {
    
    
		HttpSession session = request.getSession(false);
		if (session != null) {
    
    
			Object mutex = WebUtils.getSessionMutex(session);
			synchronized (mutex) {
    
    
				mav = invokeHandlerMethod(request, response, handlerMethod);
			}
		}
		else {
    
    
			// No HttpSession available -> no mutex necessary
			mav = invokeHandlerMethod(request, response, handlerMethod);
		}
	}
	else {
    
    
		// No synchronization on session demanded at all...
		// 执行 handler 的方法
		mav = invokeHandlerMethod(request, response, handlerMethod);	

4.6、准备执行目标方法

1、进入 invokeHandlerMethod 方法

位置:org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter

invokeHandlerMethod 方法

/**
 * Invoke the {@link RequestMapping} handler method preparing a {@link ModelAndView}
 * if view resolution is required.
 * @since 4.2
 * @see #createInvocableHandlerMethod(HandlerMethod)
 */
@Nullable
protected ModelAndView invokeHandlerMethod(HttpServletRequest request,
		HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
    
    

	// 初始化过程
	ServletWebRequest webRequest = new ServletWebRequest(request, response);
	try {
    
    
		WebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod);
		ModelFactory modelFactory = getModelFactory(handlerMethod, binderFactory);

		ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod);
		// argumentResolvers 参数解析器集合
		if (this.argumentResolvers != null) {
    
    
			invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
		}

2、参数解析器

在目标方法执行前(invocableMethod 可执行目标方法:就是目标方法,又被封装了一次)设置26个参数解析器,作用:确定将要执行的目标方法的每一个参数的值是什么。

SpringMVC目标方法能写多少种参数类型。取决于参数解析器。

确定目标方法参数解析器,并将其放入 invocableMethod 中。

在这里插入图片描述
参数解析器接口查看

/**
 * Strategy interface for resolving method parameters into argument values in
 * the context of a given request.
 *
 * @author Arjen Poutsma
 * @since 3.1
 * @see HandlerMethodReturnValueHandler
 */
public interface HandlerMethodArgumentResolver {
    
    

	/**
	 * Whether the given {@linkplain MethodParameter method parameter} is
	 * supported by this resolver.
	 * @param parameter the method parameter to check
	 * @return {@code true} if this resolver supports the supplied parameter;
	 * {@code false} otherwise
	 */
	 // 当前解析器是否支持解析这种参数
	boolean supportsParameter(MethodParameter parameter);

	/**
	 * Resolves a method parameter into an argument value from a given request.
	 * A {@link ModelAndViewContainer} provides access to the model for the
	 * request. A {@link WebDataBinderFactory} provides a way to create
	 * a {@link WebDataBinder} instance when needed for data binding and
	 * type conversion purposes.
	 * @param parameter the method parameter to resolve. This parameter must
	 * have previously been passed to {@link #supportsParameter} which must
	 * have returned {@code true}.
	 * @param mavContainer the ModelAndViewContainer for the current request
	 * @param webRequest the current request
	 * @param binderFactory a factory for creating {@link WebDataBinder} instances
	 * @return the resolved argument value, or {@code null} if not resolvable
	 * @throws Exception in case of errors with the preparation of argument values
	 */
	 // 支持就调用 resolveArgument 方法进行解析
	@Nullable
	Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
			NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception;

}

3、返回值处理器

继续 invokeHandlerMethod 方法

位置:org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter

		// 返回值处理器:目标方法可以写多少种类型的返回值
		if (this.returnValueHandlers != null) {
    
    
			invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);
		}

确定目标方法的返回值类型,并将其放入 invocableMethod 中。

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

4、返回 invocableMethod 类

返回包装后的 invocableMethod (可执行目标方法)

继续 invokeHandlerMethod 方法

位置:org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter

		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()) {
    
    
			Object result = asyncManager.getConcurrentResult();
			mavContainer = (ModelAndViewContainer) asyncManager.getConcurrentResultContext()[0];
			asyncManager.clearConcurrentResult();
			LogFormatUtils.traceDebug(logger, traceOn -> {
    
    
				String formatted = LogFormatUtils.formatValue(result, !traceOn);
				return "Resume with async result [" + formatted + "]";
			});
			// 包装后的目标方法
			invocableMethod = invocableMethod.wrapConcurrentResult(result);
		}
		// 执行并处理,invocableMethod(目标方法,内部需要准备的全部封装完毕)
		invocableMethod.invokeAndHandle(webRequest, mavContainer);

4.7、执行目标方法

1、进入 invokeAndHandle 方法

位置:org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod

invokeAndHandle 方法

/**
 * Invoke the method and handle the return value through one of the
 * configured {@link HandlerMethodReturnValueHandler HandlerMethodReturnValueHandlers}.
 * @param webRequest the current request
 * @param mavContainer the ModelAndViewContainer for this request
 * @param providedArgs "given" arguments matched by type (not resolved)
 */
public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer,
		Object... providedArgs) throws Exception {
    
    

	// 执行当前请求(真正执行目标方法)
	Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);

2、真正执行目标方法

位置:org.springframework.web.method.support.InvocableHandlerMethod

invokeForRequest 方法

/**
 * Invoke the method after resolving its argument values in the context of the given request.
 * <p>Argument values are commonly resolved through
 * {@link HandlerMethodArgumentResolver HandlerMethodArgumentResolvers}.
 * The {@code providedArgs} parameter however may supply argument values to be used directly,
 * i.e. without argument resolution. Examples of provided argument values include a
 * {@link WebDataBinder}, a {@link SessionStatus}, or a thrown exception instance.
 * Provided argument values are checked before argument resolvers.
 * <p>Delegates to {@link #getMethodArgumentValues} and calls {@link #doInvoke} with the
 * resolved arguments.
 * @param request the current request
 * @param mavContainer the ModelAndViewContainer for this request
 * @param providedArgs "given" arguments matched by type, not resolved
 * @return the raw value returned by the invoked method
 * @throws Exception raised if no suitable argument resolver can be found,
 * or if the method raised an exception
 * @see #getMethodArgumentValues
 * @see #doInvoke
 */
@Nullable
public Object invokeForRequest(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,
		Object... providedArgs) throws Exception {
    
    
	// 获取方法参数值(下面会进入这个方法)
	Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);
	if (logger.isTraceEnabled()) {
    
    
		logger.trace("Arguments: " + Arrays.toString(args));
	}
	// 进入反射工具类,利用反射调用目标方法
	return doInvoke(args);
}

在这里插入图片描述

4.8、获取方法参数值

1、进入 getMethodArgumentValues 方法

位置:org.springframework.web.method.support.InvocableHandlerMethod

getMethodArgumentValues 方法

作用:确定目标方法每一个参数的值。

/**
 * Get the method argument values for the current request, checking the provided
 * argument values and falling back to the configured argument resolvers.
 * <p>The resulting array will be passed into {@link #doInvoke}.
 * @since 5.1.2
 */
protected Object[] getMethodArgumentValues(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,
		Object... providedArgs) throws Exception {
    
    
	// 获取方法所有的参数声明(获取方法上所有参数的详细信息)
	MethodParameter[] parameters = getMethodParameters();
	// 判断参数不为空
	if (ObjectUtils.isEmpty(parameters)) {
    
    
		return EMPTY_ARGS;
	}
	// 有参数列表,就按照参数数量创建一个Object数组
	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) {
    
    
			continue;
		}
		// 判断当前解析器是否支持参数类型解析
		if (!this.resolvers.supportsParameter(parameter)) {
    
    

2、supportsParameter

位置:org.springframework.web.method.support.HandlerMethodArgumentResolverComposite

supportsParameter 方法

/**
 * Whether the given {@linkplain MethodParameter method parameter} is
 * supported by any registered {@link HandlerMethodArgumentResolver}.
 */
@Override
public boolean supportsParameter(MethodParameter parameter) {
    
    
	return getArgumentResolver(parameter) != null;
}

位置:org.springframework.web.method.support.HandlerMethodArgumentResolverComposite

getArgumentResolver方法

/**
 * Find a registered {@link HandlerMethodArgumentResolver} that supports
 * the given method parameter.
 */
@Nullable
private HandlerMethodArgumentResolver getArgumentResolver(MethodParameter parameter) {
    
    
	HandlerMethodArgumentResolver result = this.argumentResolverCache.get(parameter);
	if (result == null) {
    
    
		// 遍历26个参数解析器
		for (HandlerMethodArgumentResolver resolver : this.argumentResolvers) {
    
    
			// 挨个判断26个参数解析器谁能解析 (parameter) 参数,通过参数的注解判断
			if (resolver.supportsParameter(parameter)) {
    
    
				result = resolver;
				// 将参数解析器放入缓存中,方便后面使用
				this.argumentResolverCache.put(parameter, result);
				break;
			}
		}
	}
	return result;
}

3、回到 getMethodArgumentValues 方法

继续 getMethodArgumentValues 方法

位置:org.springframework.web.method.support.InvocableHandlerMethod

			throw new IllegalStateException(formatArgumentError(parameter, "No suitable resolver"));
		}
		try {
    
    
			// 解析这个参数的值
			args[i] = this.resolvers.resolveArgument(parameter, mavContainer, request, this.dataBinderFactory);

4、进入 resolveArgument 方法

位置:org.springframework.web.method.support.HandlerMethodArgumentResolverComposite

resolveArgument 方法

/**
 * Iterate over registered
 * {@link HandlerMethodArgumentResolver HandlerMethodArgumentResolvers}
 * and invoke the one that supports it.
 * @throws IllegalArgumentException if no suitable argument resolver is found
 */
@Override
@Nullable
public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
		NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
    
    

	// 获取当前参数的参数解析器
	HandlerMethodArgumentResolver resolver = getArgumentResolver(parameter);
	if (resolver == null) {
    
    
		throw new IllegalArgumentException("Unsupported parameter type [" +
				parameter.getParameterType().getName() + "]. supportsParameter should be called first.");
	}
	// 调用参数解析器的 resolveArgument 方法
	return resolver.resolveArgument(parameter, mavContainer, webRequest, binderFactory);
}

4.9、参数解析器

1、进入 resolveArgument 方法

位置:org.springframework.web.method.annotation.AbstractNamedValueMethodArgumentResolver

resolveArgument 方法

@Override
@Nullable
public final Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
		NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
    
    

	// 参数的名字
	NamedValueInfo namedValueInfo = getNamedValueInfo(parameter);
	MethodParameter nestedParameter = parameter.nestedIfOptional();

	// 解析名字
	Object resolvedName = resolveStringValue(namedValueInfo.name);
	// 名字为空
	if (resolvedName == null) {
    
    
		throw new IllegalArgumentException(
				"Specified name must not resolve to null: [" + namedValueInfo.name + "]");
	}

	// 解析值
	Object arg = resolveName(resolvedName.toString(), nestedParameter, webRequest);

2、进入 resolveName 方法

位置:org.springframework.web.servlet.mvc.method.annotation.PathVariableMethodArgumentResolver

resolveName 方法

@Override
@SuppressWarnings("unchecked")
@Nullable
protected Object resolveName(String name, MethodParameter parameter, NativeWebRequest request) throws Exception {
    
    
	// 从request域中获取map集合,map集合内存储的是路径变量
	Map<String, String> uriTemplateVars = (Map<String, String>) request.getAttribute(
			HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE, RequestAttributes.SCOPE_REQUEST);
	// 当map集合不为空,根据key返回value
	return (uriTemplateVars != null ? uriTemplateVars.get(name) : null);
}

属性来源?

请求一进来,UrlPathHelper 先把url地址中路径变量解析出来,并保存到 Request 请求域中,这个参数解析器,直接获取请求域中的值,但是请求域中封装了所有路径变量的值,需要解析获取id的值3。

在这里插入图片描述计算id值
在这里插入图片描述
继续 resolveArgument 方法

位置:org.springframework.web.method.annotation.AbstractNamedValueMethodArgumentResolver

	if (arg == null) {
    
     // age:"3"
		if (namedValueInfo.defaultValue != null) {
    
    
			arg = resolveStringValue(namedValueInfo.defaultValue);
		}
		else if (namedValueInfo.required && !nestedParameter.isOptional()) {
    
    
			handleMissingValue(namedValueInfo.name, nestedParameter, webRequest);
		}
		arg = handleNullValue(namedValueInfo.name, arg, nestedParameter.getNestedParameterType());
	}
	else if ("".equals(arg) && namedValueInfo.defaultValue != null) {
    
    
		arg = resolveStringValue(namedValueInfo.defaultValue);
	}

	if (binderFactory != null) {
    
    
		WebDataBinder binder = binderFactory.createBinder(webRequest, null, namedValueInfo.name);
		try {
    
    
			arg = binder.convertIfNecessary(arg, parameter.getParameterType(), parameter);
		}
		catch (ConversionNotSupportedException ex) {
    
    
			throw new MethodArgumentConversionNotSupportedException(arg, ex.getRequiredType(),
					namedValueInfo.name, parameter, ex.getCause());
		}
		catch (TypeMismatchException ex) {
    
    
			throw new MethodArgumentTypeMismatchException(arg, ex.getRequiredType(),
					namedValueInfo.name, parameter, ex.getCause());
		}
	}

	handleResolvedValue(arg, namedValueInfo.name, parameter, mavContainer, webRequest);

	return arg;
}

3、循环解析所有参数

继续 getMethodArgumentValues 方法

位置:位置:org.springframework.web.method.support.InvocableHandlerMethod

第一个参数解析出来,继续循环解析下一个参数,知道全部解析完毕。

在这里插入图片描述

4、getMethodArgumentValues 方法结束

继续getMethodArgumentValues 方法

位置:org.springframework.web.method.support.InvocableHandlerMethod

		}
		catch (Exception ex) {
    
    
			// Leave stack trace for later, exception may actually be resolved and handled...
			if (logger.isDebugEnabled()) {
    
    
				String exMsg = ex.getMessage();
				if (exMsg != null && !exMsg.contains(parameter.getExecutable().toGenericString())) {
    
    
					logger.debug(formatArgumentError(parameter, exMsg));
				}
			}
			throw ex;
		}
	}
	return args;
}

4.10、自定义类型参数 POJO

自定义类型参数是由 ServletModelAttributeMethodProcessor(参数解析器)解析的

位置:org.springframework.beans.BeanUtils

isSimpleValueType 方法

/**
 * Check if the given type represents a "simple" value type: a primitive or
 * primitive wrapper, an enum, a String or other CharSequence, a Number, a
 * Date, a Temporal, a URI, a URL, a Locale, or a Class.
 * <p>{@code Void} and {@code void} are not considered simple value types.
 * @param type the type to check
 * @return whether the given type represents a "simple" value type
 * @see #isSimpleProperty(Class)
 */
public static boolean isSimpleValueType(Class<?> type) {
    
    
	// 判断是否为简单类型(以下类型为简单类型)
	return (Void.class != type && void.class != 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));
}

位置:org.springframework.web.method.annotation.ModelAttributeMethodProcessor

resolveArgument 方法

/**
 * Resolve the argument from the model or if not found instantiate it with
 * its default if it is available. The model attribute is then populated
 * with request values via data binding and optionally validated
 * if {@code @java.validation.Valid} is present on the argument.
 * @throws BindException if data binding and validation result in an error
 * and the next method parameter is not of type {@link Errors}
 * @throws Exception if WebDataBinder initialization fails
 */
@Override
@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 = 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 {
    
    
		// Create attribute instance
		try {
    
    
			// 创建一个实例(空实体类对象对象)
			attribute = createAttribute(name, parameter, binderFactory, webRequest);
		}
		catch (BindException ex) {
    
    
			if (isBindExceptionRequired(parameter)) {
    
    
				// No BindingResult parameter -> fail with BindException
				throw ex;
			}
			// Otherwise, expose null/empty value and associated BindingResult
			if (parameter.getParameterType() == Optional.class) {
    
    
				attribute = Optional.empty();
			}
			bindingResult = ex.getBindingResult();
		}
	}

	if (bindingResult == null) {
    
    
		// Bean property binding and validation;
		// skipped in case of binding failure on construction.
		// 利用 binderFactory.createBinder 创建一个 WebDataBinder(web数据绑定器) 
		WebDataBinder binder = binderFactory.createBinder(webRequest, attribute, name);
		// 拿到 getTarget() 对象,并且不为空
		if (binder.getTarget() != null) {
    
    
			if (!mavContainer.isBindingDisabled(name)) {
    
    
				// 将原生请求中的数据与绑定器中空实体类绑定
				bindRequestParameters(binder, webRequest);
			}
			validateIfApplicable(binder, parameter);
			if (binder.getBindingResult().hasErrors() && isBindExceptionRequired(binder, parameter)) {
    
    
				throw new BindException(binder.getBindingResult());
			}
		}
		// Value type adaptation, also covering java.util.Optional
		if (!parameter.getParameterType().isInstance(attribute)) {
    
    
			attribute = binder.convertIfNecessary(binder.getTarget(), parameter.getParameterType(), parameter);
		}
		bindingResult = binder.getBindingResult();
	}

	// Add resolved attribute and BindingResult at the end of the model
	Map<String, Object> bindingResultModel = bindingResult.getModel();
	mavContainer.removeAttributes(bindingResultModel);
	mavContainer.addAllAttributes(bindingResultModel);

	return attribute;
}

WebDataBinder binder = binderFactory.createBinder(webRequest, attribute, name);
WebDataBinder :web数据绑定器,将请求参数的值绑定到指定的JavaBean里面
WebDataBinder 利用它里面的 Converters 将请求数据转成指定的数据类型。再次封装到JavaBean中

在这里插入图片描述
GenericConversionService:在设置每一个值的时候,找它里面的所有converter那个可以将这个数据类型(request带来参数的字符串)转换到指定的类型(JavaBean – Integer)
byte – > file

@FunctionalInterfacepublic interface Converter<S, T>

定制Converter

未来我们可以给WebDataBinder里面放自己的Converter;
private static final class StringToNumber implements Converter<String, T>

4.11、域对象共享

1、回到 invokeAndHandle 方法

位置:org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod

Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
// mavContainer:利用map或model添加这两个值

在这里插入图片描述

	// 设置响应状态
	setResponseStatus(webRequest);

	if (returnValue == null) {
    
    
		if (isRequestNotModified(webRequest) || getResponseStatus() != null || mavContainer.isRequestHandled()) {
    
    
			disableContentCachingIfNecessary(webRequest);
			mavContainer.setRequestHandled(true);
			return;
		}
	}
	else if (StringUtils.hasText(getResponseStatusReason())) {
    
    
		mavContainer.setRequestHandled(true);
		return;
	}

	mavContainer.setRequestHandled(false);
	Assert.state(this.returnValueHandlers != null, "No return value handlers");
	try {
    
    
		// 处理返回结果
		this.returnValueHandlers.handleReturnValue(
				returnValue, getReturnValueType(returnValue), mavContainer, webRequest);
	}
	catch (Exception ex) {
    
    
		if (logger.isTraceEnabled()) {
    
    
			logger.trace(formatErrorForReturnValue(returnValue), ex);
		}
		throw ex;
	}
}

2、处理返回值

位置:org.springframework.web.method.support.HandlerMethodReturnValueHandlerComposite

handleReturnValue 方法

/**
 * Iterate over registered {@link HandlerMethodReturnValueHandler HandlerMethodReturnValueHandlers} and invoke the one that supports it.
 * @throws IllegalStateException if no suitable {@link HandlerMethodReturnValueHandler} is found.
 */
@Override
public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
		ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {
    
    

	HandlerMethodReturnValueHandler handler = selectHandler(returnValue, returnType);
	if (handler == null) {
    
    
		throw new IllegalArgumentException("Unknown return value type: " + returnType.getParameterType().getName());
	}
	// 进入 handleReturnValue 方法
	handler.handleReturnValue(returnValue, returnType, mavContainer, webRequest);
}

3、进入 handleReturnValue 方法

位置:org.springframework.web.servlet.mvc.method.annotation.ViewNameMethodReturnValueHandler

handleReturnValue 方法

@Override
public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
		ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {
    
    

	// 如果返回值是字符串
	if (returnValue instanceof CharSequence) {
    
    
		// 拿到返回的字符串
		String viewName = returnValue.toString();
		// 保存到 mavContainer(模型和视图容器)
		mavContainer.setViewName(viewName);
		if (isRedirectViewName(viewName)) {
    
    
			mavContainer.setRedirectModelScenario(true);
		}
	}
	else if (returnValue != null) {
    
    
		// should not happen
		throw new UnsupportedOperationException("Unexpected return type: " +
				returnType.getParameterType().getName() + " in method: " + returnType.getMethod());
	}
}

4.12、继续执行

将所有的数据都放在 ModelAndViewContainer,包含要去页面地址View和Model数据。

1、回到 invokeHandlerMethod方法

位置:org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter

		if (asyncManager.isConcurrentHandlingStarted()) {
    
    
			return null;
		}

		// 返回值处理完之后,返回 getModelAndView(获取ModelAndView对象)
		return getModelAndView(mavContainer, modelFactory, webRequest);
	}
	finally {
    
    
		webRequest.requestCompleted();
	}
}

2、进入 getModelAndView 方法

位置:org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter

getModelAndView 方法

@Nullable
private ModelAndView getModelAndView(ModelAndViewContainer mavContainer,
		ModelFactory modelFactory, NativeWebRequest webRequest) throws Exception {
    
    

	// 模型工厂
	modelFactory.updateModel(webRequest, mavContainer);
	if (mavContainer.isRequestHandled()) {
    
    
		return null;
	}
	// 拿到Model
	ModelMap model = mavContainer.getModel();
	// 封装成 ModelAndView
	ModelAndView mav = new ModelAndView(mavContainer.getViewName(), model, mavContainer.getStatus());
	if (!mavContainer.isViewReference()) {
    
    
		mav.setView((View) mavContainer.getView());
	}
	// model 如果是 RedirectAttributes(重定向携带数据)
	if (model instanceof RedirectAttributes) {
    
    
		Map<String, ?> flashAttributes = ((RedirectAttributes) model).getFlashAttributes();
		HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class);
		if (request != null) {
    
    
			// 将数据全部获取,放到请求的上下文中
			RequestContextUtils.getOutputFlashMap(request).putAll(flashAttributes);
		}
	}
	// 不是就直接返回
	return mav;
}

3、进入 updateModel 方法

位置:org.springframework.web.method.annotation.ModelFactory

updateModel 方法

/**
 * Promote model attributes listed as {@code @SessionAttributes} to the session.
 * Add {@link BindingResult} attributes where necessary.
 * @param request the current request
 * @param container contains the model to update
 * @throws Exception if creating BindingResult attributes fails
 */
public void updateModel(NativeWebRequest request, ModelAndViewContainer container) throws Exception {
    
    
	// ModelAndViewContainer 中拿到默认的 model
	ModelMap defaultModel = container.getDefaultModel();
	if (container.getSessionStatus().isComplete()){
    
    
		this.sessionAttributesHandler.cleanupAttributes(request);
	}
	else {
    
    
		this.sessionAttributesHandler.storeAttributes(request, defaultModel);
	}
	if (!container.isRequestHandled() && container.getModel() == defaultModel) {
    
    
		// 更新最终的绑定结果
		updateBindingResult(request, defaultModel);
	}
}

4、进入 updateBindingResult 方法

位置:

updateBindingResult 方法

/**
 * Add {@link BindingResult} attributes to the model for attributes that require it.
 */
private void updateBindingResult(NativeWebRequest request, ModelMap model) throws Exception {
    
    
	// 拿到所有Model中的key
	List<String> keyNames = new ArrayList<>(model.keySet());
	for (String name : keyNames) {
    
    
		Object value = model.get(name);
		if (value != null && isBindingCandidate(name, value)) {
    
    
			String bindingResultKey = BindingResult.MODEL_KEY_PREFIX + name;
			if (!model.containsAttribute(bindingResultKey)) {
    
    
				WebDataBinder dataBinder = this.dataBinderFactory.createBinder(request, value, name);
				model.put(bindingResultKey, dataBinder.getBindingResult());
			}
		}
	}
}

5、回到 handleInternal 方法

位置:org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter

	}

	// 方法执行之后
	if (!response.containsHeader(HEADER_CACHE_CONTROL)) {
    
    
		if (getSessionAttributesHandler(handlerMethod).hasSessionAttributes()) {
    
    
			applyCacheSeconds(response, this.cacheSecondsForSessionAttributeHandlers);
		}
		else {
    
    
			prepareResponse(response);
		}
	}

	return mav;
}

6、回到 doDispatch 方法

位置:org.springframework.web.servlet.DispatcherServlet

			// 在执行handle之后,返回mv
			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);
	}
	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()) {
    
    
			// Instead of postHandle and afterCompletion
			if (mappedHandler != null) {
    
    
				mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
			}
		}
		else {
    
    
			// Clean up any resources used by a multipart request.
			if (multipartRequestParsed) {
    
    
				cleanupMultipart(processedRequest);
			}
		}
	}
}

7、进入 processDispatchResult 方法

位置:org.springframework.web.servlet.DispatcherServlet

processDispatchResult 方法

/**
 * Handle the result of handler selection and handler invocation, which is
 * either a ModelAndView or an Exception to be resolved to a ModelAndView.
 */
private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
		@Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv,
		@Nullable Exception exception) throws Exception {
    
    

	boolean errorView = false;

	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);
		}
	}

	// Did the handler return a view to render?
	// mv:ModelAndView
	if (mv != null && !mv.wasCleared()) {
    
    
		// 渲染页面
		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) {
    
    
		// Exception (if any) is already handled..
		mappedHandler.triggerAfterCompletion(request, response, null);
	}
}

位置:org.springframework.web.servlet.DispatcherServlet

render 方法

/**
 * Render the given ModelAndView.
 * <p>This is the last stage in handling a request. It may involve resolving the view by name.
 * @param mv the ModelAndView to render
 * @param request current HTTP servlet request
 * @param response current HTTP servlet response
 * @throws ServletException if view is missing or cannot be resolved
 * @throws Exception if there's a problem rendering the view
 */
protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception {
    
    
	// Determine locale for request and apply it to the response.
	Locale locale =
			(this.localeResolver != null ? this.localeResolver.resolveLocale(request) : request.getLocale());
	response.setLocale(locale);

	View view;
	// 获取视图名
	String viewName = mv.getViewName();
	if (viewName != null) {
    
    
		// We need to resolve the view name.
		// 解析视图
		view = resolveViewName(viewName, mv.getModelInternal(), locale, request);

8、解析视图

位置:org.springframework.web.servlet.DispatcherServlet

resolveViewName 方法

/**
 * Resolve the given view name into a View object (to be rendered).
 * <p>The default implementations asks all ViewResolvers of this dispatcher.
 * Can be overridden for custom resolution strategies, potentially based on
 * specific model attributes or request parameters.
 * @param viewName the name of the view to resolve
 * @param model the model to be passed to the view
 * @param locale the current locale
 * @param request current HTTP servlet request
 * @return the View object, or {@code null} if none found
 * @throws Exception if the view cannot be resolved
 * (typically in case of problems creating an actual View object)
 * @see ViewResolver#resolveViewName
 */
@Nullable
protected View resolveViewName(String viewName, @Nullable Map<String, Object> model,
		Locale locale, HttpServletRequest request) throws Exception {
    
    

	if (this.viewResolvers != null) {
    
    
		for (ViewResolver viewResolver : this.viewResolvers) {
    
    
			// 获取视图
			View view = viewResolver.resolveViewName(viewName, locale);
			if (view != null) {
    
    
				return view;
			}
		}
	}
	return null;
}

位置:org.springframework.web.servlet.view.ContentNegotiatingViewResolver

resolveViewName 方法

@Override
@Nullable
public View resolveViewName(String viewName, Locale locale) throws Exception {
    
    
	// 拿到所有请求域中的属性
	RequestAttributes attrs = RequestContextHolder.getRequestAttributes();
	Assert.state(attrs instanceof ServletRequestAttributes, "No current ServletRequestAttributes");
	List<MediaType> requestedMediaTypes = getMediaTypes(((ServletRequestAttributes) attrs).getRequest());
	if (requestedMediaTypes != null) {
    
    
		List<View> candidateViews = getCandidateViews(viewName, locale, requestedMediaTypes);
		// 获取到所有的视图
		View bestView = getBestView(candidateViews, requestedMediaTypes, attrs);
		if (bestView != null) {
    
    
			return bestView;
		}
	}

	String mediaTypeInfo = logger.isDebugEnabled() && requestedMediaTypes != null ?
			" given " + requestedMediaTypes.toString() : "";

	if (this.useNotAcceptableStatusCode) {
    
    
		if (logger.isDebugEnabled()) {
    
    
			logger.debug("Using 406 NOT_ACCEPTABLE" + mediaTypeInfo);
		}
		return NOT_ACCEPTABLE_VIEW;
	}
	else {
    
    
		logger.debug("View remains unresolved" + mediaTypeInfo);
		return null;
	}
}

回到render 方法

位置:org.springframework.web.servlet.DispatcherServlet

		if (view == null) {
    
    
			throw new ServletException("Could not resolve view with name '" + mv.getViewName() +
					"' in servlet with name '" + getServletName() + "'");
		}
	}
	else {
    
    
		// No need to lookup: the ModelAndView object contains the actual View object.
		view = mv.getView();
		if (view == null) {
    
    
			throw new ServletException("ModelAndView [" + mv + "] neither contains a view name nor a " +
					"View object in servlet with name '" + getServletName() + "'");
		}
	}

	// 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.render(mv.getModelInternal(), request, response);
	}
	catch (Exception ex) {
    
    
		if (logger.isDebugEnabled()) {
    
    
			logger.debug("Error rendering view [" + view + "]", ex);
		}
		throw ex;
	}
}

位置:org.springframework.web.servlet.view.AbstractView

render 方法

/**
 * Prepares the view given the specified model, merging it with static
 * attributes and a RequestContext attribute, if necessary.
 * Delegates to renderMergedOutputModel for the actual rendering.
 * @see #renderMergedOutputModel
 */
@Override
public void render(@Nullable Map<String, ?> model, HttpServletRequest request,
		HttpServletResponse response) throws Exception {
    
    

	if (logger.isDebugEnabled()) {
    
    
		logger.debug("View " + formatViewName() +
				", model " + (model != null ? model : Collections.emptyMap()) +
				(this.staticAttributes.isEmpty() ? "" : ", static attributes " + this.staticAttributes));
	}

	// 创建合并输出模型
	Map<String, Object> mergedModel = createMergedOutputModel(model, request, response);

位置:org.springframework.web.servlet.view.AbstractView

createMergedOutputModel 方法

/**
 * Creates a combined output Map (never {@code null}) that includes dynamic values and static attributes.
 * Dynamic values take precedence over static attributes.
 */
protected Map<String, Object> createMergedOutputModel(@Nullable Map<String, ?> model,
		HttpServletRequest request, HttpServletResponse response) {
    
    

	@SuppressWarnings("unchecked")
	Map<String, Object> pathVars = (this.exposePathVariables ?
			(Map<String, Object>) request.getAttribute(View.PATH_VARIABLES) : null);

	// Consolidate static and dynamic model attributes.
	int size = this.staticAttributes.size();
	size += (model != null ? model.size() : 0);
	size += (pathVars != null ? pathVars.size() : 0);

	Map<String, Object> mergedModel = new LinkedHashMap<>(size);
	mergedModel.putAll(this.staticAttributes);
	if (pathVars != null) {
    
    
		mergedModel.putAll(pathVars);
	}
	// 如果model不等于null
	if (model != null) {
    
    
		// 将model中数据放入整合模型
		mergedModel.putAll(model);
	}

	// Expose RequestContext?
	if (this.requestContextAttribute != null) {
    
    
		mergedModel.put(this.requestContextAttribute, createRequestContext(request, response, mergedModel));
	}

	return mergedModel;
}

继续 render 方法

位置:org.springframework.web.servlet.view.AbstractView

	// 准备响应
	prepareResponse(request, response);
	// 渲染合并输出的模型数据
	renderMergedOutputModel(mergedModel, getRequestToExpose(request), response);
}

进入 renderMergedOutputModel 方法

位置:org.springframework.web.servlet.view.InternalResourceView

@Override
protected void renderMergedOutputModel(
		Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception {
    
    

	// Expose the model object as request attributes.
	// 公开model作为请求域属性,将model中的数据遍历存入request域中
	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);
	}
}

猜你喜欢

转载自blog.csdn.net/zhao854116434/article/details/129962707