我将我的代码添加到此帖的末尾。
我正在使用byteBuddy 1.7.9
以及随附的ASM版本。
简而言之
我有
byte[] rawClass = ...;
ClassReader cr = new ClassReader(rawClass);
ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
MethodAdder ma = new MethodAdder(Opcodes.ASM5,cw);
cr.accept(ma,ClassReader.EXPAND_FRAMES);
MethodAdder
中的哪个位置,我想添加静态初始化程序:
@Override
public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
MethodVisitor mv = cv.visitMethod(access, name, desc, signature, exceptions);
if(mv != null){
if(!name.equals(CLINIT_NAME)) return mv;
else{
hasStaticInitialiser = true;
return new ClinitReplacer(api,mv,classname);
}
}else return null;
}
达到 hasStaticInitialiser = true
,但永远不会执行ClinitReplacer.visitCode
。
为什么呢?
整个故事
我想说我想使用byteBuddy从this example生成类B
。
为什么是bytebuddy?好吧,对于一个它应该是方便的,另一个,我需要它的类重新加载功能。
但正如您在the tutorial中所看到的,使用" pure"会有一些不便之处。字节伙伴代码。最重要的是,
如果你真的需要用跳转指令创建字节码,请确保使用ASM添加正确的堆栈映射帧,因为Byte Buddy不会自动为你包含它们。
我不想这样做。
即使我想,我也试过
builder = builder
.defineMethod("<clinit>",void.class, Modifier.STATIC)
.withParameters(new LinkedList<>())
.withoutCode()
;
所有它让我得到了
Exception in thread "main" java.lang.IllegalStateException: Illegal explicit declaration of a type initializer by class B
at net.bytebuddy.dynamic.scaffold.InstrumentedType$Default.validated(InstrumentedType.java:901)
at net.bytebuddy.dynamic.scaffold.MethodRegistry$Default.prepare(MethodRegistry.java:465)
at net.bytebuddy.dynamic.scaffold.subclass.SubclassDynamicTypeBuilder.make(SubclassDynamicTypeBuilder.java:162)
at net.bytebuddy.dynamic.scaffold.subclass.SubclassDynamicTypeBuilder.make(SubclassDynamicTypeBuilder.java:155)
at net.bytebuddy.dynamic.DynamicType$Builder$AbstractBase.make(DynamicType.java:2639)
at net.bytebuddy.dynamic.DynamicType$Builder$AbstractBase$Delegator.make(DynamicType.java:2741)
at Main.main(Main.java)
所以我做的是,在我添加了所有字段后,我停下来,获取字节码并加载课程。
然后我让ASM为我添加方法。 (在实际应用中,我还需要通过其他一些ASM访问者运行字节码。)
然后使用ByteBuddy重新加载重新检测的字节码。
重新加载失败
Exception in thread "main" java.lang.ClassFormatError
at sun.instrument.InstrumentationImpl.redefineClasses0(Native Method)
at sun.instrument.InstrumentationImpl.redefineClasses(InstrumentationImpl.java:170)
at net.bytebuddy.dynamic.loading.ClassReloadingStrategy$Strategy$1.apply(ClassReloadingStrategy.java:261)
at net.bytebuddy.dynamic.loading.ClassReloadingStrategy.load(ClassReloadingStrategy.java:171)
at Main.main(Main.java)
原因似乎是B
在反汇编时看起来像这样:
super public class B
extends A
version 51:0
{
public static final Field foo:"Ljava/util/Set;";
public Method "<init>":"()V"
stack 1 locals 1
{
aload_0;
invokespecial Method A."<init>":"()V";
return;
}
static Method "<clinit>":"()V";
} // end Class B
将其与rawClass
字节码进行比较,我们注意到
static Method "<clinit>":"()V";
并不存在,确实是由MethodAdder添加的。
然而,访客返回
return new ClinitReplacer(api,mv,classname);
从未使用过。因此静态初始化主体保持为空,导致错误分类为native
。
代码
A.java
import java.util.HashSet;
import java.util.Set;
public class A{
public static final Set foo;
static{
foo = new HashSet<String>();
foo.add("A");
}
}
Main.java
import net.bytebuddy.ByteBuddy;
import net.bytebuddy.agent.ByteBuddyAgent;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.dynamic.DynamicType;
import net.bytebuddy.dynamic.loading.ClassLoadingStrategy;
import net.bytebuddy.dynamic.loading.ClassReloadingStrategy;
import net.bytebuddy.jar.asm.*;
import net.bytebuddy.jar.asm.commons.InstructionAdapter;
import java.io.FileOutputStream;
import java.io.IOException;
import java.lang.reflect.Field;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
public class Main {
public static void main(String[] args) {
ByteBuddyAgent.install();
String targetClassname = "B";
Class superclass = A.class;
ByteBuddy byteBuddy = new ByteBuddy();
DynamicType.Builder builder = byteBuddy
.subclass(superclass)
.name(targetClassname)
;
for(Field f : superclass.getFields()){
builder = builder.defineField(f.getName(),f.getType(),f.getModifiers());
}
DynamicType.Unloaded<?> loadable = builder.make();
byte[] rawClass = loadable.getBytes();
loadable.load(A.class.getClassLoader(), ClassLoadingStrategy.Default.INJECTION);
ClassReader cr = new ClassReader(rawClass);
ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
MethodAdder ma = new MethodAdder(Opcodes.ASM5,cw);
cr.accept(ma,ClassReader.EXPAND_FRAMES);
byte[] finishedClass = cw.toByteArray();
Class unfinishedClass;
try {
unfinishedClass = Class.forName(targetClassname);
}catch(ClassNotFoundException e){
throw new RuntimeException(e);
}
ClassReloadingStrategy.fromInstalledAgent()
.load(
A.class.getClassLoader(),
Collections.singletonMap((TypeDescription)new TypeDescription.ForLoadedType(unfinishedClass), finishedClass)
);
Set<String> result;
try {
result = (Set<String>)Class.forName("B").getField("foo").get(null);
}catch(ClassNotFoundException | NoSuchFieldException | IllegalAccessException e){
throw new RuntimeException(e);
}
System.out.println(result);
}
private static void store(String name, byte[] finishedClass) {
Path path = Paths.get(name + ".class");
try {
FileChannel fc = null;
try {
Files.deleteIfExists(path);
fc = new FileOutputStream(path.toFile()).getChannel();
fc.write(ByteBuffer.wrap(finishedClass));
} finally {
if (fc != null) {
fc.close();
}
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
static class MethodAdder extends ClassVisitor implements Opcodes{
private static final String INIT_NAME = "<init>";
private static final String INIT_DESC = "()V";
private static final int CLINIT_ACCESS = ACC_STATIC;
private static final String CLINIT_NAME = "<clinit>";
private static final String CLINIT_DESC = "()V";
private static final String CLINIT_SIG = null;
private static final String[] CLINIT_EXCEPT = null;
public MethodAdder(int api, ClassVisitor cv) {
super(api, cv);
}
private String classname = null;
private boolean hasStaticInitialiser = false;
@Override
public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
classname = name;
hasStaticInitialiser = false;
cv.visit(version, access, name, signature, superName, interfaces);
}
@Override
public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
MethodVisitor mv = cv.visitMethod(access, name, desc, signature, exceptions);
if(mv != null){
if(!name.equals(CLINIT_NAME)) return mv;
else{
hasStaticInitialiser = true;
return new ClinitReplacer(api,mv,classname);
}
}else return null;
}
@Override
public void visitEnd() {
if(!hasStaticInitialiser) visitMethod(CLINIT_ACCESS,CLINIT_NAME,CLINIT_DESC,CLINIT_SIG,CLINIT_EXCEPT);
if(!hasStaticInitialiser) throw new IllegalStateException("ClinitReplacer not created");
super.visitEnd();
}
private static class ClinitReplacer extends InstructionAdapter implements Opcodes{
private final String classname;
public ClinitReplacer(int api, MethodVisitor mv, String classname) {
super(api, mv);
this.classname = classname;
}
@Override
public void visitCode() {
mv.visitCode();
InstructionAdapter mv = new InstructionAdapter(this.mv);
mv.anew(Type.getType(HashSet.class));
mv.dup();
mv.dup();
mv.invokespecial(Type.getInternalName(HashSet.class),INIT_NAME,INIT_DESC,false);
mv.putstatic(classname,"foo",Type.getDescriptor(Set.class));
mv.visitLdcInsn(classname);
mv.invokevirtual(Type.getInternalName(HashSet.class),"add","(Ljava/lang/Object;)Z",false);
mv.visitInsn(RETURN);
}
}
}
}
答案 0 :(得分:2)
问题是您的源类文件没有<clinit>
方法,因此,ASM根本不会调用visitMethod
;是你谁在
@Override
public void visitEnd() {
if(!hasStaticInitialiser) visitMethod(CLINIT_ACCESS,CLINIT_NAME,CLINIT_DESC,CLINIT_SIG,CLINIT_EXCEPT);
if(!hasStaticInitialiser) throw new IllegalStateException("ClinitReplacer not created");
super.visitEnd();
}
在这里,如果您之前没有遇到visitMethod
,则会调用<clinit>
,但是您没有对返回的MethodVisitor
执行任何操作,因此,没有人是用它做任何事情。
如果你想要一个缺席的<clinit>
来对待一个空的初始化器进行转换,你必须自己进行适当的方法调用,即
@Override
public void visitEnd() {
if(!hasStaticInitialiser) {
MethodVisitor mv = visitMethod(CLINIT_ACCESS,CLINIT_NAME,CLINIT_DESC,CLINIT_SIG,CLINIT_EXCEPT);
mv.visitCode();
mv.visitInsn(RETURN);
mv.visitMaxs(0, 0);
mv.visitEnd();
}
if(!hasStaticInitialiser) throw new IllegalStateException("ClinitReplacer not created");
super.visitEnd();
}
但请注意,您不能进行热代码替换,因为它不支持添加任何方法,包括<clinit>
。此外,热代码替换不会(重新)执行类初始化器。
但是在您的代码中,在执行ASM转换之前无需加载类型。您可以删除该行
loadable.load(A.class.getClassLoader(), ClassLoadingStrategy.Default.INJECTION);
然后只使用生成的finishedClass
字节码,例如
ClassLoadingStrategy.Default.INJECTION.load(A.class.getClassLoader(),
Collections.singletonMap(loadable.getTypeDescription(), finishedClass));
请注意,您不会看到太多影响,因为您正在注入创建HashMap
的代码,但没有对它做任何有用的事情。您可能希望将其分配给字段...
顺便说一下,编写字节数组的代码不必要地复杂化了:
private static void store(String name, byte[] finishedClass) {
Path path = Paths.get(name + ".class");
try {
FileChannel fc = null;
try {
Files.deleteIfExists(path);
fc = new FileOutputStream(path.toFile()).getChannel();
fc.write(ByteBuffer.wrap(finishedClass));
} finally {
if (fc != null) {
fc.close();
}
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
只需使用
private static void store(String name, byte[] finishedClass) {
Path path = Paths.get(name + ".class");
try {
Files.write(path, finishedClass);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
两者都是“如果它不存在则创建”和“如果存在则覆盖/截断”是默认行为。
答案 1 :(得分:1)
要回答有关在Byte Buddy中定义类型初始化程序的部分,可以使用以下方法完成:
builder = builder.invokable(isTypeInitializer()).intercept(...);
您无法显式定义类型初始值设定项,因为这些初始值设定项例如从未被反射API公开,这有助于保持Byte Buddy的类型描述模型一致。相反,您匹配类型初始化程序和Byte Buddy确保添加一个初始化程序,因为它似乎是合适的。