Spring
Фреймворк Spring — IoC-контейнер, внедрение зависимостей, скоупы и жизненный цикл бинов.
6 вопросов
JuniorТеорияОчень частоЧто такое IoC-контейнер Spring и внедрение зависимостей?
Что такое IoC-контейнер Spring и внедрение зависимостей?
Inversion of control означает, что объекты создаёт и связывает фреймворк, а не ваш код. IoC-контейнер Spring (ApplicationContext) читает определения бинов, создаёт их и поставляет каждому бину его зависимости — это и есть внедрение зависимостей, через конструктор, сеттер или поле. То есть класс объявляет, что ему нужно, а контейнер это предоставляет, вместо того чтобы класс сам вызывал new для зависимостей.
Типичные ошибки
- ✗Путать inversion of control с разворотом порядка вызовов методов
- ✗Думать, что внедрение зависимостей копирует бины, а не связывает общие ссылки
- ✗Считать, что контейнер работает только на компиляции и без роли в рантайме
Уточняющие вопросы
- →В чём компромиссы внедрения через конструктор против внедрения в поле?
- →Как контейнер выбирает, какой бин внедрить, когда типу подходят несколько?
JuniorТеорияЧастоЧто делают стереотипы @Component, @Service, @Repository и @Controller?
Что делают стереотипы @Component, @Service, @Repository и @Controller?
Все четыре помечают класс как бин под управлением Spring, который component scanning находит и регистрирует в контейнере; @Component — общая форма, а три остальные — её специализации. @Service — просто смысловая метка для бизнес-логики, @Repository вдобавок транслирует исключения персистентности в иерархию DataAccessException Spring, а @Controller помечает веб-обработчик, на методы которого маршрутизируются запросы.
Типичные ошибки
- ✗Считать, что
@Repositoryи@Serviceотличаются только названием, без добавленного поведения - ✗Ожидать, что стереотип задаёт скоуп бина
- ✗Забывать, что класс вне сканируемых пакетов не регистрируется, как бы он ни был аннотирован
Уточняющие вопросы
- →Какое дополнительное поведение даёт
@Repository, которого нет у@Service? - →Почему аннотированный класс вне сканируемых базовых пакетов не регистрируется как бин?
MiddleТеорияЧастоЧто такое Spring AOP и как он применяет сквозную функциональность вроде логирования?
Что такое Spring AOP и как он применяет сквозную функциональность вроде логирования?
Аспектно-ориентированное программирование выносит сквозную функциональность — логирование, метрики, безопасность, транзакции — из бизнес-кода в аспект: advice (код, который выполнится) плюс pointcut (место, где он выполнится). Spring AOP работает через прокси: на старте контейнер оборачивает целевой бин в JDK dynamic proxy или в подкласс CGLIB, и advice срабатывает на вызовах, приходящих через этот прокси. Значит, перехватываются только внешние вызовы бинов Spring, на уровне методов.
Типичные ошибки
- ✗Считать, что Spring AOP вплетает байткод, а не оборачивает бин в прокси
- ✗Ожидать, что аспект сработает на вызов бином собственного метода
- ✗Полагать, что аспект может советовать объект, созданный через
newвне контейнера
Уточняющие вопросы
- →Когда Spring выбирает прокси-подкласс CGLIB вместо JDK dynamic proxy?
- →Почему аспект никогда не срабатывает на
private-методе советуемого бина?
MiddleТеорияЧастоКакие у Spring скоупы бинов и потокобезопасны ли синглтоны?
Какие у Spring скоупы бинов и потокобезопасны ли синглтоны?
Скоуп управляет тем, сколько экземпляров создаёт контейнер и как долго они живут: singleton (по умолчанию) — один общий экземпляр на контейнер; prototype — новый экземпляр на каждое внедрение или запрос; request и session — один на HTTP-запрос или сессию. Стандартный singleton сам по себе НЕ потокобезопасен — скоуп лишь управляет числом экземпляров. Если singleton держит изменяемое состояние, конкурентные запросы могут его испортить; держите синглтоны без состояния.
Типичные ошибки
- ✗Считать, что контейнер делает singleton-бины потокобезопасными автоматически
- ✗Думать, что скоуп по умолчанию
prototype, а неsingleton - ✗Полагать, что N запросов к эндпоинту создают N экземпляров singleton
Уточняющие вопросы
- →Сколько экземпляров бина обслужат десять конкурентных запросов к контроллеру со скоупом singleton?
- →Когда внедрение
prototype-бина в singleton не даёт вам новый экземпляр?
MiddleДизайнЧастоНа ревью дизайна коллега защищает @Autowired на приватных полях: короче, новую зависимость можно добавить, не трогая конструктор, и в production это уже работает. На ревью — класс @Service с семью внедрёнными зависимостями, две из которых нужны одному редко вызываемому методу. Его юнит-тест вообще не запускается без полного контекста Spring, а инцидент на прошлой неделе случился из-за зависимости, которая была ещё null в момент работы инициализатора поля. Вы хотите перевести команду на внедрение через конструктор. Обоснуйте: что гарантирует внедрение через конструктор, чего не может дать внедрение в поле, чего оно стоит и когда внедрение через сеттер или поле всё-таки разумный выбор?
На ревью дизайна коллега защищает @Autowired на приватных полях: короче, новую зависимость можно добавить, не трогая конструктор, и в production это уже работает. На ревью — класс @Service с семью внедрёнными зависимостями, две из которых нужны одному редко вызываемому методу. Его юнит-тест вообще не запускается без полного контекста Spring, а инцидент на прошлой неделе случился из-за зависимости, которая была ещё null в момент работы инициализатора поля. Вы хотите перевести команду на внедрение через конструктор. Обоснуйте: что гарантирует внедрение через конструктор, чего не может дать внедрение в поле, чего оно стоит и когда внедрение через сеттер или поле всё-таки разумный выбор?
Внедрение через конструктор делает каждую зависимость явной и обязательной: объект не может существовать недосвязанным, его поля могут быть final, а класс тестируется обычным new — без контекста Spring. Оно же обнажает проблемы дизайна: семь зависимостей колют глаз, потому что конструктор делает god-класс видимым. Цена — многословность, а циклическая зависимость теперь падает на старте, вместо того чтобы прятаться. Внедрение через сеттер остаётся разумным для по-настоящему опциональных зависимостей.
Типичные ошибки
- ✗Считать, что внедрение в поле и через конструктор равнозначны, когда контекст поднят
- ✗Думать, что внедрение через конструктор разрешает циклическую зависимость, а не обнажает её
- ✗Держать контекст Spring в юнит-тестах, потому что класс не собрать через
new
Уточняющие вопросы
- →Как внедрение через конструктор превращает циклическую зависимость в падение на старте?
- →Когда
@Autowiredна сеттере всё же лучше внедрения через конструктор?
MiddleДебаггингИногдаИсправьте импорт, у которого @Transactional на чанк никогда не откатывается
Исправьте импорт, у которого @Transactional на чанк никогда не откатывается
@Transactional применяет прокси, оборачивающий бин, поэтому аннотация действует только на вызов, входящий в бин снаружи. this.importChunk(chunk) — внутренний вызов, который не покидает ImportService, идёт мимо прокси и не открывает транзакцию вовсе — каждый save просто автокоммитится. Исправление — направить вызов через прокси: внедрить бин в самого себя, вынести importChunk в отдельный бин или открыть транзакцию явно через TransactionTemplate.
Типичные ошибки
- ✗Ожидать, что
@Transactionalподействует на метод, который бин вызывает у самого себя - ✗Винить правила отката, когда транзакция вообще не открывалась
- ✗Считать, что один только
REQUIRES_NEWчинит вызов, не доходящий до прокси
Уточняющие вопросы
- →Почему пометка
importChunkкакprivateилиfinalломает@Transactionalтак же? - →Чем рискованно внедрять бин в самого себя, чтобы направить вызов через собственный прокси?