如何删除/收缩“import some.clazz.SomeClass;”Java中字节码操作库/框架的语句?
我有以下课程:如何删除/收缩“import some.clazz.SomeClass;”Java中字节码操作库/框架的语句?,java,bytecode,java-bytecode-asm,byte-buddy,bcel,Java,Bytecode,Java Bytecode Asm,Byte Buddy,Bcel,我有以下课程: package some.clazz.client; import some.clazz.SomeClass; public class SomeClassClient { ... public SomeClass getProc(); ... } 我已经从SomeClassClient类字节码中删除/收缩/删除了这个getProc Java方法 通过使用new MemberRemove.str
package some.clazz.client;
import some.clazz.SomeClass;
public class SomeClassClient {
...
public SomeClass getProc();
...
}
我已经从SomeClassClient类字节码中删除/收缩/删除了这个getProc Java方法
通过使用new MemberRemove.stripMethodsElementMatcher;ByteBuddy变换
在net.bytebuddy中:byte buddy maven plugin maven plugin。
但是导入一些.clazz.SomeClass;语句仍然存在并由CFR Java反编译器显示
SomeClassClient类中没有对SomeClass类的任何其他引用
我怎样才能从字节码中删除这个import语句?我假设它位于常量池中?
因为在尝试使用“SomeClassClient”类时,我仍然得到ClassNotFoundException
我的班级
public class MethodsRemover implements net.bytebuddy.build.Plugin {
...
@Override
public DynamicType.Builder<?> apply(DynamicType.Builder<?> builder,
TypeDescription typeDescription,
ClassFileLocator classFileLocator) {
try{
return builder.visit(new MemberRemoval().stripMethods(
ElementMatchers.any().and(
isAnnotatedWith(Transient.class)
.and(
t -> {
log.info(
"ByteBuddy transforming class: {}, strip method: {}",
typeDescription.getName(),
t
);
return true;
}
)
).or(
target -> Arrays.stream(STRIP_METHODS).anyMatch(
m -> {
Class<?> methodReturnType = getMethodReturnType(m);
String methodName = getMethodName(m);
Class<?>[] methodParameters = getMethodParameters(m);
return
isPublic()
.and(returns(
isVoid(methodReturnType)
? is(TypeDescription.VOID)
: isSubTypeOf(methodReturnType)
))
.and(named(methodName))
.and(isNoParams(m)
? takesNoArguments()
: takesArguments(methodParameters)
)
.and(t -> {
log.info(
"ByteBuddy transforming class: {}, strip method: {}",
typeDescription.getName(),
t
);
return true;
}).matches(target)
;
}
)
)
));
...
}
我添加了以下入口点,并在bytebuddy插件中将其配置为使用:
public static class EntryPoint implements net.bytebuddy.build.EntryPoint {
private net.bytebuddy.build.EntryPoint typeStrategyEntryPoint = Default.REDEFINE;
public EntryPoint() {
}
public EntryPoint(net.bytebuddy.build.EntryPoint typeStrategyEntryPoint) {
this.typeStrategyEntryPoint = typeStrategyEntryPoint;
}
@Override
public ByteBuddy byteBuddy(ClassFileVersion classFileVersion) {
return typeStrategyEntryPoint
.byteBuddy(classFileVersion)
.with(ClassWriterStrategy.Default.CONSTANT_POOL_DISCARDING)
.ignore(none()); // Traverse through all (include synthetic) methods of type
}
@Override
public DynamicType.Builder<?> transform(TypeDescription typeDescription,
ByteBuddy byteBuddy,
ClassFileLocator classFileLocator,
MethodNameTransformer methodNameTransformer) {
return typeStrategyEntryPoint
.transform(typeDescription, byteBuddy, classFileLocator, methodNameTransformer);
}
}
为了重现您的问题,我使用了以下程序,使用了同样由Byte Buddy使用的ASM库: ClassWriter cw=新的ClassWriter0; cw.visit52,ACC_ABSTRACT,无效,null,java/lang/Object,null; MethodVisitor mv=cw.visitMethod ACC|U抽象| ACC|U公共,测试,Lnon/EXISTIVE/Class;,空,空,; visitEnd mv; cw.visitEnd; 字节[]invalidclassBytes=cw.toByteArray; cw=新的ClassWriternew ClassReaderinvalidclassBytes,0; cw.visit52,ACC|u ABSTRACT | ACC|u INTERFACE,Test,null,java/lang/Object,null; mv=连续访问方法C|u STATIC|ACC|u PUBLIC,test,V,null,null; mv.visitFieldInsnGETSTATIC,java/lang/System,out,Ljava/io/PrintStream;; 生成类的mv.visitldcinnhello; mv.visitMethodInVokeVirtual, java/io/PrintStream、println、Ljava/lang/String;五、 虚假的; mv.visitinsreturn; mv.VisitMax2,1; visitEnd mv; cw.visitEnd; byte[]classBytes=cw.toByteArray; MethodHandles.lookup.DefineClassBytes; Class.forNameTest.getDeclaredMethodtest.invokenull; System.out.println; 路径p=Files.writeFiles.createTempFileClass、Test.class、classBytes; ToolProvider.findFirstjavap .ifPresentjavap->javap.runSystem.out,System.err,-c,-v,p.toString; Files.deletep; 试一试{ 类别cl=MethodHandles.lookup.defineClassinvalidclassBytes; System.out.PRINTLNLDEFINED+cl; cl.方法; } 捕捉错误{ System.out.printlngot预期错误+e; } 它首先为名为Invalid的类生成字节码,该类包含返回类型为non.existence.class的方法。然后,它使用类读取器生成一个类测试,该类读取器读取first的字节码作为类编写器的输入,类编写器将复制整个常量池,包括对不存在的类的引用 第二个类Test被转换为一个运行时类,并调用它的测试方法。此外,字节码被转储到一个临时文件中,javap在其上运行,以显示常量池。只有在这些步骤之后,才会尝试为Invalid创建运行时类,以引发错误 在我的机器上,它打印: 来自生成类的您好 类文件/C:/Users/███████████/AppData/Local/Temp/Class10921011438737096460Test.class 最后修改日期:2021年3月29日;大小312字节 SHA-256校验和63df4401143b4fb57b4815fc193f3e47fdd4c301cd76fa7f945edb415e14330a 接口测试 次要版本:0 主要版本:52 标志:0x0600 ACC_接口,ACC_摘要 本课程:8//Test 超级类:4//java/lang/Object 接口:0,字段:0,方法:1,属性:0 固定池: 1=Utf8无效 2=类别1//无效 3=Utf8 java/lang/Object 4=类3//java/lang/Object 5=Utf8测试 6=Utf8 Lnon/存在/类别; 7=Utf8测试 8=7级//试验 9=Utf8 V 10=Utf8 java/lang/System 11=类10//java/lang/System 12=Utf8输出 13=Utf8 Ljava/io/PrintStream; 14=名称和类型12:13//out:Ljava/io/PrintStream; 15=Fieldref 11.14//java/lang/System.out:Ljava/io/PrintStream; 16=生成类的Utf8 Hello 17=字符串16//Hello来自生成的类 18=Utf8 java/io/PrintStream 19=类18//java/io/PrintStream 20=Utf8打印LN 21=Utf8 Ljava/lang/String;v 22=NameAndType 20:21//println:Ljava/lang/String;v 23=Methodref 19.22//java/io/PrintStream.println:Ljava/lang/String;v 24=Utf8代码 { 公共静态空隙试验; 描述符:V 标志:0x0009 ACC\U PUBLIC,ACC\U STATIC 代码: 堆栈=2,局部变量=1,参数大小=0 0:getstatic 15//Field java/lang/System.out:Ljava/io/PrintStream; 3:ldc 17//从生成的字符串Hello 班 5:invokeVirtual23//方法java/io/PrintStream.println:Ljava/lang/String;v 8:返回 } 定义的类无效 获取了预期错误java.lang.NoClassDefFoundError:不存在/类 它表明第一类的方法Lnon/existing/class的签名;存在于第二个类文件中,但由于没有指向它的方法定义,它只是一个未使用的UTF-8类型条目,没有任何关于包含类型引用的提示,因此不会造成任何伤害 但它甚至表明,在广泛使用的热点JVM中,有一个真正的类条目指向尚未定义的类Invalid并不会阻止我们加载和使用类测试 更有趣的是,为Invalid定义运行时类的尝试也成功了,因为消息“definedclassinvalid”已经打印出来。它需要一个实际的操作在缺少的不存在的类上绊倒,比如cl.getMethods来引发错误 我做了另一个步骤,并将生成的字节码提供给CFR on。它产生了 /* *用CFR 0.150反编译。 */ 接口测试{ 公共静态孔隙试验{ 生成的类中的System.out.printlnholl; } } 显示常量池的那些悬空条目并没有导致import语句的生成 这一切都表明,您认为在转换的类中并没有主动使用类SomeClass的假设是错误的。必须积极使用导致异常和import语句生成的类 另外值得注意的是,在另一个方向上,编译包含其他未使用类的导入语句的源代码时,类文件中不会出现对这些类的引用 中给出的信息至关重要: 我忘了指定SomeClassClient在其层次结构中有一个超类和一个接口,接口用泛型返回类型定义了这个TProc getProc方法,它反过来扩展了AbstractSomeClass,并作为SomeClass传递给超类定义 javap显示: 插装前:SomeClass getProc 检测后:AbstractSomeClass getProc 其中,as CFR反汇编程序仅显示导入语句。 我在注释文本中添加了格式 你这里的是一个。由于原始类使用更具体的返回类型实现了该方法,因此编译器添加了一个合成方法,该方法重写AbstractSomeClass getProc方法并委托给SomeClass getProc 您删除了SomeClass getProc,但没有删除桥接方法。桥接方法是仍然具有对SomeClass的引用的代码。反编译器在处理桥接方法时遇到对SomeClass的引用,因此生成了import语句,但没有为桥接方法生成源代码,因为为实际目标方法生成源代码足以重现桥接方法,因此没有必要生成普通代码
要完全消除SomeClass引用,必须从字节码中删除这两个方法。对于普通Java代码,您可以简单地放松返回类型检查,因为Java语言不允许定义具有相同名称和参数类型的多个方法。因此,当模板的返回类型是引用类型时,您可以简单地匹配任何引用返回类型,以匹配任何重写方法及其所有桥接方法。当返回类型是模板返回类型的超级类型时,您可以添加对桥接方法标志的检查,但是,如上所述,对于普通Java代码,这是不必要的。为了重现您的问题,我使用了以下程序,使用了同样由Byte Buddy使用的ASM库: ClassWriter cw=新的ClassWriter0; cw.visit52,ACC_ABSTRACT,无效,null,java/lang/Object,null; MethodVisitor mv=cw.visitMethod ACC|U抽象| ACC|U公共,测试,Lnon/EXISTIVE/Class;,空,空,; visitEnd mv; cw.visitEnd; 字节[]invalidclassBytes=cw.toByteArray; cw=新的ClassWriternew ClassReaderinvalidclassBytes,0; cw.visit52,ACC|u ABSTRACT | ACC|u INTERFACE,Test,null,java/lang/Object,null; mv=连续访问方法C|u STATIC|ACC|u PUBLIC,test,V,null,null; mv.visitFieldInsnGETSTATIC,java/lang/System,out,Ljava/io/PrintStream;; 生成类的mv.visitldcinnhello; mv.visitMethodInVokeVirtual, java/io/PrintStream、println、Ljava/lang/String;五、 虚假的; mv.visitinsreturn; mv.VisitMax2,1; visitEnd mv; cw.visitEnd; byte[]classBytes=cw.toByteArray; MethodHandles.lookup.DefineClassBytes; Class.forNameTest.getDeclaredMethodtest.invokenull; System.out.println; 路径p=Files.writeFiles.createTempFileClass、Test.class、classBytes; ToolProvider.findFirstjavap .ifPresentjavap->javap.runSystem.out,System.err,-c,-v,p.toString; Files.deletep; 试一试{ 类别cl=MethodHandles.lookup.defineClassinvalidclassBytes; System.out.PRINTLNLDEFINED+cl; cl.getMethod s } 捕捉错误{ System.out.printlngot预期错误+e; } 它首先为名为Invalid的类生成字节码,该类包含返回类型为non.existence.class的方法。然后,它使用类读取器生成一个类测试,该类读取器读取first的字节码作为类编写器的输入,类编写器将复制整个常量池,包括对不存在的类的引用 第二个类Test被转换为一个运行时类,并调用它的测试方法。此外,字节码被转储到一个临时文件中,javap在其上运行,以显示常量池。只有在这些步骤之后,才会尝试为Invalid创建运行时类,以引发错误 在我的机器上,它打印: 来自生成类的您好 类文件/C:/Users/███████████/AppData/Local/Temp/Class10921011438737096460Test.class 最后修改日期:2021年3月29日;大小312字节 SHA-256校验和63df4401143b4fb57b4815fc193f3e47fdd4c301cd76fa7f945edb415e14330a 接口测试 次要版本:0 主要版本:52 标志:0x0600 ACC_接口,ACC_摘要 本课程:8//Test 超级类:4//java/lang/Object 接口:0,字段:0,方法:1,属性:0 固定池: 1=Utf8无效 2=类别1//无效 3=Utf8 java/lang/Object 4=类3//java/lang/Object 5=Utf8测试 6=Utf8 Lnon/存在/类别; 7=Utf8测试 8=7级//试验 9=Utf8 V 10=Utf8 java/lang/System 11=类10//java/lang/System 12=Utf8输出 13=Utf8 Ljava/io/PrintStream; 14=名称和类型12:13//out:Ljava/io/PrintStream; 15=Fieldref 11.14//java/lang/System.out:Ljava/io/PrintStream; 16=生成类的Utf8 Hello 17=字符串16//Hello来自生成的类 18=Utf8 java/io/PrintStream 19=类18//java/io/PrintStream 20=Utf8打印LN 21=Utf8 Ljava/lang/String;v 22=NameAndType 20:21//println:Ljava/lang/String;v 23=Methodref 19.22//java/io/PrintStream.println:Ljava/lang/String;v 24=Utf8代码 { 公共静态空隙试验; 描述符:V 标志:0x0009 ACC\U PUBLIC,ACC\U STATIC 代码: 堆栈=2,局部变量=1,参数大小=0 0:getstatic 15//Field java/lang/System.out:Ljava/io/PrintStream; 3:ldc 17//来自生成类的字符串Hello 5:invokeVirtual23//方法java/io/PrintStream.println:Ljava/lang/String;V 8:返回 } 定义的类无效 获取了预期错误java.lang.NoClassDefFoundError:不存在/类 它表明第一类的方法Lnon/existing/class的签名;存在于第二个类文件中,但由于没有指向它的方法定义,它只是一个未使用的UTF-8类型条目,没有任何关于包含类型引用的提示,因此不会造成任何伤害 但它甚至表明,在广泛使用的热点JVM中,有一个真正的类条目指向尚未定义的类Invalid并不会阻止我们加载和使用类测试 更有趣的是,为Invalid定义运行时类的尝试也成功了,因为消息“definedclassinvalid”已经打印出来。它需要一个实际的操作在缺少的不存在的类上绊倒,比如cl.getMethods来引发错误 我做了另一个步骤,并将生成的字节码提供给CFR on。它产生了 /* *用CFR 0.150反编译。 */ 接口测试{ 公共静态孔隙试验{ 生成的类中的System.out.printlnholl; } } 显示常量池的那些悬空条目并没有导致import语句的生成 这一切都表明,您认为在转换的类中并没有主动使用类SomeClass的假设是错误的。必须积极使用导致异常和import语句生成的类 另外值得注意的是,在另一个方向上,编译包含其他未使用类的导入语句的源代码时,类文件中不会出现对这些类的引用 中给出的信息至关重要: 我忘了指定SomeClassClient在其层次结构中有一个超类和一个接口,接口用泛型返回类型定义了这个TProc getProc方法,它反过来扩展了AbstractSomeClass,并作为SomeClass传递给超类定义 javap显示: 插装前:SomeClass getProc 检测后:AbstractSomeClass getProc Wh 因为CFR反汇编程序只显示导入语句。 我在注释文本中添加了格式 你这里的是一个。由于原始类使用更具体的返回类型实现了该方法,因此编译器添加了一个合成方法,该方法重写AbstractSomeClass getProc方法并委托给SomeClass getProc 您删除了SomeClass getProc,但没有删除桥接方法。桥接方法是仍然具有对SomeClass的引用的代码。反编译器在处理桥接方法时遇到对SomeClass的引用,因此生成了import语句,但没有为桥接方法生成源代码,因为为实际目标方法生成源代码足以重现桥接方法,因此没有必要生成普通代码
要完全消除SomeClass引用,必须从字节码中删除这两个方法。对于普通Java代码,您可以简单地放松返回类型检查,因为Java语言不允许定义具有相同名称和参数类型的多个方法。因此,当模板的返回类型是引用类型时,您可以简单地匹配任何引用返回类型,以匹配任何重写方法及其所有桥接方法。当返回类型是模板返回类型的超级类型时,可以添加对桥接方法标志的检查,但是,如前所述,对于普通Java代码,这是不必要的。最终我发明了一种解决方法,允许处理合成桥方法,同时仍然使用ElementMatcher-s选择要删除的方法。。。 正如@Rafael Winterhalter作者在其评论中提到的那样:当前版本的Byte Buddy lib不使用现有的MemberRemoving类处理桥接方法。因此,只需通过以下方式将其扩展为移除/剥离方法:
package com.pany.of.yours.byte.buddy;
import net.bytebuddy.ByteBuddy;
import net.bytebuddy.ClassFileVersion;
import net.bytebuddy.asm.MemberRemoval;
import net.bytebuddy.build.Plugin;
import net.bytebuddy.description.field.FieldDescription;
import net.bytebuddy.description.field.FieldList;
import net.bytebuddy.description.method.MethodDescription;
import net.bytebuddy.description.method.MethodList;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.dynamic.ClassFileLocator;
import net.bytebuddy.dynamic.DynamicType;
import net.bytebuddy.dynamic.scaffold.ClassWriterStrategy;
import net.bytebuddy.dynamic.scaffold.MethodGraph;
import net.bytebuddy.dynamic.scaffold.inline.MethodNameTransformer;
import net.bytebuddy.implementation.Implementation;
import net.bytebuddy.jar.asm.ClassVisitor;
import net.bytebuddy.matcher.ElementMatcher;
import net.bytebuddy.matcher.ElementMatchers;
import net.bytebuddy.pool.TypePool;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import static net.bytebuddy.matcher.ElementMatchers.is;
import static net.bytebuddy.matcher.ElementMatchers.isAnnotatedWith;
import static net.bytebuddy.matcher.ElementMatchers.isBridge;
import static net.bytebuddy.matcher.ElementMatchers.isPublic;
import static net.bytebuddy.matcher.ElementMatchers.isSubTypeOf;
import static net.bytebuddy.matcher.ElementMatchers.named;
import static net.bytebuddy.matcher.ElementMatchers.none;
import static net.bytebuddy.matcher.ElementMatchers.returns;
import static net.bytebuddy.matcher.ElementMatchers.takesArguments;
import static net.bytebuddy.matcher.ElementMatchers.takesNoArguments;
...
public class MethodsRemover implements Plugin {
private static final Logger log = LoggerFactory.getLogger(MethodsRemover.class);
private static final Object[][] STRIP_METHODS = {
{SomeClass.class, "getProc", void.class} //,
// other methods here
};
public MethodsRemover() {
}
@Override
public boolean matches(TypeDescription typeDefinitions) {
// return typeDefinitions.getName().equals("pkg.SomeClass");
return typeDefinitions.isAssignableTo(SomeClassSuper.class) }
@Override
public DynamicType.Builder<?> apply(DynamicType.Builder<?> builder,
TypeDescription typeDescription,
ClassFileLocator classFileLocator) {
try{
log.info(" ByteBuddy processing type =========> {}", typeDescription);
return builder.visit(new MemberRemovalEx().stripMethods(
ElementMatchers.none()// <= or you can use ElementMatchers.any();
.or(t -> { // <= + .and(..) - as a start point instead.
log.debug("ByteBuddy processing method --> {}", t);
return false;
})
.or(
isAnnotatedWith(Transient.class)
.and(t -> {
log.info(
" ByteBuddy strip transient method ++> {}",
t
);
return true;
})
)
.or(
target -> Arrays.stream(STRIP_METHODS).anyMatch(
m -> {
Class<?> methodReturnType = getMethodReturnType(m);
String methodName = getMethodName(m);
Class<?>[] methodParameters = getMethodParameters(m);
return
isPublic()
.and(returns(
isVoid(methodReturnType)
? is(TypeDescription.VOID)
: isSubTypeOf(methodReturnType)
))
.and(named(methodName))
.and(isNoParams(m)
? takesNoArguments()
: takesArguments(methodParameters)
)
.and(t -> {
log.info(
" ByteBuddy strip signature method ++> {}",
t
);
return true;
}).matches(target)
;
}
)
)
));
} catch (Exception e) {
log.error("ByteBuddy error: ", e);
throw e;
}
}
...
public static class EntryPoint implements net.bytebuddy.build.EntryPoint {
private net.bytebuddy.build.EntryPoint typeStrategyEntryPoint = Default.REDEFINE;
public EntryPoint() {
}
public EntryPoint(net.bytebuddy.build.EntryPoint typeStrategyEntryPoint) {
this.typeStrategyEntryPoint = typeStrategyEntryPoint;
}
@Override
public ByteBuddy byteBuddy(ClassFileVersion classFileVersion) {
return typeStrategyEntryPoint
.byteBuddy(classFileVersion)
.with(MethodGraph.Compiler.Default.forJVMHierarchy()) // Change hashCode/equals by including a return type
.with(ClassWriterStrategy.Default.CONSTANT_POOL_DISCARDING) // Recreate constants pool
.ignore(none()); // Traverse through all (include synthetic) methods of type
}
@Override
public DynamicType.Builder<?> transform(TypeDescription typeDescription,
ByteBuddy byteBuddy,
ClassFileLocator classFileLocator,
MethodNameTransformer methodNameTransformer) {
return typeStrategyEntryPoint
.transform(typeDescription, byteBuddy, classFileLocator, methodNameTransformer);
}
}
private class MemberRemovalEx extends MemberRemoval {
private final Junction<FieldDescription.InDefinedShape> fieldMatcher;
private final Junction<MethodDescription> methodMatcher;
public MemberRemovalEx() {
this(ElementMatchers.none(), ElementMatchers.none());
}
public MemberRemovalEx(Junction<FieldDescription.InDefinedShape> fieldMatcher,
Junction<MethodDescription> methodMatcher) {
super(fieldMatcher, methodMatcher);
this.fieldMatcher = fieldMatcher;
this.methodMatcher = methodMatcher;
}
@Override
public MemberRemoval stripInvokables(ElementMatcher<? super MethodDescription> matcher) {
return new MemberRemovalEx(this.fieldMatcher, this.methodMatcher.or(matcher));
}
@Override
public ClassVisitor wrap(TypeDescription instrumentedType,
ClassVisitor classVisitor,
Implementation.Context implementationContext,
TypePool typePool,
FieldList<FieldDescription.InDefinedShape> fields,
MethodList<?> methods,
int writerFlags,
int readerFlags) {
MethodList<MethodDescription.InDefinedShape> typeBridgeMethods =
instrumentedType.getDeclaredMethods().filter(isBridge());
int bridgeMethodCount = typeBridgeMethods.size();
if (bridgeMethodCount > 0) {
List<MethodDescription> methodsPlusBridges = new ArrayList<>(
methods.size() + bridgeMethodCount
);
methodsPlusBridges.addAll(typeBridgeMethods);
methodsPlusBridges.addAll(methods);
methods = new MethodList.Explicit<>(methodsPlusBridges);
}
return super.wrap(
instrumentedType,
classVisitor,
implementationContext,
typePool,
fields,
methods,
writerFlags,
readerFlags
);
}
}
}
下面是使用的byte buddy Maven插件配置:
<build>
<plugins>
<plugin>
<groupId>net.bytebuddy</groupId>
<artifactId>byte-buddy-maven-plugin</artifactId>
<version>${byte-buddy-maven-plugin.version}</version>
<executions>
<execution>
<id>byte.buddy.strip.methods</id>
<phase>process-classes</phase>
<goals>
<goal>transform</goal>
</goals>
<configuration>
<transformations>
<transformation>
<!-- Next plugin transformer removes @Transient annotated and some predefined methods from entities -->
<plugin>com.pany.of.yours.byte.buddy.MethodsRemover</plugin>
<!-- Optionally, specify groupId, artifactId, version of the class -->
</transformation>
</transformations>
<!-- Optionally, add 'initialization' block with EntryPoint class -->
<initialization>
<entryPoint>
com.pany.of.yours.byte.buddy.MethodsRemover$EntryPoint
</entryPoint>
</initialization>
</configuration>
</execution>
</executions>
<dependencies>
<dependency>
<groupId>some.your.aux.dependency.group</groupId>
<artifactId>dependency-artifact</artifactId>
<version>${project.version}</version>
</dependency>
</dependencies>
</plugin>
</plugins>
</build>
最终,我发明了一种解决方法,允许处理合成桥方法,同时仍然使用ElementMatcher-s选择要删除的方法。。。 正如@Rafael Winterhalter作者在其评论中提到的那样:当前版本的Byte Buddy lib不使用现有的MemberRemoving类处理桥接方法。因此,只需通过以下方式将其扩展为移除/剥离方法:
package com.pany.of.yours.byte.buddy;
import net.bytebuddy.ByteBuddy;
import net.bytebuddy.ClassFileVersion;
import net.bytebuddy.asm.MemberRemoval;
import net.bytebuddy.build.Plugin;
import net.bytebuddy.description.field.FieldDescription;
import net.bytebuddy.description.field.FieldList;
import net.bytebuddy.description.method.MethodDescription;
import net.bytebuddy.description.method.MethodList;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.dynamic.ClassFileLocator;
import net.bytebuddy.dynamic.DynamicType;
import net.bytebuddy.dynamic.scaffold.ClassWriterStrategy;
import net.bytebuddy.dynamic.scaffold.MethodGraph;
import net.bytebuddy.dynamic.scaffold.inline.MethodNameTransformer;
import net.bytebuddy.implementation.Implementation;
import net.bytebuddy.jar.asm.ClassVisitor;
import net.bytebuddy.matcher.ElementMatcher;
import net.bytebuddy.matcher.ElementMatchers;
import net.bytebuddy.pool.TypePool;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import static net.bytebuddy.matcher.ElementMatchers.is;
import static net.bytebuddy.matcher.ElementMatchers.isAnnotatedWith;
import static net.bytebuddy.matcher.ElementMatchers.isBridge;
import static net.bytebuddy.matcher.ElementMatchers.isPublic;
import static net.bytebuddy.matcher.ElementMatchers.isSubTypeOf;
import static net.bytebuddy.matcher.ElementMatchers.named;
import static net.bytebuddy.matcher.ElementMatchers.none;
import static net.bytebuddy.matcher.ElementMatchers.returns;
import static net.bytebuddy.matcher.ElementMatchers.takesArguments;
import static net.bytebuddy.matcher.ElementMatchers.takesNoArguments;
...
public class MethodsRemover implements Plugin {
private static final Logger log = LoggerFactory.getLogger(MethodsRemover.class);
private static final Object[][] STRIP_METHODS = {
{SomeClass.class, "getProc", void.class} //,
// other methods here
};
public MethodsRemover() {
}
@Override
public boolean matches(TypeDescription typeDefinitions) {
// return typeDefinitions.getName().equals("pkg.SomeClass");
return typeDefinitions.isAssignableTo(SomeClassSuper.class) }
@Override
public DynamicType.Builder<?> apply(DynamicType.Builder<?> builder,
TypeDescription typeDescription,
ClassFileLocator classFileLocator) {
try{
log.info(" ByteBuddy processing type =========> {}", typeDescription);
return builder.visit(new MemberRemovalEx().stripMethods(
ElementMatchers.none()// <= or you can use ElementMatchers.any();
.or(t -> { // <= + .and(..) - as a start point instead.
log.debug("ByteBuddy processing method --> {}", t);
return false;
})
.or(
isAnnotatedWith(Transient.class)
.and(t -> {
log.info(
" ByteBuddy strip transient method ++> {}",
t
);
return true;
})
)
.or(
target -> Arrays.stream(STRIP_METHODS).anyMatch(
m -> {
Class<?> methodReturnType = getMethodReturnType(m);
String methodName = getMethodName(m);
Class<?>[] methodParameters = getMethodParameters(m);
return
isPublic()
.and(returns(
isVoid(methodReturnType)
? is(TypeDescription.VOID)
: isSubTypeOf(methodReturnType)
))
.and(named(methodName))
.and(isNoParams(m)
? takesNoArguments()
: takesArguments(methodParameters)
)
.and(t -> {
log.info(
" ByteBuddy strip signature method ++> {}",
t
);
return true;
}).matches(target)
;
}
)
)
));
} catch (Exception e) {
log.error("ByteBuddy error: ", e);
throw e;
}
}
...
public static class EntryPoint implements net.bytebuddy.build.EntryPoint {
private net.bytebuddy.build.EntryPoint typeStrategyEntryPoint = Default.REDEFINE;
public EntryPoint() {
}
public EntryPoint(net.bytebuddy.build.EntryPoint typeStrategyEntryPoint) {
this.typeStrategyEntryPoint = typeStrategyEntryPoint;
}
@Override
public ByteBuddy byteBuddy(ClassFileVersion classFileVersion) {
return typeStrategyEntryPoint
.byteBuddy(classFileVersion)
.with(MethodGraph.Compiler.Default.forJVMHierarchy()) // Change hashCode/equals by including a return type
.with(ClassWriterStrategy.Default.CONSTANT_POOL_DISCARDING) // Recreate constants pool
.ignore(none()); // Traverse through all (include synthetic) methods of type
}
@Override
public DynamicType.Builder<?> transform(TypeDescription typeDescription,
ByteBuddy byteBuddy,
ClassFileLocator classFileLocator,
MethodNameTransformer methodNameTransformer) {
return typeStrategyEntryPoint
.transform(typeDescription, byteBuddy, classFileLocator, methodNameTransformer);
}
}
private class MemberRemovalEx extends MemberRemoval {
private final Junction<FieldDescription.InDefinedShape> fieldMatcher;
private final Junction<MethodDescription> methodMatcher;
public MemberRemovalEx() {
this(ElementMatchers.none(), ElementMatchers.none());
}
public MemberRemovalEx(Junction<FieldDescription.InDefinedShape> fieldMatcher,
Junction<MethodDescription> methodMatcher) {
super(fieldMatcher, methodMatcher);
this.fieldMatcher = fieldMatcher;
this.methodMatcher = methodMatcher;
}
@Override
public MemberRemoval stripInvokables(ElementMatcher<? super MethodDescription> matcher) {
return new MemberRemovalEx(this.fieldMatcher, this.methodMatcher.or(matcher));
}
@Override
public ClassVisitor wrap(TypeDescription instrumentedType,
ClassVisitor classVisitor,
Implementation.Context implementationContext,
TypePool typePool,
FieldList<FieldDescription.InDefinedShape> fields,
MethodList<?> methods,
int writerFlags,
int readerFlags) {
MethodList<MethodDescription.InDefinedShape> typeBridgeMethods =
instrumentedType.getDeclaredMethods().filter(isBridge());
int bridgeMethodCount = typeBridgeMethods.size();
if (bridgeMethodCount > 0) {
List<MethodDescription> methodsPlusBridges = new ArrayList<>(
methods.size() + bridgeMethodCount
);
methodsPlusBridges.addAll(typeBridgeMethods);
methodsPlusBridges.addAll(methods);
methods = new MethodList.Explicit<>(methodsPlusBridges);
}
return super.wrap(
instrumentedType,
classVisitor,
implementationContext,
typePool,
fields,
methods,
writerFlags,
readerFlags
);
}
}
}
下面是使用的byte buddy Maven插件配置:
<build>
<plugins>
<plugin>
<groupId>net.bytebuddy</groupId>
<artifactId>byte-buddy-maven-plugin</artifactId>
<version>${byte-buddy-maven-plugin.version}</version>
<executions>
<execution>
<id>byte.buddy.strip.methods</id>
<phase>process-classes</phase>
<goals>
<goal>transform</goal>
</goals>
<configuration>
<transformations>
<transformation>
<!-- Next plugin transformer removes @Transient annotated and some predefined methods from entities -->
<plugin>com.pany.of.yours.byte.buddy.MethodsRemover</plugin>
<!-- Optionally, specify groupId, artifactId, version of the class -->
</transformation>
</transformations>
<!-- Optionally, add 'initialization' block with EntryPoint class -->
<initialization>
<entryPoint>
com.pany.of.yours.byte.buddy.MethodsRemover$EntryPoint
</entryPoint>
</initialization>
</configuration>
</execution>
</executions>
<dependencies>
<dependency>
<groupId>some.your.aux.dependency.group</groupId>
<artifactId>dependency-artifact</artifactId>
<version>${project.version}</version>
</dependency>
</dependencies>
</plugin>
</plugins>
</build>
尝试使用ApacheBcelthat的JVM依赖,但通常情况下,常量池中未使用的类引用不会导致任何异常或错误。由于字节码中没有import语句,这取决于反编译器是否重新生成一些语句。但更可能的是,代码中实际使用的类导致了这两种情况。如何使用Byte Buddy设置此删除?如果要剥离此数据,可能需要重新计算常量池。使用new ByteBuddy.withClassWriterStrategy.Default.CONSTANT\u POOL\u DISCARDING.Linkage错误将导致NoClassDefFoundError。ClassNotFoundException通常是手动loadClass…或Class.forName…调用的标志。在这种情况下,查看堆栈跟踪会很有帮助。它不必是包含SomeClass用法的SomeClassClient类;它可以是某个ClassClient使用的任何其他类。现在,这是有价值的信息。请参阅我更新的答案的结尾。请尝试使用Apache BCELThat的JVM依赖项,但通常情况下,常量池中未使用的类引用不会导致任何异常或错误。由于字节码中没有import语句,这取决于反编译器是否重新生成一些语句。但更可能的是,代码中实际使用的类导致了这两种情况。如何使用Byte Buddy设置此删除?如果要剥离此数据,可能需要重新计算常量池。使用new ByteBuddy.withClassWriterStrategy.Default.CONSTANT\u POOL\u DISCARDING.Linkage错误将导致NoClassDefFoundError。ClassNotFoundException通常是手动loadClass…或Class.forName…调用的标志。在这种情况下,查看堆栈跟踪会很有帮助。它不必是包含SomeClass用法的SomeClassClient类;它可以是某个ClassClient使用的任何其他类。现在,这是有价值的信息。请参阅我更新的答案的结尾。非常感谢您的详细解释,但是ByteBuddy似乎根本没有访问桥接方法,因为假设那里应该有额外的配置,所以我的MethodsRever没有机会在stripMethods中应用其ElementMatchers:for it。试图找出必要的配置选项…MemberRemoving不处理桥接方法是正确的。您可以使用ASM并注册ClassVisitorWrapper。然后,您可以为要删除的所有方法返回null,这与成员删除具有相同的效果,但也将处理桥接方法。T 非常感谢您的详细解释,但是ByteBuddy似乎根本没有访问桥接方法,因为假设那里应该有额外的配置,所以我的MethodsRever没有机会在stripMethods中应用它的ElementMatchers:for it。试图找出必要的配置选项…MemberRemoving不处理桥接方法是正确的。您可以使用ASM并注册ClassVisitorWrapper。然后,您可以为要删除的所有方法返回null,这与成员删除具有相同的效果,但也将处理桥接方法。