SpringBoot2基础-请求参数处理和原理
tags:
- Spring Boot
- 2021尚硅谷
- 雷丰阳
categories:
- 静态文件配置
- 静态文件配置原理
- 欢迎页和自定义 Favicon
- Rest表单请求原理
- 请求映射原理
- 各种参数使用和原理
文章目录
- SpringBoot2基础-请求参数处理和原理
- 第一节 [SpringMVC](https://so.csdn.net/so/search?q=SpringMVC&spm=1001.2101.3001.7020)自动配置概览
- 第二节 静态资源访问
- 2.1 静态资源目录
- 2.2 欢迎页和自定义 Favicon
- 2.3 静态资源配置流程
- 2.4 资源处理的默认规则
- 第三节 请求参数处理-请求映射
- 3.1 rest使用与原理
- 3.2 请求映射原理
- 第四节 请求参数处理-普通参数和基本注解
- 4.1 注解方式-常用注解
- 4.2 注解方式- **@RequestAttribute**
- 4.3 注解方式-**矩阵变量@MatrixVariable使用**
- 4.4 各种参数解析原理
- 4.2 Servlet API
- 4.3 复杂参数
- 4.4 自定义类型的参数对象
第一节 SpringMVC自动配置概览
- SpringBoot封装了SpringMVC, SpringBoot中大多场景我们都无需自定义配置。
- SpringBoot自动配置了哪些SpringMVC中哪些东西。官方文档:https://docs.spring.io/spring-boot/docs/current/reference/html/spring-boot-features.html#boot-features-spring-mvc-auto-configuration
- 内容协商视图解析器和BeanName视图解析器
- 静态资源(包括webjars)
- 自动注册 Converter,GenericConverter,Formatter。比如:日期格式等自动转换
- 支持 HttpMessageConverters (后来我们配合内容协商理解原理)
- 自动注册 MessageCodesResolver (国际化用的) 用处不大,如果真的要用到国际化,一般开发两个网站。
- 静态index.html 页支持
- 自定义 Favicon
- 自动使用 ConfigurableWebBindingInitializer ,(DataBinder负责将请求数据绑定到JavaBean上)
- 不用@EnableWebMvc注解。使用 @Configuration + WebMvcConfigurer 自定义规则
- 声明 WebMvcRegistrations 改变默认底层组件
- 使用 @EnableWebMvc+@Configuration+DelegatingWebMvcConfiguration 全面接管SpringMVC
第二节 静态资源访问
- 官方文档:https://docs.spring.io/spring-boot/docs/current/reference/html/spring-boot-features.html#boot-features-spring-mvc-static-content
2.1 静态资源目录
-
只要静态资源放在类路径下: called /static (or /public or /resources or /META-INF/resources
访问 : 当前项目根路径/ + 静态资源名
- static
- public
- resources
- META-INF/resources
-
原理: 请求进来,先去找Controller看能不能处理。不能处理的所有请求又都交给静态资源处理器。静态资源也找不到则响应404页面
-
修改静态资源访问的前缀
。
- 当前项目 + static-path-pattern + 静态资源名 = 静态资源文件夹下找:http://127.0.0.1:8080/res/aaaaa.jpg
-
改变默认的静态资源路径
spring:mvc:static-path-pattern: /res/** # 配置访问前缀web:resources:static-locations: [classpath:/haha/] # 配置静态资源路径,可以写一个数组,也可以只写一个
-
webjar 这个
用的比较少
。相当于jquery弄成一个jar包。通过依赖引用。
- 自动映射 :访问资源http://localhost:8080/webjars/jquery/3.5.1/jquery.js
- https://www.webjars.org/
<dependency><groupId>org.webjars</groupId><artifactId>jquery</artifactId><version>3.5.1</version></dependency>
2.2 欢迎页和自定义 Favicon
- 静态资源路径下 index.html
- 可以配置静态资源路径
- 但是不可以配置静态资源的访问前缀。否则导致 index.html不能被默认访问
- controller能处理/index
spring:
# mvc:
# static-path-pattern: /res/** 这个会导致welcome page功能失效
- 自定义 Favicon
- favicon.ico 放在静态资源目录下即可。
spring:
# mvc:
# static-path-pattern: /res/** 这个会导致自定义 Favicon配置时效
2.3 静态资源配置流程
-
首先,SpringBoot启动默认加载 xxxAutoConfiguration 类(自动配置类)
-
SpringMVC功能的自动配置类 WebMvcAutoConfiguration,判断生效
-
看下给容器中配了什么。
- org\springframework\boot\spring-boot-autoconfigure\2.4.5\spring-boot-autoconfigure-2.4.5-sources.jar!\org\springframework\boot\autoconfigure\web\servlet\WebMvcAutoConfiguration.java
-
配置类中的配置类
如下。配置文件的相关属性和xxx进行了绑定
- 发现WebMvcProperties和spring.mvc配置文件进行绑定、
- 发现ResourceProperties和spring.resources配置进行绑定
@Configuration(proxyBeanMethods = false)@Import(EnableWebMvcConfiguration.class)@EnableConfigurationProperties({ WebMvcProperties.class,org.springframework.boot.autoconfigure.web.ResourceProperties.class, WebProperties.class })@Order(0)public static class WebMvcAutoConfigurationAdapter implements WebMvcConfigurer {
- 拓展知识:如果一个配置类只有一个有参构造器,那么有参构造器所有参数的值都会从容器中确定。
//有参构造器所有参数的值都会从容器中确定
//ResourceProperties resourceProperties;获取和spring.resources绑定的所有的值的对象
//WebMvcProperties mvcProperties 获取和spring.mvc绑定的所有的值的对象
//ListableBeanFactory beanFactory Spring的beanFactory, 容器工厂
//HttpMessageConverters 找到所有的HttpMessageConverters
//ResourceHandlerRegistrationCustomizer 找到资源处理器的自定义器。===着重===
//DispatcherServletPath DispatcherServlet处理的路径
//ServletRegistrationBean 给应用注册Servlet、Filter....public WebMvcAutoConfigurationAdapter(ResourceProperties resourceProperties, WebMvcProperties mvcProperties,ListableBeanFactory beanFactory, ObjectProvider<HttpMessageConverters> messageConvertersProvider,ObjectProvider<ResourceHandlerRegistrationCustomizer> resourceHandlerRegistrationCustomizerProvider,ObjectProvider<DispatcherServletPath> dispatcherServletPath,ObjectProvider<ServletRegistrationBean<?>> servletRegistrations) {this.resourceProperties = resourceProperties;this.mvcProperties = mvcProperties;this.beanFactory = beanFactory;this.messageConvertersProvider = messageConvertersProvider;this.resourceHandlerRegistrationCustomizer = resourceHandlerRegistrationCustomizerProvider.getIfAvailable();this.dispatcherServletPath = dispatcherServletPath;this.servletRegistrations = servletRegistrations;}
- 代码朝下找:找到函数addResourceHandlers 它处理资源处理的默认规则
2.4 资源处理的默认规则
- 函数addResourceHandlers, 在if上加断点看下默认规则怎么生效的。(我的是最新的框架,一些函数可能和视屏不一致)
- 上面从容器中获得的这个resourceProperties(resource的配置文件),有isAddMappings属性。到配置文件中测试下这个属性的作用。
- 发现默认为True, 如果配置成False下面一堆配置不生效。
@Overrideprotected void addResourceHandlers(ResourceHandlerRegistry registry) {super.addResourceHandlers(registry);if (!this.resourceProperties.isAddMappings()) {logger.debug("Default resource handling disabled");return;}ServletContext servletContext = getServletContext();// webjars的规则addResourceHandler(registry, "/webjars/**", "classpath:/META-INF/resources/webjars/");// resouseraddResourceHandler(registry, this.mvcProperties.getStaticPathPattern(), (registration) -> {registration.addResourceLocations(this.resourceProperties.getStaticLocations());if (servletContext != null) {registration.addResourceLocations(new ServletContextResource(servletContext, SERVLET_LOCATION));}});}
web:resources:static-locations: [classpath:/haha/]add-mappings: false # 禁用所有的静态资源配置cache:period: 11000 # 配置静态资源的缓存时间 以秒为单位
- 它调用了addResourceHandler,这个里面设置了静态资源的缓存。
- 找静态资源的默认位置。resourceProperties.getStaticLocations()
private static final String[] CLASSPATH_RESOURCE_LOCATIONS = { "classpath:/META-INF/resources/","classpath:/resources/", "classpath:/static/", "classpath:/public/" };
- 欢迎页面的函数WelcomePageHandlerMapping
// HandlerMapping:处理器映射。保存了每一个Handler能处理哪些请求。@Beanpublic WelcomePageHandlerMapping welcomePageHandlerMapping(ApplicationContext applicationContext,FormattingConversionService mvcConversionService, ResourceUrlProvider mvcResourceUrlProvider) {WelcomePageHandlerMapping welcomePageHandlerMapping = new WelcomePageHandlerMapping(new TemplateAvailabilityProviders(applicationContext), applicationContext, getWelcomePage(),this.mvcProperties.getStaticPathPattern());welcomePageHandlerMapping.setInterceptors(getInterceptors(mvcConversionService, mvcResourceUrlProvider));welcomePageHandlerMapping.setCorsConfigurations(getCorsConfigurations());return welcomePageHandlerMapping;}// 上面代码点入这个WelcomePageHandlerMapping, 第一个if也解释了上面定义路径欢迎页找不到因为 "/**".equals(staticPathPattern)WelcomePageHandlerMapping(TemplateAvailabilityProviders templateAvailabilityProviders,ApplicationContext applicationContext, Resource welcomePage, String staticPathPattern) {if (welcomePage != null && "/**".equals(staticPathPattern)) {logger.info("Adding welcome page: " + welcomePage);setRootViewName("forward:index.html");}else if (welcomeTemplateExists(templateAvailabilityProviders, applicationContext)) {logger.info("Adding welcome page template: index");setRootViewName("index");}}
- favicon这个和我们的代码没什么关系了,浏览器会默认发/favicon.ico
第三节 请求参数处理-请求映射
3.1 rest使用与原理
-
表单Rest风格支持(使用HTTP请求方式动词来表示对资源的操作)
- 以前:/getUser 获取用户 /deleteUser 删除用户 /editUser 修改用户 /saveUser 保存用户
- 现在: /user GET-获取用户 DELETE-删除用户 PUT-修改用户 POST-保存用户
-
前端表单中的method并不支持delete和put, 默认用get处理
,如何让它可以处理我们
表单
中的delete和put。
- 第一步:给我们带一个DEFAULT_METHOD_PARAM = "_method"的隐藏字段可以。
- 第二步:开启配置手动开启,hiddenHttpMethodFilter配置(默认不开启)
<form action="/user" method="post"><input name="_method" type="hidden" value="delete"/><input value="REST-DELETE 提交" type="submit"/>
</form>
<form action="/user" method="post"><input name="_method" type="hidden" value="PUT"/><input value="REST-PUT 提交" type="submit"/>
</form>
12345678
# 开启配置手动开启mvc:hiddenmethod:filter:enabled: true
- 函数流程:WebMvcAutoConfiguration中找到hiddenHttpMethodFilter,点击去->在点到父类中。找到org.springframework.web.filter.HiddenHttpMethodFilter的DEFAULT_METHOD_PARAM配置。
// 测试代码//@RequestMapping(value = "/user",method = RequestMethod.GET)// 简写@GetMapping("/user")public String getUser(){return "GET-张三";}//@RequestMapping(value = "/user",method = RequestMethod.POST)// 简写//@PostMapping("/user")public String saveUser(){return "POST-张三";}//@RequestMapping(value = "/user",method = RequestMethod.PUT)// 简写@PutMapping("/user")public String putUser(){return "PUT-张三";}//@RequestMapping(value = "/user",method = RequestMethod.DELETE)// 简写@DeleteMapping("/user")public String deleteUser(){return "DELETE-张三";}
- Rest原理(表单提交要使用REST的时候)
- 表单提交会带上_method=PUT
- 请求过来被HiddenHttpMethodFilter拦截
- 请求是否正常,并且是POST
- 获取到_method的值。兼容以下请求;PUT.DELETE.PATCH (传过来的值不区分大小写)
- 原生request(post),包装模式requesWrapper重写了getMethod方法,返回的是传入的值。
- 过滤器链放行的时候用wrapper。以后的方法调用getMethod是调用requesWrapper的。
@Overrideprotected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)throws ServletException, IOException {HttpServletRequest requestToUse = request;// 这里判断必须用POST提交 而且没有错误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)) {// 包装模式requesWrappe重写了getMethod方法requestToUse = new HttpMethodRequestWrapper(request, method);}}}// 过滤器链放行的时候用wrapper。以后的方法调用getMethod是调用requesWrapper的filterChain.doFilter(requestToUse, response);}
-
Rest使用客户端工具
,上面只是指的是
表单请求
- 如PostMan直接发送Put、delete等方式请求,无需Filter
- 所以它选择开启,我们实际不会用它做页面。都是前后端分离的模式,页面都是别人写的,只要掉我们接口就可以了。
-
如果我们不想用_method而想,自己定义一个隐藏字段比如:_m。写一个配置类修改,重新启动工程。
package com.atguigu.boot.config;import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.filter.HiddenHttpMethodFilter;// 没有依赖 效率高
@Configuration(proxyBeanMethods = false)
public class WebConfig {@Beanpublic HiddenHttpMethodFilter hiddenHttpMethodFilter(){HiddenHttpMethodFilter methodFilter = new HiddenHttpMethodFilter();// 修改成_m 之前的_method就不可以用了methodFilter.setMethodParam("_m");return methodFilter;}
}
3.2 请求映射原理
- CTRL+N全局搜索类DispatcherServlet,CTRL+H搜索继承树。去找重写原生HTTPServerlet的doget和dopost请求的方法。在FrameworkServlet中可以找到。
- 调用过程:doGet -> processRequest -> doService(抽象) -> 子类的doService(DispatcherServlet) -> doDispatch(这才是我们要研究的方法,每个请求都经过它)
- SpringMVC功能分析都从 org.springframework.web.servlet.DispatcherServlet的doDispatch方法。断点打在这个函数上。运行。
- 发现:getHandler(processedRequest)是确定请求用哪个Handler处理器处理的方法
- 点击去:this.handlerMappings处理器映射(所有的请求映射都在HandlerMapping中),默认有五个。展开后可以看到一些细节。
- 0中是RequestMappingHandlerMapping:保存了所有@RequestMapping 和handler的映射规则。
- 1中是SpringBoot自动配置欢迎页的 WelcomePageHandlerMapping 。访问 /能访问到index.html;
- 配置类org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration中。requestMappingHandlerMapping注册了处理标了注解的方法。
- 请求进来,挨个尝试所有的HandlerMapping看是否有请求信息。
- 如果有就找到这个请求对应的handler
- 如果没有就是下一个 HandlerMapping
- 我们需要一些自定义的映射处理,我们也可以自己给容器中放HandlerMapping。自定义 HandlerMapping
第四节 请求参数处理-普通参数和基本注解
4.1 注解方式-常用注解
- @PathVariable、@RequestHeader、@ModelAttribute、@RequestParam、@MatrixVariable、@CookieValue、@RequestBody
// car/2/owner/zhangsan@GetMapping("/car/{id}/owner/{username}")public Map<String, Object> getCar(// 路径变量id@PathVariable("id") Integer id,// 路径变量username@PathVariable("username") String name,// 获取所有的路径变量@PathVariable Map<String,String> pv,//获取请求头中User-Agent@RequestHeader("User-Agent") String userAgent,//获取所有请求头@RequestHeader Map<String,String> header,// 获取请求参数age ?age=18@RequestParam("age") Integer age,// 获取请求参数inters@RequestParam("inters") List<String> inters,// 获取所有的请求参数@RequestParam Map<String,String> params,// 获取cookie中的_ga的值@CookieValue("_ga") String _ga,// 获取cookie中所有信息@CookieValue("_ga") Cookie cookie){Map<String, Object> map = new HashMap<>();// map.put("id",id);
// map.put("name",name);
// map.put("pv",pv);
// map.put("userAgent",userAgent);
// map.put("headers",header);map.put("age",age);map.put("inters",inters);map.put("params",params);map.put("_ga",_ga);System.out.println(cookie.getName()+"===>"+cookie.getValue());return map;}// 获取请求体中的值, 只有Post方式有请求体@PostMapping("/save")public Map postMethod(@RequestBody String content){Map<String,Object> map = new HashMap<>();map.put("content",content);return map;}
4.2 注解方式- @RequestAttribute
- @RequestAttribute
@Controller // 普通的控制器,方法的返回 是要进行跳转的
public class RequestController {@GetMapping("/goto")public String goToPage(HttpServletRequest request){request.setAttribute("msg","成功了...");request.setAttribute("code",200);return "forward:/success"; //转发到 /success请求}@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 success(// 获取请求域中的msg 上面转发的时设置的属性 required = false请求域中这个属性不是必须的@RequestAttribute(value = "msg",required = false) String msg,// 这个没写可以获取全部属性 点进去看看@RequestAttribute(value = "code",required = false)Integer code,// 通过原生请求获取requestHttpServletRequest request){Object msg1 = request.getAttribute("msg");Map<String,Object> map = new HashMap<>();Object hello = request.getAttribute("hello");Object world = request.getAttribute("world");Object message = request.getAttribute("message");map.put("reqMethod_msg",msg1);map.put("annotation_msg",msg);map.put("hello",hello);map.put("world",world);map.put("message",message);return map;}
}
4.3 注解方式-矩阵变量@MatrixVariable使用
-
矩阵变量@MatrixVariable使用
-
语法: 请求路径:/cars/sell;low=34;brand=byd,audi,yd
-
面试题:页面开发,cookie禁用了,session里面的内容怎么使用;
- 默认:session.set(a,b)—> jsessionid —> cookie ----> 每次发请求携带。
- 可以使用url重写:/abc;jsesssionid=xxxx 把cookie的值使用矩阵变量的方式进行传递.
-
SpringBoot默认是禁用了矩阵变量的功能
.
- 自动配置类中configurePathMatch这个进行处理。
- 手动开启:原理。对于路径的处理。UrlPathHelper进行解析,点进去发现。removeSemicolonContent = true;(移除分号内容)支持矩阵变量。
-
<a href="/cars/sell;low=34;brand=byd,audi,yd">@MatrixVariable(矩阵变量)</a>
<a href="/cars/sell;low=34;brand=byd;brand=audi;brand=yd">@MatrixVariable(矩阵变量)</a>
<a href="/boss/1;age=20/2;age=10">@MatrixVariable(矩阵变量)/boss/{bossId}/{empId}</a>
- 手动开启矩阵变量的功能,两种方式
- 第一种写法:@Bean 给容器中直接放入WebMvcConfigurer组件
- 第二种写法:实现WebMvcConfigurer,因为有默认实现只用修改需要修改的方法。
// 第一种写法: 给容器中放入WebMvcConfigurer组件
@Configuration(proxyBeanMethods = false)
public class WebConfig {@Beanpublic HiddenHttpMethodFilter hiddenHttpMethodFilter(){HiddenHttpMethodFilter methodFilter = new HiddenHttpMethodFilter();methodFilter.setMethodParam("_m");return methodFilter;}@Beanpublic WebMvcConfigurer webMvcConfigurer(){return new WebMvcConfigurer() {@Overridepublic void configurePathMatch(PathMatchConfigurer configurer) {UrlPathHelper urlPathHelper = new UrlPathHelper();urlPathHelper.setRemoveSemicolonContent(false);configurer.setUrlPathHelper(urlPathHelper);}};}
}// 第二种写法 实现WebMvcConfigurer
@Configuration(proxyBeanMethods = false)
public class WebConfig implements WebMvcConfigurer {@Beanpublic HiddenHttpMethodFilter hiddenHttpMethodFilter(){HiddenHttpMethodFilter methodFilter = new HiddenHttpMethodFilter();methodFilter.setMethodParam("_m");return methodFilter;}@Overridepublic void configurePathMatch(PathMatchConfigurer configurer) {UrlPathHelper urlPathHelper = new UrlPathHelper();urlPathHelper.setRemoveSemicolonContent(false);configurer.setUrlPathHelper(urlPathHelper);}
}
- 矩阵变量必须有url路径变量才能被解析:要写成路径变量的表示方法{path}
@GetMapping("/cars/{path}")public Map carsSell(@MatrixVariable("low") Integer low,@MatrixVariable("brand") List<String> brand,// 获取真正的访问路径就是sell@PathVariable("path") String path){Map<String,Object> map = new HashMap<>();map.put("low",low);map.put("brand",brand);map.put("path",path);return map;}// /boss/1;age=20/2;age=10 两个路径变量 每个路径变量上有相同的变量名称@GetMapping("/boss/{bossId}/{empId}")public Map boss(@MatrixVariable(value = "age",pathVar = "bossId") Integer bossAge,@MatrixVariable(value = "age",pathVar = "empId") Integer empAge){Map<String,Object> map = new HashMap<>();map.put("bossAge",bossAge);map.put("empAge",empAge);return map;}
4.4 各种参数解析原理
-
初始:和之前一样依旧CTRL+N, 搜org.springframework.web.servlet.DispatcherServlet下的doDispatch。打断点调试。
- http://127.0.0.1:8080/car/3/owner/lisi?age=18&inters=basketball&inters=game
-
第一步:mappedHandler = getHandler(processedRequest);点进去,发现HandlerMapping中找到能处理请求的Handler(Controller.method())
- mappedHandler = getHandler(processedRequest);
-
第二步:为当前Handler 找一个适配器 HandlerAdapter; RequestMappingHandlerAdapter
-
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
-
HandlerAdapter是SpringMVC底层设计的接口。两个重要函数1. 支持接口,2. 调用处理
-
getHandlerAdapter点击去,在所有的HandlerAdapter中
确定共有四种
。
- 0 - 支持方法上标注@RequestMapping (默认)
- 1 - 支持函数式编程的
- xxxxxx
-
-
第三步:执行目标方法. 还是在DispatcherServlet中的 doDispatch
- mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
- 先追综getHandle,在连续追踪handle。找到org.springframework.web.servlet.ModelAndView。
- 执行目标方法:mav = invokeHandlerMethod(request, response, handlerMethod);
- 继续追进去看下目标方法怎么执行。发现字段,发现参数解析器和返回值处理器
- argumentResolvers参数解析器。27种
- 确定将要执行的目标方法的每一个参数的值是什么;
- SpringMVC目标方法能写多少种参数类型。取决于参数解析器。
- returnValueHandlers返回值处理器。15种
-
第四步:真正执行目标方法。
- 通过把invocableMethod.invokeAndHandle(webRequest, mavContainer);。继续点进去。
- Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);这个会先回到我们的Controller然后在回到下面函数。setResponseStatus(webRequest);
- 继续点进去invokeForRequest看怎么执行控制器方法的。
- 获取方法参数值:Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);
- 继续点进去getMethodArgumentValues可以看到:如何获取参数值
- 继续点进去invokeForRequest看怎么执行控制器方法的。
-
第五步:如何获取参数值。
- 获取每个参数的详细信息:MethodParameter[] parameters = getMethodParameters();
- 声明一个参数长度相同的args,最终把它返回。Object[] args = new Object[parameters.length];
- 遍历参数:确定每一个参数参数解析器是否支持。if (!this.resolvers.supportsParameter(parameter)) 。具体是增强for循环一个个判断所有参数解析器的。
- 解析参数值:确定参数支持后通过args[i] = this.resolvers.resolveArgument(parameter, mavContainer, request, this.dataBinderFactory);解析参数值。点进去。
- AbstractNamedValueMethodArgumentResolver中resolveArgument是具体解析函数。
-
第六步:目标方法执行完成
- 将所有的数据都放在mavContainer;包含要去的页面地址View。还包含Model数据
- 将所有的数据都放在mavContainer;包含要去的页面地址View。还包含Model数据
-
第七步:处理派发结果
- org.springframework.web.servlet.DispatcherServlet#processDispatchResult
- processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
- 一直追踪到视图解析方法:InternalResourceView:
-org.springframework.web.servlet.view.InternalResourceView#renderMergedOutputModel
4.2 Servlet API
- WebRequest、ServletRequest、MultipartRequest、 HttpSession、javax.servlet.http.PushBuilder、Principal、InputStream、Reader、HttpMethod、Locale、TimeZone、ZoneId
- 如下函数参数就是:HttpServletRequest request 类型。
@GetMapping("/goto")public String goToPage(HttpServletRequest request){request.setAttribute("msg","成功了...");request.setAttribute("code",200);return "forward:/success"; //转发到 /success请求}
- 访问路径打断点。看下流程。
- org.springframework.web.method.support.HandlerMethodArgumentResolverComposite#getArgumentResolver中解析到ServletRequestMethodArgumentResolver对象
- 点击进去发现下面:
@Overridepublic 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) && !parameter.hasParameterAnnotations()) ||InputStream.class.isAssignableFrom(paramType) ||Reader.class.isAssignableFrom(paramType) ||HttpMethod.class == paramType ||Locale.class == paramType ||TimeZone.class == paramType ||ZoneId.class == paramType);}
4.3 复杂参数
- Map、Model(map、model里面的数据会被放在request的请求域 request.setAttribute)、Errors/BindingResult、RedirectAttributes( 重定向携带数据)、ServletResponse(response)、SessionStatus、UriComponentsBuilder、ServletUriComponentsBuilder
// Map<String,Object> map, Model model, HttpServletRequest request 都是可以给request域中放数据,最后通过request.getAttribute()获取@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";}
- 和上面分析一样断点追踪:
- Map、Model类型的参数,底层都会返回 mavContainer.getModel();
- BindingAwareModelMap 是Model 也是Map,mavContainer.getModel(); 获取到值的
4.4 自定义类型的参数对象
- 可以自动类型转换与格式化,可以级联封装。
package com.atguigu.boot.bean;import lombok.Data;import java.util.Date;/*** 姓名: <input name="userName"/> <br/>* 年龄: <input name="age"/> <br/>* 生日: <input name="birth"/> <br/>* 宠物姓名:<input name="pet.name"/><br/>* 宠物年龄:<input name="pet.age"/>*/
@Data
public class Person {private String userName;private Integer age;private Date birth;private Pet pet;}@Data
public class Pet {private String name;private Integer age;}
- 先运行一下让index.html出来,在加断点点击表单请求。断点看下原理, Person怎么把页面中的数据跟我们Person的每个属性一一绑定。
@PostMapping("/saveuser")public Person saveuser(Person person){return person;}
-
确定是ServletModelAttributeMethodProcessor这个参数处理器完成的。
-
WebDataBinder :web数据绑定器
,将请求参数的值绑定到指定的JavaBean里面
- WebDataBinder binder = binderFactory.createBinder(webRequest, attribute, name);
- WebDataBinder 利用它里面的 Converters 将请求数据转成指定的数据类型。再次封装到JavaBean中
- GenericConversionService:在设置每一个值的时候,找它里面的所有converter那个可以将这个数据类型(request带来参数的字符串)转换到指定的类型(JavaBean – Integer)
byte – > file
-
添加自定义转换器让它可以识别 这种对象
// 添加自定义转换器让它可以识别 <input name="pet" value="啊猫,3"/> 这种对象@Overridepublic void addFormatters(FormatterRegistry registry) {registry.addConverter(new Converter<String, Pet>() {@Overridepublic Pet convert(String source) {// 啊猫,3if(StringUtils.hasLength(source)){Pet pet = new Pet();String[] split = source.split(",");pet.setName(split[0]);pet.setAge(Integer.parseInt(split[1]));return pet;}return null;}});}