2017-04-06 6 views
0

私は教育目的でカスタムクラスローダーを実装しようとしています。ClassCastException jarからロードするカスタムクラスについて

私はAppクラスからロードするjarファイルにJarClassLoaderのモジュール「Weather」を持っています。 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 AIN方法私は、次のような出力が得られます。

== 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 
│ 
└─── 

更新: 私は

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

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

に変更を行いました

回避策です。どのように正しい方法でそれを行うには?

更新:私は理解し、その後、@Costi Ciudatu「天気モジュールの依存関係としてモジュール 『メニュー』宣言」モジュールWeatherからModuleインターフェイスを削除することが可能です。

今私は、次のしている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をパッケージ化しようとしたとエラーました:私は正しい動作のためのMaven pom.xmlファイルを設定する必要がありますどのように

[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 

を?

答えて

2

天気予報モジュールにはModuleクラスのコピーが含まれていてはなりません。 それ以外の場合は、ClassCastExceptionの根本原因であるそのクラスの2つのコピーが作成されます。

天気モジュールをメニューモジュールに依存させるか、別の方法でModuleクラスを抽出します。要するに、クラスパスにModuleの単一バージョンで終わるようにする必要があります。

+0

回答いただきありがとうございます。 「天気モジュールをメニューモジュールに依存させる」方法(私はIntelliJアイデアを使用します) – Woland

+0

あなたはmavenを使用しているようです。だから、 "メニュー"モジュールを天気モジュールの '依存性 'として宣言するだけです。 –

+0

最新のアップデートをご覧いただけますか? :)私はあなたのアドバイスを適用しようとしました。 – Woland

1

問題は、JARファイルから重複したクラス(またはこの場合はインターフェイス)を読み込むことと関係しています。 Moduleクラスは互換性がなく、2つの異なる場所からロードされます。一般的に、クラスを手動で読み込んでクラスを混在させたり、1つのパッケージ内にクラスを読み込んだりすることはできません。

+0

ありがとうございます。どのようにして 'Module'インターフェースをweather-moduleから除外できますか?そのために右のMavenの設定は何ですか? – Woland

2

クラスModuleは、Appクラスのメインクラスローダによってロードされます。 WeatherクラスはJarClassLoaderによってロードされます。これを行うことによって、親クラスModuleは、親クラスローダーが使用されないので、JarClassLoaderによって(再び)ロードされます。だからあなたは2つの同様のに終わるが、クラスインスタンスModuleと等しくはない。各クラスはそのクラスローダーへの参照を持っているので、それらは異なっており互換性がありません。

主な問題は、以前に別のクラスローダーによってロードされたものであっても、すべてのクラスをロードすることです。 cacheClasses()にclassDataのみをキャッシュし、親クラスローダーにfindLoadedClass()がない場合にのみdefineClass()を呼び出してください。

しかし、これは完全にあなたのクラスローダーの依存関係を完全に倍増させるのに役立ちません。動作は、クラスがロードされる順序によって異なります。この作業を行うには、天気モジュールを分割する必要があります。

+0

ありがとうございます。どのように "この仕事をするにはあなたの天気モジュールを分割する必要があります"?どういう意味ですか? – Woland

関連する問題