← Назад к вопросам

Что такое прокси в Spring?

2.3 Middle🔥 191 комментариев
#Spring Boot и Spring Data#Spring Framework

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI28 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Прокси в Spring Framework

Прокси (Proxy) — это один из ключевых механизмов Spring, который позволяет добавлять дополнительную функциональность к объектам без их изменения. Это критально важно для реализации AOP, транзакций, кэширования и других cross-cutting concerns.

Что такое прокси-объект?

Прокси — это объект-посредник, который перехватывает вызовы методов оригинального объекта и добавляет дополнительное поведение:

// Оригинальный объект
@Service
public class UserService {
    public void saveUser(User user) {
        System.out.println("Пользователь сохранен");
    }
}

// Spring создает прокси, который выглядит так:
public class UserService$$CGLIBProxy extends UserService {
    @Override
    public void saveUser(User user) {
        // Логирование
        System.out.println("[LOG] Вызов saveUser");
        
        // Транзакция
        beginTransaction();
        
        // Вызов оригинального метода
        super.saveUser(user);
        
        // Завершение транзакции
        commitTransaction();
    }
}

Типы прокси в Spring

1. JDK Dynamic Proxy

Используется для интерфейсов. Работает с любым интерфейсом через Reflection API:

// Интерфейс
public interface UserService {
    void saveUser(User user);
}

// Реализация
@Service
public class UserServiceImpl implements UserService {
    @Override
    public void saveUser(User user) {
        System.out.println("User saved");
    }
}

// Spring создает прокси, который имплементирует UserService
// UserService proxy = new UserServiceImpl(); // Прокси

Как это работает:

InvocationHandler handler = (proxy, method, args) -> {
    System.out.println("До вызова метода");
    Object result = method.invoke(userServiceImpl, args);
    System.out.println("После вызова метода");
    return result;
};

UserService proxy = (UserService) Proxy.newProxyInstance(
    UserService.class.getClassLoader(),
    new Class[]{UserService.class},
    handler
);

Ограничения:

  • Работает только с интерфейсами
  • Не может перехватывать private методы
  • Не может перехватывать static методы

2. CGLIB Proxy (Code Generation Library)

Используется для классов. Создает подкласс оригинального класса:

// Класс без интерфейса
@Service
public class OrderService {
    public void processOrder(Order order) {
        System.out.println("Order processed");
    }
}

// Spring создает подкласс:
// public class OrderService$$CGLIBProxy extends OrderService { ... }

Как это работает:

  • Spring генерирует подкласс в runtime
  • Переопределяет методы для добавления функциональности
  • Требует непустого конструктора

Ограничения:

  • Не может перехватывать private методы
  • Не может перехватывать final методы
  • final классы не могут быть проксированы
  • Медленнее JDK proxy при создании

Механизм AOP с прокси

// Аспект
@Aspect
@Component
public class TransactionAspect {
    
    @Before("execution(* com.example.service.*.*(..))") 
    public void beginTransaction() {
        System.out.println("BEGIN TRANSACTION");
    }
    
    @After("execution(* com.example.service.*.*(..))") 
    public void commitTransaction() {
        System.out.println("COMMIT TRANSACTION");
    }
}

// Сервис
@Service
public class PaymentService {
    public void transfer(User from, User to, double amount) {
        System.out.println("Transferring " + amount);
        from.setBalance(from.getBalance() - amount);
        to.setBalance(to.getBalance() + amount);
    }
}

// Spring создает прокси:
// 1. Перехватывает вызов transfer()
// 2. Вызывает @Before (beginTransaction)
// 3. Вызывает оригинальный метод
// 4. Вызывает @After (commitTransaction)

Использование @Transactional

@Service
public class UserService {
    
    @Transactional  // Spring создает прокси для этого метода
    public void createUser(User user) {
        userRepository.save(user);
        // Если выброс exception — rollback автоматически
    }
}

// Spring создает что-то вроде:
public class UserService$$Proxy {
    public void createUser(User user) {
        Transaction tx = beginTransaction();
        try {
            userService.createUser(user); // оригинальный код
            tx.commit();
        } catch (Exception e) {
            tx.rollback();
            throw e;
        }
    }
}

Кэширование с прокси

@Service
public class UserService {
    
    @Cacheable("users") // Spring создает прокси для кэширования
    public User getUserById(Long id) {
        System.out.println("Querying from database");
        return userRepository.findById(id).orElse(null);
    }
}

// Spring создает прокси, который:
// 1. Проверяет кэш
// 2. Если есть в кэше — возвращает из кэша
// 3. Если нет — вызывает оригинальный метод
// 4. Сохраняет результат в кэш

Проблемы с прокси

Проблема 1: Вызов через this

@Service
public class UserService {
    
    @Transactional
    public void method1() {
        System.out.println("Method 1");
        method2(); // ❌ Не будет использован прокси!
    }
    
    @Transactional
    public void method2() {
        System.out.println("Method 2");
    }
}

// Решение: инжектировать себя или использовать ApplicationContext
@Service
public class UserService {
    
    private final UserService self;
    
    public UserService(UserService self) {
        this.self = self;
    }
    
    @Transactional
    public void method1() {
        System.out.println("Method 1");
        self.method2(); // ✅ Теперь используется прокси
    }
}

Проблема 2: Private методы

@Service
public class UserService {
    
    @Transactional
    public void publicMethod() {
        privateMethod(); // ❌ Прокси не перехватит!
    }
    
    @Transactional
    private void privateMethod() {
        // Эта аннотация не работает
    }
}

Проблема 3: Вызов через интерфейс

// ✅ Правильно
UserService service = applicationContext.getBean(UserService.class);
service.saveUser(user); // Работает с прокси

// ❌ Неправильно
UserService service = new UserService();
service.saveUser(user); // Не работает с прокси

Выбор типа прокси

// Настройка в @EnableAspectJAutoProxy
@Configuration
@EnableAspectJAutoProxy(proxyTargetClass = true) // true = CGLIB, false = JDK
public class AopConfig {
}

// Или через свойства приложения
spring.aop.proxy-target-class=true

Когда использовать:

  • JDK Proxy: есть интерфейс (по умолчанию для интерфейсов)
  • CGLIB: нет интерфейса или нужно проксировать конкретный класс

Таблица сравнения

ХарактеристикаJDK ProxyCGLIB Proxy
Требует интерфейсДаНет
Работает с классамиНетДа
Скорость созданияБыстроМедленнее
Скорость выполненияНемного быстрееНемного медленнее
Перехватывает privateНетНет
Перехватывает finalНетНет
Что такое прокси в Spring? | PrepBro