[英]ClassCastException on custom class loading from jar
我正在嘗試出於教育目的實現自定義類加載器。
我在jar
文件中有“天氣”模塊,我希望通過JarClassLoader
從App
類加載該模塊。
從這里的類加載器(它從指定的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
的單個版本。
類Module
由App
類的主類加載器加載。 Weather
類由JarClassLoader
加載。 通過這樣做, JarClassLoader
也會(再次)加載父類Module
,因為未使用父類的loder。 因此,您最終遇到了兩個相似但不相等的類實例Module
。 由於每個類都有對其類加載器的引用,因此它們是不同的,因此不兼容。
主要問題是您加載了所有類,甚至包括之前由另一個類加載器加載的那些類。 嘗試僅將classData緩存在cacheClasses()
並且僅在父類加載器中沒有findLoadedClass()
時才調用defineClass()。
但這並不能完全幫助您,因為您已將類加載器中的依賴項增加了一倍。 該行為將取決於類的加載順序。 為此,您必須拆分天氣模塊。
問題與從JAR文件加載重復的類(在這種情況下為接口)有關。 模塊類不兼容,是從兩個不同的位置加載的。 通常,您不應該將手動加載的類混合在一起,也不應該在單個包中import
/自動加載它們。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.