Search code examples
javaspringaspectjcglibbyte-buddy

How to track the state of a POJO using code generation


We looking for a solution to track the state of a client POJO instance in a performant manner. What we expect is: every time a change is made on a POJO this state is made by using setters. We created an OGNL-based watching / event-bus and when change is made we will send proper OgnlChangeEvent to our event-bus.

So far we looked into AspectJ / cglib / object graph Diff solution but they all occupied too much CPU time. Our current solution is based on Spring MethodInterceptor and we are creating a new Proxy instance every time a Getter method is invoked.

At this point we are starting to look at code generation solutions and we stumbled on Byte Buddy. Is this direction is the right way to do so? Can we generate a new Class that will extend our client POJO State and notify about its OGNL prefix until setter method is invoked?


Solution

  • Byte Buddy is a code generation tool and it is of course possible to implement such a solution. For creating a class that intercepts a setter, you would write code like:

    new ByteBuddy()
      .subclass(UserPojo.class)
      .method(ElementMatchers.isSetter())
      .intercept(MethodDelegation.to(MyInterceptor.class)
                 .andThen(SuperMethodCall.INSTANCE)
      .make();
    

    Where you would write an interceptor like this:

    public class MyInterceptor {
      public static void intercept(Object value) {
        // business logic comes here
      }
    }
    

    This way, you can add some code every time a setter is invoked which is triggered before the original code. You can also overload the intercept method with all primitive types to avoid the boxing for the argument. Byte Buddy figures out what to do for you.

    I am however confused what you mean by performant. The above code turns out to me the same as creating a class like:

    class UserClass {
      String value;
      void setValue(String value) {
        this.value = value;
      }
    }
    
    class InstrumentedUserClass extends UserClass {
      @Override
      void setValue(String value) {
        MyInterceptor.intercept(value);
        super.setValue(value);
      }
    }
    

    The performance is mainly influenced by the performance of what you do within the intercept method.

    Finally, I do not understand how cglib does not work for you but using Spring - which is build on top of cglib - does work. I suspect that there is some problem with your interception logic you should look into.