Динамическое генерирование прокси-классов в Java

Наверно каждому java разработчику рано или поздно потребуется использовать прокси-классы.
Под катом представлены простые примеры, выполненные при помощи JDK proxy, cglib, javassist и byte buddy.


image


Поставим себе самую простую задачу:


  • создать прокси-класс для экземпляра класса User
  • в прокси-классе необходимо перехватить метод с названием «getName»
  • результат вывода перехваченного метода должен быть в upper case


Класс пользователя, над которым будем ставить эксперименты
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 Стандартные средства — JDK proxy


Импорты
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());


Недостаток, мы можем создать прокси-класс только который реализует интерфейсы класса User. Тоесть кастить прокси-класс в User нельзя


2 cglib


Импорты
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


Импорты
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


Импорты
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();
    }
}


Производительность, простота, современность — выбирайте для себя то, что больше подходит вашему проекту.


Для сравнения производительности предлагаю ознакомится со статьей
Testing the performance of 4 Java runtime code generators: cglib, javassist, JDK proxy & Byte Buddy

© Habrahabr.ru