SpringBoot2基础-请求参数处理和原理
SpringBoot2基础-请求参数处理和原理
tags:
- Spring Boot
- 2021尚硅谷
- 雷丰阳
categories:
- 静态文件配置
- 静态文件配置原理
- 欢迎页和自定义 Favicon
- Rest表单请求原理
- 请求映射原理
- 各种参数使用和原理
文章目录
第一节 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下面一堆配置不生效。
@Override
protected 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/");
// resouser
addResourceHandler(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能处理哪些请求。
@Bean
public 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的。
@Override
protected 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 {
@Bean
public 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,
// 通过原生请求获取request
HttpServletRequest 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 {
@Bean
public HiddenHttpMethodFilter hiddenHttpMethodFilter(){
HiddenHttpMethodFilter methodFilter = new HiddenHttpMethodFilter();
methodFilter.setMethodParam("_m");
return methodFilter;
}
@Bean
public WebMvcConfigurer webMvcConfigurer(){
return new WebMvcConfigurer() {
@Override
public void configurePathMatch(PathMatchConfigurer configurer) {
UrlPathHelper urlPathHelper = new UrlPathHelper();
urlPathHelper.setRemoveSemicolonContent(false);
configurer.setUrlPathHelper(urlPathHelper);
}
};
}
}
// 第二种写法 实现WebMvcConfigurer
@Configuration(proxyBeanMethods = false)
public class WebConfig implements WebMvcConfigurer {
@Bean
public HiddenHttpMethodFilter hiddenHttpMethodFilter(){
HiddenHttpMethodFilter methodFilter = new HiddenHttpMethodFilter();
methodFilter.setMethodParam("_m");
return methodFilter;
}
@Override
public 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对象
- 点击进去发现下面:
@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) && !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"/> 这种对象
@Override
public void addFormatters(FormatterRegistry registry) {
registry.addConverter(new Converter<String, Pet>() {
@Override
public Pet convert(String source) {
// 啊猫,3
if(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;
}
});
}
相关文章
- 前端基础之CSS(1)
- 极客时间-左耳听风-程序员攻略-前端基础和底层原理
- 产品手记|一些基础概念
- redis竟然还有哨兵模式,所以更加不能再扩展了,必须要在已有的基础上进行深挖!!!
- 【基础入门题069】数制转换(九)
- Spark累加器实现原理及基础编程
- Java基础--String类底层实现原理及API示例详解
- SQL Select查询原理--查询语句执行原则(基础)
- Kbone基础 - [05] Kbone + Vue 项目手工搭建流程
- SQL Select查询原理--查询语句执行原则(基础)
- Futter基础第4篇: 实现基础列表组件、 水平列表组件 、 图标组件【ListView、Axis.vertical、Axis.horizontal】
- Kotlin基础从入门到进阶系列讲解(基础篇)Fragment的基本使用
- Database之SQL:RDBMS关系型数据库的简介、SQL语言的简介(原理/各大方言对比)、基础(SQL执行顺序/五大类函数/索引/视图/事务/安全/存储过程/游标/优化)之详细攻略
- 100天精通Python(基础篇)——第29天:标准库sys常用函数、方法(基础+代码实战)
- 零基础想转行从事Python?需要掌握如下技能
- 云小课|MRS基础原理之Hudi介绍
- 云小课|MRS基础原理之CarbonData入门
- 云小课|MRS基础原理之ClickHouse组件介绍
- C#语言基础原理及优缺点
- 机器学习——信息论基础
- 7.第二章 Linux基础入门和帮助 -- Linux 基础和帮助(三)
- Kubernetes基础自学系列 | DaemonSet、Job、CronJob控制器
- Selenium3.0基础 — 自动化测试概述
- (01)ORB-SLAM2源码无死角解析-(40) EPnP 算法原理详解→理论基础四:QR分解(豪斯霍尔德Householder变换)
- (01)ORB-SLAM2源码无死角解析-(38) EPnP 算法原理详解→理论基础二:分情况求得beta初始解
- Java中八种基础类型
- PyTorch目标检测基础<01>:torch基础知识与tensor数据类型