habrahabr

Динамическое генерирование прокси-классов в 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