事件简介
技术背景简介
类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 框架的了解和日常使用的经验,可以大概描绘出车祸现场:
- 我们依赖的后端提供的maven版本中存在高版本jdk编译的class
- 我们在进行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部署,导致部署失败。
解决方法:
- 提升部署的Java版本——不可行,此项目存在大量的旧框架,提升Java版本意味着大量的包升级
- 降低编译版本——可行
定位问题所在
首先,检测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
输出情况如下:
因为打印的东西实在有点多,13页,我们只截一部分的图。
检测高版本jar包引入路径
进入项目目录,执行如下命令:
mvn dependency:tree
得到maven依赖树,涉及公司内部代码,就不粘了。确定两个高版本maven包。
敲定解决方案
打卡下班,明天开怼!!!!
反思
- 在以后我们作为Dubbo中的服务提供者角色时,提供包时要考虑到版本兼容性,可以考虑依据groupid进行多jdk编译版本的maven包维护
- 在进行版本升级时,尽量邮件或者当面通知所有调用方,避免为他人带来不便
- 好好学学shell命令,这次一半多时间是花在写shell上。
参考文献
https://blog.csdn.net/sweettool/article/details/77203193
https://www.runoob.com/linux/linux-shell-variable.html