Что такое паттерн self-inject?
Что проверяют
Знаете ли вы, что вызов собственного метода мимо прокси не работает, и зачем бин иногда внедряет сам себя. Вопрос не про внедрение зависимостей вообще, а про конкретный обход ограничения прокси в Spring.
Как отвечать
Транзакционность, кэширование и асинхронность работают через прокси: контейнер подменяет бин обёрткой, и поведение добавляется при вызове снаружи. Внутренний вызов идёт по своей же ссылке на объект, прокси в этот момент не участвует, и аннотация на внутреннем методе просто не срабатывает.
Self-inject это приём, когда бин получает ссылку на самого себя, то есть на свой прокси, и внутренние вызовы делает через неё.
@Service
public class ReportService {
private final ReportService self;
public ReportService(@Lazy ReportService self) {
this.self = self;
}
public void processAll(List<Report> reports) {
reports.forEach(self::processOne);
}
@Transactional
public void processOne(Report report) {
report.process();
}
}Отложенное внедрение здесь обязательно, иначе контейнер не соберёт бин, который требует сам себя. Честный ответ добавляет, что приём это признак перегруженного класса: обычное решение вынести метод с аннотацией в отдельный бин, а для явной границы транзакции есть шаблон транзакции.
Где спотыкаются
Называют self-inject любым внедрением через конструктор. Это не так: обычная зависимость это другой бин, а здесь бин ссылается на собственный прокси, и весь смысл в обходе внутреннего вызова.
Ставят транзакционность на приватный метод или на метод, вызываемый изнутри того же класса, и не понимают, почему отката не происходит. Прокси видит только внешние вызовы, а приватные методы не перехватываются вовсе.
Разводят кольца ссылок между бинами и считают, что раз ссылка на себя разрешена, циклы это норма. Самоссылка контейнером поддержана специально, а обычные циклы между разными бинами в современных версиях по умолчанию запрещены и требуют либо отложенной инициализации, либо, что лучше, перепроектирования.