I have the following class:
package some.clazz.client;
import some.clazz.SomeClass;
public class SomeClassClient {
...
public SomeClass getProc();
...
}
I've removed/shrunk/deleted this getProc()
Java method from SomeClassClient
class bytecode
by using new MemberRemoval().stripMethods(ElementMatcher);
ByteBuddy transformation
in net.bytebuddy:byte-buddy-maven-plugin
Maven Plugin.
But import some.clazz.SomeClass;
statement is still present and shown by CFR Java Decompiler
!
There are no any another reference to SomeClass
class in SomeClassClient
class.
How can I remove this import statement from bytecode (really I'm assuming it's located in constant pool)? Because I'm still getting ClassNotFoundException when trying to use 'SomeClassClient' class.
My class
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)
;
}
)
)
));
...
}
I've added the following EntryPoint and configured it in bytebuddy plugin to use:
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);
}
}
Eventually I've invented a workaround that allows to handle the synthetic bridge methods and at the same time still to use ElementMatcher-s to select methods to remove... As mentioned @Rafael Winterhalter (author) above in its comment: Byte-Buddy lib at its current (v1.10.22 at the moment) version does not handle bridge methods by using its existing MemberRemoval class. So just extend it to remove/strip methods in the following manner:
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
);
}
}
}
And also here is the used byte-buddy Maven plugin configuration:
<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>