ByteBuddy Proxy Interface

I am trying to convert a Cglib proxy to ByteBuddy. Cglib has a net.sf.cglib.proxy.Proxy interface to intercept all method calls. I am checking the ByteBuddy documentation but cannot find such an example. Without such an interface, for every object that I create with ByteBuddy, I repeat the same thing over and over. Is there a better way to do this with ByteBuddy?

Here is my code snippet:

Services:

public class MyService { public void sayFoo() { System.out.println("foo"); } public void sayBar() { System.out.println("bar"); } } 

interceptor:

 public class MyServiceInterceptor { public void sayFoo(@SuperCall Callable<Void> zuper) { try { zuper.call(); } catch (Exception e) { e.printStackTrace(); } } public void sayBar(@SuperCall Callable<Void> zuper) { try { zuper.call(); } catch (Exception e) { e.printStackTrace(); } } } 

Test:

 import net.bytebuddy.ByteBuddy; import net.bytebuddy.ClassFileVersion; import net.bytebuddy.dynamic.ClassLoadingStrategy; import net.bytebuddy.instrumentation.MethodDelegation; import net.bytebuddy.instrumentation.method.matcher.MethodMatchers; public class Main { public static void main(String[] args) throws Exception { ByteBuddy buddy = new ByteBuddy(ClassFileVersion.forCurrentJavaVersion()); Class<? extends MyService> serviceClass = buddy.subclass(MyService.class) .method(MethodMatchers.named("sayFoo").or(MethodMatchers.named("sayBar"))) .intercept(MethodDelegation.to(new MyServiceInterceptor())) .make() .load(Main.class.getClassLoader(), ClassLoadingStrategy.Default.WRAPPER) .getLoaded(); MyService service = serviceClass.newInstance(); service.sayFoo(); service.sayBar(); } } 
+6
source share
1 answer

Byte Buddy will look at any possible target method and bind it if possible. If there is more than one possible target method, it will link the most specific one or throw an exception if it is ambiguous. In your example, the bindings will be ambiguous, but since you called the interceptor methods (in MyServiceInterceptor ) identical to the intercept methods (in Service ), Byte Buddy believed that intercepting each method using the interceptor method with the same name is probably what you wanted to do. As mentioned in the javadoc MethodInterceptor , Byte Buddy will be:

  • Find any method that it can bind and drop the rest.
  • Make sure the method is annotated with @BindingPriority and select the ones that have the highest priority.
  • An interception with a method that has the same name for the intercepted method, if there is at least one.
  • Choose a method with the most specific types of arguments if you use the @Argument annotation and allow the most specific binding, similar to the Java compiler that identifies the purpose of an overloaded method call.
  • Accepts a method with most parameters.

You can also change this default behavior by adding / removing AmbiguityResolver s .

If you want to specify one interceptor method that is capable of intercepting any method with a super method , than you can write the following interceptor class:

 public class MyServiceInterceptor { @RuntimeType public static Object intercept(@SuperCall Callable<?> zuper) throws Exception { return zuper.call(); } } 

The method name does not matter, Byte Buddy will bind the interceptor, since it is the only possible target. You need to add the @RuntimeType annotation, since the @RuntimeType proxy returns Object , and the byte-budd should use (and possibly unbox) the value inside the intercept method.

With this interceptor (note that the method is also static , so Byte Buddy does not need to add a field to hold an instance of MyServiceInterceptor ), you can simply write:

 public class Main { public static void main(String[] args) throws Exception { Class<? extends MyService> serviceClass = new ByteBuddy() .subclass(MyService.class) .method(ElementMatchers.named("sayFoo").or(ElementMatchers.named("sayBar"))) .intercept(MethodDelegation.to(MyServiceInterceptor.class)) .make() .load(Main.class.getClassLoader(), ClassLoadingStrategy.Default.WRAPPER) .getLoaded(); MyService service = serviceClass.newInstance(); service.sayFoo(); service.sayBar(); } } 

and you will get the desired results. As shown in the example, you can write

 new ByteBuddy(); 

instead

 new ByteBuddy(ClassFileVersion.forCurrentJavaVersion()); 

It is the same.

Byte Buddy does not use any interfaces because it wants to avoid depending on the generated class with any of the Byte Buddy classes. That way, you can reuse the classes that you generate, even loading them with a ClassLoader that is not aware of Byte Buddy dependencies.

+9
source

Source: https://habr.com/ru/post/974422/


All Articles