HandlerMethodArgumentResolver(四):自定参数解析器处理特定场景需求,介绍PropertyNamingStrategy的使用【享学Spring MVC】(中)

简介: HandlerMethodArgumentResolver(四):自定参数解析器处理特定场景需求,介绍PropertyNamingStrategy的使用【享学Spring MVC】(中)

作为一个"合格"的coder,理应发出如题这样的疑问。

譬如上例我这么写,你可以猜猜是什么结果:


@ResponseBody
@GetMapping("/test/curruser")
public Object testCurrUser(@CurrUser @RequestParam CurrUserVo currUser) {
    return currUser;
}


表面上看起来木有毛病,但请求:/test/curruser?currUser=fsx。报错如下:


Resolved [org.springframework.web.method.annotation.MethodArgumentConversionNotSupportedException: Failed to convert value of type 'java.lang.String' to required type 'com.fsx.validator.CurrUserVo'; 
nested exception is java.lang.IllegalStateException: Cannot convert value of type 'java.lang.String' to required type 'com.fsx.validator.CurrUserVo': no matching editors or conversion strategy found]


调试源码可以发现它最终使用的参数解析器是:RequestParamMethodArgumentResolver,而并非我们自定义的CurrUserArgumentResolver。so可得出结论:我们自定义的参数解析器的优先级是低于Spring内置的。

那么到底是什么样的优先级规则呢?我这里不妨给指出如下,供以大家学习:


1、首先就得从RequestMappingHandlerAdapter说起,它对参数解析器的加载(初始化)顺序:


RequestMappingHandlerAdapter:
  @Override
  public void afterPropertiesSet() {
    // 显然,也是允许你自己通过setArgumentResolvers()方法手动添加的~~~
    // 加入你调用过set方法,这里就不会执行啦~~~~~(一般不建议手动set)
    if (this.argumentResolvers == null) {
      List<HandlerMethodArgumentResolver> resolvers = getDefaultArgumentResolvers();
      this.argumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers);
    }
    ... 
  }
  private List<HandlerMethodArgumentResolver> getDefaultArgumentResolvers() {
    List<HandlerMethodArgumentResolver> resolvers = new ArrayList<>();
    // Annotation-based argument resolution
    // 加载处理所有内置注解的解析器们
    resolvers.add(new RequestParamMethodArgumentResolver(getBeanFactory(), false));
    resolvers.add(new RequestParamMapMethodArgumentResolver());
    resolvers.add(new PathVariableMethodArgumentResolver());
    ...
    // Type-based argument resolution
    // 比如request、response等等这些的解析器们
    resolvers.add(new ServletRequestMethodArgumentResolver());
    ...
    // Custom arguments 
    // 加载自定义的解析器们(我们自定义的在这里会被加载进来)
    if (getCustomArgumentResolvers() != null) {
      resolvers.addAll(getCustomArgumentResolvers());
    }
    // Catch-all
    // 加载这两个用于兜底
    resolvers.add(new RequestParamMethodArgumentResolver(getBeanFactory(), true));
    resolvers.add(new ServletModelAttributeMethodProcessor(true));
    return resolvers;
  }


2、RequestMappingHandlerAdapter这个Bean配置处如下:


WebMvcConfigurationSupport:
  @Bean
  public RequestMappingHandlerAdapter requestMappingHandlerAdapter() {
    RequestMappingHandlerAdapter adapter = createRequestMappingHandlerAdapter();
    // 内容协商管理器
    adapter.setContentNegotiationManager(mvcContentNegotiationManager());
    // 消息转换器们
    adapter.setMessageConverters(getMessageConverters());
    // ConfigurableWebBindingInitializer:配置数据绑定、校验的相关配置项
    adapter.setWebBindingInitializer(getConfigurableWebBindingInitializer());
    // 参数解析器、返回值解析器
    adapter.setCustomArgumentResolvers(getArgumentResolvers());
    adapter.setCustomReturnValueHandlers(getReturnValueHandlers());
    ...
  }


WebMvcConfigurationSupport应该没有不熟悉它的了,它用于开启WebMVC的配置支持~

从这个源码(配置顺序)中可以很清晰的得出答案:为何本例加了@RequestParam注解就访问就报错了;同样也解释了为何入参不能是Map(但Object类型是可以的~)。


在介绍场景二之前,我先介绍一个类:PropertyNamingStrategy


PropertyNamingStrategy


它表示序列化/反序列化过程中:Java属性到序列化key的一种命名策略。


默认情况下从字符串反序列为一个Java对象,要求需要完全一样才能反序列赋值成功。但了解了这些策略之后,可以帮你带来更好的兼容性,下面以最为常用的两个JSON库为例分别讲解~


Gson库对应的类叫FieldNamingStrategy,功能类似。因为我个人使用较少,所以此处忽略它~


fastjson中


fastjson在1.2.15版本(2016年6月)中提供了这个功能,它以枚举的形式管理:


public enum PropertyNamingStrategy {
  CamelCase, // 骆驼:
  PascalCase, // 帕斯卡:
  SnakeCase, // 蛇形:
  KebabCase; // 烤肉串:
  // 提供唯一一个实例方法:转换translate
    public String translate(String propertyName) {
      switch (this) {
        case SnakeCase: { ... }
        case KebabCase: { ... }
        case PascalCase: { ... }
        case CamelCase: { ... }
      }
    }
}

针对此4种策略,给出使用用例如下:


public static void main(String[] args)  {
    String propertyName = "nameAndAge";
    System.out.println(PropertyNamingStrategy.CamelCase.translate(propertyName)); // nameAndAge
    System.out.println(PropertyNamingStrategy.PascalCase.translate(propertyName)); // NameAndAge
    // 下面两种的使用很多的情况:下划线
    System.out.println(PropertyNamingStrategy.SnakeCase.translate(propertyName)); // name_and_age
    System.out.println(PropertyNamingStrategy.KebabCase.translate(propertyName)); // name-and-age
}


继续演示使用Fastjson序列化/反序列化的时候的示例:


public static void main(String[] args) {
    DemoVo vo = new DemoVo();
    vo.setDemoName("fsx");
    vo.setDemoAge(18);
    vo.setDemoNameAndAge("fsx18");
    PropertyNamingStrategy strategy = PropertyNamingStrategy.SnakeCase;
    // 序列化配置对象
    SerializeConfig config = new SerializeConfig();
    config.propertyNamingStrategy = strategy;
    // 反序列化配置对象
    ParserConfig parserConfig = new ParserConfig();
    parserConfig.propertyNamingStrategy = strategy;
    // 序列化对象
    String json = JSON.toJSONString(vo, config);
    System.out.println("序列化vo对象到json -> " + json);
    // 反序列化对象
    vo = JSON.parseObject(json, DemoVo.class, parserConfig);
    System.out.println("反序列化json到vo -> " + vo);
}


运行打印:

序列化vo对象到json -> {"demo_age":18,"demo_name":"fsx","demo_name_and_age":"fsx18"}
反序列化json到vo -> Main.DemoVo(demoName=fsx, demoAge=18, demoNameAndAge=fsx18)


若策略是SnakeCase,它是支持下划线_到驼峰格式的Java属性的相互转换的。若使用另外三种,我把结果摘录如下:


CamelCase:
序列化vo对象到json -> {"demoAge":18,"demoName":"fsx","demoNameAndAge":"fsx18"}
反序列化json到vo -> Main.DemoVo(demoName=fsx, demoAge=18, demoNameAndAge=fsx18)
PascalCase:
序列化vo对象到json -> {"DemoAge":18,"DemoName":"fsx","DemoNameAndAge":"fsx18"}
反序列化json到vo -> Main.DemoVo(demoName=fsx, demoAge=18, demoNameAndAge=fsx18)
KebabCase:
序列化vo对象到json -> {"demo-age":18,"demo-name":"fsx","demo-name-and-age":"fsx18"}
反序列化json到vo -> Main.DemoVo(demoName=fsx, demoAge=18, demoNameAndAge=fsx18)


FastJson默认使用CamelCase

题外话:除了上面那样分别在序列化时临时制定序列化、反序列化策略外,还可以用如下方式指定:


全局指定策略


SerializeConfig.getGlobalInstance().propertyNamingStrategy = PropertyNamingStrategy.PascalCase;


@JSONType指定


@JSONType(naming = PropertyNamingStrategy.SnakeCase)
private static class DemoVo {
    @JSONField(name = "name")
    private String demoName;
    private Integer demoAge;
    private Object demoNameAndAge;
}


@JSONField没有指定name属性,那就会使用PropertyNamingStrategy策略~


jackson中


除了fastjson,作为全球范围内更为流行的jackson自然也是支持此些策略的。


// was abstract until 2.7 在2.7版本之前一直是抽象类
public class PropertyNamingStrategy implements java.io.Serializable {
    public static final PropertyNamingStrategy SNAKE_CASE = new SnakeCaseStrategy();
    public static final PropertyNamingStrategy UPPER_CAMEL_CASE = new UpperCamelCaseStrategy();
    public static final PropertyNamingStrategy LOWER_CAMEL_CASE = new PropertyNamingStrategy();
    public static final PropertyNamingStrategy KEBAB_CASE = new KebabCaseStrategy();
  // 上面几个策略都是@since 2.7,这个基于@since 2.4
    public static final PropertyNamingStrategy LOWER_CASE = new LowerCaseStrategy();
  // 提供的API方法如下:
  public String nameForField(MapperConfig<?> config, AnnotatedField field, String defaultName);
    public String nameForGetterMethod(MapperConfig<?> config, AnnotatedMethod method, String defaultName);
    public String nameForSetterMethod(MapperConfig<?> config, AnnotatedMethod method, String defaultName);
    public String nameForConstructorParameter(MapperConfig<?> config, AnnotatedParameter ctorParam, String defaultName);
  // 所有策略都使用静态内部类来实现(只需要实现translate方法即可)
  public static class SnakeCaseStrategy extends PropertyNamingStrategyBase
  public static class UpperCamelCaseStrategy extends PropertyNamingStrategyBase
  ...
}


下面结合它的注解@JsonNaming来演示它的使用:

@Getter
@Setter
@ToString
// 此注解只能标注在类上
@JsonNaming(value = PropertyNamingStrategy.SnakeCaseStrategy.class)
private static class DemoVo {
    private String demoName;
    private Integer demoAge;
    @JsonProperty("diyProp")
    private Object demoNameAndAge;
}
public static void main(String[] args) throws IOException {
    DemoVo vo = new DemoVo();
    vo.setDemoName("fsx");
    vo.setDemoAge(18);
    vo.setDemoNameAndAge("fsx18");
    // 序列化对象
    ObjectMapper objectMapper = new ObjectMapper();
    String json = objectMapper.writeValueAsString(vo);
    System.out.println("序列化vo对象到json -> " + json);
    // 反序列化对象
    vo = objectMapper.readValue(json,DemoVo.class);
    System.out.println("反序列化json到vo -> " + vo);
}


打印输出结果:


序列化vo对象到json -> {"demo_name":"fsx","demo_age":18,"diyProp":"fsx18"}
反序列化json到vo -> Main.DemoVo(demoName=fsx, demoAge=18, demoNameAndAge=fsx18)


显然基于字段的注解@JsonProperty它的优先级是高于@JsonNaming

除此之外,jackson还提供了更多实用注解,有兴趣的可以自行去了解


image.png


我个人意见:jackson可能是由于功能设计得太过于全面了,使用起来有反倒很多不便之处,学习成本颇高。因为个人觉得还是我天朝的Fastjson好用啊~


说明:这些策略在异构的语言交互时是很有用的,因为各种语言命名规范都不尽相同,有了它们就可以有很好的兼容性。

如:.net命名都是大写开头形如DemoName表示属性名

如:js/python喜欢用下划线形全小写如demo_name表示属性名

相关文章
|
15天前
|
搜索推荐 Java Spring
Spring Filter深度解析
【10月更文挑战第21天】Spring Filter 是 Spring 框架中非常重要的一部分,它为请求处理提供了灵活的控制和扩展机制。通过合理配置和使用 Filter,可以实现各种个性化的功能,提升应用的安全性、可靠性和性能。还可以结合具体的代码示例和实际应用案例,进一步深入探讨 Spring Filter 的具体应用和优化技巧,使对它的理解更加全面和深入。
|
24天前
|
JSON 前端开发 Java
Spring MVC——获取参数和响应
本文介绍了如何在Spring框架中通过不同的注解和方法获取URL参数、上传文件、处理cookie和session、以及响应不同类型的数据。具体内容包括使用`@PathVariable`获取URL中的参数,使用`MultipartFile`上传文件,通过`HttpServletRequest`和`@CookieValue`获取cookie,通过`HttpSession`和`@SessionAttribute`获取session,以及如何返回静态页面、HTML代码片段、JSON数据,并设置HTTP状态码和响应头。
42 1
Spring MVC——获取参数和响应
|
10天前
|
JavaScript API 开发工具
<大厂实战场景> ~ Flutter&鸿蒙next 解析后端返回的 HTML 数据详解
本文介绍了如何在 Flutter 中解析后端返回的 HTML 数据。首先解释了 HTML 解析的概念,然后详细介绍了使用 `http` 和 `html` 库的步骤,包括添加依赖、获取 HTML 数据、解析 HTML 内容和在 Flutter UI 中显示解析结果。通过具体的代码示例,展示了如何从 URL 获取 HTML 并提取特定信息,如链接列表。希望本文能帮助你在 Flutter 应用中更好地处理 HTML 数据。
93 1
|
27天前
|
存储
让星星⭐月亮告诉你,HashMap的put方法源码解析及其中两种会触发扩容的场景(足够详尽,有问题欢迎指正~)
`HashMap`的`put`方法通过调用`putVal`实现,主要涉及两个场景下的扩容操作:1. 初始化时,链表数组的初始容量设为16,阈值设为12;2. 当存储的元素个数超过阈值时,链表数组的容量和阈值均翻倍。`putVal`方法处理键值对的插入,包括链表和红黑树的转换,确保高效的数据存取。
51 5
|
28天前
|
Java Spring
Spring底层架构源码解析(三)
Spring底层架构源码解析(三)
|
28天前
|
XML Java 数据格式
Spring底层架构源码解析(二)
Spring底层架构源码解析(二)
|
28天前
|
Java API Spring
springboot学习七:Spring Boot2.x 拦截器基础入门&实战项目场景实现
这篇文章是关于Spring Boot 2.x中拦截器的入门教程和实战项目场景实现的详细指南。
19 0
springboot学习七:Spring Boot2.x 拦截器基础入门&实战项目场景实现
|
28天前
|
Java API Spring
springboot学习六:Spring Boot2.x 过滤器基础入门&实战项目场景实现
这篇文章是关于Spring Boot 2.x中过滤器的基础知识和实战项目应用的教程。
21 0
springboot学习六:Spring Boot2.x 过滤器基础入门&实战项目场景实现
|
23天前
|
XML Java 数据格式
Spring IOC容器的深度解析及实战应用
【10月更文挑战第14天】在软件工程中,随着系统规模的扩大,对象间的依赖关系变得越来越复杂,这导致了系统的高耦合度,增加了开发和维护的难度。为解决这一问题,Michael Mattson在1996年提出了IOC(Inversion of Control,控制反转)理论,旨在降低对象间的耦合度,提高系统的灵活性和可维护性。Spring框架正是基于这一理论,通过IOC容器实现了对象间的依赖注入和生命周期管理。
59 0
|
29天前
|
XML Java 数据格式
手动开发-简单的Spring基于注解配置的程序--源码解析
手动开发-简单的Spring基于注解配置的程序--源码解析
45 0

推荐镜像

更多
下一篇
无影云桌面