请求参数处理
SpringBoot在底层使用SpringMVC完成web请求处理。
⭐️请求映射与Rest风格
形如@xxxMapping
这样的注解用来处理请求映射,最常见的如@RequestMapping
。
SpringBoot现在支持Rest风格,Rest风格使用HTTP请求方式动词来表示对资源的操作,例如:
获取用户 | 删除用户 | 修改用户 | 保存用户 | |
---|---|---|---|---|
传统风格 | getUser | deletUser | updateUser | saveUser |
Rest风格 | GET | DELETE | PUT | POST |
传统命名风格命名繁琐,对每一种行为都要单独命名一次,后期行为功能很多时极其繁琐,推荐使用Rest风格,减少命名负担,按照约定的命名即可。
@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-张三";
}
SpringMVC中使用Rest风格关键是要配置一个核心FilterHiddenHttpMethodFilter
,否则仍然只能处理get和post请求。SpringBoot中已经自动配置,但需要在表单中进行如下配置:
<!-- 测试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"> <!--其他请求方法类型也用post-->
<input name="_method" type="hidden" value="delete"/> <!--关键在这里-->
<input name="_m" 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>
通过对WebMvcAutoConfiguration
源码的分析:
@Bean
@ConditionalOnMissingBean({HiddenHttpMethodFilter.class}) //没有该Bean时
@ConditionalOnProperty( //主配置文件属性定义
prefix = "spring.mvc.hiddenmethod.filter",
name = {"enabled"},
matchIfMissing = false //默认为false
)
public OrderedHiddenHttpMethodFilter hiddenHttpMethodFilter() {
return new OrderedHiddenHttpMethodFilter();
}
可知同时要在主配置文件中手动开启spring.mvc.hiddenmethod.filter=true
。
💡本质上仍然只有get和post请求,只不过配一个过滤器,过滤器根据该表单中隐藏参数的名字去找对应的Rest请求方法名。
表单提交Rest风格请求方法原理
表单提交必须带_method
的值,例如_method=PUT
。表单请求发送到服务器被HiddenHttpMethodFilter
拦截。判断请求是否是POST,服务器获取_method
的值。SpringBoot内部重写了getMethod
方法修改方法名并返回。除GET和POST外还兼容PUT, DELETE, PATCH方法名。
⚠️该原理仅适用于表单提交请求,因为表单仅有put和get。
@GetMapping
可以使用@GetMapping("/user")
替换@RequestMapping("/user", method = RequestMethod.GET)
,类似的Spring还提供@DeleteMapping, @PutMapping, @PostMapping
,效果一样。
修改默认_method名字
容器中的HiddenHttpMethodFilter
中methodParam=_methond
,想要替换的话自己创建一个HiddenHttpMethodFilter
即可,Spring根据条件匹配优先使用用户自己创建的。
package com.atguigu.boot.config;
@Configuration(proxyBeanMethods = false) //单实例
public class WebConfig {
@Bean
public HiddenHttpMethodFilter hiddenHttpMethodFilter(){
HiddenHttpMethodFilter methodFilter = new HiddenHttpMethodFilter();
methodFilter.setMethodParam("_m"); //修改方法名
return methodFilter;
}
}
请求映射原理
看一下SpringBoot中DispatcherServelt
的继承链:
SpringMVC功能分析都从org.springframework.web.servlet.DispatcherServlet
中的doDispatch()
方法出发:
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
HttpServletRequest processedRequest = request;
HandlerExecutionChain mappedHandler = null;
boolean multipartRequestParsed = false;
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
try {
ModelAndView mv = null;
Exception dispatchException = null;
try {
processedRequest = checkMultipart(request);
multipartRequestParsed = (processedRequest != request);
// 找到当前请求使用哪个Handler(Controller的方法)处理
mappedHandler = getHandler(processedRequest);
//HandlerMapping:处理器映射。/xxx->>xxxx
...
所有的请求映射都在HandlerMapping
中。例如RequestMappingHandlerMapping
保存了所有@RequestMapping
和handler
的映射规则:
💡HandlerMapping
其实就保存了每个请求谁来处理。
请求进来,挨个尝试所有的HandlerMapping
看是否有请求信息,如果有就找到这个请求对应的handler,如果没有就继续遍历下一个 HandlerMapping
。
需要自定义映射处理时,也可以自己给容器自定义HandlerMapping
。
⭐️请求传参与基本注解
SpringBoot几乎全注解开发,仅在参数列表中使用注解,SpringBoot就会自动帮这些参数自动确定好值并传入。
基本注解
注解 | 作用 |
---|---|
@PathVariable | 路径变量 |
@RequestHeader | 获取请求头 |
@ModelAttribute | 获取request域属性 |
@RequestParam | 获取请求参数 |
@MatrixVariable | 矩阵变量 |
@CookieValue | 获取cookie值 |
@RequestBody | 获取请求体 |
每个注解具体该怎么用点开注解源码参考其介绍即可:
⭐️@PathVariable
获取路径变量,注意路径变量和请求参数参数做区分。
- 路径变量:
/car/{id}/owner/{username}
- 请求参数:
?car=id&owner=username
@GetMapping("/car/{id}/owner/{username}") //@PathVariable获取路径上的路径变量,可以有多个
public Map<String, Object> getCar(@PathVariable("id") Integer id, @PathVariable("username") String username,
@PathVariable Map<String, String> pv //把所有变量的值封装到Map中
){
Map<String, Object> map = new HashMap<>();
map.put("id", id);
map.put("username", username);
map.put("pv", pv);
return map;
}
@RequestHeader
用来获取请求头相关的信息。
//测试@GetMapping
@GetMapping("/headers")
public Map<String, String> getCar(@RequestHeader("User-Agent")String userAgent, //获取哪个属性就写哪个名字
@RequestHeader Map<String, String> headers){ //把所有请求头相关信息封装到Map中
Map<String, Object> map = new HashMap<>();
map.put("User-Agent", userAgent);
map.put("headers", headers);
return headers;
}
⭐️@RequestParam
用来获取请求参数,如www.xxx.com?car=id&owner=username
。
//测试@RequestParam
@RequestMapping("/params")
public Map<String, Object> testParams(@RequestParam("username") String username, @RequestParam("password") String password, @RequestParam Map<String, String> allParams //获取所有params
){
Map<String, Object> map = new HashMap<>();
map.put("username", username);
map.put("password", password);
map.put("allParams", allParams);
return map;
}
@RequestBody
//测试@RequestBody,表单提交内容放在请求体中
@PostMapping("/save")
public Map postMethod(@RequestBody String content){
Map<String, Object> map = new HashMap<>();
map.put("content", content);
return map;
}
❗️@RequestAttribute
用来获取request域属性,等同于request.getAtribute("xxx");
,常用于页面转发时。
@ResponseBody
@GetMapping("/success")
//请求域中的所有东西都可以使用@RequestAttribute获取
public Map<String, Object> success(@RequestAttribute("msg") String msg, @RequestAttribute int code){
Map<String, Object> map = new HashMap<>();
map.put("msg", msg);
return map;
}
❓@MatrixVariable
矩阵变量注解必须绑定在路径变量中才能被解析,注意域请求参数变量区分:
- 请求参数:
/cars/{path}?xxx=xxx&aaa=ccc
- 矩阵变量:
/cars/sell;low=34;brand=byd,audi,yd
,key=value之间以 ; 分隔,一个key用多个value用 , 分割。
//测试@MatrixVariable,访问语法:http://localhost:8080/cars/sell;low=34;brand=byd,audi,yd
@GetMapping("/cars/{path}")
public Map carSell(@MatrixVariable("low") Integer low, @MatrixVariable("brand") List<String> brand,
@PathVariable("path") String path){
Map<String, Object> map = new HashMap<>();
map.put("low", low);
map.put("brand", brand);
map.put("path", path);
return map;
}
⚠️矩阵变量功能默认关闭,需要自己创建Bean修改配置来开启:
@Bean
public WebMvcConfigurer webMvcConfigurer(){
return new WebMvcConfigurer() {
@Override
public void configurePathMatch(PathMatchConfigurer configurer) {
UrlPathHelper urlPathHelper = new UrlPathHelper();
//开启矩阵变量功能生效
urlPathHelper.setRemoveSemicolonContent(false);
configurer.setUrlPathHelper(urlPathHelper);
}
};
}
常见面试题中的应用
页面开发中cookie禁用了,session里面的内容怎么使用?
原本的过程是session.set(a,b)
后产生jsessionid放在cookie中,每次发请求携带。
禁用后使用矩阵变量实现url重写,例如:/abc;jsesssionid=xxxx
,把cookie的值使用矩阵变量的方式进行传递。
请求传参
除了使用注解来获取各种数据,Spring处理请求的方法的参数列表非常灵活,可以直接传入诸多复杂参数,方便用户操作。
1. 直接传入ServletAPI
请求映射的参数列表可以直接传入Servlet API来获取,包括WebRequest、ServletRequest、MultipartRequest、 HttpSession、javax.servlet.http.PushBuilder、Principal、InputStream、Reader、HttpMethod、Locale、TimeZone、ZoneId
,非常方便。
底层由ServletRequestMethodArgumentResolver
负责解析以上的参数。
💡Spring所有的参数解析工作都由类似xxxxResolver的类来处理
2. 传入复杂参数
例如Map
、Model
(map
、model
里面的数据会被默认放在request的请求域中,相当于 request.setAttribute
)、Errors/BindingResult
、RedirectAttributes
(重定向携带数据)、ServletResponse
、SessionStatus
、UriComponentsBuilder
、ServletUriComponentsBuilder
。
Map<String,Object> map, Model model, HttpServletRequest request
都可以给request
域中放入数据。
3. 传入自定义对象参数
SpringBoot支持直接在请求参数上传入自定义参数,**SpringBoot可以自动类型转换与格式化,可以级联封装。**页面提交的请求数据,无论是GET还是POST方式都可以和对象属性进行绑定。
🈚️参数处理原理
SpringBoot通过注解自动确定参数值的底层原理。
🈚️POJO封装原理
请求传入对象参数自动封装的底层原理。