TGViewer
Библиотека джависта | Java, Spring, Maven, Hibernate Библиотека джависта | Java, Spring, Maven, Hibernate @javaproglib · 22K subscribers
Post #7443 2.64K
✏️ Spring Proxying и self-invocation

Spring AOP использует прокси. То есть вместо настоящего бина в контейнер помещается его обёртка. Эта обёртка перехватывает вызовы и применяет к ним различные действия, такие как логирование, управление транзакциями, проверка безопасности или другие, заданные пользователем.

Способ создания такой обёртки выбирается автоматически, но у разных способов есть свои особенности и ограничения.

🔵 JDK Dynamic Proxy vs CGLIB

JDK proxy работает только с интерфейсами. Он создаёт объект, который реализует тот же интерфейс, и все вызовы идут через специальный обработчик (InvocationHandler). Внутри есть оригинальный объект, но снаружи виден только прокси.

CGLIB работает по-другому: он создаёт подкласс вашего класса прямо во время работы программы и переопределяет методы, вставляя в них перехват. Поэтому CGLIB не может работать с final-методами и классами, т.к. подкласс не может переопределить final-метод.

Начиная с Spring Boot 2.0, CGLIB используется по умолчанию даже для классов с интерфейсами (spring.aop.proxy-target-class=true). Раньше, если был интерфейс, использовался JDK proxy.

🔵 Self-invocation

Тут всё становится не так очевидно. Разберём такой код:

@Service
public class OrderService {

public void placeOrder() {
// какая-то логика
sendNotification();
}

@Async
public void sendNotification() {
// должно выполняться асинхронно
}
}


sendNotification() помечен аннотацией @Async, но при вызове из placeOrder() он будет выполняться синхронно. Потому что placeOrder() вызывает this.sendNotification(), то есть напрямую метод оригинального объекта, минуя прокси. Перехватывать нечего, аспект не срабатывает.

То же самое с @Transactional, @Cacheable, любым AOP-аспектом.

🔵 Как это исправить

1. Самое простое и правильное решение — вынести метод в отдельный бин. Тогда вызов пойдёт через прокси другого бина, и перехват сработает.

2. Можно получить прокси самого себя через контекст:

@Service
public class OrderService {

@Autowired
private ApplicationContext context;

public void placeOrder() {
context.getBean(OrderService.class).sendNotification();
}

@Async
public void sendNotification() { ... }
}


3. Использовать AopContext.currentProxy():
((OrderService) AopContext.currentProxy()).sendNotification();


Для этого потребуется @EnableAspectJAutoProxy(exposeProxy = true). Выглядит не очень красиво, но иногда оправдано, если рефакторинг слишком сложен.

4. Перейти от Spring AOP к AspectJ с compile-time или load-time weaving. Тогда аспекты вшиваются прямо в байткод, прокси не нужен, и self-invocation работает как надо. Но это уже совсем другая история, требующая более сложной настройки.

🔵 Почему это важно

Ошибки, связанные с self-invocation, не всегда легко воспроизвести. В тестах, где бин заменяется моком, поведение может быть другим. А в интеграционных тестах с поднятым контекстом – проявляются.

Без понимания, как работает проксирование, сложно понять причину проблемы, потому что код выглядит правильно, аннотации на месте, и ошибок компиляции нет.

══════ Навигация ══════
Вакансии • Задачи • Собесы

🐸 Библиотека джависта

#CoreJava
  • 👍 15
  • ❤ 6
  • 🔥 3
More from @javaproglib
  1. Oct 17, 2026☕️ Скользящие окна в Stream без ручного буфера Нужно сравнить соседние измерения или обраб…
  2. Oct 16, 2026🧑‍💻 Какие навыки помогают вам в разработке в 2026 году? Что вы чаще используете в работе…
  3. Oct 16, 2026🧩 Неизменяемый список всё ещё может измениться Collections.unmodifiableList() запрещает м…
  4. Oct 15, 2026💡 Logpoints: логи без правки кода и нового деплоя Чтобы увидеть значение переменной, необ…
  5. Oct 14, 2026Post #7897
  6. Oct 14, 2026Почему разработчик планирует отдавать больше задач Claude Винсент Шмальбах описал план пер…
Threads Profile ViewerView any public Threads profile without an account.Open ThreadLook →Writing with AI? Make it sound human.Metric37 rewrites AI drafts so they read naturally. Free AI detector, 1,500 words free.Try Metric37 →