7

抽象クラスがあるとしましょう:

abstract class Foo extends Bar {

    public abstract int foo();

}

実行時に拡張してClassオブジェクトを作成したいと思います。動的に生成されたクラスを持つことができれば幸いです。

class FooImpl extends Foo {

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

}

これはClassオブジェクトで表され、リフレクションを使用しての新しいインスタンスを作成できます。重要なのは、実行時にメソッド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;
        }
    }
}
4

3 に答える 3

6

CGLibの使用を検討することをお勧めします。Javaの動的プロキシが実行できることを実行できますが、抽象クラスとインターフェースに対して実行できます。また、これを実行するためのjava.lang.reflect.Proxyと同様のAPIがあります。CGLibはとにかく舞台裏でASMを使用しますが、CGLibを使用することで、バイトコードを直接作成する必要がなくなります。

これを行うために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();
    }
}
于 2011-09-15T04:07:43.463 に答える
1

言うプロパティから値5を読み取って、それを返すのを妨げているのは何ですか?単純すぎるので、ここで達成したいintを返すよりも複雑なものが必要だと思います。実行時にクラスを生成すると非常にコストがかかるという上記の投稿に同意します。ビジネスロジックを事前に知っている場合は、ファクトリパターンを適用して、実行時に定義済みインターフェイスの目的の実装をロードできます。これがJDBCライブラリの仕組みです。

事前にビジネスロジックを知らず、その多くを持っている場合は、既成のルールエンジンを使用してロジックを処理し、結果をJavaプログラムに返すことでメリットが得られる可能性があります。頻繁に変更される場合は特に、ルールエンジンでこのロジックを維持する方がはるかに簡単です。

于 2011-09-15T04:33:18.053 に答える
0

はい、そのアプローチは機能するはずです。ただし、クラスの生成を多く行うと、コストがかかります。(おそらく、バイトコードファイルを生成してロードするための数十万の命令について話しているでしょう。そして、ロード時にクラスを表すためのメモリが必要です。)

別のアプローチ(これも高価です)は、ソースコードを生成し、実行時にコンパイルしてロードすることです。

最後に、オブジェクトのロジックをテーブル駆動にするか、ある種のインタープリターを使用して実装するアプローチを検討する必要があります。実際に異なるクラスが必要な場合は、Javaの動的プロキシクラスメカニズムを使用してこれをまとめることができます。例:参照java.lang.reflect.Proxy

于 2011-09-15T03:35:03.403 に答える