2011-09-15 3 views
7

의 내가 추상 클래스가 있다고 가정 해 보자 : 나는 런타임에 확장 할런타임시 추상 메소드 구현?

abstract class Foo extends Bar { 

    public abstract int foo(); 

} 

클래스 개체를 만들 수 있습니다. 클래스 객체에 의해 표현 될 것이다

class FooImpl extends Foo { 

    @Override 
    public int foo() { 
     return 5; 
    } 

} 

나는 다음의 새로운 인스턴스를 만들 반사를 사용할 수 : 희망은 내가 동적으로 생성 된 클래스를 가질 수있는 것이다. 핵심은 런타임에 foo() 메서드의 반환 값을 결정하고 싶습니다. 내 생각은 ASM을 사용하여 클래스의 바이트 코드를 만든 다음 ClassLoader 객체에서 반향을 사용하여 클래스를 정의하는 것입니다.

ASM을 사용하고 생성 된 바이트에 대해 ClassLoader # defineClass 메소드를 리플렉션하면 하드 코딩되지 않은 값으로 런타임시 추상 메소드를 구현하는 가장 좋은 방법은 무엇입니까?

그렇다면 어떻게해야할까요? 내 직감은 ASMifierClassVisitor를 활용하는 것이지만 정확한 방법은 잘 모르겠습니다. 다른 모든 것이 실패 할 경우 특정 클래스를 정의하는 데 필요한 JVM 명령어를 수동으로 처리 할 수 ​​있지만 더 쉬운 방법이 있어야한다고 생각합니다.

아니요, 가장 좋은 방법은 무엇이며 어떻게하면 가장 좋은 방법을 사용할 수 있습니까?

편집 : 모든 답변을 확인한 결과, 그 중 아무도 내가 원하는 것을 정확히 결정하지 못했습니다. 결국 ASM과 관련하여 소량의 구현을 작성했습니다. 여기에 게시해야한다고 생각했습니다.

import org.objectweb.asm.*; 

import java.io.File; 
import java.io.FileOutputStream; 
import java.io.IOException; 
import java.io.PrintWriter; 
import java.lang.reflect.Field; 
import java.lang.reflect.Method; 
import java.util.HashMap; 

/** 
* Created by IntelliJ IDEA. 
* User: Matt 
* Date: 9/17/11 
* Time: 12:42 PM 
*/ 
public class OverrideClassAdapter extends ClassAdapter { 

    private final HashMap<String, Object> code; 
    private final String className; 

    private final ClassWriter writer; 

    private String superName; 

    public OverrideClassAdapter(ClassWriter writer, String className, Queue<int[]> constructorCode, HashMap<String, Object> code) { 
     super(writer); 
     this.writer = writer; 
     this.className = className; 
     this.code = code; 
    } 

    @Override 
    public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) { 
     this.superName = name; 
     if((access & Opcodes.ACC_ABSTRACT) != 0) 
      access &= ~Opcodes.ACC_ABSTRACT; 
     if((access & Opcodes.ACC_INTERFACE) != 0) 
      access &= ~Opcodes.ACC_INTERFACE; 
     cv.visit(version, access, className, signature, name, null); 
    } 

    @Override 
    public void visitSource(String source, String debug) { 
    } 

    @Override 
    public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { 
     boolean isAbstract = (access & Opcodes.ACC_ABSTRACT) != 0; 
     if(isAbstract) 
      access &= ~Opcodes.ACC_ABSTRACT; 
     MethodWriter mw = (MethodWriter) cv.visitMethod(access, name, desc, signature, exceptions); 
     Object value = code.get(name); 
     if(isAbstract || value != null) { 
      if(value instanceof BytecodeValue) { 
       BytecodeValue returnableValue = (BytecodeValue) value; 
       int[] byteCode = new int[returnableValue.getValueCode().length + 1]; 
       System.arraycopy(returnableValue.getValueCode(), 0, byteCode, 0, returnableValue.getValueCode().length); 
       if(returnableValue.getValueCode().length > 1 && returnableValue.getValueCode()[1] == 0) { 
        byteCode[1] = writer.newConst(returnableValue.getValue()); 
       } 
       byteCode[byteCode.length - 1] = returnableValue.getReturnCode(); 
       value = byteCode; 
      } 
      return new OverrideMethodAdapter(mw, (int[]) value); 
     } 
     return mw; 
    } 

    private class OverrideMethodAdapter extends MethodAdapter { 

     private final int[] code; 

     private final MethodWriter writer; 

     public OverrideMethodAdapter(MethodWriter writer, int[] code) { 
      super(writer); 
      this.writer = writer; 
      this.code = code; 
     } 

     @Override 
     public void visitEnd() { 
      try { 
       Field code = MethodWriter.class.getDeclaredField("code"); 
       code.setAccessible(true); 
       ByteVector bytes = new ByteVector(); 
       for(int b : this.code) 
        bytes.putByte(b); 
       code.set(writer, bytes); 
      } catch (Exception e) { 
       e.printStackTrace(); 
      } 
     } 
    } 

    public static byte[] extendClassBytes(Class clazz, String className, HashMap<String, Object> methodImpls) throws IOException { 
     ClassReader cr = new ClassReader(clazz.getName()); 
     ClassWriter cw = new ClassWriter(0); 
     cr.accept(new OverrideClassAdapter(cw, className, methodImpls), ClassReader.SKIP_DEBUG); 
     cr = new ClassReader(cw.toByteArray()); 
     cw = new ClassWriter(ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES); 
     cr.accept(cw, ClassReader.SKIP_DEBUG); 
     //CheckClassAdapter.verify(new org.objectweb.asm.ClassReader(cw.toByteArray()), true, new PrintWriter(System.out)); 
     /*File file = new File(className + ".class"); 
     new FileOutputStream(file).write(cw.toByteArray());*/ 
     return cw.toByteArray(); 
    } 


    public static Class extendClass(Class clazz, String className, HashMap<String, Object> methodImpls) throws IOException { 
     return defineClass(extendClassBytes(clazz, className, methodImpls), className); 
    } 

    public static Class defineClass(byte[] code, String name) { 
     try { 
      Method method = ClassLoader.class.getDeclaredMethod("defineClass", String.class, byte[].class, int.class, int.class); 
      method.setAccessible(true); 
      return (Class) method.invoke(Thread.currentThread().getContextClassLoader(), name, code, 0, code.length); 
     } catch (Exception e) { 
      e.printStackTrace(); 
      return null; 
     } 
    } 
} 
+0

당신은 합성 방법을 봐야 할 것 같아요. – Shahzeb

+0

런타임시'foo'의 반환 값을 결정 하시겠습니까? 클래스가 임의의 타입을 반환하면 어떻게 호출할까요? –

+0

런타임시 foo의 값은 클래스가 생성 될 때 결정되므로 나중에 변경할 필요가 없습니다. – mburke13

답변

5

CGLib을 사용하는 것이 좋습니다. Java의 동적 프록시가 할 수있는 일을 할 수 있지만 인터페이스뿐만 아니라 추상 클래스도 수행 할 수 있으며 java.lang.reflect.Proxy와 비슷한 API를 사용합니다. 어쨌든 CGLib는 ASM을 사용하지만 CGLib을 사용하면 바이트 코드를 직접 만들어야 할 필요가 없습니다.

package cglibtest; 

import java.lang.reflect.Method; 

import net.sf.cglib.proxy.Enhancer; 
import net.sf.cglib.proxy.MethodInterceptor; 
import net.sf.cglib.proxy.MethodProxy; 

public class CGLibTest 
{ 
    public static void main(String... args) 
    { 
     MyAbstract instance = (MyAbstract)Enhancer.create(MyAbstract.class, new MyInterceptor(42)); 
     System.out.println("Value from instance: " + instance.valueMethod()); 
    } 

    public static class MyInterceptor implements MethodInterceptor 
    { 
     private final Object constantValue; 

     public MyInterceptor(Object constantValue) 
     { 
      this.constantValue = constantValue; 
     } 

     @Override 
     public Object intercept(Object obj, Method method, Object[] args, 
       MethodProxy proxy) throws Throwable 
     { 
      if ("valueMethod".equals(method.getName())) 
       return(constantValue); 
      else 
       return(null); 
     } 
    } 

    public static abstract class MyAbstract 
    { 
     public abstract int valueMethod(); 
    } 
} 
+0

cglib를 확인했는데 생성 된 클래스에서 추상 메소드를 구현하는 방법에 대해 약간 분실했습니다. 내가해야할 일을보기 위해 볼 수있는 문서 나 책자가 있습니까? – mburke13

+0

@Matt는 웹 사이트 (예 : [examples] (http://cglib.sourceforge.net/howto.html) 또는 [javadocs] (http://cglib.sourceforge.net)와 같은 자세한 정보를 보려면 예제를 추가했습니다. /apidocs/index.html). – prunge

+0

예제와 링크를 가져 주셔서 감사합니다. 내 질문에 가장 적합한 답변을 선택했습니다. 호기심에서 추상적 인 방법을 구현하는 가장 좋은 방법은 방법 인터셉터를 사용하고 있습니까? 그 일을 끝내고 난 방법의 코드를 실행하기 위해 각 메소드의 이름을 검사해야 할 때 4 또는 5 추상 메소드를 말하는 클래스에 어떤 일이 발생하는지 궁금합니다. 다소 어수선해 보입니다 (ie if (abstractMethod1.equals ( – mburke13

0

네, 그 방법이 효과가 있습니다. 그러나 많은 클래스 생성을한다면 비용이 많이 듭니다. 바이트 코드 파일을 생성하고로드 할 때 수십만 가지의 명령어를 사용하고 있습니다.로드 될 때 클래스를 나타내는 메모리가 필요합니다.

또 다른 접근법은 (또한 값 비싼) 소스 코드를 컴파일하고 런타임에로드하십시오.

마지막으로 개체의 로직을 테이블로 만들거나 일종의 인터프리터를 사용하여 구현하는 방법을 고려해야합니다. 실제로 에 다른 클래스가 있으려면이 필요하면 Java의 동적 프록시 클래스 메커니즘을 사용하여이 클래스를 마무리 할 수 ​​있습니다. 예 : java.lang.reflect.Proxy

+0

프록시 옵션을 찾고 있었지만 추상적 인 메서드가 추상 클래스에서 선언되어야하고 인터페이스가 아니라는 사실로 인해 제한됩니다. 클래스를 확장하기 위해 추상 클래스가 필요합니다. 클래스 생성은 시작시 완료되며 10-100 클래스보다 클 수 없습니다. – mburke13

+0

@Matt - 추상 클래스를 리팩토링하여 인터페이스를 구현하는 추상 클래스이며, 추상 클래스의 인스턴스를 프록시 내에 래핑 할 수 있습니다. 그 수의 클래스의 경우 코드 생성 방식이 너무 비싸지 않아야하지만, 생성 된 메소드가 복잡해야하는 경우이 문제를 해결하는 것은 복잡하고 (잠재적으로) 약한 방법입니다. (그렇지 않으면 테이블 중심의 접근 방식이 더 간단해질 것입니다.) –

1

무엇에서 값 (5)을 읽는 당신을 말 중지의 속성을 다시 반환 :

여기 CGLIB이 작업을 수행하기 위해 사용하는 방법의 예? 너무 간단해서 여기에서 성취하고자하는 int를 반환하는 것보다 더 복잡한 것이 있어야합니다. 나는 런타임에 클래스를 생성하는 것이 매우 비쌀 것이라고 위의 게시물에 동의합니다. 비즈니스 로직을 미리 알고있는 경우 Factory 패턴을 적용하여 런타임에 정의 된 인터페이스 구현을로드 할 수 있습니다. 이것이 바로 JDBC 라이브러리의 작동 방식입니다.

비즈니스 로직을 미리 알지 못하고 많은 것을 가지고 있다면, 선반 규칙 엔진을 사용하여 로직을 처리하고 Java 프로그램으로 결과를 되돌릴 수 있습니다. 규칙 엔진에서 자주 변경되는 경우이 논리를 유지하는 것이 훨씬 쉽습니다.

관련 문제