Web开发之请求参数处理篇
1、Rest风格支持
1.1、对Rest风格的理解和使用默认的hiddenMethodFilter
- 使用http 请求方式动词来表示对资源的操作
- 以前:/getUser 获取用户; /deleteUser 删除用户;editUser 修改用户;/saveUser 保存用户
- 现在:/user GET-获取用户 ;DELETE-删除用户;PUT-修改用户;POST-保存用户
- 但是如果想让前端表单传递的方式存在 put 和 delete(因为表单提交只有 get 和 post)
- 需要添加一个 核心 filter (HiddenHttpMethodFilter)
- 用法:表单 method=post,隐藏域 _menthod=put
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kK22sMzr-1691736513763)(C:\Users\ASUS\Desktop\java\7_springboot核心技术\image\1669122749806.png)]
- 但是 在WebMvcAutoConfiguration 配置类中已经配置了这个 filter,需要满足两个条件!!
- 条件一:需要在配置文件中配置这个属性为 true(手动开启:spring.mvc.hiddenmethod.filter.enabled = true)
- 条件二:表单的 method 的方式必须为 post,然后再定义一个属性为==_method,然后该属性就可以定义实际的提交方式==
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-l5hCmJAx-1691736513764)(C:\Users\ASUS\Desktop\java\7_springboot核心技术\image\1669122887412.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-y7s9qAmz-1691736513764)(C:\Users\ASUS\Desktop\java\7_springboot核心技术\image\1669123684757.png)]
- 再核心配置文件中配置开启 filter
# 开启页面表达的 rest 功能
spring:
mvc:
hiddenmethod:
filter:
enabled: true
- 前端使用 delete 和 put 请求方式
- 1:form 表单 method 方式一定 为 post
- 2:通过 name=_method 属性指定真正的请求方式
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LwtxHGlc-1691736513765)(C:\Users\ASUS\Desktop\java\7_springboot核心技术\image\1669124009439.png)]
- Rest原理(表单)(doFilterInternal()方法)
- 1:表单提交带上 _method=delete
- 2:请求过来被 filter 拦截
- 看是否为 post 请求,并且程序没错
- 3:获取 _method 属性值,转大写,然后看 ALLOWED_METHODS 数组是否存在我们的方式
- ALLOWED_METHODS 兼容:GET,POST,PATCH
- 4:原生 reques(post),包装模式 requestWrapper 重写了 getMethod()方法,返回传入的值(DELETE)
- 5:再将过滤器放行 filterChain.doFilter(requestToUse, response);
- 6:因为重写了 getMethod()方法,然后后者调用这个方法的时候,会获取到我们_method 传入的值
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HWIAk06I-1691736513766)(C:\Users\ASUS\Desktop\java\7_springboot核心技术\image\1669124739941.png)]
1.2、自定义hiddenMethodFilter
@Configuration(proxyBeanMethods = false)
public class WebConfig {
@Bean
public HiddenHttpMethodFilter hiddenHttpMethodFilter() {
HiddenHttpMethodFilter methodFilter = new HiddenHttpMethodFilter();
//这里就设置了前端 name 属性应该为 _m,而不再是 _method
methodFilter.setMethodParam("_m");
return methodFilter;
}
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vGlyLjvz-1691736513767)(C:\Users\ASUS\Desktop\java\7_springboot核心技术\image\1669125859379.png)]
2、请求映射原理
- 问题:每次发请求,springboot 是怎么找到使用哪个方法来处理这个请求?
- 我们知道springBoot中所有的请求都会来到 DispatherServlet ,因为 springboot 底层还是使用的是 springmvc ;**所以 DispatherServlet 是处理所有请求的开始
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NKoCrXia-1691736513767)(C:\Users\ASUS\Desktop\java\7_springboot核心技术\image\1669128101704.png)]
- 所以每一个请求进来,都会调用 doDispather(request,response)方法,这个就是所有请求的入口方法==
- handler:就是我们编写的 controlle
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
HttpServletRequest processedRequest = request;
//HandlerExecutionChain 程序执行链
HandlerExecutionChain mappedHandler = null;
//是否为文件上传请求,默认为false
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);
if (mappedHandler == null) {
noHandlerFound(processedRequest, response);
return;
}
- getHandler()方法源码
@Nullable
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
if (this.handlerMappings != null) {
for (HandlerMapping mapping : this.handlerMappings) {
HandlerExecutionChain handler = mapping.getHandler(request);
if (handler != null) {
return handler;
}
}
}
return null;
}
//this.handlerMappings==>处理器集合映射(/xxxx请求 被 xxxx处理器处理) 现在这个集合中有5个 handlerMappings
//1: RequestMappingHandlerMapping 保存了@RequestMapping 和 handler 的映射规则
//2: WelcomePageHandlerMapping 保存欢迎页处理映射规则
//3: BeanNameUrlHandlerMapping
//4:RouterFunctionMapping
//5:SimpleUrlHandlerMapping
- RequestMappingHandlerMapping 中的处理映射规则
- 所有的请求映射都在 handlerMapping中
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gCbGIbdY-1691736513768)(C:\Users\ASUS\Desktop\java\7_springboot核心技术\image\1669186162316.png)]
-
请求映射的流程:
- 1:通过访问的路径,遍历每个 handlerMapping,然后找到 合适的处理器去处理
-
再WebMvcAutoConfiguration 中,springboot为我们自动装配了 RequestMappingHandlerMapping 和WelcomePageHandlerMapping
- 所有如果我们如果想自定义一些处理请求,我们也可以给容器中添加 handlerMapping
3、Springmvc普通参数与参数注解
3.1、注解
(这些注解都基本上可以都写一个 map,可以将全部属性封装到里面(Map(String,String))
- @PathVariable(路径变量)
@GetMapping("/car/{id}/owner/{username}")
public Map<String, Object> getCar(@PathVariable("id") Integer id,
@PathVariable("username") String name,
@PathVariable Map<String, String> pv) {}
//访问请求为 localhost:8080/car/1/owner/张三
//则 1 为 id,会赋给 id
//张三为 username 会赋值给 name
//其中 这个 hashmap的pv 属性会封装所有的值
{"pv":{"id":"1","username":"张三"},"id":1,"username":"张三"}
- @RequestHeader(获取请求头)
@GetMapping("/header")
public Map<String, Object> getHeader(@RequestHeader Map<String, String> header) {
Map<String, Object> map = new HashMap<>();
map.put("header", header);
return map;
}
//获取所有的请求头的信息,并且封装到 header中,map的key和value必须都为 String
//想获取单个 @RequestHeader("host") String host
- @RequestParam(获取请求参数)(get和post请求都可以获取)
@GetMapping("/param")
public Map<String, Object> getParam(@RequestParam("name") String username, @RequestParam("inter") List<String> list,
@RequestParam Map<String, String> hasMap) {
Map<String, Object> map = new HashMap<>();
map.put("username",username);
map.put("interList",list);
map.put("hasMap",hasMap);
return map;
}
//请求参数为 localhost:8080/param?name=张三&inter=喝酒&inter=烫头
//name会封装到 username中
//所有 inter 属性会封装到 list<String> 中
//hasMap 会将所有的参数和第一个属性值
//{"interList":["抽烟","喝酒"],"hasMap":{"name":"张三","inter":"抽烟"},"username":"张三"}
- @MatrixVariable(矩阵变量)
- 注意:我们的矩阵变量是一定要绑定在路径之中的,也就是需要写路径变量({xxxx})
- SpringBoot默认禁用调了 矩阵变量的功能,所以我们需要手动开启这个功能
- 对于所有的路径处理,都是通过 UrlPathHelper 进行解析的,但是这个类中存在一个属性为 removeSemicolonContent (删除分号之后的内容)默认为 true(这个属性就是来支持矩阵变量的)
- 所有我们需要自定义自己的 configurePathMatch(配置路径匹配)
@Configuration(proxyBeanMethods = false)
public class WebConfig implements WebMvcConfigurer {
@Override
public void configurePathMatch(PathMatchConfigurer configurer) {
UrlPathHelper urlPathHelper=new UrlPathHelper();
urlPathHelper.setRemoveSemicolonContent(false); //设置为不移除分号之后的内容
configurer.setUrlPathHelper(urlPathHelper);
}
}
//比如在cookie禁用的时候,需要找到session则可以使用 /abc;jsessionId=xxxxx(重写url可以解决这个问题)
/boos/1/2 ====> /boos/1;age=20/2;name=12;name=15(其中;前是请求路径,分号后是矩阵变量);
前端:<a href="/cars/shell;low=34;brand=byd,bmw"/>
后端:/cars/{path};
//我们的矩阵变量是一定要绑定在路径之中的,所有这里的不能写成@GetMapping("/cars/shell")
@GetMapping("/cars/{path}")
public Map<String, Object> getMax(@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; //{"path":"shell","low":34,"brand":["byd","bmw"]}
}
//如果矩阵变量出现重复的请求,我们需要使用 pathVar属性指定获取哪一个 {}的属性
前端:<a href="/boss/1;age=20/2;age=20"></a>
后端:/boos/{boosId}/{empId}
@GetMapping("/boos/{boosId}/{empId}")
public Map<String,Object> getAge(@PathVariable("boosId") String boosId,
@PathVariable("empId") String empId,
@MatrixVariable(value = "age",pathVar = "boosId") Integer boosAge,
@MatrixVariable(value = "age",pathVar = "empId") Integer empAge){
Map<String, Object> map = new HashMap<>();
map.put("boosId", boosId);
map.put("empId", empId);
map.put("boosAge", boosAge);
map.put("empAge", empAge);
return map;
}
- @CookieValue(获取cookie的值)
@GetMapping("/cookie")
public Map<String, Object> getCookie(@CookieValue("_ga") String _ga,@CookieValue("_ga") Cookie cookie) {}
//Cookie这个对象会获取到请求头传入的所有的 cookie信息
- @RequestBody(获取请求体【Post请求体中的数据】)
@PostMapping("/post")
public Map<String, Object> getPost(@RequestBody String postBody) {
Map<String, Object> map = new HashMap<>();
map.put("postBody",postBody);
return map;
}
//获取post请求体里的内容
//{"postBody":"java=usernameJava&css=usernameCss"}
- @RequestAttribute(获取request中的数据)(页面转发)
3.2、参数获取原理
- HandlerMapping 中找到能处理请求的 controller(也就是 controller 的方法)
- 通过当前 handler 找到一个 适配器(handlerAdapter)
//全部内容在 DispacherServlet 的 doDispacher()方法
protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
if (this.handlerAdapters != null) {
for (HandlerAdapter adapter : this.handlerAdapters) {
if (adapter.supports(handler)) {
return adapter;
}
}
}
}
handlerAdapters 集合包括;
1:RequestMappingHandlerAdapter:支持方法上标注 @RequestMapping;
2:HandlerFunctionAdapter:支持函数式编程,就是容器中controller声明了很多函数式方法
3:HttpRequestHandlerAdapter;
4:SimpleControllerHandlerAdapter;
adapter.supports(handler) 通过这个方法,看这个handler是否可以被 adapter处理;
//实际上调用处理程序。
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
//在深入handler方法就是(执行handler方法)(执行目标方法)
mav = invokeHandlerMethod(request, response, handlerMethod);
//参数解析器(确定将要目标方法的参数是什么)(27种)所有springmvc目标方法能写多少种参数类型,取决于有多少个参数解析器
1:this.argumentResolvers 获取所有的参数解析器(比如:RequestParamMethodArgumentResolver.....)
如果参数标注了@RequestParam 就用:RequestParamMethodArgumentResolver来获取参数
1:先判断当前解析器是否支持解析那种参数 supportsParameter()方法
2:如果支持就调用参数解析器的 resolverArgument()方法
//返回值解析器(确定该方法的返回值是什么?就用哪种处理器进行处理,共15种)
2:this.returnValueHandlers 比如:ModelAndViewMethodReturnValueHandler(处理返回值为ModelAndView的方法);
//真正执行的地方(前面只是封装)
invocableMethod.invokeAndHandle(webRequest, mavContainer);
//真正执行目标方法·
Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
3.3、ServletAPI
- HttpServlet,HttpSession…
//这是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);
}
3.4、复杂的参数
- Map,Model,RedirectAttributes,ServletRequest
- Map和Model里面的数据会被放在 request的请求域中(request.setAttribute())
- RedirectAttributes:重定向携带数据
- ServletResponse:response
@GetMapping("/goFor")
public String goFor(Map<String, String> map,
Model model,
HttpServletRequest request,
HttpServletResponse response) {
map.put("map", "我是map里面的数据");
model.addAttribute("model", "我是model中的数据");
request.setAttribute("request", "我是request中的数据");
Cookie cookie = new Cookie("cookie", "我是cookie");
response.addCookie(cookie);
return "forward:/toFor"; //转发,在没有配置视图解析器,这里就好转发到请求
}
@ResponseBody
@GetMapping("/toFor")
public Map<String, Object> toFor(HttpServletRequest request) {
Map<String, Object> map = new HashMap<>();
map.put("mapIs", request.getAttribute("map"));
map.put("model", request.getAttribute("model"));
map.put("request", request.getAttribute("request"));
return map;
//{"request":"我是request中的数据","model":"我是model中的数据","mapIs":"我是map里面的数据"}
}
3.5、研究自定义对象参数的封装过程
<!--前端-->
<form action="/user" method="post">
username<input type="text" name="name"/><br/>
age<input type="text" name="age"/><br/>
<input type="submit" value="提交">
</form>
@PostMapping("/user")
public Map<String, Object> getUser(User user) { //它是怎么封装上去的?
Map<String, Object> map = new HashMap<>();
map.put("name", user.getName());
map.put("age", user.getAge());
return map;
}
ServletModelAttributeMethodProcessor(自定义的参数 是这个参数解析器解析的);
//支持的判断(是否为简单类型)
@Override
public boolean supportsParameter(MethodParameter parameter) {
//false(是否存在注解)
return (parameter.hasParameterAnnotation(ModelAttribute.class) ||
//判断不是简单类型
(this.annotationNotRequired && !BeanUtils.isSimpleProperty(parameter.getParameterType())));
}
//会先创建一个数据绑定器(数据绑定器会将请求参数的值绑定到指定的javaBean里)(用convters来实现参数的转化)
WebDataBinder binder=binderFactory.createBinder()
1:然后内部存在很多类型转换器 converters(比如:将String->Intenger):因为http是传输的文本,要转换为其他数据类型,则需要转换器 converters;
2:存在124个类型转换器(converter 看谁能支持);GeneicConversionService在设置每一个值的时候,找它里面的所有的convter哪个可以将这个数据类型转换到指定类型(String->Intenger)
总结:webDataBinder利用它里面的 converters 将请求数据转成指定数据类型,然后封装到 javaBean中
3.6、自定义属性绑定器(convter)
<form action="/pojo" method="post">
username<input name="name" value="王"/><br/>
age<input name="age" value="12"/><br/>
car<input type="text" value="加菲猫,12"/> <!--现在提交的方式是 catName,catAge-->
<input type="submit" value="提交">
</form>
- convter 接口(函数式接口)
- S–>原来的类型
- T–>想要转成的类型
@FunctionalInterface
public interface Converter<S, T> {}
- 自定义convter
@Configuration(proxyBeanMethods = false)
public class WebConfig implements WebMvcConfigurer {
//除了默认注册的转换器和格式化程序外,还添加转换器和格式化器。
@Override
public void addFormatters(FormatterRegistry registry) {
//添加参数类型转换器
registry.addConverter(new Converter<String, Cat>() {
@Override
public Cat convert(String source) {
//source是原来传入的数据,转为cat,是用逗号隔开的
return new Cat(source.split(",")[0], Integer.valueOf(source.split(",")[1]));
}
});
}
}
WebConfig implements WebMvcConfigurer {
//除了默认注册的转换器和格式化程序外,还添加转换器和格式化器。
@Override
public void addFormatters(FormatterRegistry registry) {
//添加参数类型转换器
registry.addConverter(new Converter<String, Cat>() {
@Override
public Cat convert(String source) {
//source是原来传入的数据,转为cat,是用逗号隔开的
return new Cat(source.split(“,”)[0], Integer.valueOf(source.split(“,”)[1]));
}
});
}
}