2017-04-06 9 views
0

교육 목적으로 클래스 로더를 구현하려고합니다.ClassCastException jar에서 사용자 정의 클래스로드 중

내가 App 클래스에서로드하려는 jar 파일에 "Weather"모듈이 있습니다. JarClassLoader.

클래스 로더에서 here (가 지정된 항아리에서 모든 클래스로드) :

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(); 
     } 

    } 
} 

문제점 : 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 
│ 
└─── 

업데이트 : 나는

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

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

에서 변경 한

해결 방법입니다. 그것을 올바른 방법으로하는 방법?

업데이트 : 나는 다음 모듈 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 

내가 올바른 작동을 위해 받는다는 pom.xml 파일을 구성하는 방법을?

답변

2

날씨 모듈에는 Module 클래스 사본이 없어야합니다. 그렇지 않으면 ClassCastException의 근본 원인 인 해당 클래스의 복사본이 2 개 있습니다.

날씨 모듈을 메뉴 모듈에 의존 시키거나 Module 클래스를 별도로 추출하십시오. 요약하면 클래스 패스에 Module의 단일 버전으로 끝나야합니다.

+0

답변 해 주셔서 감사합니다. "날씨 모듈을 메뉴 모듈에 의존하게 만들기"어떻게하는가? (IntelliJ 아이디어를 사용합니다.) – Woland

+0

Maven을 사용하고있는 것 같습니다. 따라서 "메뉴"모듈을 날씨 모듈의 '의존성'으로 선언하면됩니다. –

+0

최신 업데이트를 볼 수 있습니까? :) 귀하의 조언을 적용하려고했습니다. – Woland

1

이 문제는 JAR 파일에서 중복 클래스 (또는 인터페이스,이 경우)를로드하는 것과 관련이 있습니다. Module 클래스는 서로 다른 두 위치에서로드되며 호환되지 않습니다. 일반적으로 수동으로 클래스를로드하고 하나의 패키지 내에 클래스를로드하거나 자동으로로드하는 클래스를 섞어서는 안됩니다.

+0

감사합니다. weather-module에서'Module' 인터페이스를 어떻게 제외시킬 수 있습니까? 그것을 위해 올바른 설정은 무엇입니까? – Woland

2

클래스 ModuleApp 클래스의 기본 클래스 로더에 의해로드됩니다. Weather 클래스는 JarClassLoader에 의해로드됩니다. 이렇게하면 부모 클래스 Module도 부모 클래스 로더가 사용되지 않기 때문에 JarClassLoader에 의해 다시로드됩니다. 따라서 두 개의 비슷한 에 있지만 클래스 인스턴스 Module과 같지 않게됩니다. 각 클래스는 클래스 로더에 대한 참조를 가지므로 서로 다르므로 호환되지 않습니다.

주요 문제는 이전에 다른 클래스 로더에 의해로드 된 클래스까지 모든 클래스를로드한다는 것입니다. cacheClasses()에 classData 만 캐시하려고 시도하고 부모 클래스 로더에 findLoadedClass()이없는 경우에만 defineClass()를 호출하십시오.

그러나 이것은 클래스 로더의 의존성이 완전히 두 배로 증가했기 때문에 완전히 도움이되지 않습니다. 동작은 클래스가로드되는 순서에 따라 달라집니다. 이 작업을하려면 날씨 모듈을 분리해야합니다.

+0

감사합니다. "이 작업을하려면 날씨 모듈을 분리해야합니까?" 무슨 소리 야? – Woland