SpringBoot2——web开发(请求参数处理——普通参数与基本注解)

普通参数与基本注解

注解

在这里我们将学到web开发的相关注解

  • @PathVariable(路径变量)
  • @RequestHeader(请求头)
  • @RequestParam(获取请求参数)
  • @CookieValue(cookie信息)
  • @RequestBody(获取请求体【POST】)
  • @RequestAttribute(获取request域属性)
  • @MatrixVariable(矩阵变量)

@PathVariable

路径变量注解,在rest风格的情况下,我们通常将参数格式写成 /name/Tom/id/1 ,那么需要获取路径中的请求参数,就需要使用到@PathVariable

    @GetMapping("/car/{id}/brand/{name}")
    public Map<String,Object> test(@PathVariable("id") Integer id,
                                   @PathVariable("name") String brand,
                                   //不定义变量名的注解,可以定义为map形式,直接封装成k=v的格式
                                   @PathVariable Map<String,String> pv){
        HashMap<String, Object> map = new HashMap<>();
        map.put("id",id);
        map.put("name",brand);
        map.put("pv",pv);
        return map;
    }

@RequestHeader

请求头注解,可以获取客户端发送请求时,请求头中携带的信息

    @GetMapping("/car/{id}/brand/{name}")
    public Map<String,Object> test(@RequestHeader("User-Agent") String user_agent,
                                   @RequestHeader Map<String,String> header){
        HashMap<String, Object> map = new HashMap<>();
        map.put("ka",user_agent);
        map.put("header",header);
        return map;
    }

@RequestParam

此注解可以获取请求参数,如: /name/Tom/id/1?gender=boy&inter=basketball&inter=football,相同的名称可以使用list来接收

    @GetMapping("/car/{id}/brand/{name}")
    public Map<String,Object> test(@RequestParam("carname") String carname,
                                   @RequestParam("inter")List<String> inter{
        HashMap<String, Object> map = new HashMap<>();
        map.put("carname",carname);
        map.put("inter",inter);
        return map;
    }

@CookieValue

同请求头类似,可以获取cookie属性,也可以将获取到的属性封装为cookie对象

    @GetMapping("/car/{id}/brand/{name}")
    public Map<String,Object> test(@CookieValue("Idea-21d59d66") String cookieValue,
                                   @CookieValue("Idea-21d59d66") Cookie cookies){
        HashMap<String, Object> map = new HashMap<>();
        map.put("cookie",cookieValue);
        map.put("cookies",cookies);
        return map;
    }

@RequestBody

此注解可以封装表单中的信息

    @PostMapping("/user")
    public Map<String,Object> getUser(@RequestBody() String context){
        HashMap<String, Object> map = new HashMap<>();
        map.put("context",context);
        return map;
    }

@RequestAttribute

获取请求域中的信息,模拟页面转发,goPage将数据写入到request域中,在其他方法中读取

@Controller
public class GoController {
    @GetMapping("/goto")
    public String goPage(HttpServletRequest request){
        request.setAttribute("msg","成功了。。。");
        request.setAttribute("code","200");
        return "forward:/success";
    }
    @ResponseBody
    @GetMapping("/success")
    public Map<String,Object> getmap(@RequestAttribute("msg") String msg,
                                     @RequestAttribute("code") String code){
        HashMap<String, Object> map = new HashMap<>();
        map.put("msg",msg);
        map.put("code",code);
        return map;
    }
}

@MatrixVariable

读取矩阵变量,这就得介绍一下什么是矩阵变量了,语法如下,每一个变量之间用分号隔开

语法: 请求路径:/cars/sell;low=34;brand=byd,audi,yd

    // /car/sell;low=20;hight=40
    @GetMapping("/car/{path}")
    public Map test2(@MatrixVariable("low") String low,
                     @MatrixVariable("brand") List<String> brand,
                     @PathVariable("path") String path){
        HashMap<String, Object> map = new HashMap<>();
        map.put("low",low);
        map.put("brand",brand);
        map.put("path",path);
        return map;
    }

进行如上设置还不够,SpringBoot关闭了矩阵变量,我们可以从 源码中探索

所有自动配置的属性都存放在WebMvcAutoConfiguration,在这里面对请求路径进行了设置

        public void configurePathMatch(PathMatchConfigurer configurer) {
            if (this.mvcProperties.getPathmatch().getMatchingStrategy() == MatchingStrategy.PATH_PATTERN_PARSER) {
                configurer.setPatternParser(new PathPatternParser());
            }

            configurer.setUseSuffixPatternMatch(this.mvcProperties.getPathmatch().isUseSuffixPattern());
            configurer.setUseRegisteredSuffixPatternMatch(this.mvcProperties.getPathmatch().isUseRegisteredSuffixPattern());
            this.dispatcherServletPath.ifAvailable((dispatcherPath) -> {
                String servletUrlMapping = dispatcherPath.getServletUrlMapping();
                if (servletUrlMapping.equals("/") && this.singleDispatcherServlet()) {
                    //主要看这个路径帮助器
                    UrlPathHelper urlPathHelper = new UrlPathHelper();
                    urlPathHelper.setAlwaysUseFullPath(true);
                    configurer.setUrlPathHelper(urlPathHelper);
                }

            });
        }

urlPathHelper中有一个removeSemicolonContent属性被设置为true,而其代表阶段路径中分号后的数据,也就是说我们要想其生效,必须修改值为false

我们发现urlPathHelper所在的类继承了WebMvcConfigurer,WebMvcConfigurer下有一个属性为configurePathMatch,也就是说我们只要重新进行配置即可

@Configuration(proxyBeanMethods = false)
public class MyConfig {
    @Bean
    public WebMvcConfigurer getConfig(){
        return new WebMvcConfigurer() {
            @Override
            public void configurePathMatch(PathMatchConfigurer configurer) {
                UrlPathHelper urlPathHelper = new UrlPathHelper();
                // 不移除;后面的内容。矩阵变量功能就可以生效
                urlPathHelper.setRemoveSemicolonContent(false);
                configurer.setUrlPathHelper(urlPathHelper);
            }
        };
    }
}

在使用矩阵变量时,可能会出现两个变量名相同的情况,比如我要找1号boss年龄变量为20,2号员工年龄变量为30,发送的请求为:/boss/1;age=20/2;age=30

这个时候我们需要使用@MatrixVariable注解下的pathVar来指定变量名称

    // /boss/1;age=20/2;age=10
    @GetMapping("/boss/{bossId}/{empId}")
    public Map test3(@MatrixVariable(value = "age",pathVar = "bossId") String boss,
                     @MatrixVariable(value = "age",pathVar = "empId") String emp){
        HashMap<String, Object> map = new HashMap<>();
        map.put("boss",boss);
        map.put("emp",emp);
        return map;
    }

参数处理原理

所有请求过的都会过DispatcherServlet的doDispatch方法,先说一下大概的逻辑再一一详细讲解

  1. 调用getHandler方法,找到处理该请求的Handler(这个在小黄之前的文章中有提到过) 点击跳转
  2. 调用getHandlerAdapter方法,为当前的handler找到一个适配器HandlerAdapter (requestMappingHandlerAdapter)
  3. 执行适配器的handler方法,确定方法参数的每一个值

先来看第二步,为当前的Handler找到一个适配器
SpringBoot为我们提供了4种适配器,毫无疑问这里需要的是requestMappingHandlerAdapter
requestMappingHandlerAdapter——支持方法上标注注解@RequestMapping
HandlerFuncationAdapter——支持函数式编程
在这里插入图片描述

执行适配器的handler方法

mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

执行目标方法

mav = this.invokeHandlerMethod(request, response, handlerMethod);

这个方法中我们注意到两个对象

argumentResolvers

参数解析器,里面包含了27种SpringBoot提供的参数解析器,比如RequestParam、PathVariable等,通过这里我们也可以得知,在方法形参中,我们有这么多的注解可以标识

在这里插入图片描述

而这个参数解析器的方法非常有意思,一共就两个方法,一个是判断一个是执行,所以这个的工作原理非常清晰

  1. 判断当前解析器是否支持这种函数
  2. 支持的话就调用resolveArgument

在这里插入图片描述

returnValueHandlers

返回值处理器,这里我们可以看到处理请求方法允许的返回值

在这里插入图片描述

接下来主要看以下方法,此方法

invocableMethod.invokeAndHandle(webRequest, mavContainer, new Object[0]);

这个方法进去之后,会执行以下两条语句,通过断点调试的方法,发现第一条语句执行完成之后,先跳转到controller中的断点再接着执行第二条语句,这里可以说明处理请求的方法在this.invokeForRequest()中

public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception {
        Object returnValue = this.invokeForRequest(webRequest, mavContainer, providedArgs);
        this.setResponseStatus(webRequest);
}

通过以下方法来获取参数值

 Object[] args = this.getMethodArgumentValues(request, mavContainer, providedArgs);
protected Object[] getMethodArgumentValues(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception {
        MethodParameter[] parameters = this.getMethodParameters();
        if (ObjectUtils.isEmpty(parameters)) {
            return EMPTY_ARGS;
        } else {
            Object[] args = new Object[parameters.length];
			//遍历请求参数
            for(int i = 0; i < parameters.length; ++i) {
                MethodParameter parameter = parameters[i];
                parameter.initParameterNameDiscovery(this.parameterNameDiscoverer);
                args[i] = findProvidedArgument(parameter, providedArgs);
                if (args[i] == null) {
                    //判断27个参数解析器是否支持解析该参数
                    if (!this.resolvers.supportsParameter(parameter)) {
                        throw new IllegalStateException(formatArgumentError(parameter, "No suitable resolver"));
                    }

                    try {
                        args[i] = this.resolvers.resolveArgument(parameter, mavContainer, request, this.dataBinderFactory);
                    } catch (Exception var10) {
                        if (logger.isDebugEnabled()) {
                            String exMsg = var10.getMessage();
                            if (exMsg != null && !exMsg.contains(parameter.getExecutable().toGenericString())) {
                                logger.debug(formatArgumentError(parameter, exMsg));
                            }
                        }

                        throw var10;
                    }
                }
            }

            return args;
        }
    }

挨个判断27个参数解析器是否支持该参数

    @Nullable
    private HandlerMethodArgumentResolver getArgumentResolver(MethodParameter parameter) {
        HandlerMethodArgumentResolver result = (HandlerMethodArgumentResolver)this.argumentResolverCache.get(parameter);
        if (result == null) {
            Iterator var3 = this.argumentResolvers.iterator();

            while(var3.hasNext()) {
                HandlerMethodArgumentResolver resolver = (HandlerMethodArgumentResolver)var3.next();
                if (resolver.supportsParameter(parameter)) {
                    result = resolver;
                    this.argumentResolverCache.put(parameter, resolver);
                    break;
                }
            }
        }

        return result;
    }

解析参数的值

    @Nullable
    public final Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer, NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
        AbstractNamedValueMethodArgumentResolver.NamedValueInfo namedValueInfo = this.getNamedValueInfo(parameter);
        MethodParameter nestedParameter = parameter.nestedIfOptional();
        Object resolvedName = this.resolveEmbeddedValuesAndExpressions(namedValueInfo.name);
        if (resolvedName == null) {
            throw new IllegalArgumentException("Specified name must not resolve to null: [" + namedValueInfo.name + "]");
        } else {
            Object arg = this.resolveName(resolvedName.toString(), nestedParameter, webRequest);
            if (arg == null) {
                if (namedValueInfo.defaultValue != null) {
                    arg = this.resolveEmbeddedValuesAndExpressions(namedValueInfo.defaultValue);
                } else if (namedValueInfo.required && !nestedParameter.isOptional()) {
                    this.handleMissingValue(namedValueInfo.name, nestedParameter, webRequest);
                }

                arg = this.handleNullValue(namedValueInfo.name, arg, nestedParameter.getNestedParameterType());
            } else if ("".equals(arg) && namedValueInfo.defaultValue != null) {
                arg = this.resolveEmbeddedValuesAndExpressions(namedValueInfo.defaultValue);
            }

            if (binderFactory != null) {
                WebDataBinder binder = binderFactory.createBinder(webRequest, (Object)null, namedValueInfo.name);

                try {
                    arg = binder.convertIfNecessary(arg, parameter.getParameterType(), parameter);
                } catch (ConversionNotSupportedException var11) {
                    throw new MethodArgumentConversionNotSupportedException(arg, var11.getRequiredType(), namedValueInfo.name, parameter, var11.getCause());
                } catch (TypeMismatchException var12) {
                    throw new MethodArgumentTypeMismatchException(arg, var12.getRequiredType(), namedValueInfo.name, parameter, var12.getCause());
                }

                if (arg == null && namedValueInfo.defaultValue == null && namedValueInfo.required && !nestedParameter.isOptional()) {
                    this.handleMissingValueAfterConversion(namedValueInfo.name, nestedParameter, webRequest);
                }
            }

            this.handleResolvedValue(arg, namedValueInfo.name, parameter, mavContainer, webRequest);
            return arg;
        }
    }

总结

  1. 调用getHandler方法,找到处理该请求的Handler(这个在小黄之前的文章中有提到过) 点击跳转
  2. 调用getHandlerAdapter方法,为当前的handler找到一个适配器HandlerAdapter (requestMappingHandlerAdapter)
  3. 执行适配器的handler方法,确定方法参数的每一个值
    1. 执行目标方法,过程中可以发现两个对象,分别是参数解析器、返回值处理器
    2. 调用this.getMethodArgumentValues获取参数值的集合
      1. 遍历请求参数
      2. 挨个判断请求参数是否能被27个参数解析器解析
      3. 解析参数的值
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值