Dynamically generate proxy classes in Java

  • Tutorial

Probably every java developer will need to use proxy classes sooner or later.
Below the cat are simple examples made using the JDK proxy, cglib, javassist and byte buddy.


image

We set ourselves the simplest task:


  • create proxy class for instance of User class
  • in the proxy class you need to intercept a method called "getName"
  • the output of the intercepted method should be in the upper case

The user class to be experimented with.
public class User implements IUser {
    private final String name;
    public User() {
        this(null);
    }
    public User(String name) {
        this.name = name;
    }
    @Override
    public String getName() {
        return name;
    }
}

1 Standard Tools - JDK proxy


Imports
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;

User user = new User("Вася");
InvocationHandler handler = (proxy, method, args) -> {
    if(method.getName().equals("getName")){
        return ((String)method.invoke(user, args)).toUpperCase();
    }
    return method.invoke(user, args);
};
IUser userProxy = (IUser) Proxy.newProxyInstance(user.getClass().getClassLoader(), User.class.getInterfaces(), handler);
assertEquals("ВАСЯ", userProxy.getName());

The disadvantage is that we can only create a proxy class that implements the interfaces of the User class. That is, you cannot cast a proxy class in User


2 cglib


Imports
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;

User user = new User("Вася");
MethodInterceptor handler = (obj, method ,  args,  proxy) -> {
    if(method.getName().equals("getName")){
        return ((String)proxy.invoke(user, args)).toUpperCase() ;
    }
    return proxy.invoke(user, args);
};
User userProxy = (User) Enhancer.create(User.class, handler);
assertEquals("ВАСЯ", userProxy.getName());

3 javassist


Imports
import javassist.util.proxy.MethodHandler;
import javassist.util.proxy.ProxyFactory;
import javassist.util.proxy.ProxyObject;

User user = new User("Вася");
MethodHandler handler = (self, overridden, forwarder, args) -> {
    if(overridden.getName().equals("getName")){
        return ((String)overridden.invoke(user, args)).toUpperCase();
    }
    return overridden.invoke(user, args);
};
ProxyFactory factory = new ProxyFactory();
factory.setSuperclass(User.class);
Object instance = factory.createClass().newInstance();
((ProxyObject) instance).setHandler(handler);
User userProxy = (User) instance;
assertEquals("ВАСЯ", userProxy.getName());

4 Byte Buddy


Imports
import net.bytebuddy.ByteBuddy;
import net.bytebuddy.implementation.MethodDelegation;
import static net.bytebuddy.matcher.ElementMatchers.named;

User user = new User("Вася");
User userProxy = new ByteBuddy()
    .subclass(User.class)
    .method(named("getName"))
    .intercept(MethodDelegation.to(new MyInterceptor(user)))
    .make()
    .load(User.class.getClassLoader())
    .getLoaded()
    .newInstance();
assertEquals("ВАСЯ", userProxy.getName());

MyInterceptor
public  class MyInterceptor {
    User user;
    public MyInterceptor(User user) {
        this.user = user;
    }
    public String getName() {
        return user.getName().toUpperCase();
    }
}

Productivity, simplicity, modernity - choose for yourself what suits your project more.


For performance comparison, I propose to read the article
Testing the performance of 4 Java runtime code generators: cglib, javassist, JDK proxy & Byte Buddy


Also popular now: