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

  • Tutorial

Наверно каждому 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

Поделиться публикацией
Похожие публикации
Ммм, длинные выходные!
Самое время просмотреть заказы на Фрилансим.
Мне повезёт!
Реклама
Комментарии 15
  • 0
    А что для создания проксей используется в таких фреймворках, как Spring, Hibernate?
  • 0
    Отличное начало! Теперь каждый из подходов можно развернуть сильней — получится цикл статей о проксировании.
    • +1

      Похоже что Spring также собирается поддерживать Byte Buddy:
      https://jira.spring.io/browse/SPR-8190

      • 0
        Оно уже достаточно давно висит. И последний пост автора bytebuddy тоже уже давно. Фи гзнает, я движения не особо вижу. На гитхабе надо посмотреть, наверное.
      • 0
        Кто-нибудь может объяснить зачем использовать прокси-классы, если есть наследование?
        Можно ведь создать класс — наследник User, переопределить в нем нужные методы, и вместо прокси-класса использовать этот класс-наследник?
        • 0
          Тут фишка в том что к уже созданному экземпляру мы подвязываем дополнительную логику и делаем это в runtime
          • 0
            Dynamic proxy на то и dynamic, что они создаются на лету, в рантайме. На момент компиляции может не быть информации о том, какой класс мы будем проксировать.
            • 0
              Например на лету создать реализацию интерфейса. Класса как такового нету. Есть только прокси создаваемый из/для интерфейчса.
              • 0

                самый простой пример. Аннотации спринга.
                Ставите над методом @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();
                    }
                
                }
                • 0
                  Не, частый вопрос это когда транзакция в транзакции)
              • 0
                Del
                • 0
                  Наверное если мы делаем динамический прокси, было бы логичнее использовать аргументы хэндлера а не завязываться на глобальный объект. Так например CGLib это будет вот так
                  MethodInterceptor handler = (obj, method ,  args,  proxy) -> {
                      if(method.getName().equals("getName")){
                          return ((String)proxy.invokeSuper(proxy, args)).toUpperCase() ;
                      }
                      return proxy.invokeSuper(proxy, args);
                  };
                  

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