在ByteBuddy

时间:2017-02-21 13:25:48

标签: java bytecode bytecode-manipulation byte-buddy

我尝试做什么

我正在尝试使用Bytebuddy 1.6.7重新命名并重命名类来拦截它的构造函数

动机

我正在研究SAAS系统,用户可以在其中提供带注释的java类,系统应该在存储或启动之前对其进行检测。我开发了管道来执行我需要的仪器,但它可以在两个地方使用。

第一个模块对类进行检测并存储它们而不加载。它使用类名作为“组件”id,因此我不想创建检测类型的子类以避免类名中不必要的后缀。这就是我想使用rebase的原因。

另一个模块工具已经加载了类并立即使用它们而不关心类名。在这个模块中,我想重用第一个模块中的代码,并在加载之前另外更改已检测类型的名称。

代码

完整的工作示例是here

我想知道为什么以下方法适用于内部类,但不适用于根类。

private static void rebaseConstructorSimple(Class<?> clazz) throws InstantiationException, IllegalAccessException {
    new ByteBuddy()
            .rebase(clazz)
            .name(clazz.getName() + "Rebased")
            .constructor(ElementMatchers.any())
            .intercept(SuperMethodCall.INSTANCE.andThen(
                    MethodDelegation.to(new ConstructorInterceptor()
                    )))
            .make()
            .load(clazz.getClassLoader(), ClassLoadingStrategy.Default.INJECTION)
            .getLoaded()
            .newInstance();
}

如果我提供root类,我会得到以下异常:

Exception in thread "main" java.lang.IllegalStateException: Cannot call super (or default) method for public OuterClassRebased()
    at net.bytebuddy.implementation.SuperMethodCall$Appender.apply(SuperMethodCall.java:97)
    at net.bytebuddy.implementation.bytecode.ByteCodeAppender$Compound.apply(ByteCodeAppender.java:134)
    at net.bytebuddy.dynamic.scaffold.TypeWriter$MethodPool$Record$ForDefinedMethod$WithBody.applyCode(TypeWriter.java:614)
    at net.bytebuddy.dynamic.scaffold.TypeWriter$MethodPool$Record$ForDefinedMethod$WithBody.applyBody(TypeWriter.java:603)
    at net.bytebuddy.dynamic.scaffold.TypeWriter$Default$ForInlining$RedefinitionClassVisitor$CodePreservingMethodVisitor.visitCode(TypeWriter.java:3912)
    at net.bytebuddy.jar.asm.MethodVisitor.visitCode(Unknown Source)
    at net.bytebuddy.jar.asm.ClassReader.b(Unknown Source)
    at net.bytebuddy.jar.asm.ClassReader.accept(Unknown Source)
    at net.bytebuddy.jar.asm.ClassReader.accept(Unknown Source)
    at net.bytebuddy.dynamic.scaffold.TypeWriter$Default$ForInlining.create(TypeWriter.java:2894)
    at net.bytebuddy.dynamic.scaffold.TypeWriter$Default.make(TypeWriter.java:1612)
    at net.bytebuddy.dynamic.scaffold.inline.RebaseDynamicTypeBuilder.make(RebaseDynamicTypeBuilder.java:200)
    at net.bytebuddy.dynamic.scaffold.inline.AbstractInliningDynamicTypeBuilder.make(AbstractInliningDynamicTypeBuilder.java:92)
    at net.bytebuddy.dynamic.DynamicType$Builder$AbstractBase.make(DynamicType.java:2560)
    at net.bytebuddy.dynamic.DynamicType$Builder$AbstractBase$Delegator.make(DynamicType.java:2662)
    at TestConstructorInterceptor.rebaseConstructorSimple(TestConstructorInterceptor.java:35)
    at TestConstructorInterceptor.main(TestConstructorInterceptor.java:89)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:497)
    at com.intellij.rt.execution.application.AppMain.main(AppMain.java:147)

变通方法

解决方法1

正如我前面提到的,如果没有加载类,我可以避免上课:

public static void rebaseConstructorNotLoaded(String classPath, String className) throws Exception {
    ClassFileLocator.ForFolder folderClassLoader = new ClassFileLocator.ForFolder(new File(classPath));
    TypePool typePool = TypePool.Default.ofClassPath();
    TypeDescription typeDescription = typePool.describe(className).resolve();

    new ByteBuddy()
            .rebase(typeDescription, folderClassLoader)
            .constructor(ElementMatchers.any())
            .intercept(
                    SuperMethodCall.INSTANCE.andThen(
                            MethodDelegation.to(new ConstructorInterceptor())))
            .make()
            .load(TestConstructorInterceptor.class.getClassLoader(), ClassLoadingStrategy.Default.INJECTION)
            .getLoaded()
            .newInstance();
}

解决方法2

此外,我还可以通过以下步骤实现目标:重命名重命名,make。然后创建SimpleClassLoader,它只包含新类名的生成字节。拦截构造函数

public static void rebaseWithIntermediateMake(Class<?> clazz) throws IllegalAccessException, InstantiationException {
    DynamicType.Unloaded<?> unloaded = new ByteBuddy()
            .rebase(clazz)
            .name(clazz.getName() + "Rebased")
            .make();
    ClassFileLocator dynamicTypesLocator = getClassFileLocatorForDynamicTypes(unloaded);
    TypeDescription typeDescription = unloaded.getTypeDescription();
    new ByteBuddy()
            .rebase(typeDescription, dynamicTypesLocator)
            .constructor(ElementMatchers.any())
            .intercept(
                    SuperMethodCall.INSTANCE.andThen(
                            MethodDelegation.to(new ConstructorInterceptor())))
            .make()
            .load(clazz.getClassLoader(), ClassLoadingStrategy.Default.INJECTION)
            .getLoaded()
            .newInstance();
}

private static ClassFileLocator getClassFileLocatorForDynamicTypes(DynamicType.Unloaded<?> unloaded) {
    Map<TypeDescription, byte[]> allTypes = unloaded.getAllTypes();
    Map<String, byte[]> nameByteMap = new HashMap<>();
    for (Map.Entry<TypeDescription, byte[]> entry : allTypes.entrySet()) {
        nameByteMap.put(entry.getKey().getName(), entry.getValue());
    }
    return new ClassFileLocator.Simple(nameByteMap);
}

研究

我检查了ByteBuddy代码,并且可能找到了导致第一次测试失败的代码。

当在RebaseDynamicTypeBuilder here中构造MethodRebaseResolver时,我们在得到的methodRebaseResolver中获得了0 instrumentedMethods。看来,matching期间的RebasableMatcher在instrumentedMethodTokens中具有单个值:

MethodDescription.Token{name='<init>', modifiers=1, typeVariableTokens=[],
 returnType=void, parameterTokens=[], exceptionTypes=[], annotations=[],
 defaultValue=null, receiverType=class net.bytebuddy.dynamic.TargetType}

但与以下目标相匹配

MethodDescription.Token{name='<init>', modifiers=1, typeVariableTokens=[],
 returnType=void, parameterTokens=[], exceptionTypes=[], annotations=[],
 defaultValue=null, receiverType=class OuterClass}

标记是不同的,因为它们具有不同的接收器类型,并且构造函数没有被检测。

问题

最后,问题是:如果想使用rebase + rename,我在做概念错误的事情。可能这是一个错误?

0 个答案:

没有答案