2017-04-06 141 views
0

我试图实现自定义类加载器的教育目的。自定义类从jar加载ClassCastException

我有jar文件中的模块“Weather”,我想从App类加载JarClassLoader

类加载器从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(); 
     } 

    } 
} 

问题:当我运行m艾因方法我得到以下输出:

== 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文件正确的工作?

回答

2

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

使天气模块取决于菜单模块或者单独提取Module类。最重要的是,你应该确保你的类路径中只有一个版本的Module

+0

谢谢你和其他人的答案。 “使天气模块取决于菜单模块”如何做到这一点? (我使用IntelliJ的想法) – Woland

+0

你似乎在使用maven。因此,您只需将“菜单”模块声明为天气模块的“依赖”即可。 –

+0

你能看到最新的更新吗? :)我试图应用你的建议。 – Woland

1

该问题与从JAR文件中加载重复的类(在本例中为接口)有关。 Module类不兼容,从两个不同的位置加载。一般来说,您不应该混合手动加载课程和将它们自动加载到一个包中。

+0

谢谢。我怎样才能从天气模块中排除'Module'接口?什么是正确的maven配置? – Woland

2

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

主要的问题是你加载所有的类,甚至加载其他类加载器之前。尝试仅在cacheClasses()中缓存classData,并且只有在父类加载器中没有findLoadedClass()的情况下调用defineClass()。

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

+0

谢谢。如何“做这项工作,你必须拆分你的天气模块”?你什么意思? – Woland