Search code examples
javabyte-buddy

ByteBuddy: How to implement field access interceptor?


I'am trying to make a OGM to translate object to Vertex for the OrientDB. Currently i'am using GCLib but i read that ByteBuddy could implements two critical things that if work, it will improve the OGM speed.

  1. Could ByteBuddy implement field access control? I read the doc but it's not clear or I do not understand it.

  2. Dinamically add default empty constructor.

The current problem is this: We do not know the class definition that will be passed as a parameter. The idea is to redefine the class and implement the empty constructor if it not have one, add a field named __BB__Dirty to set the object as dirty if an assign operation was detected and force the implementation of an interface to talk with the object.

Example: A generic class:

public class Example {
   int i = 0;
   String stringField;

   public Example(Strinf s) {
       stringField = s;
   }

   public void addToI(){
       i++;
   }
}

Now we have an interface like this:

public interface DirtyCheck {
    public boolean isDirty();
}

So, I want to force the Example class to implement the interface, the method isDirty(), a field to work on and a default contructor so the class should be translated to:

public class Example implements DirtyCheck {
   int i = 0;
   String stringField;

   boolean __BB__dirty = false;

   public Example() {

   }

   public Example(Strinf s) {
       stringField = s;
   }

   public void addToI(){
       i++;
   }

   public boolean isDirty() {
       return this.__BB__dirty;
   }
}

and the some magically assigner so if any field (except __BB__dirty) is modified, the __BB__dirty field is set to True;

I have tried the first part of this but I fail :(

...
ByteBuddyAgent.install();

Example ex = new ByteBuddy()
                .redefine(Example.class)
                .defineField("__BB__Dirty", boolean.class, Visibility.PUBLIC)
                .make()
                .load(Example.class.getClassLoader(), ClassReloadingStrategy.fromInstalledAgent())
                .getLoaded().newInstance();
 ....

 ex.addToI();    // <--- this should set __BB__dirty to true since it
                 //      assign a value to i.

But i get this error:

Exception in thread "main" java.lang.UnsupportedOperationException: class redefinition failed: attempted to change the schema (add/remove fields)
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:297)
at net.bytebuddy.dynamic.loading.ClassReloadingStrategy.load(ClassReloadingStrategy.java:173)
at net.bytebuddy.dynamic.DynamicType$Default$Unloaded.load(DynamicType.java:4350)
at Test.TestBB.<init>(TestBB.java:33)
at Test.TestBB.main(TestBB.java:23)

I'am stuck in the very first stage to solve the problem with BB. Thanks


Solution

  • The Java virtual machine does not support changing the layout of classes that are already loaded when redefining a class. This is not a limitation of Byte Buddy but the VM implementation.

    In order to do what you want, you should look at the AgentBuilder API which allows you to modify classes before they are loaded. Creating an agent does however require you to add it explicitly as an agent on startup (opposed to adding the library to the class path.

    You can implement the interface by calling:

    .implement(DirtyCheck.class).intercept(FieldAccessor.of("__dirty__");
    

    You can also add a default constructor by simply defining one:

    .defineConstructor(Visibility.PUBLIC).intercept(SuperMethodCall.INSTANCE)
    

    The latter definition requires the super class to define a default constructor.