Spring 50例常见错误(八)

文章整理来源:Spring编程常见错误50例_spring_spring编程_bean_AOP_SpringCloud_SpringWeb_测试_事务_Data-极客时间

案例18:当 @PathVariable 遇到 /

        从 URL 中获取参数,但当路径中的参数含有字符 '/' 时,会出现 Not Found 错误,而字符 '/' 在参数末尾时,又会被自动去掉        

@RestController
public class HelloWorldController {
    @RequestMapping(path = "/hi1/{name}", method = RequestMethod.GET)
    public String hello1(@PathVariable("name") String name){
        return name;
    }
}

        解析:根据 URL 获取匹配的 MappingHandler 执行方法过程, 1.  根据 Path 进行精确匹配;2.假设 Path 没有精确匹配上,则执行模糊匹配;3. 根据匹配情况返回结果

@Nullable
protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception {
   List<Match> matches = new ArrayList<>();
   //尝试按照 URL 进行精准匹配
   List<T> directPathMatches = this.mappingRegistry.getMappingsByUrl(lookupPath);
   if (directPathMatches != null) {
      //精确匹配上,存储匹配结果
      addMatchingMappings(directPathMatches, matches, request);
   }
   if (matches.isEmpty()) {
      //没有精确匹配上,尝试根据请求来匹配
      addMatchingMappings(this.mappingRegistry.getMappings().keySet(), matches, request);
   }

   if (!matches.isEmpty()) {
      Comparator<Match> comparator = new MatchComparator(getMappingComparator(request));
      matches.sort(comparator);
      Match bestMatch = matches.get(0);
      if (matches.size() > 1) {
         //处理多个匹配的情况
      }
      //省略其他非关键代码
      return bestMatch.handlerMethod;
   }
   else {
      //匹配不上,直接报错
      return handleNoMatch(this.mappingRegistry.getMappings().keySet(), lookupPath, request);
   }

        解决:可以先用"**"匹配上路径,等进入方法后再尝试去解析 

private AntPathMatcher antPathMatcher = new AntPathMatcher();

@RequestMapping(path = "/hi1/**", method = RequestMethod.GET)
public String hi1(HttpServletRequest request){
    String path = (String) request.getAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE);
    //matchPattern 即为"/hi1/**"
    String matchPattern = (String) request.getAttribute(HandlerMapping.BEST_MATCHING_PATTERN_ATTRIBUTE); 
    return antPathMatcher.extractPathWithinPattern(matchPattern, path); 
};

 

案例19:错误使用 @RequestParam、@PathVarible 等注解

        第二种在项目应用中有可能会出现失败

// 1. 在 @RequestParam 中指定参数名
@RequestMapping(path = "/hi1", method = RequestMethod.GET)
public String hi1(@RequestParam("name") String name){
    return name;
};
---------------------------------------------

// 2. 未指定参数名
@RequestMapping(path = "/hi2", method = RequestMethod.GET)
public String hi2(@RequestParam String name){
    return name;
};

        当参数名不存在,@RequestParam 也没有指明,自然就无法决定到底要用什么名称去获取请求参数,所以就会报本案例的错误。

        解决:必须显式在 @RequestParam 中指定请求参数名

        使用第一种

案例20:未考虑参数是否可选

        下面的例子在 请求 URL 为 xxxx/hi4?name=xiaoming&address=beijing 时并不会出问题

@RequestMapping(path = "/hi4", method = RequestMethod.GET)
public String hi4(@RequestParam("name") String name, @RequestParam("address") String address){
    return name + ":" + address;
};

        而在 请求 URL 为 xxxx/hi4?name=xiaoming ,未含有 address ,则会报如下错误

        解析:注解名(@RequestParam)来确定解析发生的位置是在 RequestParamMethodArgumentResolver 的父类 AbstractNamedValueMethodArgumentResolver#resolveArgument 中

public final Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
      NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {

   // 1.查看 namedValueInfo 的默认值,如果存在则使用它
   NamedValueInfo namedValueInfo = getNamedValueInfo(parameter);

   MethodParameter nestedParameter = parameter.nestedIfOptional();
   //省略其他非关键代码
   //获取请求参数
   Object arg = resolveName(resolvedName.toString(), nestedParameter, webRequest);
   if (arg == null) {
      if (namedValueInfo.defaultValue != null) {
         arg = resolveStringValue(namedValueInfo.defaultValue);
      }

      // 2. 在 @RequestParam 没有指明默认值时,会查看这个参数是否必须,如果必须,则按错误处理
      else if (namedValueInfo.required && !nestedParameter.isOptional()) {
         handleMissingValue(namedValueInfo.name, nestedParameter, webRequest);
      }
    
      // 3.  如果不是必须,则按 null 去做具体处理
      arg = handleNullValue(namedValueInfo.name, arg, nestedParameter.getNestedParameterType());
   }
   //省略后续代码:类型转化等工作
   return arg;
}

       方法中处理请求参数有以下步骤: 1. 查看 namedValueInfo 的默认值,如果存在则使用它; 2. 在 @RequestParam 没有指明默认值时,会查看这个参数是否必须,如果必须,则按错误处理;3.  如果不是必须,则按 null 去做具体处理

        解决:1.设置 @RequestParam 的默认值

@RequestParam(value = "address", defaultValue = "no address") String address

                2.设置 @RequestParam 的 required 值

@RequestParam(value = "address", required = false) String address)

                3. 标记任何名为 Nullable 且 RetentionPolicy 为 RUNTIME 的注解

@RequestParam(value = "address") @Nullable String address

                4. 修改参数类型为 Optional

@RequestParam(value = "address") Optionaladdress

案例21:请求参数格式错误

         日期格式的 URL 来访问,例如 xxx/hi6?date=2021-5-1 20:26:53,我们会发现 Spring 并不能完成转化,而是报错如下:"Failed to convert value of type 'java.lang.String' to required type 'java.util.Date"

@RequestMapping(path = "/hi6", method = RequestMethod.GET)
public String hi6(@RequestParam("Date") Date date){
    return "date is " + date ;
};

        解析:还是参考上面的 resolveArgument 方法

public final Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
      NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
   //省略其他非关键代码
   Object arg = resolveName(resolvedName.toString(), nestedParameter, webRequest);
   //以此为界,前面代码为解析请求参数,后续代码为转化解析出的参数
   if (binderFactory != null) {
      WebDataBinder binder = binderFactory.createBinder(webRequest, null, namedValueInfo.name);
      try {
         arg = binder.convertIfNecessary(arg, parameter.getParameterType(), parameter);
      }
      //省略其他非关键代码
   }
   //省略其他非关键代码
   return arg;
}

        在 convertIfNecessary 中会根据 sourceClass 和 targetClass 类型寻找到对应的构造函数,

public Object convert(@Nullable Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
   if (source == null) {
      return null;
   }
   Class<?> sourceClass = sourceType.getType();
   Class<?> targetClass = targetType.getType();
   //根据源类型去获取构建出目标类型的方法:可以是工厂方法(例如 valueOf、from 方法)也可以是构造器
   Member member = getValidatedMember(targetClass, sourceClass);
   try {
      if (member instanceof Method) {
         //如果是工厂方法,通过反射创建目标实例
      }
      else if (member instanceof Constructor) {
         //如果是构造器,通过反射创建实例
         Constructor<?> ctor = (Constructor<?>) member;
         ReflectionUtils.makeAccessible(ctor);
         return ctor.newInstance(source);
      }
   }
   catch (InvocationTargetException ex) {
      throw new ConversionFailedException(sourceType, targetType, source, ex.getTargetException());
   }
   catch (Throwable ex) {
      throw new ConversionFailedException(sourceType, targetType, source, ex);
   }

        最终会调用下面的 String 转 Date 的构造函数,而 “2021-5-1 20:26:53” 在转换为 Date 并不符合格式要求,因此会抛出错误

public Date(String s) {
    this(parse(s));
}

        解决: 1. 使用 Date 支持的格式

http://localhost:8080/hi6?date=Sat, 12 Aug 1995 13:30:00 GMT

                2. 使用好内置格式转化器

@DateTimeFormat(pattern="yyyy-MM-dd HH:mm:ss") Date date

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值