从jar加载自定义类的ClassCastException

时间:2017-04-06 21:22:16

标签: java maven classloader dynamic-class-loaders

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

我在jar文件中有“天气”模块,我希望App类加载JarClassLoader。{/ p>

来自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");
    }
}

App类:

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"))

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

更新:据我了解,可以从模块Module删除Weather接口,然后“声明”菜单“模块作为天气模块的依赖项”{ {3}}

现在我有以下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文件才能正常工作?

3 个答案:

答案 0 :(得分:2)

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

的根本原因

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

答案 1 :(得分:2)

ModuleApp类的主要类加载器加载。 Weather类由JarClassLoader加载。通过执行此操作,父类Module也(再次)由JarClassLoader加载,因为未使用父类加载器。因此,您最终会遇到两个类似的但不相等的类实例Module。由于每个类都有对其类加载器的引用,因此它们是不同的,因此不兼容。

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

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

答案 2 :(得分:1)

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