【问题标题】:ClassCastException on custom class loading from jar从 jar 加载自定义类时出现 ClassCastException
【发布时间】:2017-09-02 02:55:21
【问题描述】:

我正在尝试为教育目的实现自定义类加载器。

我在jar 文件中有模块“天气”,我想通过JarClassLoaderApp 类加载它。

来自here 的类加载器(它从指定的jar 加载所有类):

package com.example.classloading;

import java.io.IOException;
import java.io.InputStream;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

public class JarClassLoader extends ClassLoader {
    private HashMap<String, Class<?>> cache = new HashMap<String, Class<?>>();
    private String jarFileName;
    private String packageName;
    private static String WARNING = "Warning : No jar file found. Packet unmarshalling won't be possible. Please verify your classpath";

    public JarClassLoader(String jarFileName, String packageName) {
        this.jarFileName = jarFileName;
        this.packageName = packageName;

        cacheClasses();
    }

    private void cacheClasses() {
        try {
            JarFile jarFile = new JarFile(jarFileName);
            Enumeration entries = jarFile.entries();
            while (entries.hasMoreElements()) {
                JarEntry jarEntry = (JarEntry) entries.nextElement();
                // simple class validation based on package name
                if (match(normalize(jarEntry.getName()), packageName)) {
                    byte[] classData = loadClassData(jarFile, jarEntry);
                    if (classData != null) {
                        Class<?> clazz = defineClass(stripClassName(normalize(jarEntry.getName())), classData, 0, classData.length);
                        cache.put(clazz.getName(), clazz);
                        System.out.println("== class " + clazz.getName() + " loaded in cache");
                    }
                }
            }
        }
        catch (IOException IOE) {
            System.out.println(WARNING);
        }
    }

    public synchronized Class<?> loadClass(String name) throws ClassNotFoundException {
        Class<?> result = cache.get(name);
        if (result == null)
            result = cache.get(packageName + "." + name);
        if (result == null)
            result = super.findSystemClass(name);    
        System.out.println("== loadClass(" + name + ")");    
        return result;
    }

    private String stripClassName(String className) {
        return className.substring(0, className.length() - 6);
    }

    private String normalize(String className) {
        return className.replace('/', '.');
    }

    private boolean match(String className, String packageName) {
        return className.startsWith(packageName) && className.endsWith(".class");
    }

    private byte[] loadClassData(JarFile jarFile, JarEntry jarEntry) throws IOException {
        long size = jarEntry.getSize();
        if (size == -1 || size == 0)
            return null;

        byte[] data = new byte[(int)size];
        InputStream in = jarFile.getInputStream(jarEntry);
        in.read(data);

        return data;
    }
}

接口和实现(只是模板,没有任何具体逻辑):

package com.example.classloading;   

public interface Module {
    public void demo(String str);
}    


package com.example.classloading;   

public class Weather implements Module {
    public void demo(String str) {
        System.out.println("hello from weather module");
    }
}

应用类:

import com.example.classloading.JarClassLoader;
import com.example.classloading.Module;

public class App {
    public static void main(String[] args) {
        JarClassLoader jarClassLoader = new JarClassLoader("classloading/weather-module/target/weather-module-1.0-SNAPSHOT.jar", "com.example.classloading");
        try {
            Class<?> clas = jarClassLoader.loadClass("com.example.classloading.Weather");
            Module sample = (Module) clas.newInstance();
            sample.demo("1");
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        }

    }
}

问题:当我运行 main 方法时,我得到以下输出:

== loadClass(java.lang.Object)
== class com.example.classloading.Module loaded in cache
== class com.example.classloading.Weather loaded in cache
== loadClass(com.example.classloading.Weather)
Exception in thread "main" java.lang.ClassCastException: com.example.classloading.Weather cannot be cast to com.example.classloading.Module
    at App.main(App.java:12)

逻辑或语法有问题吗? Module 没有被应用类加载器加载?


文件树(略微简化):

├───classloading
│   │   pom.xml
│   │
│   ├───menu-module
│   │   │   pom.xml
│   │   │
│   │   ├───src
│   │   │   ├───main
│   │   │   │   ├───java
│   │   │   │   │   │   App.java
│   │   │   │   │   │
│   │   │   │   │   └───com
│   │   │   │   │       └───example
│   │   │   │   │           └───classloading
│   │   │   │   │                   JarClassLoader.java
│   │   │   │   │                   Module.java
│   │   │   │   │
│   │   │   │   └───resources
│   │   │   └───test
│   │   │       └───java
│   │   └───target
│   │       ├───classes
│   │       │   │   App.class
│   │       │   │
│   │       │   └───com
│   │       │       └───example
│   │       │           └───classloading
│   │       │                   JarClassLoader.class
│   │       │                   Module.class
│   │       │
│   │       └───generated-sources
│   │           └───annotations
│   └───weather-module
│       │   pom.xml
│       │   
│       ├───src
│       │   ├───main
│       │   │   ├───java
│       │   │   │   └───com
│       │   │   │       └───example
│       │   │   │           └───classloading
│       │   │   │                   Module.java
│       │   │   │                   Weather.java
│       │   │   │
│       │   │   └───resources
│       │   └───test
│       │       └───java
│       └───target
│           │   weather-module-1.0-SNAPSHOT.jar
│           │
│           ├───classes
│           │   │   Module.class
│           │   │   Weather.class
│           │   │
│           │   └───com
│           │       └───example
│           │           └───classloading
│           │                   Module.class
│           │                   Weather.class
│           │
│           ├───maven-archiver
│           │       pom.properties
│           │
│           └───maven-status
│               └───maven-compiler-plugin
│                   ├───compile
│                   │   └───default-compile
│                   │           createdFiles.lst
│                   │           inputFiles.lst
│                   │
│                   └───testCompile
│                       └───default-testCompile
│                               inputFiles.lst
│
└───

更新: 我在JarClassLoadercacheClasses()做了更改

if (match(normalize(jarEntry.getName()), packageName))

if (match(normalize(jarEntry.getName()), packageName) 
&& !normalize(jarEntry.getName()).contains("Module"))

这是一种解决方法。如何以正确的方式做到这一点?

更新:据我了解,可以从模块Weather 中删除Module 接口,然后“将“菜单”模块声明为天气模块的依赖项”@Costi Ciudatu

现在我有以下pom.xml 文件:

菜单模块

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>classloading</artifactId>
        <groupId>java-tasks</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>menu</artifactId>

    <dependencies>
        <dependency>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-api</artifactId>
            <version>2.8.1</version>
        </dependency>
        <dependency>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-core</artifactId>
            <version>2.8.1</version>
        </dependency>
    </dependencies>

</project>

天气模块

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>java-tasks</groupId>
    <artifactId>weather-module</artifactId>
    <version>1.0-SNAPSHOT</version>

    <dependencies>
        <dependency>
            <groupId>java-tasks</groupId>
            <artifactId>menu</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
    </dependencies>

</project>

类加载

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>java-tasks</groupId>
    <artifactId>classloading</artifactId>
    <packaging>pom</packaging>
    <version>1.0-SNAPSHOT</version>
    <modules>
        <module>weather-module</module>
        <module>menu-module</module>
    </modules>

</project>

问题:我尝试打包weather-module 并得到错误:

[INFO] Scanning for projects...
[INFO]                                                                         
[INFO] ------------------------------------------------------------------------
[INFO] Building weather-module 1.0-SNAPSHOT
[INFO] ------------------------------------------------------------------------
[WARNING] The POM for java-tasks:menu:jar:1.0-SNAPSHOT is missing, no dependency information available
[INFO] ------------------------------------------------------------------------
[INFO] BUILD FAILURE
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 0.471 s
[INFO] Finished at: 2017-04-07T09:15:38+03:00
[INFO] Final Memory: 8M/245M
[INFO] ------------------------------------------------------------------------
[ERROR] Failed to execute goal on project weather-module: Could not resolve dependencies for project java-tasks:weather-module:jar:1.0-SNAPSHOT: Could not find artifact java-tasks:menu:jar:1.0-SNAPSHOT -> [Help 1]
[ERROR] 
[ERROR] To see the full stack trace of the errors, re-run Maven with the -e switch.
[ERROR] Re-run Maven using the -X switch to enable full debug logging.
[ERROR] 
[ERROR] For more information about the errors and possible solutions, please read the following articles:
[ERROR] [Help 1] http://cwiki.apache.org/confluence/display/MAVEN/DependencyResolutionException

我应该如何配置 maven pom.xml 文件才能正常工作?

【问题讨论】:

    标签: java maven classloader dynamic-class-loaders


    【解决方案1】:

    您的天气模块不应包含Module 类的副本。 否则,您最终会得到该类的两个副本,这是ClassCastException 的根本原因。

    使天气模块依赖于菜单模块或将Module 类提取到一个单独的模块中。最重要的是,您应该确保在您的类路径中使用单一版本的 Module

    【讨论】:

    • 感谢您和其他人的回答。 “让天气模块依赖于菜单模块”怎么做? (我使用 IntelliJ 的想法)
    • 您似乎在使用 Maven。因此,您需要做的就是将“菜单”模块声明为天气模块的dependency
    • 你能看到最新的更新吗? :) 我试着采纳你的建议。
    • 完成后,从天气模块中删除Modul.java 文件。
    • 尝试在类加载目录中运行mvn package。这样,您可以一次构建两个模块。否则,您需要先install 菜单模块,以便稍后可用于天气模块。
    【解决方案2】:

    问题与从 JAR 文件加载重复的类(或接口,在本例中)有关。 Module 类不兼容,从两个不同的位置加载。通常,您不应在单个包中混合手动加载类和importing/自动加载它们。

    【讨论】:

    • 谢谢。如何从天气模块中排除Module 接口?什么是正确的 Maven 配置?
    【解决方案3】:

    Module 类由 App 类的主类加载器加载。 Weather 类由JarClassLoader 加载。通过这样做,父类Module 也(再次)由JarClassLoader 加载,因为未使用父类加载器。所以你最终会得到两个相似的但不相等类实例Module。由于每个类都有对其类加载器的引用,因此它们是不同的,因此不兼容。

    主要的问题是你加载所有的类,甚至那些之前被另一个类加载器加载的类。尝试只缓存cacheClasses() 中的classData,只有在父类加载器中没有findLoadedClass() 时才调用defineClass()。

    但这不会完全有帮助,因为您的类加载器中的依赖项完全加倍。行为将取决于加载类的顺序。要完成这项工作,您必须拆分天气模块。

    【讨论】:

    • 谢谢。如何“要完成这项工作,您必须拆分天气模块”?什么意思?
    猜你喜欢
    • 2017-05-24
    • 2019-09-08
    • 2012-09-05
    • 2017-01-17
    • 2013-07-30
    • 2015-11-18
    • 2018-07-05
    • 2016-12-06
    • 1970-01-01
    相关资源
    最近更新 更多