在 ByteBuddy 变基期间拦截构造函数
Intercept constructors during rebase in ByteBuddy
我想做什么
我正在尝试变基并重命名 class 以使用 Bytebuddy 1.6.7 拦截它的构造函数
动机
我在 SAAS 系统上工作,用户可以在其中提供带注释的 java classes,系统应该在存储或启动之前检测它们。我已经开发了执行我需要的仪器的管道,但它可以在两个地方使用。
第一个模块检测 classes 并在不加载的情况下存储它们。它使用 class 名称作为 "component" id,所以我不想创建一个子 class 检测类型以避免 class 名称中不必要的后缀。这就是为什么我想使用变基。
另一个模块仪器已经加载 classes 并立即使用它们并且不关心 class 名称。在这个模块中,我想重用第一个模块中的代码,并在加载之前更改检测类型的名称。
代码
完整的工作示例是 here。
我想知道,为什么以下方法适用于内部 classes,但不适用于根 classes。
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 class,我会得到以下异常:
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
正如我之前提到的,如果 class 未加载,我可以避免 class:
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
我也可以通过以下步骤实现目标:Rebase with rename, make。然后创建 SimpleClassLoader,它包含刚刚为新 class 名称生成的字节。拦截构造函数
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 中构造 MethodRebaseResolver 时 here we got 0 instrumentedMethods in resulting methodRebaseResolver. Seems, that RebasableMatcher during matching 在 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,我是否在概念上做错了什么。这可能是一个错误?
你是对的,你找到了a bug that I have just fixed。对于内部 classes,重命名分辨率略有不同,这使它起作用。
至于你的问题,链接解决方案可能是最好的主意。然而,更好的方法是使用 Java 代理。这样,您可以保证不会过早地加载 class。
我想做什么
我正在尝试变基并重命名 class 以使用 Bytebuddy 1.6.7 拦截它的构造函数
动机
我在 SAAS 系统上工作,用户可以在其中提供带注释的 java classes,系统应该在存储或启动之前检测它们。我已经开发了执行我需要的仪器的管道,但它可以在两个地方使用。
第一个模块检测 classes 并在不加载的情况下存储它们。它使用 class 名称作为 "component" id,所以我不想创建一个子 class 检测类型以避免 class 名称中不必要的后缀。这就是为什么我想使用变基。
另一个模块仪器已经加载 classes 并立即使用它们并且不关心 class 名称。在这个模块中,我想重用第一个模块中的代码,并在加载之前更改检测类型的名称。
代码
完整的工作示例是 here。
我想知道,为什么以下方法适用于内部 classes,但不适用于根 classes。
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 class,我会得到以下异常:
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
正如我之前提到的,如果 class 未加载,我可以避免 class:
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
我也可以通过以下步骤实现目标:Rebase with rename, make。然后创建 SimpleClassLoader,它包含刚刚为新 class 名称生成的字节。拦截构造函数
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 中构造 MethodRebaseResolver 时 here we got 0 instrumentedMethods in resulting methodRebaseResolver. Seems, that RebasableMatcher during matching 在 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,我是否在概念上做错了什么。这可能是一个错误?
你是对的,你找到了a bug that I have just fixed。对于内部 classes,重命名分辨率略有不同,这使它起作用。
至于你的问题,链接解决方案可能是最好的主意。然而,更好的方法是使用 Java 代理。这样,您可以保证不会过早地加载 class。