一个传递依赖导致的应用崩溃

31 篇文章 1 订阅
2 篇文章 0 订阅
  • 问题

最近我们有个应用合并了一个任务的代码之后应用无法启动,报下面的错误:

Caused by: java.lang.NoSuchMethodError: org.springframework.http.converter.json.Jackson2ObjectMapperBuilder.modulesToInstall([Lcom/fasterxml/jackson/databind/Module;)Lorg/springframework/http/converter/json/Jackson2ObjectMapperBuilder;
at org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration$JacksonObjectMapperBuilderConfiguration.configureModules(JacksonAutoConfiguration.java:259)
at org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration$JacksonObjectMapperBuilderConfiguration.jacksonObjectMapperBuilder(JacksonAutoConfiguration.java:186)
at org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration$JacksonObjectMapperBuilderConfiguration$EnhancerBySpringCGLIB$82b81f51.CGLIB$jacksonObjectMapperBuilder$1(<generated>)
at org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration$JacksonObjectMapperBuilderConfiguration$$EnhancerBySpringCGLIB$$82b81f51$$FastClassBySpringCGLIB$$ce0faa75.invoke(<generated>)
at org.springframework.cglib.proxy.MethodProxy.invokeSuper(MethodProxy.java:228)
at org.springframework.context.annotation.ConfigurationClassEnhancer$BeanMethodInterceptor.intercept(ConfigurationClassEnhancer.java:309)
at org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration$JacksonObjectMapperBuilderConfiguration$$EnhancerBySpringCGLIB$$82b81f51.jacksonObjectMapperBuilder(<generated>)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:606)
at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:162)
... 16 more

从日志里面可以很明显的看出来框架里面调用的org.springframework.http.converter.json.Jackson2ObjectMapperBuilder.modulesToInstal这个方法不存在,参数类型不对,调用传入的是数组类型,但是jar里面的方法定义是Class类型的。以前也遇到过类似的问题一般是Spring版本差异导致的,应用依赖的spring-web 4.1.4,这个版本确实不存在上面的那个方法,但是查看4.1.7的版本发现这个方法是存在的,讲道理应该是直接升级spring-web版本的,但是查看线上依赖的也是这个版本而且一直正常运行,而直接修改spring版本号有无法预估的风险,需要时间充分测试才能上线,为了降低风险还是从最近的任务入手,看是最近哪些任务修改导致了这次错误。
由于线上代码是正常运行的,所以为了比较差异最快的办法是把线上应用依赖的jar和当前测试环境依赖的jar包都拉出来进行对比。比较发现当前测试版本多依赖了下面几个可疑的jar包:

spring-boot-starter-web-1.1.10.RELEASE.jar
spring-boot-starter-tomcat-1.1.10.RELEASE.jar
jackson-core-2.3.4.jar
jackson-databind-2.3.4.jar
jackson-module-jaxb-annotations-2.3.3.jar

看代码提交记录这几个变化都是由于新任务引入了一个oms-service-api包间接引入的。看到上面的差异马上就把目光锁定在spring-boot-starter-web和spring-boot-starter-tomcat这两个,因为应用使用了spring boot框架并开启了AutoConfiguration特性,所以怀疑是这两个jar额外触发了一系列bean的加载导致进入了之前没进入过的框架逻辑导致的,马上exclude掉这两个依赖重新启动,但是应用还是无法启动,还是一样的错误。没办法只能挨个试,最后发现把jackson-databind这个依赖exclude掉之后,应用就正常启动了。。。

  • 原因分析

为啥classpath下多了这个jar包就会导致spring-boot中的错误呢?查看一下org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration$JacksonObjectMapperBuilderConfiguration这个内部类的代码:
在这里插入图片描述

在这里插入图片描述
这个内部类定义了一个ConditionalOnClass注解。spring在加载bean配置时,会调用spring-context的Condition框架对所有带Conditional注解的bean进行条件判断是否需要跳过bean配置的加载。定义了ConditionalOnClass注解的bean配置在加载该bean时会检查value中的Class是否存在于应用的classpath中,只有存在时才加载,否则直接跳过,可以查看org.springframework.boot.autoconfigure.condition.OnClassCondition中的源码。而ObjectMapper这个类刚好是jackson-bind这个jar包中的,所以classpath新加了jackson-bind包之后,org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration$JacksonObjectMapperBuilderConfiguration这个bean配置的加载就不会跳过了,容器就会初始化Jackson2ObjectMapperBuilder这个bean,然后就会走到configureModules这个有问题的方法。从这也可以看出来,jackson-bind其实是背锅的,应用中本来就一直存在隐患,只是之前没有暴露,而它让错误暴露了,根本原因还是应用中依赖了spring不兼容的版本,终极解决方案还是要把spring-web版本号升到4.1.7。

  • 结语

maven虽好,它强大的依赖管理能替我们开发省不少事,但是在开发中在增加了任何新依赖时都要谨慎对待它带来的间接依赖有没有副作用,有时候一个小小的版本号变化会直接导致应用崩溃,最近已经遇到过多起传递依赖导致的问题了。在对外开放api时最好关掉传递依赖,比如把optional设置成true或者把scope设置成provide,特别是spring这种关键的依赖,可能会直接把依赖方搞崩。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值