0

I am trying to replace my CGLib inteceptor with a ByteBuddy implementation but it's not working. In CGLIB I catch every method call and send it to one method in the ObjectProxy. I have readed this and this and a lot's of others but it still don't work.

Here is my draft code: TestByteBuddy.java

import static java.lang.ClassLoader.getSystemClassLoader;
import java.util.logging.Level;
import java.util.logging.Logger;
import net.bytebuddy.ByteBuddy;
import net.bytebuddy.description.modifier.Visibility;
import net.bytebuddy.dynamic.loading.ClassLoadingStrategy;
import net.bytebuddy.implementation.MethodDelegation;
import net.bytebuddy.matcher.ElementMatchers;


/**
 *
 * @author Marcelo D. Ré {@literal <marcelo.re@gmail.com>}
 */
public class TestByteBuddy {
    private final static Logger LOGGER = Logger.getLogger(TestByteBuddy.class .getName());
    static {
        if (LOGGER.getLevel() == null) {
            LOGGER.setLevel(Level.INFO);
        }
    }

    public TestByteBuddy() {
        System.out.println("Inite BB test...");
    }
    
    public void test() {
        
        try {
            Class<?> poClass = new ByteBuddy()
                    .subclass(BBFoo.class)
                    .defineField("___ogm___interceptor", ObjectProxy.class, Visibility.PUBLIC)
                    .implement(ITest.class)
                    .method(ElementMatchers.any()) // isDeclaredBy(ITest.class)
                    .intercept(MethodDelegation.toField("___ogm___interceptor"))   // MethodDelegation.to(bbi)
                    .make()
                    .load(getSystemClassLoader(), ClassLoadingStrategy.Default.WRAPPER)
                    .getLoaded();

            // crear la instancia
            BBFoo po = (BBFoo) poClass.newInstance();
            
            // agregar el interceptor
            ObjectProxy bbi = new ObjectProxy();
            bbi.setInternalState("primer objeto");
            poClass.getField("___ogm___interceptor").set(po, bbi);
            
            
            // crear la instancia
            BBFoo po2 = (BBFoo) poClass.newInstance();
            // agregar el interceptor
            ObjectProxy bbi2 = new ObjectProxy();
            bbi2.setInternalState("segundo objeto");
            poClass.getField("___ogm___interceptor").set(po2, bbi2);
            
            // testear los objetos
         
            System.out.println("*************************************");
            System.out.println("invocar a setTestString....");
            po.setTestString("Objeto 1");
            po.sayHello();

            System.out.println(""+po.testString);
            ((ITest)po).testCall();
            ((ITest)po).incValCall();
            System.out.println("ObjectProxy InternalState: "+((ITest)po).getInternalState());
            
            System.out.println("*************************************");
            System.out.println("invocar a setTestString....");
            po2.setTestString("Objeto 2");
            
            po2.sayHello();
            System.out.println(""+po.testString);
         
            ((ITest)po2).testCall();
            ((ITest)po2).incValCall();
            System.out.println("ObjectProxy InternalState: "+((ITest)po2).getInternalState());
            System.out.println("*************************************");
            
        } catch (SecurityException ex) {
            Logger.getLogger(TestByteBuddy.class.getName()).log(Level.SEVERE, null, ex);
        } catch (NoSuchFieldException ex) {
            Logger.getLogger(TestByteBuddy.class.getName()).log(Level.SEVERE, null, ex);
        } catch (InstantiationException ex) {
            Logger.getLogger(TestByteBuddy.class.getName()).log(Level.SEVERE, null, ex);
        } catch (IllegalAccessException ex) {
            Logger.getLogger(TestByteBuddy.class.getName()).log(Level.SEVERE, null, ex);
        }
        
    }
        
    public static void main(String[] args) {
        new TestByteBuddy().test();
    }
}

ObjectProxy.java

import java.lang.reflect.Method;
import java.util.logging.Level;
import java.util.logging.Logger;
import net.bytebuddy.implementation.bind.annotation.AllArguments;
import net.bytebuddy.implementation.bind.annotation.Origin;
import net.bytebuddy.implementation.bind.annotation.RuntimeType;
import net.bytebuddy.implementation.bind.annotation.SuperMethod;
import net.bytebuddy.implementation.bind.annotation.This;

/**
 *
 * @author Marcelo D. Ré {@literal <marcelo.re@gmail.com>}
 */
public class ObjectProxy implements ITest {
    private final static Logger LOGGER = Logger.getLogger(ObjectProxy.class .getName());
    static {
        if (LOGGER.getLevel() == null) {
            LOGGER.setLevel(Level.INFO);
        }
    }

    String internalState; 

    public ObjectProxy() {
    
    }
    
    @Override
    public String getInternalState() {
        return internalState;
    }
    
    @Override
    public void setInternalState(String internalState) {
        this.internalState = internalState;
    }
    
    @Override
    public void testCall() {
        System.out.println("ObjectProxy TestCall");
    }

    @Override
    public int incValCall() {
        System.out.println("ObjectProxy  incValCall");
        return 0;
    }
    
    // a este método se llama cuando se usa MethodDeletation
    @RuntimeType
    public Object intercept(@This Object self, 
                            @Origin Method method, 
                            @AllArguments Object[] args, 
                            @SuperMethod Method superMethod) throws Throwable {
        Object res = null;
        System.out.println(">>>>>>>>> Intercepted: "+method.getName());
        switch(method.getName()){
            case "testCall": 
                System.out.println("intercept TestCall");
                this.testCall();
                break;
        
            case "incValCall": 
                System.out.println("intercept incValCall");
                this.incValCall();
                break;
                
            default: 
                System.out.println("intercept default: "+self.getClass().toString() +" : "+ superMethod.getName()+" : "+method.getName());
                res = superMethod.invoke(self, args);
            }
            System.out.println("^^^^^^^^^^^^^^^^");
        return res;
     }

ITest.java

public interface ITest {
    public void testCall();
    public int incValCall();
    
    public String getInternalState();
    public void setInternalState(String internalState) ;
    
}

BBFoo.java

public class BBFoo {
    private final static Logger LOGGER = Logger.getLogger(BBFoo.class .getName());
    static {
        if (LOGGER.getLevel() == null) {
            LOGGER.setLevel(Level.INFO);
        }
    }
    
    public String testString;
    private int val = 0;
    
    public void sayHello(){
        System.out.println("Hola mundo! soy "+this.testString);
    }
    
    public int incVal() {
        val++;
        return val;
    }
    
    public void setTestString(String s) {
        testString = s;
    }
    
    public String getTestString() {
        return testString;
    }
}

And this is the output that I get: enter image description here


First edition after the reply of Rafael

As suggested I have re-edited the TestByteBuddy.java and now it look like this:


import static java.lang.ClassLoader.getSystemClassLoader;
import java.util.logging.Level;
import java.util.logging.Logger;
import net.bytebuddy.ByteBuddy;
import net.bytebuddy.description.modifier.Visibility;
import net.bytebuddy.dynamic.loading.ClassLoadingStrategy;
import net.bytebuddy.implementation.MethodDelegation;
import net.bytebuddy.matcher.ElementMatchers;

/**
 *
 * @author Marcelo D. Ré {@literal <marcelo.re@gmail.com>}
 */
public class TestByteBuddy {
    private final static Logger LOGGER = Logger.getLogger(TestByteBuddy.class .getName());
    static {
        if (LOGGER.getLevel() == null) {
            LOGGER.setLevel(Level.INFO);
        }
    }

    public TestByteBuddy() {
        System.out.println("Inite BB test...");
    }
    
    private <T> T getProxyIntance(Class<T> c) {
        T po = null;
        try {
            Class<?> poClass = new ByteBuddy()
                    .subclass(c)
                    .defineField("___ogm___interceptor", ObjectProxy.class, Visibility.PUBLIC)
                    .implement(ITest.class)
                    .method(ElementMatchers.any()) // isDeclaredBy(ITest.class)
                    .intercept(MethodDelegation   // This.class,Origin.class,AllArguments.class,SuperMethod.class)
                                .withDefaultConfiguration() //.withBinders(TargetMethodAnnotationDrivenBinder.ParameterBinder.DEFAULTS)
                                .filter(ElementMatchers.named("intercept"))
                                .toField("___ogm___interceptor"))   // MethodDelegation.to(bbi)
                    .make()
                    .load(getSystemClassLoader(), ClassLoadingStrategy.Default.WRAPPER)
                    .getLoaded();

            // crear la instancia
            po = (T) poClass.newInstance();
            
            // agregar el interceptor
            ObjectProxy bbi = new ObjectProxy();
            poClass.getField("___ogm___interceptor").set(po, bbi);
            
        } catch (NoSuchFieldException | InstantiationException | IllegalAccessException ex) {
            Logger.getLogger(TestByteBuddy.class.getName()).log(Level.SEVERE, null, ex);
        }
        return po;
    }
    
    public void test() {
        
        try {


            // crear la instancia
            BBFoo po = this.getProxyIntance(BBFoo.class);
            
            // crear la instancia
            BBFoo po2 = this.getProxyIntance(BBFoo.class);
            
            
            // testear los objetos
            System.out.println("*************************************");
            System.out.println("invocar a setTestString....");
            po.setTestString("Objeto 1");
            po.sayHello();

            System.out.println(""+po.testString);
            ((ITest)po).testCall();
            int i = ((ITest)po).incValCall();
            System.out.println("ObjectProxy InternalState: "+((ITest)po).getInternalState());
            
            System.out.println("*************************************");
            System.out.println("invocar a setTestString....");
            po2.setTestString("Objeto 2");
            
            po2.sayHello();
            System.out.println(""+po.testString);
         
            ((ITest)po2).testCall();
            i = ((ITest)po2).incValCall();
            System.out.println("ObjectProxy InternalState: "+((ITest)po2).getInternalState());
            System.out.println("*************************************");
          
            BBFooEx poEx = this.getProxyIntance(BBFooEx.class);
            
            BBFooExEx poExEx = this.getProxyIntance(BBFooExEx.class);
            
        } catch (SecurityException ex) {
            Logger.getLogger(TestByteBuddy.class.getName()).log(Level.SEVERE, null, ex);
        } 
        
    }
        
    public static void main(String[] args) {
        new TestByteBuddy().test();
    }
}

and I have to add the @IgnoreForBinding annotation at every method in ObjectProxy.java except intercept.

But now, if I add this two more class, it fail: BBFooEx.java

public class BBFooEx extends BBFoo {
    private final static Logger LOGGER = Logger.getLogger(BBFooEx.class .getName());
    static {
        if (LOGGER.getLevel() == null) {
            LOGGER.setLevel(Level.INFO);
        }
    }
    private String svex;
    
    
    public BBFooEx() {
        this(null, "default");
    }
    
    public BBFooEx(String s, String svex) {
        super(s);
        this.svex = svex;
    }
    
    public HashMap<String, String> hmString;
    public HashMap<String, BBFooEx> ohmSVE;
    
    public void initHashMapString() {
        this.hmString = new HashMap<>();
        this.hmString.put("hmString 1", "hmString 1");
        this.hmString.put("hmString 1", "hmString 2");
        this.hmString.put("hmString 1", "hmString 3");
    }
    
    
}

BBFooExEx.java

public class BBFooExEx extends BBFooEx {
    private final static Logger LOGGER = Logger.getLogger(BBFooExEx.class .getName());
    static {
        if (LOGGER.getLevel() == null) {
            LOGGER.setLevel(Level.INFO);
        }
    }

    public BBFooExEx() {
        ohmSVE = new HashMap<>();
        initHashMapString();
    }
    
    
}

When ByteBuddy try to instantiate the BBFooExEx it fail with a NullPointerException:

Exception in thread "main" java.lang.NullPointerException
    at bb.BBFooExEx$ByteBuddy$bUrgo9zW.initHashMapString(Unknown Source)
    at bb.BBFooExEx.<init>(BBFooExEx.java:26)
    at bb.BBFooExEx$ByteBuddy$bUrgo9zW.<init>(Unknown Source)
    at java.base/jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
    at java.base/jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
    at java.base/jdk.internal.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
    at java.base/java.lang.reflect.Constructor.newInstance(Constructor.java:490)
    at java.base/java.lang.Class.newInstance(Class.java:584)
    at bb.TestByteBuddy.getProxyIntance(TestByteBuddy.java:50)
    at bb.TestByteBuddy.test(TestByteBuddy.java:110)

Marcelo D. Ré
  • 181
  • 2
  • 10

1 Answers1

0

You are probably targeting the wrong method sometimes. Use: MethodDelegation.withDefaultConfiguration() .filter(named("intercept")) .toField(...)

This way, you assure that Byte Buddy only attempts the method you are aiming for.

Rafael Winterhalter
  • 42,759
  • 13
  • 108
  • 192
  • Thanks Rafael! but now I get an exception: Exception in thread "main" java.lang.IllegalArgumentException: None of [public java.lang.Object bb.ObjectProxy.intercept(java.lang.Object,java.lang.reflect.Method,java.lang.Object[],java.lang.reflect.Method) throws java.lang.Throwable] allows for delegation from public abstract void bb.ITest.setInternalState(java.lang.String) at net.bytebuddy.implementation.bind.MethodDelegationBinder$Processor.bind(MethodDelegationBinder.java:1099) at net.bytebuddy.implementation.MethodDelegation$Appender.apply(MethodDelegation.java:1344) – Marcelo D. Ré Apr 14 '22 at 13:27
  • It's seems to it's necessary to mark every ObjectProxy method with @IgnoreForBinding to get it work. With that, the above example start to work but something is still wrong in real life. – Marcelo D. Ré Apr 18 '22 at 14:22