009_Web开发之请求参数处理篇

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 集合包括;
1RequestMappingHandlerAdapter:支持方法上标注 @RequestMapping;
2HandlerFunctionAdapter:支持函数式编程,就是容器中controller声明了很多函数式方法
3HttpRequestHandlerAdapter;
4SimpleControllerHandlerAdapter;

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]));
}
});

}

}


























  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值