分享一个开发环境与部署环境的差异导致的问题

问题描述

在开发环境中执行没有错误,但是打包 jar 包到 linux 上执行报错

错误信息

java.lang.NullPointerException
	at org.neo4j.ogm.MetaData.entityType(MetaData.java:277) ~[neo4j-ogm-core-2.0.5.jar!/:?]
	at org.neo4j.ogm.session.Neo4jSession.entityType(Neo4jSession.java:485) ~[neo4j-ogm-core-2.0.5.jar!/:?]
	at org.neo4j.ogm.session.delegates.LoadByTypeDelegate.loadAll(LoadByTypeDelegate.java:59) ~[neo4j-ogm-core-2.0.5.jar!/:?]
	at org.neo4j.ogm.session.delegates.LoadByTypeDelegate.loadAll(LoadByTypeDelegate.java:112) ~[neo4j-ogm-core-2.0.5.jar!/:?]
	at org.neo4j.ogm.session.Neo4jSession.loadAll(Neo4jSession.java:151) ~[neo4j-ogm-core-2.0.5.jar!/:?]
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[?:1.8.0_171]
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[?:1.8.0_171]
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[?:1.8.0_171]
	at java.lang.reflect.Method.invoke(Method.java:498) ~[?:1.8.0_171]
	at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:333) ~[spring-aop-4.3.3.RELEASE.jar!/:4.3.3.RELEASE]
	at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:190) ~[spring-aop-4.3.3.RELEASE.jar!/:4.3.3.RELEASE]
	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:157) ~[spring-aop-4.3.3.RELEASE.jar!/:4.3.3.RELEASE]
	at org.springframework.aop.support.DelegatingIntroductionInterceptor.doProceed(DelegatingIntroductionInterceptor.java:133) ~[spring-aop-4.3.3.RELEASE.jar!/:4.3.3.RELEASE]
	at org.springframework.aop.support.DelegatingIntroductionInterceptor.invoke(DelegatingIntroductionInterceptor.java:121) ~[spring-aop-4.3.3.RELEASE.jar!/:4.3.3.RELEASE]
	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179) ~[spring-aop-4.3.3.RELEASE.jar!/:4.3.3.RELEASE]
	at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:213) ~[spring-aop-4.3.3.RELEASE.jar!/:4.3.3.RELEASE]
	at com.sun.proxy.$Proxy181.loadAll(Unknown Source) ~[?:?]
	at org.springframework.data.neo4j.template.Neo4jTemplate.loadAll(Neo4jTemplate.java:100) ~[spring-data-neo4j-4.1.3.RELEASE.jar!/:?]
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[?:1.8.0_171]
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[?:1.8.0_171]
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[?:1.8.0_171]
	at java.lang.reflect.Method.invoke(Method.java:498) ~[?:1.8.0_171]
	at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:333) ~[spring-aop-4.3.3.RELEASE.jar!/:4.3.3.RELEASE]
	at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:190) ~[spring-aop-4.3.3.RELEASE.jar!/:4.3.3.RELEASE]
	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:157) ~[spring-aop-4.3.3.RELEASE.jar!/:4.3.3.RELEASE]
	at org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:136) ~[spring-tx-4.3.3.RELEASE.jar!/:4.3.3.RELEASE]
	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179) ~[spring-aop-4.3.3.RELEASE.jar!/:4.3.3.RELEASE]
	at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:213) ~[spring-aop-4.3.3.RELEASE.jar!/:4.3.3.RELEASE]
	at com.sun.proxy.$Proxy183.loadAll(Unknown Source) ~[?:?]
	at org.springframework.data.neo4j.repository.GraphRepositoryImpl.findAll(GraphRepositoryImpl.java:125) ~[spring-data-neo4j-4.1.3.RELEASE.jar!/:?]
	at org.springframework.data.neo4j.repository.GraphRepositoryImpl.findAll(GraphRepositoryImpl.java:120) ~[spring-data-neo4j-4.1.3.RELEASE.jar!/:?]

问题定位

在 neo4j-ogm-core 包中的 ClassPathScanner 类的 scanClassFileEntry 方法


可以看出 classInfos 中没有找到对应类 这里在本地开发环境无法重现
后面会给出为什么本地无法重现

public ClassInfo classInfo(String name) {
        if (classInfos.containsKey(name)) {
            return classInfos.get(name);
        }
        ClassInfo classInfo = _classInfo(name, NodeEntity.class.getName(), "label");
        if (classInfo != null) {
            classInfos.put(name, classInfo);
            return classInfo;
        }
        classInfo = _classInfo(name, RelationshipEntity.class.getName(), "type");
        if (classInfo != null) {
            classInfos.put(name, classInfo);
            return classInfo;
        }
        classInfo = domainInfo.getClassSimpleName(name);
        if (classInfo != null) {
            classInfos.put(name, classInfo);
            return classInfo;
        }
        // not found
        classInfos.put(name, null);
        return null;
    }

解决方式

自行添加 org.neo4j.ogm.scanner.ClassPathScanner 类并修改 scanClassFileEntry 方法

    private void scanClassFileEntry(InputStream inputStream, ZipEntry entry) throws IOException {

        String name = entry.getName();

        LOGGER.trace("Scanning class entry: {}", name);

        for (String pathToScan : classPaths) {
            if (name.contains(pathToScan)) {
                LOGGER.debug("{} found in {}", pathToScan, name);
                processor.process(inputStream);
                break;
            }
        }
    }

定位问题过程 (idea 远程调试 + 本地开发调试)进行比对

执行 jar 包时开启远程调试 -Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=5005

java -Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=5005 -Xms4048m -Xmx4048m -jar datamanage-business-bigdata-catalog-0.0.1-SNAPSHOT.jar
idea 添加远程配置

  1. 报错位置

  1. 开发和生产比对

开发

生产

开发中 calssInfos中有146个而生产只有64个

  1. 排查 classInfos 差异位置 跟踪 MetaData 生成位置

可以看出从这个位置开始开发和生产发生了不同


这里是上面方法的调用链

看出生产和开发的 pathFiles 不同


生产环境,会执行 scanZipFile 进行扫描

这里是开发环境,会执行 scanFolder 进行扫描

生产上执行位置, 这是重点!!! 其中 path 多了 BOOT-INFO/classes
这里截图的并不友好,实际在扫描neo4j实体时本应该加载进 classInfo 但是这里比对时导致没有加载进 classInfo

开发环境这里可以看出会执行 processor.process(inputStream);

processor.process() 方法

	@Override
    public void process(final InputStream inputStream) throws IOException {
        ClassInfo classInfo = new ClassInfo(inputStream);
        String className = classInfo.name();
        String superclassName = classInfo.superclassName();
        LOGGER.debug("Processing: {} -> {}", className, superclassName);
        if (className != null) {
            ClassInfo thisClassInfo = classNameToClassInfo.get(className)
            if (thisClassInfo == null) {
                thisClassInfo = classInfo;
                classNameToClassInfo.put(className, thisClassInfo);            
            }
            if (!thisClassInfo.hydrated()) {
                thisClassInfo.hydrate(classInfo);
                ClassInfo superclassInfo = classNameToClassInfo.get(superclassName);
                if (superclassInfo == null) {
                    classNameToClassInfo.put(superclassName, new ClassInfo(superclassName, thisClassInfo));
                } else {
                    superclassInfo.addSubclass(thisClassInfo);
                }
            }
            if (thisClassInfo.isEnum()) {
                LOGGER.debug("Registering enum class: {}", thisClassInfo.name());
                enumTypes.add(thisClassInfo.getUnderlyingClass());
            }
        }
    }

scan 扫描的部分代码

    private void scanFolder(File folder, int prefixSize) throws IOException {
        String absolutePath = folder.getPath();
        String relativePath = prefixSize > absolutePath.length() ? "" : absolutePath.substring(prefixSize);
        File[] subFiles = folder.listFiles();
        if (subFiles != null) {
            for (final File subFile : subFiles) {
                if (subFile.isDirectory()) {
                    scanFolder(subFile, prefixSize);
                } else if (subFile.isFile()) {
                    String leafSuffix = "/" + subFile.getName();
                    scanFile(subFile, relativePath + leafSuffix);
                }
            }
        }
    }

    private void scanZipFile(final ZipFile zipFile) throws IOException {
        LOGGER.debug("Scanning zipFile {}", zipFile.getName());
        for (Enumeration<? extends ZipEntry> entries = zipFile.entries(); entries.hasMoreElements();) {
            final ZipEntry entry = entries.nextElement();
            if (!entry.isDirectory()) {
                try (InputStream inputStream = zipFile.getInputStream(entry) ) {
                    if (entry.getName().endsWith(".class")) {
                        scanClassFileEntry(inputStream, entry);
                    } else if (isArchive(entry.getName())) {
                        scanZippedEntry(inputStream, entry);
                    }
                }
            }
        }
    }
    private void scanClassFileEntry(InputStream inputStream, ZipEntry entry) throws IOException {
        String name = entry.getName();
        LOGGER.trace("Scanning class entry: {}", name);
        for (String pathToScan : classPaths) {
            if (name.contains(pathToScan)) {
                LOGGER.debug("{} found in {}", pathToScan, name);
                processor.process(inputStream);
                break;
            }
        }
    }
	   public void scan(List<String> classPaths, ClassFileProcessor processor) {
        this.classPaths = classPaths;
        this.processor = processor;
        Set<File> classPathElements = getUniqueClasspathElements(classPaths);
        LOGGER.debug("Classpath elements:");
        for (File classPathElement : classPathElements) {
            LOGGER.debug(classPathElement.getPath());
        }
        try {
            for (File classPathElement : classPathElements) {
                String path = classPathElement.getPath();
                if (classPathElement.isDirectory()) {
                    scanFolder(classPathElement, path.length() + 1);
                } else if (classPathElement.isFile()) {
                    if (isArchive(path)) {
                        scanZipFile(new ZipFile(classPathElement));
                    } else {
                        scanFile(classPathElement, classPathElement.getName());
                    }
                }
            }
            processor.finish();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
1 个赞

虽然不懂。但是感觉很牛逼。点赞就完事儿了。:+1: