目录
一、请求映射
1.1 Rest风格
1.2 Rest风格的原理
1.3 请求映射的原理
二、参数处理
2.1 普通参数注解
2.2 @MatrixVariable注解
2.3 ServletAPI 类型的参数
2.4 复杂参数
2.5 自定义对象参数
三、参数处理原理
3.1 执行目标方法
3.2 参数解析器-HandlerMethodArgumentResolver
3.3 返回值处理器 HandlerMethodReturnValueHandler
3.4 自定义类型参数封装POJO
3.5 目标方法执行完成
@RequestMapping注解可以用下面几个注解代替
@Getmapping @PostMapping @DeleteMapping @PutMapping
分别代表 获取、保存、删除、修改的请求类型。url采用路径式的风格。
使用RestFul风格提交请求时,要先在配置文件中开启Rest功能,然后表单要加上一个隐藏域,表明请求方式。
application.yml:
spring:
mvc:
hiddenmethod:
filter:
enabled: true #开启页面表单的Rest功能
index.html:
controller:
@RestController
public class testRESTful {
@GetMapping("/user")
public String testGet(){
return "get user";
}
@PostMapping("/user")
public String testPost(){
return "post user";
}
@PutMapping("/user")
public String testPut(){
return "put user";
}
@DeleteMapping("/user")
public String testDelete(){
return "delete user";
}
}
Rest原理(表单提交要使用REST的时候)
过滤器的HttpMethodRequestWrapper重写了getMethod方法,封装了_method的值,这里用到了包装模式,返回的是我们想要的请求类型。
过滤器链放行的时候用wrapper,以后的方法调用getMethod是调用HttpMethodRequestWrapper的,就是包装后的,而不是原生的。这样就可以使用PUT、DELTETE请求啦。
doGet(HttpServlet)->processRequest(HttpServletBean)->
doService(FramworkServlet)->doDispatch(DispatchServlet)
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);
... ...
@PathVariable 将Rest风格路径中动态的参数映射到处理器方法的形参中
@RequestHeader 请求头信息和控制器方法形参创建映射关系
@ModelAttribute
@RequestParam 请求参数和控制器方法形参创建映射关系
@MatrixVariable 获取矩阵变量
@CookieValue cookie数据和控制器方法形参创建映射关系
@RequestBody 获取请求体
@ResponseBody 标注方法,返回值作为响应体
@RequestAttritube 获取request域属性(页面跳转时取域数据)
@PathVariable
@RestController
public class testParam {
@GetMapping("/car/{id}/owner/{username}")
public Map getCar(@PathVariable("id") Integer id,@PathVariable Map vars){
Map map=new HashMap<>();
map.put("id",id);
map.put("vars",vars);
return map;
}
}
@RequestHeader
@RestController
public class testParam {
@GetMapping("/car/{id}/owner/{username}")
public Map getCar(@RequestHeader Map headers,@RequestHeader("User-Agent") String header){
Map map=new HashMap<>();
map.put("header",header);//某个key 对应的 value
map.put("headers",headers);//所有key 对应的 value
return map;
}
}
结果:
{"headers":{"host":"localhost:8080","connection":"keep-alive","sec-ch-ua":"\" Not;A Brand\";v=\"99\", \"Microsoft Edge\";v=\"103\", \"Chromium\";v=\"103\"","sec-ch-ua-mobile":"?0","sec-ch-ua-platform":"\"Windows\"","upgrade-insecure-requests":"1","user-agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.5060.134 Safari/537.36 Edg/103.0.1264.77","accept":"text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9","sec-fetch-site":"same-origin","sec-fetch-mode":"navigate","sec-fetch-user":"?1","sec-fetch-dest":"document","referer":"http://localhost:8080/index.html","accept-encoding":"gzip, deflate, br","accept-language":"zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6","cookie":"Idea-78910da9=e89c6d10-8e05-4fd7-8e94-8d41c1b52e21"},"header":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.5060.134 Safari/537.36 Edg/103.0.1264.77"}
@RequestParam
@RestController
public class testParam {
@GetMapping("/car/{id}/owner/{username}")
public Map getCar(@RequestParam("age") Integer age,@RequestParam Map params){
Map map=new HashMap<>();
map.put("age",age);
map.put("params",params);
return map;
}
}
结果:
{"params":{"age":"18","interest":"rap"},"age":18}
@CookieValue
@RestController
public class testParam {
@GetMapping("/car/{id}/owner/{username}")
public Map getCar(@CookieValue("Idea-78910da9") String c){
Map map=new HashMap<>();
map.put("Idea-78910da9",c);
return map;
}
}
结果:
{"Idea-78910da9":"e89c6d10-8e05-4fd7-8e94-8d41c1b52e21"}
@RestController
public class testParam {
@PostMapping("/car")
public Map getCar(@RequestBody String body){
Map map=new HashMap<>();
map.put("RequestBody",body);
return map;
}
}
结果:
{"RequestBody":"brand=Toyota & model=Camary"}
矩阵变量请求是一种新的请求风格,严格来说矩阵变量的请求需要用到rest风格但是又不同于rest.
//queryString请求方式
/cars?brand=audi&model=A6&price=500000
//REST请求
/cars/audi/A6/500000
//MatrixVariable矩阵变量
/cars;brand=audi;model=A6;price=500000
这个功能默认是关闭的,要在配置类中手动开启:
@Configuration
public class myConfig implements WebMvcConfigurer {
@Override
public void configurePathMatch(PathMatchConfigurer configurer) {
UrlPathHelper urlPathHelper = new UrlPathHelper();
urlPathHelper.setRemoveSemicolonContent(false);//矩阵变量功能生效
configurer.setUrlPathHelper(urlPathHelper);
}
}
下面是遇到一个path时的情况:
@MatrixVariable
@GetMapping("/cars/{path}")
public Map carsSell(@MatrixVariable("brand") String brand,
@MatrixVariable("model") String model,
@MatrixVariable("price") List price,
@PathVariable("path") String path){
Map map = new HashMap<>();
map.put("brand",brand);
map.put("model",model);
map.put("price",price);
map.put("path",path);
return map;
}
结果:
{"path":"sell","price":["500000","4000000"],"model":"A6","brand":"audi"}
下面是遇到多个path时的情况:
@MatrixVariable
@GetMapping("/cars/{id1}/{id2}")
public Map carBrand(@MatrixVariable(value = "brand", pathVar = "id1") String id1brand,
@MatrixVariable(value = "brand", pathVar = "id2") String id2brand){
Map map = new HashMap<>();
map.put("id1brand",id1brand);
map.put("id2brand",id2brand);
return map;
}
结果:
{"id1brand":"audi","id2brand":"benz"}
WebRequest、ServletRequest、MultipartRequest、 HttpSession、javax.servlet.http.PushBuilder、Principal、InputStream、Reader、HttpMethod、Locale、TimeZone、ZoneId
如何获取到这些参数呢?
是ServletRequestMethodArgumentResolver 帮我们解析以上部分参数的。
如Map、Model(map、model里面的数据会被放在request的请求域)、Errors/BindingResult、RedirectAttributes( 重定向携带数据)、ServletResponse(response)、SessionStatus、UriComponentsBuilder、ServletUriComponentsBuilder
Map、Model类型的参数,会返回 mavContainer.getModel();---> BindingAwareModelMap 是ModeMap类型的,最终都会在DispatcherServlet中封装成一个ModelAndView。
如何使用已经在SpringMVC中介绍了。
就是自动将表单提交的内容与javaBean的属性相对应。可以自动类型转换与格式化,可以级联封装。
/**
* 姓名:
* 年龄:
* 生日:
* 宠物姓名:
* 宠物年龄:
*/
@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;
}
// Actually invoke the handler.
//DispatcherServlet -- doDispatch
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
mav = invokeHandlerMethod(request, response, handlerMethod); //执行目标方法
//ServletInvocableHandlerMethod
Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
//获取方法的参数值
Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);
确定将要执行的目标方法的每一个参数的值是什么;SpringMVC目标方法能写多少种参数类型。取决于参数解析器
解析器挨个判断是否支持解析这种参数,如果支持就调用 resolveArgument 方法解析
@Nullable
private HandlerMethodArgumentResolver getArgumentResolver(MethodParameter parameter) {
HandlerMethodArgumentResolver result = this.argumentResolverCache.get(parameter);
if (result == null) {
for (HandlerMethodArgumentResolver resolver : this.argumentResolvers) {
if (resolver.supportsParameter(parameter)) {
result = resolver;
this.argumentResolverCache.put(parameter, result);
break;
}
}
}
return result;
}
这个处理器会根据返回值类型去调用对应的处理类进行处理。
返回值类型 | 处理类 |
---|---|
String(方法上无ResponseBody注解) | ViewNameMethodReturnValueHandler |
View | ViewMethodReturnValueHandler |
ModelAndView | ModelAndViewMethodReturnValueHandle |
Model | ModelMethodProcessor |
Map | MapMethodProcessor |
HttpHeaders | HttpHeadersReturnValueHandler |
ModelAttribute(或者是自定义对象) | ModelAttributeMethodProcessor |
HttpEntity | HttpEntityMethodProcessor |
ResponseEntity | ResponseBodyEmitterReturnValueHandler |
ResponseBody注解 | RequestResponseBodyMethodProcessor |
由ServletModelAttributeMethodProcessor 参数处理器处理,它支持自定义参数绑定,在底层使用了WebDataBinder数据绑定器,而数据绑定器中有一个conversionService,conversionService中注册了很多converters,converters会帮助我们进行类型转换
将所有的数据都放在 ModelAndViewContainer;包含要去的页面地址View,还包含Model数据。接下来还是封装成 ModelAndView,最后处理派发结果,将数据放到请求域中。