54

一个离奇的 ArrayIndexOutOfBoundsException 异常的排查过程

 5 years ago
source link: https://mp.weixin.qq.com/s/VZww7SQu9ubis8LZz_Pxew?amp%3Butm_medium=referral
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

qyqmQvy.jpg!web

今天同事遇到了一个离奇的ArrayIndexOutOfBoundsException,找我协助定位,定位的过程很有意思,故而记录一下。

先按时序复盘一下

  • 项目原先可正常运行。

  • 没有修改任何依赖的情况下,从另一个项目移植了工具类 BeanValidationUtil 后,报如下异常:

    org.springframework.web.context.ContextLoader.initWebApplicationContext(ContextLoader.java:307) | Context initialization failed
    org.springframework.beans.factory.BeanDefinitionStoreException: Failed to read candidate component class: file [/Users/xxxxxxxxx/BeanValidationUtil.class]; nested exception is java.lang.ArrayIndexOutOfBoundsException: 48959
        at org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider.findCandidateComponents(ClassPathScanningCandidateComponentProvider.java:260)
        at org.springframework.context.annotation.ClassPathBeanDefinitionScanner.doScan(ClassPathBeanDefinitionScanner.java:242)
        at org.springframework.context.annotation.ComponentScanBeanDefinitionParser.parse(ComponentScanBeanDefinitionParser.java:84)
        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)
        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:384)
        at org.springframework.web.context.ContextLoader.initWebApplicationContext(ContextLoader.java:283)
        at org.springframework.web.context.ContextLoaderListener.contextInitialized(ContextLoaderListener.java:111)
        at org.apache.catalina.core.StandardContext.listenerStart(StandardContext.java:5135)
        at org.apache.catalina.core.StandardContext.startInternal(StandardContext.java:5658)
        at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:145)
        at org.apache.catalina.core.ContainerBase.addChildInternal(ContainerBase.java:1015)
        at org.apache.catalina.core.ContainerBase.addChild(ContainerBase.java:991)
        at org.apache.catalina.core.StandardHost.addChild(StandardHost.java:652)
        at org.apache.catalina.startup.HostConfig.manageApp(HostConfig.java:2015)
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
        at java.lang.reflect.Method.invoke(Method.java:498)
        at org.apache.tomcat.util.modeler.BaseModelMBean.invoke(BaseModelMBean.java:301)
        at com.sun.jmx.interceptor.DefaultMBeanServerInterceptor.invoke(DefaultMBeanServerInterceptor.java:819)
        at com.sun.jmx.mbeanserver.JmxMBeanServer.invoke(JmxMBeanServer.java:801)
        at org.apache.catalina.mbeans.MBeanFactory.createContext(MBeanFactory.java:789)
        at org.apache.catalina.mbeans.MBeanFactory.createStandardContext(MBeanFactory.java:573)
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
        at java.lang.reflect.Method.invoke(Method.java:498)
        at org.apache.tomcat.util.modeler.BaseModelMBean.invoke(BaseModelMBean.java:301)
        at com.sun.jmx.interceptor.DefaultMBeanServerInterceptor.invoke(DefaultMBeanServerInterceptor.java:819)
        at com.sun.jmx.mbeanserver.JmxMBeanServer.invoke(JmxMBeanServer.java:801)
        at javax.management.remote.rmi.RMIConnectionImpl.doOperation(RMIConnectionImpl.java:1468)
        at javax.management.remote.rmi.RMIConnectionImpl.access$300(RMIConnectionImpl.java:76)
        at javax.management.remote.rmi.RMIConnectionImpl$PrivilegedOperation.run(RMIConnectionImpl.java:1309)
        at javax.management.remote.rmi.RMIConnectionImpl.doPrivilegedOperation(RMIConnectionImpl.java:1401)
        at javax.management.remote.rmi.RMIConnectionImpl.invoke(RMIConnectionImpl.java:829)
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
        at java.lang.reflect.Method.invoke(Method.java:498)
        at sun.rmi.server.UnicastServerRef.dispatch(UnicastServerRef.java:324)
        at sun.rmi.transport.Transport$1.run(Transport.java:200)
        at sun.rmi.transport.Transport$1.run(Transport.java:197)
        at java.security.AccessController.doPrivileged(Native Method)
        at sun.rmi.transport.Transport.serviceCall(Transport.java:196)
        at sun.rmi.transport.tcp.TCPTransport.handleMessages(TCPTransport.java:568)
        at sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run0(TCPTransport.java:826)
        at sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.lambda$run$0(TCPTransport.java:683)
        at java.security.AccessController.doPrivileged(Native Method)
        at sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run(TCPTransport.java:682)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
        at java.lang.Thread.run(Thread.java:745)
    Caused by: java.lang.ArrayIndexOutOfBoundsException: 48959
        at org.springframework.asm.ClassReader.readUnsignedShort(Unknown Source)
        at org.springframework.asm.ClassReader.accept(Unknown Source)
        at org.springframework.asm.ClassReader.accept(Unknown Source)
        at org.springframework.core.type.classreading.SimpleMetadataReader.<init>(SimpleMetadataReader.java:54)
        at org.springframework.core.type.classreading.SimpleMetadataReaderFactory.getMetadataReader(SimpleMetadataReaderFactory.java:80)
        at org.springframework.core.type.classreading.CachingMetadataReaderFactory.getMetadataReader(CachingMetadataReaderFactory.java:101)
        at org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider.findCandidateComponents(ClassPathScanningCandidateComponentProvider.java:236)
        ... 68 more

造成异常的工具类

/**
 * JSR349 Bean Validation工具类
 *
 * @author limu.zl
 */
public class BeanValidationUtil {
    /**
     * 验证失败时抛出ConstraintViolationException
     *
     * @param object 待验证对象
     * @param groups 验证group
     */
    public static void validateWithException(Object object, Class<?>... groups) {
        ValidatorFactory validatorFactory = Validation.buildDefaultValidatorFactory();

        // 国际化,强制用中文,ref:https://docs.oracle.com/javase/tutorial/i18n/locale/create.html
        Locale locale = new Locale.Builder().setLanguage("zh").setRegion("CN").build();

        // 国际化,指定语言,ref:https://www.ibm.com/developerworks/cn/java/j-cn-hibernate-validator/index.html
        MessageInterpolator interpolator = new LocalizedMessageInterpolator(validatorFactory.getMessageInterpolator(), locale);

        Validator classValidator = validatorFactory.usingContext()
                .messageInterpolator(interpolator)
                .getValidator();

        Set<ConstraintViolation<Object>> constraintViolations = classValidator.validate(object, groups);
        if (!constraintViolations.isEmpty()) {
            String messages = constraintViolations.stream()
                    .filter(t -> t instanceof ConstraintViolationImpl)
                    .map(t -> {
                        ConstraintViolationImpl t1 = (ConstraintViolationImpl) t;
                        return String.format("出问题的字段:%s,问题:%s", t1.getPropertyPath(), t1.getMessage());
                    })
                    .collect(Collectors.toList())
                    .toString();
            throw new IllegalArgumentException(messages);
        }
    }
}

分析

如代码所示, BeanValidationUtil 是个工具类,根本不在Spring容器上下文里,但异常栈却报到Spring的包里去了。

  • 由于项目没有修改依赖,所以包冲突问题不太可能出现(这个项目原先也使用JSR349做Bean Validation),而且冲突的话异常应该是NoClassDefFoundError之类的异常。

  • 尝试降低Hibernate Validation的版本到4.x,故障依旧。

  • 百度、谷歌类似异常,无果。

经过20分钟的源码定位也没找到问题所在,于是我尝试逐步删除BeanValidationUtil的代码。发现当把lambda语法删光之后,项目就能正常启动了。

突然灵光一现,问同事 : “这TM是不是个非常古老的项目啊?”

同事:“对啊,四五年了吧……”

于是分析了下pom.xml,发现用的是 Spring 3 。隐约记得Spring 3不完全兼容JDK8,这个类中使用了Java 8的语法,所以导致了问题。

结果确认

既然猜测是Spring 3和Java 8不兼容导致,故而在搜索时,将关键词改为: spring 3 java 8 ArrayIndexOutOfBoundsException ,果然印证了自己的想法。

  • ArrayOutOfBoundsException on Bean creation while using Java 8 constructs

  • Spring BeanDefinitionStoreExcept-nested exception is java.lang.ArrayIndexOutOfBoundsException: 53804

反思

犯了经验主义错误,基于Spring 3的项目已经三四年没有见过了(Dubbo不算,哈哈哈。因为Dubbo当初的版本虽然依赖了Spring 3,但其实实际项目一般都会exclude掉,换上Spring 4),一直以为是个Spring 4的项目,没有从Spring版本与JDK的兼容性的方向上去考虑。

浪费了半小时。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK