排查由于编译版本过高导致的部署失败的问题

事件简介

技术背景简介

类Dubbo分布式系统,本人职责网关,通过Maven包调用多个后端通用平台接口,以将不同平台的数据进行整理融合,并向前端提供基于需求的偏垂直接口。

事件描述

进行技术修改后准备上预发,通过基于jackens的集成平台进行编译后部署失败,报错如下:

18:34:15.374 [localhost-startStop-1] ERROR org.springframework.web.context.ContextLoader - Context initialization failed
org.springframework.beans.factory.BeanDefinitionStoreException: Unexpected exception parsing XML document from class path resource [context/spring-config-context.xml]; nested exception is java.lang.UnsupportedClassVersionError: javax/annotation/ManagedBean : Unsupported major.minor version 52.0 (unable to load class javax.annotation.ManagedBean)
at org.springframework.beans.factory.xml.XmlBeanDefinitionReader.doLoadBeanDefinitions(XmlBeanDefinitionReader.java:412)
at org.springframework.beans.factory.xml.XmlBeanDefinitionReader.loadBeanDefinitions(XmlBeanDefinitionReader.java:334)
at org.springframework.beans.factory.xml.XmlBeanDefinitionReader.loadBeanDefinitions(XmlBeanDefinitionReader.java:302)
at org.springframework.beans.factory.support.AbstractBeanDefinitionReader.loadBeanDefinitions(AbstractBeanDefinitionReader.java:174)
at org.springframework.beans.factory.support.AbstractBeanDefinitionReader.loadBeanDefinitions(AbstractBeanDefinitionReader.java:209)
at org.springframework.beans.factory.support.AbstractBeanDefinitionReader.loadBeanDefinitions(AbstractBeanDefinitionReader.java:180)
at org.springframework.web.context.support.XmlWebApplicationContext.loadBeanDefinitions(XmlWebApplicationContext.java:125)
at org.springframework.web.context.support.XmlWebApplicationContext.loadBeanDefinitions(XmlWebApplicationContext.java:94)
at org.springframework.context.support.AbstractRefreshableApplicationContext.refreshBeanFactory(AbstractRefreshableApplicationContext.java:131)
at org.springframework.context.support.AbstractApplicationContext.obtainFreshBeanFactory(AbstractApplicationContext.java:522)
at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:436)
at org.springframework.web.context.ContextLoader.configureAndRefreshWebApplicationContext(ContextLoader.java:385)
at org.springframework.web.context.ContextLoader.initWebApplicationContext(ContextLoader.java:284)
at org.springframework.web.context.ContextLoaderListener.contextInitialized(ContextLoaderListener.java:111)
at org.apache.catalina.core.StandardContext.listenerStart(StandardContext.java:5016)
at org.apache.catalina.core.StandardContext.startInternal(StandardContext.java:5528)
at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:150)
at org.apache.catalina.core.ContainerBase.addChildInternal(ContainerBase.java:901)
at org.apache.catalina.core.ContainerBase.addChild(ContainerBase.java:877)
at org.apache.catalina.core.StandardHost.addChild(StandardHost.java:652)
at org.apache.catalina.startup.HostConfig.deployDescriptor(HostConfig.java:672)
at org.apache.catalina.startup.HostConfig$DeployDescriptor.run(HostConfig.java:1882)
at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:471)
at java.util.concurrent.FutureTask.run(FutureTask.java:262)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615)
at java.lang.Thread.run(Thread.java:745)
Caused by: java.lang.UnsupportedClassVersionError: javax/annotation/ManagedBean : Unsupported major.minor version 52.0 (unable to load class javax.annotation.ManagedBean)
at org.apache.catalina.loader.WebappClassLoader.findClassInternal(WebappClassLoader.java:2961)
at org.apache.catalina.loader.WebappClassLoader.findClass(WebappClassLoader.java:1210)
at org.apache.catalina.loader.WebappClassLoader.loadClass(WebappClassLoader.java:1690)
at org.apache.catalina.loader.WebappClassLoader.loadClass(WebappClassLoader.java:1571)
at org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider.registerDefaultFilters(ClassPathScanningCandidateComponentProvider.java:201)
at org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider.(ClassPathScanningCandidateComponentProvider.java:104)
at org.springframework.context.annotation.ClassPathBeanDefinitionScanner.(ClassPathBeanDefinitionScanner.java:138)
at org.springframework.context.annotation.ClassPathBeanDefinitionScanner.(ClassPathBeanDefinitionScanner.java:112)
at org.springframework.context.annotation.ComponentScanBeanDefinitionParser.createScanner(ComponentScanBeanDefinitionParser.java:129)
at org.springframework.context.annotation.ComponentScanBeanDefinitionParser.configureScanner(ComponentScanBeanDefinitionParser.java:99)
at org.springframework.context.annotation.ComponentScanBeanDefinitionParser.parse(ComponentScanBeanDefinitionParser.java:83)
at org.springframework.beans.factory.xml.NamespaceHandlerSupport.parse(NamespaceHandlerSupport.java:73)
at org.springframework.beans.factory.xml.BeanDefinitionParserDelegate.parseCustomElement(BeanDefinitionParserDelegate.java:1419)
at org.springframework.beans.factory.xml.BeanDefinitionParserDelegate.parseCustomElement(BeanDefinitionParserDelegate.java:1409)
at org.springframework.beans.factory.xml.DefaultBeanDefinitionDocumentReader.parseBeanDefinitions(DefaultBeanDefinitionDocumentReader.java:184)
at org.springframework.beans.factory.xml.DefaultBeanDefinitionDocumentReader.doRegisterBeanDefinitions(DefaultBeanDefinitionDocumentReader.java:140)
at org.springframework.beans.factory.xml.DefaultBeanDefinitionDocumentReader.registerBeanDefinitions(DefaultBeanDefinitionDocumentReader.java:111)
at org.springframework.beans.factory.xml.XmlBeanDefinitionReader.registerBeanDefinitions(XmlBeanDefinitionReader.java:493)
at org.springframework.beans.factory.xml.XmlBeanDefinitionReader.doLoadBeanDefinitions(XmlBeanDefinitionReader.java:390)
... 26 common frames omitted

问题排查与探索

错误日志解析

经搜索,此问题出现的原因是.java文件使用高版本jdk进行编译,然后部署在低版本jre上。导致Java虚拟机加载.class文件时无法识别开头的版本编号,进而导致部署失败。

问题关键字:高版本编译、低版本部署.class版本编号

我遇到的问题,在日志中表示的很明确:

如果是纯粹的Spring自定义标签解析,且完全在xml中完成所有定义。在解析xml并生成BeanDefinition时没有问题,这个很好理解,因为我们在存储Bean定义时仅将类名以字符串形式存储,不进行类加载。

但是在这里,根据调用堆栈,我们调用了parseCustomElement(),及定制标签的解析,在定制标签的解析中,我们发现有类似ConpoentScan的字样,虽然我们没有把Spring原理完全读完,但是根据我们对Spring 框架的了解和日常使用的经验,可以大概描绘出车祸现场:

  1. 我们依赖的后端提供的maven版本中存在高版本jdk编译的class
  2. 我们在进行xml标签的解析时,通过引入其他基于Spring的框架,启动了对Java代码的扫描,类似ComponentScan,以达到通过XML进行基本信息配置、通过JAVA注解简化业务相关配置,以提高编码效率,在加载要扫描项目中的类时,接触到了高版本的class,jvm无法完成类加载操作,故报错。

另外,我们的报错是52,Java版本和编号的对应关系如下:

  • Java 1.2 uses major version 46
  • Java 1.3 uses major version 47
  • Java 1.4 uses major version 48
  • Java 5 uses major version 49
  • Java 6 uses major version 50
  • Java 7 uses major version 51
  • Java 8 uses major version 52
  • Java 9 uses major version 53

因为项目太过老旧且代码极其混乱,目前此项目的运行环境还是Java7。

综合以上描述,问题陈述如下:

我们使用Java8编译,使用Java7部署,导致部署失败。

解决方法:

  1. 提升部署的Java版本——不可行,此项目存在大量的旧框架,提升Java版本意味着大量的包升级
  2. 降低编译版本——可行

定位问题所在

首先,检测jackens配置,经检测,编译配置正常,是使用jdk1.7进行的编译。猜测是后端背着我们偷偷发包升级编译版本了

检测maven依赖的Java版本号

依赖版本检测

从jackens上把打好的war包下下来,100M。。。。。其实有用的不多,实在没时间清理。。。从网上搜索的到反编译获得Java版本的命令:

javap -verbose MyClass | grep "major"

但是我们从war包中找到的后端依赖都是jar,需要进行解压,上网查询的到结果如下:

jar xf ~/a/b/c.jar

此命令可以进行解压,但是会将解压结果输出到当前目录,且不可定制

unzip ~/a/b/c.jar -d ~/d/e/f

此命令可以在解压是进行目录控制,但是在某些jar包解压时,可能存在文件覆盖问题,不知道原因,可能是后端打的包有点烂吧,我们后续在探究。

考虑优劣情况,我们采用第一条和第二条命令进行判断,通过在shell切换目录实现解压后文件的隔离

因为我们项目依赖的包太多,故采用shell脚本进行判断:

for file in `ls ~/Desktop/xxx-20190626182927/WEB-INF/lib`;do
    echo "=================================开始检测包${file}==========================="
    mkdir ~/Desktop/target/temp/${file}
    echo "完成临时目录创建"
    cp ~/Desktop/xxx-20190626182927/WEB-INF/lib/${file} ~/Desktop/target/temp/${file}/${file}

    cd ~/Desktop/target/temp/${file}
    jar xf ~/Desktop/target/temp/${file}/${file}
    echo "完成解压,抽取一个类,生成此类的Java版本为"

    javap -verbose $(find ~/Desktop/target/temp/${file} -name *.class | head -1) | grep "major" 
    
done

输出情况如下:

1.png

因为打印的东西实在有点多,13页,我们只截一部分的图。

检测高版本jar包引入路径

进入项目目录,执行如下命令:

mvn dependency:tree

得到maven依赖树,涉及公司内部代码,就不粘了。确定两个高版本maven包。

敲定解决方案

打卡下班,明天开怼!!!!

反思

  1. 在以后我们作为Dubbo中的服务提供者角色时,提供包时要考虑到版本兼容性,可以考虑依据groupid进行多jdk编译版本的maven包维护
  2. 在进行版本升级时,尽量邮件或者当面通知所有调用方,避免为他人带来不便
  3. 好好学学shell命令,这次一半多时间是花在写shell上

参考文献

https://blog.csdn.net/sweettool/article/details/77203193

https://www.runoob.com/linux/linux-shell-variable.html

你可能感兴趣的:(排查由于编译版本过高导致的部署失败的问题)