Динамическое генерирование прокси-классов в Java
Наверно каждому java разработчику рано или поздно потребуется использовать прокси-классы.
Под катом представлены простые примеры, выполненные при помощи JDK proxy, cglib, javassist и byte buddy.
Поставим себе самую простую задачу:
- создать прокси-класс для экземпляра класса 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