Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to implement a wrapper decorator in Java?

The problem is to create a dynamic enhanced version of existing objects.

I cannot modify the object's Class. Instead I have to:

  • subclass it
  • wrap the existing object in the new Class
  • delegate all the original method calls to the wrapped object
  • implement all methods that are defined by another interface

The interface to add to existing objects is:

public interface EnhancedNode {

  Node getNode();
  void setNode(Node node);

  Set getRules();
  void setRules(Set rules);

  Map getGroups();
  void setGroups(Map groups);

}

With Byte Buddy I managed to subclass and to implement my interface. The problem is the delegation to the wrapped object. The only way to do this that I found is using reflection what is too slow (I have heavy load on the application and performance is critical).

So far my code is:

Class<? extends Node> proxyType = new ByteBuddy()
     .subclass(node.getClass(), ConstructorStrategy.Default.IMITATE_SUPER_TYPE_PUBLIC)
     .method(anyOf(finalNode.getClass().getMethods())).intercept(MethodDelegation.to(NodeInterceptor.class))
     .defineField("node", Node.class, Visibility.PRIVATE)
     .implement(EnhancedNode.class).intercept(FieldAccessor.ofBeanProperty())
     .defineField("groups", Map.class, Visibility.PRIVATE)
     .implement(EnhancedNode.class).intercept(FieldAccessor.ofBeanProperty())
     .defineField("rules", Set.class, Visibility.PRIVATE)
     .implement(EnhancedNode.class).intercept(FieldAccessor.ofBeanProperty())
     .make()
     .load(getClass().getClassLoader(), ClassLoadingStrategy.Default.WRAPPER)
     .getLoaded();
enhancedClass = (Class<N>) proxyType;
EnhancedNode enhancedNode = (EnhancedNode) enhancedClass.newInstance();
enhancedNode.setNode(node);

where Node is the object to subclass/wrap. The NodeInterceptor forwards the invoked methods to the getNode property.

Here the code of the NodeInterceptor:

public class NodeInterceptor {

  @RuntimeType
  public static Object intercept(@Origin Method method,
                               @This EnhancedNode proxy,
                               @AllArguments Object[] arguments)
        throws Exception {
      Node node = proxy.getNode();
      Object res;
      if (node != null) {
          res = method.invoke(method.getDeclaringClass().cast(node), arguments);
      } else {
          res = null;
      }
      return res;
  }
}

Everything is working but the intercept method is too slow, I'm planning to use ASM directly to add the implementation of every method of Node but I hope there is a simpler way using Byte Buddy.

like image 754
Teg Avatar asked Jan 04 '16 16:01

Teg


People also ask

Is a wrapper a decorator?

Wrappers around the functions are also knows as decorators which are a very powerful and useful tool in Python since it allows programmers to modify the behavior of function or class. Decorators allow us to wrap another function in order to extend the behavior of the wrapped function, without permanently modifying it.

What is a wrapper pattern in Java?

A Wrapper class "wraps" an object of another class. It is used to implement a design pattern that has an instance of an object and presents its own interface or behavior to that object, without changing the original class (this is the key point!). These patterns demonstrate delegation.

How do you wrap a method in Java?

wrap(int[] array, int offset, int length) The wrap() method wraps an int array into a buffer. The new buffer will be backed by the given int array; that is, modifications to the buffer will cause the array to be modified and vice versa.


1 Answers

You probably want to use a Pipe rather than the reflection API:

public class NodeInterceptor {

  @RuntimeType
  public static Object intercept(@Pipe Function<Node, Object> pipe,
                                 @FieldValue("node") Node proxy) throws Exception {
      return proxy != null
        ? pipe.apply(proxy);
        : null;
  }
}

In order to use a pipe, you first need to install it. If you have Java 8 available, you can use java.util.Function. Otherwise, simply define some type:

interface Function<T, S> { S apply(T t); }

yourself. The name of the type and the method are irrelevant. The install the type:

MethodDelegation.to(NodeInterceptor.class)
                .appendParameterBinder(Pipe.Binder.install(Function.class));

Are you however sure that the reflection part is the critical point of your application's performance problems? Are you caching the generated classes correctly and is your cache working efficiently? The reflection API is faster than its reputation, especially since use Byte Buddy tends to imply monomorphic call sites.

Finally, some general feedback. You are calling

.implement(EnhancedNode.class).intercept(FieldAccessor.ofBeanProperty())

multiple times. This has no effect. Also, method.getDeclaringClass().cast(node) is not necessary. The reflection API does the cast for you.

like image 185
Rafael Winterhalter Avatar answered Sep 21 '22 03:09

Rafael Winterhalter