Создает ли Java Proxy объект того же типа, что и исходный?

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

Ответ

Нет, Java Dynamic Proxy создает объект другого типа.

1. Как работает java.lang.reflect.Proxy:

  • Создает новый класс во время выполнения, который реализует те же интерфейсы, что и оригинальный объект.
  • Этот прокси-класс не наследует от класса оригинального объекта.
  • Все вызовы методов перехватываются InvocationHandler.

2. Пример:

interface UserService {
    String getUserName(int id);
    void deleteUser(int id);
}

class UserServiceImpl implements UserService {
    public String getUserName(int id) {
        return "User_" + id;
    }
    public void deleteUser(int id) {
        System.out.println("Deleting user " + id);
    }
}

// Создание прокси
UserService realService = new UserServiceImpl();

UserService proxy = (UserService) Proxy.newProxyInstance(
    UserService.class.getClassLoader(),
    new Class[]{UserService.class}, // Только интерфейсы!
    new InvocationHandler() {
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            // Логирование до вызова
            System.out.println("Calling method: " + method.getName());

            // Вызов оригинального метода
            Object result = method.invoke(realService, args);

            // Логирование после вызова
            System.out.println("Method completed");
            return result;
        }
    }
);

// proxy - это НЕ UserServiceImpl, а объект сгенерированного класса
System.out.println(proxy.getClass()); // com.sun.proxy.$Proxy0
System.out.println(proxy instanceof UserServiceImpl); // false
System.out.println(proxy instanceof UserService); // true

3. Ограничения Dynamic Proxy:

  • Работает только с интерфейсами.
  • Не может перехватывать вызовы final методов.
  • Не проксирует вызовы внутри класса (self-invocation).

4. Альтернативы для классов:

  • CGLIB/ByteBuddy: Создают подкласс, переопределяя методы.
  • Spring AOP: Использует CGLIB по умолчанию для классов без интерфейсов.

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

  • Транзакции (Spring @Transactional)
  • Кеширование
  • Логирование
  • Безопасность (проверка прав доступа)