2010-05-24 1 views
9

Java에서 클래스를로드 할 수 있습니까? 클래스의 패키지 이름/정식 이름을 '가짜'로 만들 수 있습니까? 나는 명백한 방법으로이 작업을 시도했지만 ClassDefNotFoundException의 "클래스 이름이 일치하지 않습니다"라는 메시지가 나타납니다.다른 패키지 이름을 사용하여 Java에서 동적으로 클래스로드하기

내가 이것을하는 이유는 리플렉션을 사용하지 않고 직접 사용할 수 있도록 기본 패키지로 작성된 API를로드하려고하기 때문입니다. 패키지 및 패키지 이름 가져 오기를 나타내는 폴더 구조의 클래스에 대해 코드가 컴파일됩니다. 예 :

 
./com/DefaultPackageClass.class 
// ... 
import com.DefaultPackageClass; 
import java.util.Vector; 
// ... 

내 현재 코드는 다음과 같다 :

어쩌면
public Class loadClass(String name) throws ClassNotFoundException { 
    if(!CLASS_NAME.equals(name)) 
      return super.loadClass(name); 

    try { 
     URL myUrl = new URL(fileUrl); 
     URLConnection connection = myUrl.openConnection(); 
     InputStream input = connection.getInputStream(); 
     ByteArrayOutputStream buffer = new ByteArrayOutputStream(); 
     int data = input.read(); 

     while(data != -1){ 
      buffer.write(data); 
      data = input.read(); 
     } 

     input.close(); 

     byte[] classData = buffer.toByteArray(); 

     return defineClass(CLASS_NAME, 
       classData, 0, classData.length); 

    } catch (MalformedURLException e) { 
     throw new UndeclaredThrowableException(e); 
    } catch (IOException e) { 
     throw new UndeclaredThrowableException(e); 
    } 

} 

답변

12

As Pete mentioned이 작업은 ASM 바이트 코드 라이브러리를 사용하여 수행 할 수 있습니다. 사실,이 라이브러리는 실제로 이러한 클래스 이름 재 매핑 (RemappingClassAdapter)을 처리하기위한 클래스와 함께 제공됩니다.

public class Customer { 

} 

public class Order { 

    private Customer customer; 

    public Order(Customer customer) { 
     this.customer = customer; 
    } 

    public Customer getCustomer() { 
     return customer; 
    } 

    public void setCustomer(Customer customer) { 
     this.customer = customer; 
    } 

} 
:

public class MagicClassLoader extends ClassLoader { 

    private final String defaultPackageName; 

    public MagicClassLoader(String defaultPackageName) { 
     super(); 
     this.defaultPackageName = defaultPackageName; 
    } 

    public MagicClassLoader(String defaultPackageName, ClassLoader parent) { 
     super(parent); 
     this.defaultPackageName = defaultPackageName; 
    } 

    @Override 
    public Class<?> loadClass(String name) throws ClassNotFoundException { 
     byte[] bytecode = ...; // I will leave this part up to you 
     byte[] remappedBytecode; 

     try { 
      remappedBytecode = rewriteDefaultPackageClassNames(bytecode); 
     } catch (IOException e) { 
      throw new RuntimeException("Could not rewrite class " + name); 
     } 

     return defineClass(name, remappedBytecode, 0, remappedBytecode.length); 
    } 

    public byte[] rewriteDefaultPackageClassNames(byte[] bytecode) throws IOException { 
     ClassReader classReader = new ClassReader(bytecode); 
     ClassWriter classWriter = new ClassWriter(classReader, 0); 

     Remapper remapper = new DefaultPackageClassNameRemapper(); 
     classReader.accept(
       new RemappingClassAdapter(classWriter, remapper), 
       0 
      ); 

     return classWriter.toByteArray(); 
    } 

    class DefaultPackageClassNameRemapper extends Remapper { 

     @Override 
     public String map(String typeName) { 
      boolean hasPackageName = typeName.indexOf('.') != -1; 
      if (hasPackageName) { 
       return typeName; 
      } else { 
       return defaultPackageName + "." + typeName; 
      } 
     } 

    } 

} 

가 나는 기본 패키지에 속하는 둘 다 두 개의 클래스를 생성, 설명하기 : 다음은이 클래스를 사용하여 클래스 로더의 예입니다

이전에이 표시됩니다.

 
> javap -private -c Order 
Compiled from "Order.java" 
public class com.mycompany.Order extends com.mycompany.java.lang.Object{ 
private com.mycompany.Customer customer; 

public com.mycompany.Order(com.mycompany.Customer); 
    Code: 
    0: aload_0 
    1: invokespecial #30; //Method "com.mycompany.java/lang/Object"."":()V 
    4: aload_0 
    5: aload_1 
    6: putfield #32; //Field customer:Lcom.mycompany.Customer; 
    9: return 

public com.mycompany.Customer getCustomer(); 
    Code: 
    0: aload_0 
    1: getfield #32; //Field customer:Lcom.mycompany.Customer; 
    4: areturn 

public void setCustomer(com.mycompany.Customer); 
    Code: 
    0: aload_0 
    1: aload_1 
    2: putfield #32; //Field customer:Lcom.mycompany.Customer; 
    5: return 

} 

당신이 볼 수 있듯이

가, 다시 매핑이 com.mycompany.Order 모든 Order 참조를 변경하고 모든 Customer 참조했습니다
 
> javap -private -c Order 
Compiled from "Order.java" 
public class Order extends java.lang.Object{ 
private Customer customer; 

public Order(Customer); 
    Code: 
    0: aload_0 
    1: invokespecial #10; //Method java/lang/Object."":()V 
    4: aload_0 
    5: aload_1 
    6: putfield #13; //Field customer:LCustomer; 
    9: return 

public Customer getCustomer(); 
    Code: 
    0: aload_0 
    1: getfield #13; //Field customer:LCustomer; 
    4: areturn 

public void setCustomer(Customer); 
    Code: 
    0: aload_0 
    1: aload_1 
    2: putfield #13; //Field customer:LCustomer; 
    5: return 

} 

이은 (기본 패키지로 com.mycompany 사용) 매핑 후 Order의 목록입니다 com.mycompany.Customer. 기본 패키지에 속한

  • 기본 패키지에 속한, 또는
  • 사용하는 다른 클래스 :

    이 클래스 로더는 모든 중 클래스를로드해야합니다.

0

더 합리적 자리에 기본 패키지에서 API를 이동하기 쉬울 것? 소스 코드에 액세스 할 수없는 것 같습니다. 패키지가 클래스 파일로 인코딩되어 있는지 확실하지 않으므로 간단히 API 클래스를 옮기는 것이 좋습니다. 그렇지 않으면 JAD와 같은 Java 디 컴파일러가 일반적으로 잘 수행되므로 디 컴파일 된 소스에서 패키지 이름을 변경하고 다시 컴파일 할 수 있습니다.

+0

아니요, 출처에 액세스 할 수 없습니다. 클래스를 디렉토리로 옮기는 것은 컴파일시에는 보이지만 런타임에는 작동하지 않습니다. 가능하다면 수업을 해킹하지 않을 것입니다. –

+0

런타임에 클래스의 패키지를 변경하는 것이 해킹이 아니라는 말입니까? 나에게 문제는 단지 더 나쁜 해킹이고 다른 하나를 사용하는 것이다. – FelixM

1

ASM으로 무언가를 노크 할 수 있어야합니다.로드 할 때가 아닌 빌드시에 패키지 이름을 한 번 바꾸는 것이 더 쉽습니다.