springmvc 默认json解析实现 jackson 踩坑实录

        最近在优化修改某个项目代码时碰到一个问题,某个接口采用json 方式进行前后端数据交互,原始代码时用一个字符串接受json,然后手动通过fastjson 转换成对应的javabean,其实这个参数解析工作完全可以交给spring框架去执行,无需手动解析,你只需定义对应的javaBean,@RequestBody 这个注解就可以轻松实现 json 数据的自动解析和绑定功能。

        然而我改成注解形式的方式接受参数后,一个莫名的问题来了,有一个字段参数没有接受到前端传过来的值。此处为了演示该问题,我这里单独建了个demo工程演示还原问题场景,对应javaBean:

@Data
public class User {
    private String userName;
    private Integer userAge;
    private String NICKName;
}

 

 对应参数接受代码如下:

@PostMapping("hello2")
public String hello2(@RequestBody User user) throws InterruptedException {
    log.info("hello:{}",user);
    Thread.sleep(1000);
    return user.toString();
}

 看到代码,可能有些老司机,已经知道问题出在哪里了,没错,

NICKName

这个属性无法接受到前端传过来的值,这里暂且不说结论,说下我当时的思路:

        要解析json参数并且赋值给javaBean(User对象),那么势必会调用对象的get/set 方法,那么问题很可能就出在这里,因为我使用了@Data, lombok(一个自动实现get/set toString等方法的框架非常好用,可以大量缩减源代码,使你的代码简洁明了,便于维护) 框架,实现自动get/set 方法以及 toString 方法,于是我手动写了所有属性的get/方法,不使用lombok,结果是仍然无法接受到该参数(mmp).

        虽然上面的思路没有通过验证,但是也使问题变得简单了,现在基本可以确定是springmvc 框架解析参数时导致的问题,而且对比其他参数差异,无非就是 “NICKName” 该属性的命名不符合java 驼峰命名规则,于是我修改了该参数命名“nickName”,结果是后台完美解析了该参数。

            至此,基本已经可以确定是springmvc 解析参数导致的,但是还不清楚问题到底出在哪个环节。于是下面开始了源码分析debug模式,我们知道springmvc 默认的json 解析使用的是jackson。

 下面是我的debug过程,当然中间饶了许多弯子,这里直接给出正确的跟踪过程:

如图,整个方法执行,spring采用了反射机制实现,这里

Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);

就是解析 controller 层接受的参数,继续跟:

继续跟踪:

方法走到了

org.springframework.web.servlet.mvc.method.annotation.AbstractMessageConverterMethodArgumentResolver

中,这个类是干嘛的呢,它是用来解析 reqeust body 的 参数的,并且是个抽象类,也就是说他会有多个实现。我们继续往下走:

注意到该类中持有一个 HttpMessageConverter 的结合引用List<HttpMessageConverter<?>> messageConverters;,如下图:

我们直接跳过前面七个HttpMessageConverter,应为我们知道默认是由 jackson实现的,所以继续跟踪:

此处我们看到第一个这个方法:

JsonDeserializer<Object> deser = _findCachedDeserializer(type)

很明显spring 首先会从缓存中获取 对应的 javaBean 序列化解析器。没有找到才会走下面的方法:

deser = _createAndCacheValueDeserializer(ctxt, factory, type);

其实阅读spring 的源码,发现很多地方都使用了类似的方法实现,我们继续往下跟踪:

通过方法名和源码注释我们看到,这里是创建并且会缓存还序列化实现:

 

继续往下跟:

 

 

到这里,我们已经很接近事实的真相了(长舒一口气),通过类名:

POJOPropertiesCollector

其实我们就知道,这个类的功能就是pojo 也就是javabean 属性解析,

我们继续往下跟:

 

这里注意如何解析 NICKName属性的,

最后我们跟踪到 BeanUtil 类中,这里就是最终解析我们pojo 属性的地方,看源代码:

 

/**
 * Method called to figure out name of the property, given 
 * corresponding suggested name based on a method or field name.
 *
 * @param basename Name of accessor/mutator method, not including prefix
 *  ("get"/"is"/"set")
 */
protected static String legacyManglePropertyName(final String basename, final int offset)
{
    final int end = basename.length();
    if (end == offset) { // empty name, nope
        return null;
    }
    // next check: is the first character upper case? If not, return as is
    char c = basename.charAt(offset);
    char d = Character.toLowerCase(c);
    
    if (c == d) {
        return basename.substring(offset);
    }
    // otherwise, lower case initial chars. Common case first, just one char
    StringBuilder sb = new StringBuilder(end - offset);
    sb.append(d);
    int i = offset+1;
    for (; i < end; ++i) {
        c = basename.charAt(i);
        d = Character.toLowerCase(c);
        if (c == d) {
            sb.append(basename, i, end);
            break;
        }
        sb.append(d);
    }
    return sb.toString();
}

看注释以及源代码知道,这里是用get/set 方法名来解析pojo 的属性名称,并且是按照 驼峰表示类解析:

注意这段代码,如果去掉“get”之后首字符是大写,这里会转成小写,一直到遇到第一个小写字母位置。所以"NICKName" 属性最终被解析成了 nickname,

所以这就导致我们始终无法解析到前段传过来的值。至此我们终于搞清楚导致整个问题的原因,整个例子充分给我们证明了一个java 开发中的一个公理:“约定大于配置”。即我们参数命名一定要按照驼峰标识,按照大家约定好的规范编码可以减少很多不必要的麻烦以及bug,以上写的洋洋洒洒,有不对之处欢迎指正。

欢迎关注个人公众号:

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值