Наверно каждому 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());
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
Комментарии (15)
olegchir
13.02.2018 17:47Отличное начало! Теперь каждый из подходов можно развернуть сильней — получится цикл статей о проксировании.
gkislin
13.02.2018 18:33+1Похоже что Spring также собирается поддерживать Byte Buddy:
https://jira.spring.io/browse/SPR-8190asm0dey
14.02.2018 13:42Оно уже достаточно давно висит. И последний пост автора bytebuddy тоже уже давно. Фи гзнает, я движения не особо вижу. На гитхабе надо посмотреть, наверное.
genew
14.02.2018 11:39Кто-нибудь может объяснить зачем использовать прокси-классы, если есть наследование?
Можно ведь создать класс — наследник User, переопределить в нем нужные методы, и вместо прокси-класса использовать этот класс-наследник?PqDn Автор
14.02.2018 12:51Тут фишка в том что к уже созданному экземпляру мы подвязываем дополнительную логику и делаем это в runtime
arandomic
14.02.2018 12:52Dynamic proxy на то и dynamic, что они создаются на лету, в рантайме. На момент компиляции может не быть информации о том, какой класс мы будем проксировать.
StanislavL
14.02.2018 13:39Например на лету создать реализацию интерфейса. Класса как такового нету. Есть только прокси создаваемый из/для интерфейчса.
ohotNik_alex
14.02.2018 14:36самый простой пример. Аннотации спринга.
Ставите над методом @transactional, а в прокси-классе создается обертка вокруг вызова.
Частый вопрос на собеседовании — сработает ли такая аннотация если вызвать метод из того же класса, где он располагается. ответ — нет, так как произойдет вызов без обертки. чтобы сработал вызов нужно сделать @Autowire класса в себя и вызвать уже через поле.
package ru.incbt.cds.api.rest.function; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; /** * @author Okhonchenko Aleksander * @since 14.02.2018 */ @Service public class App { @Autowired private App app; @Transactional public void transactedMethod() { //some work with db } public void someMethod() { //вызов без обертки transactedMethod(); //вызов с магической оберткой app.transactedMethod(); } }
dmitryk100
14.02.2018 17:13Наверное если мы делаем динамический прокси, было бы логичнее использовать аргументы хэндлера а не завязываться на глобальный объект. Так например CGLib это будет вот так
MethodInterceptor handler = (obj, method , args, proxy) -> { if(method.getName().equals("getName")){ return ((String)proxy.invokeSuper(proxy, args)).toUpperCase() ; } return proxy.invokeSuper(proxy, args); };
DSolodukhin
А что для создания проксей используется в таких фреймворках, как Spring, Hibernate?
arandomic
jdk proxy и cglib
Lure_of_Chaos
еще можно вспомнить javassist
PqDn Автор
В Hibernate переходят на Byte Buddy