【尚硅谷】SpringBoot2零基础入门教程-讲师:雷丰阳
笔记
路还在继续,梦还在期许
编写controller类,类上写 @Controller注解,在类中每一个方法上写 @RequestMapping注解,声明当前方法可以处理的请求,这个声明过程称为请求映射。
请求映射中常用的方式是@RequestMapping注解。
开发常使用REST风格编写请求映射。
以前:路径
/getUser:获取用户
/deleteUser:删除用户
/editUser:修改用户
/saveUser:保存用户
现在:路径+请求方式(使用HTTP请求方式动词来表示对资源的操作)
/user:GET-获取用户
/user:DELETE-删除用户
/user:PUT-修改用户
/user:POST-保存用户
浏览器只能发送GET或POST请求,需要后端配合使用过滤器,对请求进行包装。
在web.xml文件中配置过滤器(HiddenHttpMethodFilter)。
使用POST方式提交表单,表单中写隐藏域 _method=请求方式。
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风格
测试页面
测试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-张三";
}
位置: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风格功能是选择性开启。
//在spring boot 配置类中 自定义 hiddenHttpMethodFilter
@Bean
public HiddenHttpMethodFilter hiddenHttpMethodFilter(){
HiddenHttpMethodFilter methodFilter = new HiddenHttpMethodFilter();
// 改成自定义名字
methodFilter.setMethodParam("_m");
return methodFilter;
}
梦回 SpringMVC ,重新讲了一遍 SpringMVC 中 DispatcherServlet 的继承关系与功能的实现。
SpringMVC 功能分析都从 org.springframework.web.servlet.DispatcherServlet 内 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);
位置:org.springframework.web.servlet.DispatcherServlet(IDEA查询文件快捷键:Ctrl+Shift+N)
进入 getHandler 方法
/**
* Return the HandlerExecutionChain for this request.
* 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:保存了首页访问规则
作用:保存了所有@RequestMapping 和handler的映射规则。
根据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;
}
位置: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);
}
}
位置: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();
}
}
位置: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);
}
}
SpringBoot 自动配置欢迎页的 WelcomePageHandlerMapping,访问 /能访问到index.html
SpringBoot 自动配置了默认 的 RequestMappingHandlerMapping
请求进来,挨个尝试所有的 HandlerMapping 看是否有请求信息
我们需要一些自定义的映射处理,我们也可以自己给容器中放HandlerMapping
重新讲了 SpringMVC 获取的请求参数,加入 springboot 矩阵模型和 request域对象共享。
@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.
* 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即可
* 重写内容与方式一相同
*/
}
页面
<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的Cookiea>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=xxxp>
<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 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 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 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 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;
}
}
WebRequest、ServletRequest、MultipartRequest、 HttpSession、javax.servlet.http.PushBuilder、Principal、InputStream、Reader、HttpMethod、Locale、TimeZone、ZoneId
在执行 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;
}
挨个调用参数解析器内部匹配规则,这里查看的是 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);
}
位置: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));
}
位置: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;
}
Map、Model、Errors/BindingResult、RedirectAttributes( 重定向携带数据)、ServletResponse(response)、SessionStatus、UriComponentsBuilder、ServletUriComponentsBuilder
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;
}
开始循环判断可以执行的参数解析器
位置: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;
}
从缓存中获取对应的参数解析器,进入解析器内部方法查看如何解析的。
位置: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;
}
}
位置: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();
}
执行目标方法
位置:org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod
invokeAndHandle 方法
最终目标方法的返回值 “forward:/success”
可以自动类型转换与格式化,可以级联封装。
bean
/**
* 姓名:
* 年龄:
* 生日:
* 宠物姓名:
* 宠物年龄:
*
*/
@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;
}
}
数据绑定:页面提交的请求数据(GET、POST)都可以和对象属性进行绑定
自定义类型参数是由 ServletModelAttributeMethodProcessor(参数解析器)解析的
用户只需要给方法参数位置标记注解,SpringMVC就可以自动在调用目标方法的时候为参数赋值。
分析自动赋值的过程以及源码。
这个类是SpringMVC处理请求的入口,doDispatch方法是核心,将断点打带doDispatch方法中。
位置:org.springframework.web.servlet.DispatcherServlet
doDispatch 方法
/**
* Process the actual dispatching to the handler.
* 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.
*
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);
位置: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注解中映射中心有的映射关系。
位置: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());
为当前的 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 - 支持函数式编程的
(其它的就不在描述)…
位置: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));
}
位置: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");
}
位置: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());
位置: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);
}
位置:org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter
handleInternal 方法
@Nullable
protected abstract ModelAndView handleInternal(HttpServletRequest request,
HttpServletResponse response, HandlerMethod handlerMethod) throws Exception;
位置: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);
位置: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);
}
在目标方法执行前(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;
}
继续 invokeHandlerMethod 方法
位置:org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter
// 返回值处理器:目标方法可以写多少种类型的返回值
if (this.returnValueHandlers != null) {
invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);
}
确定目标方法的返回值类型,并将其放入 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);
位置: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);
位置:org.springframework.web.method.support.InvocableHandlerMethod
invokeForRequest 方法
/**
* Invoke the method after resolving its argument values in the context of the given request.
* 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.
*
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);
}
位置: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.
* 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)) {
位置: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;
}
继续 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);
位置: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);
}
位置: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);
位置: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。
位置: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;
}
继续 getMethodArgumentValues 方法
位置:位置:org.springframework.web.method.support.InvocableHandlerMethod
第一个参数解析出来,继续循环解析下一个参数,知道全部解析完毕。
继续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;
}
自定义类型参数是由 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.
* {@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
定制Converter
未来我们可以给WebDataBinder里面放自己的Converter;
private static final class StringToNumber implements Converter
位置: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;
}
}
位置: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);
}
位置: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());
}
}
将所有的数据都放在 ModelAndViewContainer,包含要去页面地址View和Model数据。
位置:org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter
if (asyncManager.isConcurrentHandlingStarted()) {
return null;
}
// 返回值处理完之后,返回 getModelAndView(获取ModelAndView对象)
return getModelAndView(mavContainer, modelFactory, webRequest);
}
finally {
webRequest.requestCompleted();
}
}
位置: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;
}
位置: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);
}
}
位置:
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());
}
}
}
}
位置: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;
}
位置: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);
}
}
}
}
位置: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.
* 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);
位置:org.springframework.web.servlet.DispatcherServlet
resolveViewName 方法
/**
* Resolve the given view name into a View object (to be rendered).
* 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);
}
}