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

相关文章
|
2月前
|
前端开发 Java 数据库连接
SpringBoot参数校验底层原理和实操。深度历险、深度解析(图解+秒懂+史上最全)
SpringBoot参数校验底层原理和实操。深度历险、深度解析(图解+秒懂+史上最全)
SpringBoot参数校验底层原理和实操。深度历险、深度解析(图解+秒懂+史上最全)
|
6月前
|
存储 Java 文件存储
微服务——SpringBoot使用归纳——Spring Boot使用slf4j进行日志记录—— logback.xml 配置文件解析
本文解析了 `logback.xml` 配置文件的详细内容,包括日志输出格式、存储路径、控制台输出及日志级别等关键配置。通过定义 `LOG_PATTERN` 和 `FILE_PATH`,设置日志格式与存储路径;利用 `&lt;appender&gt;` 节点配置控制台和文件输出,支持日志滚动策略(如文件大小限制和保存时长);最后通过 `&lt;logger&gt;` 和 `&lt;root&gt;` 定义日志级别与输出方式。此配置适用于精细化管理日志输出,满足不同场景需求。
1309 1
|
8月前
|
XML Java 开发者
Spring底层架构核心概念解析
理解 Spring 框架的核心概念对于开发和维护 Spring 应用程序至关重要。IOC 和 AOP 是其两个关键特性,通过依赖注入和面向切面编程实现了高效的模块化和松耦合设计。Spring 容器管理着 Beans 的生命周期和配置,而核心模块为各种应用场景提供了丰富的功能支持。通过全面掌握这些核心概念,开发者可以更加高效地利用 Spring 框架开发企业级应用。
223 18
|
9月前
|
设计模式 XML Java
【23种设计模式·全精解析 | 自定义Spring框架篇】Spring核心源码分析+自定义Spring的IOC功能,依赖注入功能
本文详细介绍了Spring框架的核心功能,并通过手写自定义Spring框架的方式,深入理解了Spring的IOC(控制反转)和DI(依赖注入)功能,并且学会实际运用设计模式到真实开发中。
【23种设计模式·全精解析 | 自定义Spring框架篇】Spring核心源码分析+自定义Spring的IOC功能,依赖注入功能
|
7月前
|
传感器 监控 安全
智慧工地云平台的技术架构解析:微服务+Spring Cloud如何支撑海量数据?
慧工地解决方案依托AI、物联网和BIM技术,实现对施工现场的全方位、立体化管理。通过规范施工、减少安全隐患、节省人力、降低运营成本,提升工地管理的安全性、效率和精益度。该方案适用于大型建筑、基础设施、房地产开发等场景,具备微服务架构、大数据与AI分析、物联网设备联网、多端协同等创新点,推动建筑行业向数字化、智能化转型。未来将融合5G、区块链等技术,助力智慧城市建设。
310 0
|
10月前
|
XML Java 数据库连接
Spring高手之路25——深入解析事务管理的切面本质
本篇文章将带你深入解析Spring事务管理的切面本质,通过AOP手动实现 @Transactional 基本功能,并探讨PlatformTransactionManager的设计和事务拦截器TransactionInterceptor的工作原理,结合时序图详细展示事务管理流程,最后引导分析 @Transactional 的代理机制源码,帮助你全面掌握Spring事务管理。
147 2
Spring高手之路25——深入解析事务管理的切面本质
|
9月前
|
JSON 自然语言处理 Java
OpenAI API深度解析:参数、Token、计费与多种调用方式
随着人工智能技术的飞速发展,OpenAI API已成为许多开发者和企业的得力助手。本文将深入探讨OpenAI API的参数、Token、计费方式,以及如何通过Rest API(以Postman为例)、Java API调用、工具调用等方式实现与OpenAI的交互,并特别关注调用具有视觉功能的GPT-4o使用本地图片的功能。此外,本文还将介绍JSON模式、可重现输出的seed机制、使用代码统计Token数量、开发控制台循环聊天,以及基于最大Token数量的消息列表限制和会话长度管理的控制台循环聊天。
3125 7
|
10月前
|
Java 开发者 Spring
深入解析:Spring AOP的底层实现机制
在现代软件开发中,Spring框架的AOP(面向切面编程)功能因其能够有效分离横切关注点(如日志记录、事务管理等)而备受青睐。本文将深入探讨Spring AOP的底层原理,揭示其如何通过动态代理技术实现方法的增强。
327 8
|
10月前
|
前端开发 Java 开发者
Spring MVC中的请求映射:@RequestMapping注解深度解析
在Spring MVC框架中,`@RequestMapping`注解是实现请求映射的关键,它将HTTP请求映射到相应的处理器方法上。本文将深入探讨`@RequestMapping`注解的工作原理、使用方法以及最佳实践,为开发者提供一份详尽的技术干货。
829 2
|
10月前
|
前端开发 Java Spring
探索Spring MVC:@Controller注解的全面解析
在Spring MVC框架中,`@Controller`注解是构建Web应用程序的基石之一。它不仅简化了控制器的定义,还提供了一种优雅的方式来处理HTTP请求。本文将全面解析`@Controller`注解,包括其定义、用法、以及在Spring MVC中的作用。
229 2

热门文章

最新文章

推荐镜像

更多
  • DNS