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表示属性名

相关文章
|
24天前
|
负载均衡 算法 Java
Spring Cloud全解析:负载均衡算法
本文介绍了负载均衡的两种方式:集中式负载均衡和进程内负载均衡,以及常见的负载均衡算法,包括轮询、随机、源地址哈希、加权轮询、加权随机和最小连接数等方法,帮助读者更好地理解和应用负载均衡技术。
|
8天前
|
Java 对象存储 开发者
解析Spring Cloud与Netflix OSS:微服务架构中的左右手如何协同作战
Spring Cloud与Netflix OSS不仅是现代微服务架构中不可或缺的一部分,它们还通过不断的技术创新和社区贡献推动了整个行业的发展。无论是对于初创企业还是大型组织来说,掌握并合理运用这两套工具,都能极大地提升软件系统的灵活性、可扩展性以及整体性能。随着云计算和容器化技术的进一步普及,Spring Cloud与Netflix OSS将继续引领微服务技术的发展潮流。
21 0
|
19天前
|
缓存 前端开发 Java
【Java面试题汇总】Spring,SpringBoot,SpringMVC,Mybatis,JavaWeb篇(2023版)
Soring Boot的起步依赖、启动流程、自动装配、常用的注解、Spring MVC的执行流程、对MVC的理解、RestFull风格、为什么service层要写接口、MyBatis的缓存机制、$和#有什么区别、resultType和resultMap区别、cookie和session的区别是什么?session的工作原理
【Java面试题汇总】Spring,SpringBoot,SpringMVC,Mybatis,JavaWeb篇(2023版)
|
22天前
|
XML 监控 Java
Spring Cloud全解析:熔断之Hystrix简介
Hystrix 是由 Netflix 开源的延迟和容错库,用于提高分布式系统的弹性。它通过断路器模式、资源隔离、服务降级及限流等机制防止服务雪崩。Hystrix 基于命令模式,通过 `HystrixCommand` 封装对外部依赖的调用逻辑。断路器能在依赖服务故障时快速返回备选响应,避免长时间等待。此外,Hystrix 还提供了监控功能,能够实时监控运行指标和配置变化。依赖管理方面,可通过 `@EnableHystrix` 启用 Hystrix 支持,并配置全局或局部的降级策略。结合 Feign 可实现客户端的服务降级。
100 23
|
6天前
|
存储 缓存 Java
在Spring Boot中使用缓存的技术解析
通过利用Spring Boot中的缓存支持,开发者可以轻松地实现高效和可扩展的缓存策略,进而提升应用的性能和用户体验。Spring Boot的声明式缓存抽象和对多种缓存技术的支持,使得集成和使用缓存变得前所未有的简单。无论是在开发新应用还是优化现有应用,合理地使用缓存都是提高性能的有效手段。
13 1
|
6天前
|
XML 缓存 前端开发
springMVC02,restful风格,请求转发和重定向
文章介绍了RESTful风格的基本概念和特点,并展示了如何使用SpringMVC实现RESTful风格的请求处理。同时,文章还讨论了SpringMVC中的请求转发和重定向的实现方式,并通过具体代码示例进行了说明。
springMVC02,restful风格,请求转发和重定向
|
2月前
|
Java 数据库连接 Spring
后端框架入门超详细 三部曲 Spring 、SpringMVC、Mybatis、SSM框架整合案例 【爆肝整理五万字】
文章是关于Spring、SpringMVC、Mybatis三个后端框架的超详细入门教程,包括基础知识讲解、代码案例及SSM框架整合的实战应用,旨在帮助读者全面理解并掌握这些框架的使用。
后端框架入门超详细 三部曲 Spring 、SpringMVC、Mybatis、SSM框架整合案例 【爆肝整理五万字】
|
2月前
|
缓存 Java 开发者
Spring高手之路22——AOP切面类的封装与解析
本篇文章深入解析了Spring AOP的工作机制,包括Advisor和TargetSource的构建与作用。通过详尽的源码分析和实际案例,帮助开发者全面理解AOP的核心技术,提升在实际项目中的应用能力。
23 0
Spring高手之路22——AOP切面类的封装与解析
|
2月前
|
Java 微服务 Spring
Spring Cloud全解析:配置中心之解决configserver单点问题
但是如果该configserver挂掉了,那就无法获取最新的配置了,微服务就出现了configserver的单点问题,那么如何避免configserver单点呢?
|
2月前
|
Java 数据安全/隐私保护 Spring
揭秘Spring Boot自定义注解的魔法:三个实用场景让你的代码更加优雅高效
揭秘Spring Boot自定义注解的魔法:三个实用场景让你的代码更加优雅高效
下一篇
无影云桌面