簡體   English   中英

從jar加載自定義類時發生ClassCastException

[英]ClassCastException on custom class loading from jar

我正在嘗試出於教育目的實現自定義類加載器。

我在jar文件中有“天氣”模塊,我希望通過JarClassLoaderApp類加載該模塊。

這里的類加載器(它從指定的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
│
└───

更新 :我在JarClassLoader cacheClasses()進行了更改

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

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

這是解決方法。 如何以正確的方式做到這一點?

更新 :據我了解,可以從Weather Module中刪除Module接口,然后“將“菜單”模塊聲明為Weather模塊的依賴項” @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文件以使其正常工作?

您的天氣模塊不應包含Module類的副本。 否則,最終將得到該類的兩個副本,這是ClassCastException的根本原因。

使天氣模塊依賴於菜單模塊,或者將Module類提取到單獨的Module中。 底線,您應該確保最終在類路徑中使用Module的單個版本。

ModuleApp類的主類加載器加載。 Weather類由JarClassLoader加載。 通過這樣做, JarClassLoader也會(再次)加載父類Module ,因為未使用父類的loder。 因此,您最終遇到了兩個相似但不相等的類實例Module 由於每個類都有對其類加載器的引用,因此它們是不同的,因此不兼容。

主要問題是您加載了所有類,甚至包括之前由另一個類加載器加載的那些類。 嘗試僅將classData緩存在cacheClasses()並且僅在父類加載器中沒有findLoadedClass()時才調用defineClass()。

但這並不能完全幫助您,因為您已將類加載器中的依賴項增加了一倍。 該行為將取決於類的加載順序。 為此,您必須拆分天氣模塊。

問題與從JAR文件加載重復的類(在這種情況下為接口)有關。 模塊類不兼容,是從兩個不同的位置加載的。 通常,您不應該將手動加載的類混合在一起,也不應該在單個包中import /自動加載它們。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM