Создается ли новый класс при проксировании объекта в Java?

«Создается ли новый класс при проксировании объекта в Java?» — вопрос из категории Паттерны, который задают на 10% собеседований Java Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

Да, при динамическом проксировании создается новый класс во время выполнения.

1. Механизм создания прокси-класса:

// 1. Исходный интерфейс
interface DatabaseService {
    void save(String data);
    String findById(int id);
}

// 2. Реализация
class DatabaseServiceImpl implements DatabaseService {
    public void save(String data) {
        System.out.println("Saving: " + data);
    }
    public String findById(int id) {
        return "Data_" + id;
    }
}

// 3. Создание прокси с логированием
DatabaseService proxy = (DatabaseService) Proxy.newProxyInstance(
    DatabaseService.class.getClassLoader(),
    new Class[]{DatabaseService.class},
    (p, method, args) -> {
        long start = System.nanoTime();
        System.out.println("[LOG] Start: " + method.getName());

        try {
            // Вызов оригинального метода
            return method.invoke(new DatabaseServiceImpl(), args);
        } finally {
            long time = System.nanoTime() - start;
            System.out.println("[LOG] End: " + method.getName() + ", time: " + time + "ns");
        }
    }
);

// 4. Проверка типа
System.out.println("Proxy class: " + proxy.getClass().getName());
// Вывод: com.sun.proxy.$Proxy0
System.out.println("Is DatabaseService: " + (proxy instanceof DatabaseService)); // true
System.out.println("Is DatabaseServiceImpl: " + (proxy instanceof DatabaseServiceImpl)); // false

2. Что происходит под капотом:

  1. JVM генерирует байткод нового класса.
  2. Класс реализует указанные интерфейсы.
  3. Каждый метод делегирует вызов InvocationHandler.invoke().
  4. Класс кешируется для повторного использования.

3. CGLIB для проксирования классов:

// Пример с Spring (упрощенно)
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(UserService.class); // Наследуем от КЛАССА
enhancer.setCallback(new MethodInterceptor() {
    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        System.out.println("Before: " + method.getName());
        return proxy.invokeSuper(obj, args);
    }
});

UserService cglibProxy = (UserService) enhancer.create();
// cglibProxy - подкласс UserService

4. Сравнение подходов:

Критерий Java Dynamic Proxy CGLIB/ByteBuddy
Тип цели Только интерфейсы Классы (не final)
Наследование Реализация интерфейсов Наследование класса
Производительность Быстрее создание, медленнее вызов Медленнее создание, быстрее вызов
Ограничения Только public методы Не работает с final классами/методами

5. Практические применения:

  • Транзакционность: @Transactional в Spring
  • Ленивая загрузка: Hibernate lazy loading
  • Кеширование: @Cacheable
  • Валидация: Перехват вызовов для проверки аргументов
  • Метрики: Замер времени выполнения методов