Ответ
Spring-прокси — это объекты-заместители, которые Spring создаёт для поддержки сквозной функциональности (AOP), такой как транзакции, кэширование и безопасность.
Принцип работы: Когда вы вызываете метод бина, управление сначала передаётся прокси, который применяет дополнительное поведение (например, открывает транзакцию), а затем делегирует вызов целевому объекту.
Два основных типа прокси в Spring:
- JDK Dynamic Proxy (по умолчанию для бинов с интерфейсами).
- Создаётся во время выполнения на основе интерфейсов бина.
- Требует, чтобы бин реализовывал хотя бы один интерфейс.
- CGLIB Proxy (используется, если интерфейсов нет или задано
proxyTargetClass=true).- Создаёт прокси через наследование от класса бина.
- Не работает для
finalклассов и методов.
Пример с транзакционным прокси:
@Service
public class UserService {
@Transactional // Spring создаст прокси для управления транзакцией
public User createUser(String name) {
// Логика сохранения в БД
return userRepository.save(new User(name));
}
}
Важные нюансы:
- Самовызов (
this.createUser()) внутри класса не проходит через прокси, так как вызывается метод напрямую, а не у прокси-объекта. Для обхода используют инъекцию самого бина (@Autowired private UserService self) илиAopContext.currentProxy(). - Проверить, является ли объект прокси:
AopUtils.isAopProxy(bean).
Ответ 18+ 🔞
Давай разберём эту вашу прокси-махинацию, а то как будто про шпионов речь, а не про код. Весь этот цирк с заместителями, который Spring вытворяет у нас под капотом, пока мы чай пьём.
Вот представь: у тебя есть обычный бин, простой работяга, который должен юзеров в базу пихать. А тут приходит менеджер-прокси, такой хитрожопый посредник, и говорит: «Стоять! Нельзя просто так взять и сохранить. Сначала я транзакцию открою, потом безопасность проверю, может, ещё логи напишу, а уж потом, если всё чики-пуки, ты своё дело делай». И управление через него идёт. Весь этот AOP — это и есть навешивание таких вот надзирателей на твои методы.
А создаёт он этих надзирателей двумя основными способами, тут уже на выбор, как повезёт:
- JDK Dynamic Proxy. Это когда твой бин — хороший мальчик и реализует интерфейс. Тогда Spring, этот хитрый ёж, во время работы приложения на лету генерит прокси-класс, который этот интерфейс и реализует. Вызываешь ты
userService.save(), а на самом деле дергаешь метод у этой сгенерированной мартышлюшки, которая уже потом дергает твой настоящий бин. - CGLIB Proxy. А это уже тяжёлая артиллерия, когда твой класс — упрямый мудак и интерфейсов не признаёт. Тогда Spring наследуется от твоего же класса и создаёт его подкласс-прокси. По сути, делает его незаконнорожденного ребёнка, который все папины методы переопределяет, чтобы свою дичь вставить. Но если у папы (твоего класса) методы
final— всё, пиши пропало, наследоваться не получится, ебаный рот этого CGLIB!
Вот смотри, как это выглядит в коде, когда всё прилично:
@Service
public class UserService {
@Transactional // Вот эта аннотация — сигнал для Spring: «Эй, надень на этот метод наручники с транзакцией!»
public User createUser(String name) {
// Какая-то логика
return userRepository.save(new User(name));
}
}
Вызываешь ты userService.createUser("Вася"). А на самом деле дергаешь не самого UserService, а его прокси-клона. Клон сначала говорит: «Так, ща транзакцию откроем...», делает свои дела, потом вызывает настоящий метод createUser у оригинального бина, и только потом коммитит или откатывает всё. Красота!
Но есть один пиздецкий подводный камень, о который все бьются! Самовызов.
Представь внутри этого же класса другой метод:
public void someInternalMethod() {
this.createUser("Петя"); // ВНУТРЕННИЙ вызов!
}
Вот тут-то и жопа. Потому что this — это ссылка на настоящий, оригинальный объект, а не на его прокси-обёртку! И когда ты зовёшь this.createUser(), ты обходишь всю эту охрану с транзакциями стороной, вызываешь метод напрямую. Прокси остаётся в стороне и плачет. Транзакция не откроется, и будет тебе боль и страдание.
Как с этим бороться, спросишь? Есть два грязных хакерских способа, на которые идёт Spring-сообщество:
- Инжектнуть самого себя (звучит как шизофрения, да):
@Autowired private UserService self. И вызывать черезself.createUser()— это уже вызов через прокси. - Использовать
AopContext.currentProxy()— это как крикнуть: «Эй, а где мой прокси-двойник?», и он тебе его подаст. Способ так себе, требует дополнительных настроек.
А если хочешь на глазок определить, имеешь дело с оригиналом или уже с подставной уткой, есть утилитка: AopUtils.isAopProxy(bean). Вернёт true — поздравляю, ты общаешься с прокси. Вернёт false — перед тобой голый, ничем не прикрытый бин, со всеми вытекающими.
Вот такая, блядь, матрешка получается. Один бин в другом, поверх ещё прокси, и всё это крутится, чтобы твои транзакции не разъебались. Ёперный театр!