Spring #
1. Зачем мы используем Spring? Почему его так любят? #
Spring - это один из самых популярных фреймворков для разработки приложений на Java. Его используют благодаря широкому набору возможностей, гибкости и модульности
2. Inversion of Control #
Inversion of control (инверсия контроля) - принцип, при котором мы передаём управление созданием и настройкой объектов специальному контейнеру Spring
3. Dependency Injection #
Dependency Injection (внедрение зависимостей) - механизм предоставления объекту его зависимостей извне (вместо того, чтобы объект создавал их самостоятельно). DI упрощает управление зависимостями и делает код более модульным, тестируемым и легко расширяемым
Dependency Injection vs dependency lookup
| Критерий | Dependency Injection | Dependency Lookup |
|---|---|---|
| Суть | Зависимости предоставляются извне (контейнером или фреймворком) | Объект сам запрашивает зависимости (чаще из контекста) |
| Простота тестирования | Легче тестировать, зависимости можно заменять mock-объектами | Требует мокирования контекста или фабрики для тестов |
| Уровень контроля | Контейнер управляет зависимостями, объект их не создаёт | Объект сам решает, как получить зависимости |
| Пример | Spring внедряет зависимость через аннотации или XML-конфигурацию | Вручную вызов методаApplicationContext.getBean(...) |
Аннотация @Lookup над методом позволяет использовать Dependency Lookup
@Lookup
public WeatherService getWeatherServiceBean() {
return null;
}
Под капотом спринг делает
@Lookup
public WeatherService getWeatherServiceBean(){
return applicationbContext.getBean(WeatherService.class);
}
Когда пригодится способ внедрения через setter?
- Опциональные зависимости - зависимость не обязательна для работы объекта и используется только в некоторых сценариях
- Динамическое изменение зависимостей - когда требуется сменить зависимость во время выполнения программы
- Библиотеки или старый код - для интеграции с кодом, где нельзя контролировать процесс создания объектов
4. Какие способы внедрения зависимостей знаешь? #
- Через конструктор
- Через сеттер
- Через поле
| Способ | Описание | Плюсы | Минусы | Примеры использования |
|---|---|---|---|---|
| Через конструктор | Зависимости передаются при создании объекта | - Гарантия неизменяемости - Обязательные зависимости явно видны - Высокая тестируемость | - Неудобно для большого числа зависимостей - Не подходит для опциональных зависимостей | Когда зависимости обязательны и не изменяются после создания |
| Через setter | Зависимости устанавливаются с помощью метода после создания объекта | - Подходит для опциональных зависимостей - Зависимости можно изменять в runtime | - Возможна ситуация, когда объект используется без установленной зависимости (NPE) - Менее явный | Когда зависимости опциональны или могут изменяться |
| Через поля (field) | Зависимости внедряются напрямую в поля объекта, обычно с помощью аннотаций | - Минимум кода - Простота при использовании фреймворков (Spring, CDI) | - Возможна ситуация, когда объект используется без установленной зависимости (NPE) - Сложнее тестировать - Нарушение принципа явности зависимостей | Быстрое внедрение в Spring через @Autowired |
5. Почему лучше использовать конструктор? #
- Неизменяемость - зависимости устанавливаются один раз при создании объекта, и их невозможно изменить (через final поля)
- Обязательность зависимостей - устанавливает чёткое требование, что объект не может существовать без переданных зависимостей
- Тестируемость - проще создавать mock-объекты для тестов
- Явность - видно, какие зависимости требуются для работы объекта
6. @Autowired #
Аннотация @Autowired используется для автоматического внедрения зависимостей, может стоять на
- конструкторе
- методе
- поле
7. @Qualifier vs @Primary #
Если есть два одинаковых по типу бина, Spring не знает какой именно использовать и выбрасывает исключение. Установка над одним из бинов аннотации @Primary
делает его использование предпочтительным (дефолтным). Если нам нужно использовать в работе оба бина, можно поставить @Qualifier(имя бина) для каждого бина в месте внедрения (работает только с @Autowired)
| Аннотация | Описание | Когда использовать | Место указания |
|---|---|---|---|
@Primary | Указывает бин по умолчанию при выборе среди нескольких | Когда нужен бин, который используется чаще всего | В месте объявления бина |
@Qualifier | Явно указывает, какой бин использовать среди нескольких | Когда нужно внедрить конкретный бин, несмотря на наличие @Primary | В месте внедрения бина (над полем, методом или перед аргументами в конструкторе - рядом с Autowired) |
8. Что такое Bean? #
Bean в Spring - это объект, управляемый контейнером Spring IoC. Это основной строительный блок приложения, который регистрируется и конфигурируется контейнером
Аннотация @Bean
Аннотация @Bean используется для указания метода, который возвращает объект (бин) и эти бины в дальнейшем можно внедрять в другие компоненты. Используется для явного объявления бина в Java-конфигурации (в классе, помеченном как @Configuration)
Также для определения бинов можно использовать методы по умолчанию. Это позволяет создавать конфигурации бинов путем реализации интерфейсов с определениями бинов
public interface BaseConfig {
@Bean
default TransferServiceImpl transferService() {
return new TransferServiceImpl();
}
}
@Configuration
public class AppConfig implements BaseConfig {
}
Что такое BeanDefinition?
BeanDefinition - это метаданные (описание бина), указывающие на то, как должен быть создан, сконфигурирован и управляем конкретный бин Он содержит:
- Класс бина
- Его скоуп
- Зависимости
- Методы инициализации и уничтожения
Как Spring создает бины?
- Чтение конфигурации - Spring считывает конфигурацию из
@Configuration, XML или аннотаций на классах - Создание BeanDefinition - на основе конфигурации Spring формирует BeanDefinition для каждого бина
- Инициализация бина:
- вызывается конструктор или фабричный метод
- выполняется внедрение зависимостей (через конструктор, поля или сеттеры)
- Настройка бина - постобработка через
BeanPostProcessor(например, для работы с@PostConstruct) - Добавление в контейнер - готовый бин помещается в
ApplicationContext
Как конфигурируется бин?
- Аннотации (
@Bean,@Component,@Value,@Scope) - XML-конфигурация
- Java-код (в классе с
@Configuration) - Средства типа
@PostConstructи@PreDestroyдля управления жизненным циклом
Можно вставить Bean в статическое поле?
Нет, Spring не поддерживает внедрение зависимостей в статические поля напрямую. Решение - использовать сеттер или вызвать контекст через ApplicationContext
Как сделать ленивую инициализацию бина?
Используйте аннотацию @Lazy
9. Скоуп бинов. Web скоуп #
- Singleton
- Для каждого бина создается только один экземпляр на весь контекст приложения и переиспользуется везде, где он требуется
- Используется по-умолчанию
- Жизненный цикл бина начинается при загрузке контекста, и заканчивается при его завершении
- Prototype
- Каждый раз, когда запрашивается бин, создается новый экземпляр
- Бины со скоупом
prototypeне уничтожаются Spring, так как они выходят из под его контроля сразу после создания - НЕ ХРАНЯТСЯ в контексте Spring-а
- Web скоуп:
- request - cоздаётся один экземпляр бина на каждый HTTP-запрос
- session - cоздаётся один экземпляр бина на каждую HTTP-сессию
- application - cоздаётся один экземпляр бина на весь
ServletContext(аналогично синглтону, но для веб-приложения) - websocket - cоздаётся один экземпляр бина на каждую WebSocket-сессию
Как создать свой Скоуп
- Реализовать интерфейс Scope
public class CustomScope implements Scope {
private Map<String, Object> beans = new HashMap<>();
@Override
public Object get(String name, ObjectFactory<?> objectFactory) {
return beans.computeIfAbsent(name, k -> objectFactory.getObject());
}
@Override
public Object remove(String name) {
return beans.remove(name);
}
// другие методы...
}
- Зарегистрировать его в контексте:
@Bean
public static CustomScopeConfigurer customScopeConfigurer() {
CustomScopeConfigurer configurer = new CustomScopeConfigurer();
configurer.addScope("customScope", new CustomScope());
return configurer;
}
10. Как обеспечить, чтобы при внедрении бина со скоупом prototype в singleton-бин каждый вызов получал новый экземпляр? #
- Внедрение
ObjectProvider(илиObjectFactory)
@Component
@Scope("singleton")
public class MySingletonService {
private final ObjectProvider<MyPrototypeBean> prototypeProvider;
public MySingletonService(ObjectProvider<MyPrototypeBean> prototypeProvider) {
this.prototypeProvider = prototypeProvider;
}
public void doWork() {
// каждый вызов — новый бин
MyPrototypeBean prototype = prototypeProvider.getObject();
prototype.action();
}
}
Плюсы:
- просто, не нужно AOP-прокси
Минусы: - зависит от Spring API, сложнее тестировать вне контекста
@Lookup-метод (метод-инъекция)
@Component
@Scope("singleton")
public abstract class MySingletonService {
public void doWork() {
MyPrototypeBean prototype = createPrototype();
prototype.action();
}
@Lookup
protected abstract MyPrototypeBean createPrototype();
}
@Component
@Scope("prototype")
public class MyPrototypeBean {
public void action() { … }
}
Spring генерирует подкласс, переопределяющий createPrototype() и вызывающий getBean(...)
Плюсы:
- нет явных провайдеров в коде
Минусы: - менее очевидно, опирается на генерацию подклассов
- Прокси-bean (
proxyMode = TARGET_CLASS)
@Component
@Scope(value = "prototype", proxyMode = ScopedProxyMode.TARGET_CLASS)
public class MyPrototypeBean { … }
@Component
@Scope("singleton")
public class MySingletonService {
private final MyPrototypeBean prototype; // это прокси
public MySingletonService(MyPrototypeBean prototype) {
this.prototype = prototype;
}
public void doWork() {
prototype.action(); // при каждом вызове прокси подхватит новый экземпляр
}
}
Плюсы:
- автоматический проксинг, код остаётся простым
Минусы: - прокси-обёртка, возможны проблемы с навигацией методов,
instanceof, необходимость проксирования класса
- Получение из
ApplicationContext
@Component
public class MySingletonService {
@Autowired
private ApplicationContext ctx;
public void doWork() {
MyPrototypeBean prototype = ctx.getBean(MyPrototypeBean.class);
prototype.action();
}
}
Плюсы:
- универсальный способ для любых бинов
Минусы: - прямая зависимость от контекста, менее чистый код
Проблемы и нюансы
- Неявное смешение слоёв - Внедрение контекста или провайдера в бизнес-код может затруднить тестирование
- Множественные реализации - если есть несколько
MyPrototypeBean(например,FastPrototype,SlowPrototype), нужно уточнять:
public MySingletonService(
@Qualifier("fastPrototypeBean") ObjectProvider<MyPrototypeBean> fastProvider) { … }
- Уничтожение prototype-бинов - контейнер сам не вызывает
@PreDestroyу prototype-бинов. Если в них есть ресурсы, придётся очищать вручную - Прокси и AOP - при использовании
proxyModeметоды, помеченныеfinalилиprivate, не проксируются. Это может ломать логику - Сериализация - если singleton-бин сериализуется, прокси-ссылки на prototype-бин могут привести к проблемам при десериализации
11. Жизненный цикл Bean. Вопросы связанные с классами, входящими в жизненный цикл бина #
Spring управляет созданием, инициализацией и уничтожением бинов в рамках контейнера IoC. Этот жизненный цикл можно разделить на несколько этапов

Парсинг конфигураций, сканирование аннотаций и создание BeanDefinition Spring (BeanDefinitionReader) ищет классы, которые помечены аннотациями, указывающими на то, что они являются бинами (или содержат таковые). Эти аннотации включают:
@Component,@Service,@Repository,@Controller,@ConfigurationSpring сканирует классы в пакетах, указанных в аннотации@ComponentScan, или в пакете, где находится класс с аннотацией@SpringBootApplication. Найденные классы парсятся, и для них создаются BeanDefinitionBeanDefinition- это специальный интерфейс, через который можно получить доступ к метаданным будущего бина. В зависимости от конфигурации будет использоваться тот или иной механизм парсинга конфигурации. Все они складываются вConcurrentHashMap, в которой ключ - имя бина, а объект самBeanDefinition. При старте приложения в IoC контейнер попадут бины, которые имеют скоуп синглтон, а остальные создадутся тогда, когда они нужны. Сначала регистрируются все@Configurationдля дальнейшего парсингаНастройка созданных BeanDefinition Есть возможность повлиять на бины до их создания. Для этого существует интерфейс
BeanFactoryPostProcessor, реализовав который, получаем доступ к созданнымBeanDefinitionи можем их изменить при помощи методаpostProcessBeanFactory.Создание экземпляра бинов Сначала из мапы c
BeanDefinition-ами достаются бины, необходимые для настройки других бинов, а потом остальные (рекурсивное разрешение зависимостей). Созданием занимаетсяBeanFactory(вызывает конструкторы), но можно и делегировать это фабрике, реализующей интерфейсFactoryBeanВнедрение зависимостей (через поля и сеттеры)
Настройка созданных бинов Бины уже созданы, их можно донастроить. Интерфейс
BeanPostProcessorпозволяет вклиниться в процесс настройки бинов до того, как они попадут в контейнер. Контекст автоматически обнаруживает бины реализующие этот интерфейс и помечает их. Есть два методаpostProcessBeforeInitializationвызывается до init методаpostProcessAfterInitialization- после
Сначала сработает метод
postProcessBeforeInitialization()всех имеющихсяBeanPostProcessor-ов. Затем, при наличии, будет вызванinit-метод. В спринге если какой-то изBeanPostProcessorчто-то меняет в классе (в т.ч. добавляет проксирование), он должен делать это на этапе послеinit. Следовательно,initвсегда работает на оригинальный метод до проксирования. В конце бины пройдут черезpostProcessAfterInitialization()._Примечание: если бин имплементирует_ `InitializingBean`_, то Spring вызовет метод_ `afterPropertiesSet()` _перед init-методом. Интерфейс не рекомендуется к использованию как устаревший_Бины созданы На этом этапе их можно получить с помощью метода
ApplicationContext.getBean()Закрытие контекста Когда контекст закрывается, бин уничтожается. Если в бине есть
@PreDestroyметод, то перед уничтожением бина он вызовется. Если в@Beanопределён метод destroyMethod, то будет вызван и он
12. @ComponentScan #
Аннотация @ComponentScan указывает Spring где искать классы, помеченные аннотацией @Component или его производными:
@RestController, @Controller, @Repository, @Service и т.д.
13. @Conditional в Spring #
Аннотация @Conditional в Spring используется для того, чтобы подключать бины или выполнять конфигурации только при соблюдении определённых условий. Она позволяет гибко управлять поведением приложения, включая или исключая определенные компоненты
@ConditionalOnBean
Аннотация @ConditionalOnBean указывает, что бин или конфигурация должны быть зарегистрированы в контексте только если определённый бин уже существует
@Configuration
public class MyConfig {
@Bean
public MyService myService() {
return new MyService();
}
@Bean
@ConditionalOnBean(MyService.class)
public DependentService dependentService() {
return new DependentService();
}
@ConditionalOnProperty
Аннотация @ConditionalOnProperty указывает, что бин или конфигурация должны быть зарегистрированы в контексте только если определённое свойство в файле конфигурации имеет нужное значение
# application.properties
feature.enabled=true
@Configuration
public class MyConfig {
@Bean
@ConditionalOnProperty(prefix = "feature", name = "enabled", havingValue = "true", matchIfMissing = false)
public FeatureService featureService() {
return new FeatureService();
}
}
14. @Service, @Repository, @Component #
| Аннотация | Описание | Применение | Семантика |
|---|---|---|---|
@Component | Общая аннотация для обозначения компонента, который будет управляться контейнером Spring | Для обычных бинов | Может быть использована для любых типов компонентов. Обычно используется, когда нет более точного типа |
@Service | Специализация @Component, указывающая, что бин представляет собой сервисный слой | Для сервисов | Используется для классов, реализующих бизнес-логику. По сути, это более семантическое обозначение, чем @Component |
@Repository | Специализация @Component, указывающая, что бин представляет собой слой доступа к данным (DAO) | Для классов, работающих с данными | Используется для классов, которые инкапсулируют логику доступа к данным (например, через JDBC или JPA). Аннотация также может активировать обработку исключений, специфичных для DAO |
Singleton-антипаттерн. Если мы на класс поставим @Service, это будет Singleton? Да. Однако это не является антипаттерном, поскольку Spring управляет жизненным циклом и внедрением зависимостей, что решает многие проблемы, присущие обычному Singleton
@Bean vs @Component
| Аннотация | Место применения | Применение |
|---|---|---|
@Bean | Над методом в классе-конфигурации | При необходимости настраивать бин программно |
@Component | Над классом | Для автоматического сканирования |
15. @Controller и @RestController #
@Controller - базовая версия аннотации для контроллера из Spring Web MVC, в основном используется для Server-Side Rendering. Если нужно вернуть данные (например, JSON), нужно добавить @ResponseBody к методу
@RestController - комбинация @Controller и @ResponseBody. Используется в REST. Всегда возвращает данные (например, JSON или XML) в теле HTTP-ответа. Нет необходимости добавлять @ResponseBody к каждому методу
16. @ResponseBody vs ResponseEntity #
ResponseEntity - это класс, экземпляр которого содержит в себе и тело ответа, и HTTP-статус
@ResponseBody - аннотация указывает, что результат метода должен быть возвращён как тело HTTP-ответа (JSON, XML)
17. Spring MVC. Что это? Какие проблемы решает? #
Spring Web MVC - один фреймворков внутри экосистемы Spring. Предназначен для написания веб-приложений с применением архитектурного паттерна Model-View-Controller. Решает следующие проблемы
- Чёткое разделение логики. Разбиваем логику в соответствии с MVC
- Поддержка различных шаблонизаторов: JSP, Thymeleaf и др.
- Упрощает обработку HTTP-запросов через контроллеры
18. Парадигма AOP #
AOP (Aspect Oriented Programming) - парадигма программирования, основанная на идее разделения основного и служебного функционала. Служебный функционал описывается в классах-аспектах и позволяет вызывать его по определённым условиям (до, после метода), не вмешиваясь в основной код
19. Циклическая зависимость #
Циклическая зависимость - зависимость, при которой два бина зависят друг от друга, а потому Spring не может их инициализировать. Решение проблемы:
- аннотация
@Lazyу одного из бинов - внедрение зависимости через сеттер у одного из бинов
С версии 2.6 спринг запрещает циклические зависимости (а потому такое внедрение не будет работать без @Lazy), но можно убрать этот запрет через
spring.main.allow-circular-references=true - рефакторинг
Например, есть 2 сервиса
@Service
@RequiredArgsConstructor
public class OrderService {
private final PaymentService paymentService;
...
}
@Service
@RequiredArgsConstructor
public class PaymentService {
private final OrderService orderService;
...
}
Для устранения циклической зависимости проектируем сервис-посредник - CheckoutService
@Service
@RequiredArgsConstructor
public class CheckoutService {
private final OrderService orderService;
private final PaymentService paymentService;
public CheckoutService(
OrderService orderService,
PaymentService paymentService) {
this.orderService = orderService;
this.paymentService = paymentService;
}
public void checkout() {
orderService.createOrder();
paymentService.processPayment();
}
}
@Service
@RequiredArgsConstructor
public class OrderService {
public void createOrder() {...}
}
@Service
@RequiredArgsConstructor
public class PaymentService {
public void processPayment() {...}
}
20. Spring Boot #
Spring Boot — это фреймворк на основе Spring, который позволяет быстрее и проще создавать готовые к работе приложения. Ключевые преимущества:
- Стартеры — это готовые наборы библиотек (модулей), которые включают все необходимые зависимости для работы с конкретными технологиями
- Встроенный сервер - В Spring Boot сервер встроен прямо в приложение. Например, когда вы добавляете стартер
spring-boot-starter-web, Spring Boot автоматически включает Tomcat или Jetty - Автоматическая конфигурация - Spring Boot использует механизм автоматической конфигурации, который анализирует, какие библиотеки находятся в вашем проекте, и автоматически настраивает необходимые компоненты.
- Упрощение сборки - В обычном Spring для развертывания приложения вам нужно собрать WAR файл и развернуть его на сервере. В Spring Boot приложение собирается в исполняемый JAR файл, который содержит все зависимости и сервер.
Как Spring Boot работает под капотом
Автоконфигурация (Auto Configuration):
- Spring Boot анализирует классы и зависимости в вашем проекте и пытается настроить приложение так, чтобы оно работало “из коробки”.
- Например, если в проекте есть зависимость от
spring-boot-starter-web, то Spring Boot автоматически настраиваетDispatcherServlet, который обрабатывает HTTP-запросы.
Сканирование компонентов:
- При запуске приложения Spring Boot сканирует пакеты для поиска аннотированных классов (например, с аннотациями
@Component,@Service,@Repository,@Controllerи т. д.), чтобы зарегистрировать бины. - Это сканирование начинается с класса, аннотированного
@SpringBootApplication, который обычно находится в корне проекта. Пакеты и подпакеты, расположенные ниже этого класса, будут сканироваться на наличие компонентов.
- При запуске приложения Spring Boot сканирует пакеты для поиска аннотированных классов (например, с аннотациями
Встроенный сервер:
- Spring Boot может использовать встроенные сервера, такие как Tomcat, Jetty или Undertow, что позволяет запустить приложение как самодостаточный исполнимый JAR или WAR файл.
Процесс инициализации:
- Когда приложение запускается, Spring Boot автоматически запускает и настраивает все необходимые компоненты и зависимости. Весь процесс инициализации централизован и автоматизирован.
Основной класс с
@SpringBootApplication:- Этот класс является точкой входа в приложение. Он выполняет несколько задач:
- Включает автоматическое конфигурирование с помощью
@EnableAutoConfiguration. - Разрешает сканирование компонентов с помощью
@ComponentScan. - Включает настройку конфигурации Spring с помощью
@Configuration.
- Включает автоматическое конфигурирование с помощью
- Этот класс является точкой входа в приложение. Он выполняет несколько задач:
Где Spring Boot ищет бины?
Основной класс с
@SpringBootApplication:- Spring Boot начинает сканировать пакеты с местоположения класса, аннотированного
@SpringBootApplication. - Все классы в этом пакете и его подпакетах будут автоматически обработаны на наличие аннотаций для создания бинов (например,
@Component,@Service,@Repositoryи т. д.).
- Spring Boot начинает сканировать пакеты с местоположения класса, аннотированного
Пакет с
@SpringBootApplication:- По умолчанию Spring Boot сканирует только текущий пакет и его подпакеты. Поэтому важно, чтобы основной класс с аннотацией
@SpringBootApplicationнаходился в корне пакета, чтобы гарантировать, что все компоненты будут найдены.
- По умолчанию Spring Boot сканирует только текущий пакет и его подпакеты. Поэтому важно, чтобы основной класс с аннотацией
Дополнительные настройки сканирования:
- Можно явно указать, какие пакеты нужно сканировать, с помощью аннотации
@ComponentScan:
- Можно явно указать, какие пакеты нужно сканировать, с помощью аннотации
@SpringBootApplication
@ComponentScan(basePackages = "com.example.myapp")
public class MyApplication {
public static void main(String[] args) {
SpringApplication.run(MyApplication.class, args);
}
}
Это полезно, если компоненты находятся в другом пакете или если вы хотите ограничить область сканирования.
Использование профилей:
- Если в проекте используются разные профили (например, для разных сред), Spring Boot будет также искать компоненты, соответствующие активному профилю, при условии что они аннотированы как
@Profile.
- Если в проекте используются разные профили (например, для разных сред), Spring Boot будет также искать компоненты, соответствующие активному профилю, при условии что они аннотированы как
21. Spring Boot vs Spring #
- Spring — это основной фреймворк с полной конфигурацией и гибкостью.
- Spring Boot — это расширение Spring, которое делает процесс разработки проще, автоматизируя конфигурацию и позволяя быстро запускать приложение с минимальной настройкой.
| Критерий | Spring | Spring Boot |
|---|---|---|
| Цель | Мощный фреймворк для создания Java-приложений. | Упрощение разработки с использованием Spring. Автоматизация конфигурации. |
| Конфигурация | Требуется вручную настроить XML или Java-конфигурацию. | Автоконфигурация, минимальные настройки для старта проекта. |
| Запуск | Нужно вручную настроить веб-сервер (например, Tomcat). | Встроенные серверы (Tomcat, Jetty, Undertow) для простого запуска. |
| Гибкость | Большая гибкость, полная настройка каждого компонента. | Меньше гибкости, но намного быстрее и проще для старта проекта. |
| Сложность | Более высокая сложность настройки и конфигурации. | Упрощенная настройка и конфигурация. |
| Использование | Используется, когда нужна полная настройка и контроль. | Используется для быстрого старта и простоты разработки. |
| Конфигурационные файлы | Использует XML или Java-конфигурацию для настройки бинов. | Использует application.properties или application.yml для настройки. |
22. Spring JDBC #
Spring JDBC - это модуль Spring, который упрощает взаимодействие с базами данных, используя JDBC (Java Database Connectivity). Он предлагает упрощённый подход к выполнению SQL-запросов, обработке результатов и управлению ресурсами
23. Spring Data JPA #
Spring Data JPA - это модуль Spring, который упрощает работу с JPA (Java Persistence API) и предоставляет удобные способы доступа к данным. Благодаря автоконфигурациям позволяет разработчикам сосредоточиться на бизнес-логике, не заботясь о низкоуровневых деталях доступа к данным
24. Блокировка по умолчанию в Spring #
В Spring, по умолчанию, используется оптимистичная блокировка для управления конкурентным доступом к данным. Это означает, что при обновлении сущностей предполагается, что конфликты не произойдут, и изменения вносятся в базу данных без блокировок
25. @Transactional. Как работает? #
@Transactional — это аннотация, которая позволяет автоматически управлять транзакциями. Когда метод аннотирован @Transactional, Spring берет на себя начало, коммит или откат транзакции
Что можно пометить аннотацией @Transactional?
- Класс. Все его методы станут
@Transactional - Метод
Дефолтный propagation
Propagation.REQUIRED
Какие способы управления транзакциями в Spring вы знаете?
- Аннотация
@Transactional - TransactionManager
- TransactionTemplate
TransactionManager и TransactionTemplate
- Когда требуется точный контроль над транзакциями (например, управлять несколькими транзакциями в одном потоке)
@Transactional
- Когда нужно простое управление транзакциями
- Когда транзакция не требует сложной логики управления
@Transactional повесить на private метод?
Если вы поместите аннотацию @Transactional на private метод, то транзакция не будет создана или применена, потому что Spring использует прокси-механизм для управления транзакциями.
26. Какие есть атрибуты у @Transactional? #
| Атрибут | Описание | Значение по умолчанию |
|---|---|---|
propagation | Определяет необходимость создания транзакции (создавать новую или использовать существующую) | REQUIRED |
isolation | Уровень изоляции транзакции (как управлять конкурентным доступом) | DEFAULT |
readOnly | Только для чтения (не позволяет изменять данные) | false |
timeout | Максимальное время выполнения (в секундах) | -1 (бесконечно) |
rollbackFor | Перечисляет исключения, при которых делается rollback | Пусто |
noRollbackFor | Исключения, при которых rollback не делается | Пусто |
27. Атрибут propagation у @Transactional #
Атрибут propagation определяет необходимость создания транзакции (создавать новую или использовать уже открытую). Возможные значения:
- REQUIRED - применяется по умолчанию. При входе в
@Transactionalметод будет использована уже существующая транзакция или создана новая, в случае отсутствия - SUPPORTS - метод будет выполняться в рамках транзакции, если она существует. Если ее нет, метод выполнится без транзакции
- MANDATORY - требует внешнюю транзакцию, иначе выбрасывает исключение
- REQUIRES_NEW - для внутреннего метода создается своя отдельная транзакция. Пока выполняется внутренний метод, внешняя транзакция приостанавливается. Результат выполнения внутренней не повлияет на результат выполнения внешней
- NOT_SUPPORTED - метод всегда выполняется вне транзакции, даже если существует текущая транзакция. Текущая транзакция приостанавливается
- NEVER - запрещает выполнение метода в транзакции, выбрасывает исключение, если транзакция существует
- NESTED - создаёт подтранзакцию внутри основной транзакции. Эта подтранзакция может откатиться отдельно от основной, но зависит от успешного завершения основной транзакции. Если основная транзакция откатится, подтранзакция тоже откатится, даже если она завершилась успешно
28. @Transaction Что будет если вызвать метод из метода в одном классе при различных сценариях #
Если вы вызовете метод, помеченный @Transactional, из другого метода того же класса, то транзакция не начнётся или не будет соблюдена, если вызов происходит напрямую. Потому что вызов внутри класса не проходит через прокси, который отвечает за управление транзакцией. Таким образом, аннотация @Transactional не будет обработана
@Service
public class MyService {
@Transactional
public void transactionalMethod() {
// Транзакция активируется, если вызов через прокси
}
public void callTransactionalMethod() {
transactionalMethod(); // Прямой вызов, транзакция НЕ активируется
}
}
Как исправить?
- Использовать вызов через другой бин Если транзакционный метод вызывается из другого бина, вызов пройдёт через прокси, и транзакция будет активирована
@Service
public class MyService {
@Transactional
public void transactionalMethod() {
// Транзакция активируется
}
}
@Service
public class AnotherService {
@Autowired
private MyService myService;
public void callMethod() {
myService.transactionalMethod(); // Вызов через прокси, транзакция активируется
}
}
- Использовать self-injection Частный случай предыдущего пункта - внедрение бина в самого себя
@Service
public class MyService {
@Autowired
private MyService self;
@Transactional
public void transactionalMethod() {
// Транзакция активируется
}
public void callTransactionalMethod() {
self.transactionalMethod(); // Вызов через прокси, транзакция активируется
}
}
Примечание: с версии 2.6 Spring Boot запрещает создавать циклические зависимости. Чтобы приведённый выше код отработал, необходимо установить свойство spring.main.allow-circular-references=true
В случае же с внедрением через конструктор, даже это не поможет. Существует несколько альтернативных вариантов self-injection, один из них - получение бина через ApplicationContext
@Service
public class MyService implements ApplicationContextAware {
private ApplicationContext context;
@Override
public void setApplicationContext(ApplicationContext context) throws BeansException {
this.context = context;
}
@Transactional
public void transactionalMethod() {
// Транзакция активируется
}
public void callTransactionalMethod() {
// Вызов через прокси, транзакция активируется
context.getBean(MyService.class).transactionalMethod()
}
}
29. Как гарантировать корректное обновление и сохранение изменений в сущностях, полученных в методе с @Transactional #
@Transactionalна сервисе гарантирует, что все изменения managed-сущностей будут автоматически сохранены при коммите (dirty checking)- Не нужно вызывать
save()для уже загруженных entity - только для новых или detached (merge()под капотом) flush()позволяет досрочно слить изменения в БД, если требуется промежуточный результат (generated value, ранняя проверка constraints)- Для чтения использовать
readOnly=true- это ускоряет выполнение - Следить за корректностью атрибутов
propagationиisolation, чтобы избежать неожиданных rollback’ов и блокировок
30. @Transactional и Исключения #
| Ситуация | Будет ли откат транзакции? | Причина |
|---|---|---|
Выброшено RuntimeException | Да | Spring откатывает по умолчанию |
Выброшена Error | Да | Расценивается как критическая ошибка |
Выброшено Checked Exception (Exception, IOException и др.) | Нет | Spring НЕ откатывает checked exceptions по умолчанию |
Checked exception + rollbackFor=Exception.class | Да | Ты явно указал, для каких исключений откатывать |
| RuntimeException + noRollbackFor=RuntimeException.class | Нет | Ты запретил откатывать для этого исключения |
| Исключение поймано (try/catch) и не проброшено наружу | Нет | Spring видит, что метод завершился “успешно” |
Поймано исключение, но вызывается TransactionAspectSupport.currentTransactionStatus().setRollbackOnly() | Да | Ручной указатель отката |
| Метод private / final / static | @Transactional не отработает | Прокси не может обернуть метод |
| Вызов метода внутри того же класса | @Transactional не отработает | Self-invocation - прокси не используется |
| Метод вызван через spring bean | Да | Прокси перехватывает вызов |
31. @Async #
Аннотация @Async используется в Spring для выполнения методов асинхронно, то есть в отдельном потоке, чтобы не блокировать основной поток выполнения
Особенности:
- метод, помеченный аннотацией
@Async, выполняется в отдельном потоке - возвращаемый тип может быть
void,Future, илиCompletableFuture - работает через прокси, поэтому вызов такого метода должен происходить у бина, а не внутри класса
- асинхронные вызовы используют пул потоков. По умолчанию используется пул
SimpleAsyncTaskExecutor, но можно указать свойExecutorилиTaskExecutorчерез единственный параметр аннотации
32. Starter в Spring. Плюсы и минусы #
Стартеры в Spring - это готовые наборы библиотек (модулей), которые включают все необходимые зависимости для работы с конкретными технологиями
Плюсы:
- упрощение конфигурации
- согласованность версий зависимостей
- сокращение времени разработки
- модульность
- документация и поддержка Минусы:
- избыточные зависимости
- меньшая гибкость
Какие отличия стартера от библиотеки?
| Характеристика | Starter | Library |
|---|---|---|
| Состав | Включает набор библиотек | Одиночная библиотека |
| Настройка | Автоматическая | Требует ручной настройки |
| Пример | spring-boot-starter-data-jpa | hibernate-core |
33. Spring Criteria API #
Spring Criteria API (часть Hibernate) предоставляет гибкий способ построения запросов к БД. Позволяет составлять запросы с использованием Java-классов
Ключевые особенности Criteria API
- Динамическое построение запросов - удобно для ситуаций, когда структура запроса может меняться в рантайме в зависимости от условий
- Безопасность типов - ошибки в синтаксисе запросов обнаруживаются на этапе компиляции
- Ленивость - запрос выполняется только при обращении к результатам
- Поддержка сложных запросов - подзапросы, группировки, сортировки, фильтрации и агрегатные функции
Пример использования
@Entity
public class Employee {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private double salary;
// getters и setters
}
@Repository
public class EmployeeRepository {
@PersistenceContext
private EntityManager entityManager;
public List<Employee> findEmployeesWithSalaryGreaterThan(double minSalary) {
// Получаем CriteriaBuilder
CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
// Создаём CriteriaQuery
CriteriaQuery<Employee> criteriaQuery = criteriaBuilder.createQuery(Employee.class);
// Корневой объект (FROM Employee)
Root<Employee> root = criteriaQuery.from(Employee.class);
// Условие (WHERE salary > :minSalary)
criteriaQuery.select(root)
.where(criteriaBuilder.gt(root.get("salary"), minSalary));
// Выполняем запрос
return entityManager.createQuery(criteriaQuery).getResultList();
}
}
34. @Scheduled #
Аннотация @Scheduled используется для выполнения методов по расписанию. Она позволяет запускать задачи с фиксированным интервалом времени, фиксированной задержкой или на основе cron-выражений
35. Понятие Controller и Servlet в Java. Это одно и то же? Если разное, в чем отличия? #
Понятия сервлета и контроллера принадлежат разным концепциям, хотя оба используются для обработки HTTP-запросов в Java веб-приложениях.
Servlet - это Java-класс, который обрабатывает HTTP-запросы (HTTP GET, POST и т.д.) и возвращает HTTP-ответы. Он является основой для создания серверных приложений в Java
Controller - это часть архитектуры MVC (Model-View-Controller). В контексте Java это обычно класс, аннотированный, например, @Controller в Spring MVC, который обрабатывает HTTP-запросы и управляет их маршрутизацией
Контроллер абстрагирует разработчика от низкоуровневых деталей (например, работы с HTTP-запросами и ответами), позволяя сосредоточиться на бизнес-логике
Основные отличия Servlet и Controller
| Критерий | Servlet | Controller |
|---|---|---|
| Область применения | Низкоуровневая обработка HTTP-запросов | Высокоуровневый компонент архитектуры MVC |
| Ручная работа | Требуется больше ручной работы для маршрутизации и обработки данных | Маршрутизация и обработка запросов автоматизированы |
| Инструменты | Использует контейнер сервлетов (например, Tomcat) | Использует веб-фреймворки (например, Spring MVC) |
| Гибкость | Полный контроль над HTTP-запросами и ответами | Сосредоточен на бизнес-логике, делегируя детали фреймворку |
| Уровень абстракции | Низкий: работа напрямую с HttpServletRequest и HttpServletResponse | Высокий: аннотации и абстрагирование низкоуровневой логики |
| Пример | HttpServlet, GenericServlet | @Controller в Spring, @RestController |
36. Что такое объект Filter? В какой момент вызывается? #
Filter - это интерфейс из Java Servlet API, который позволяет перехватывать и изменять HTTP-запросы и ответы. Фильтры часто используются для задач, которые необходимо выполнить до или после обработки запроса в сервлете (или контроллере в случае Spring), например:
- Аутентификация и авторизация
- Логирование запросов
- Сжатие ответов
- Кэширование
- Добавление или изменение заголовков запросов и ответов
Когда вызывается фильтр? HTTP Request -> Filter chain -> DispatcherServlet -> Controller -> … ->Response -> Filters
Фильтры вызываются до попадания HTTP-запроса в DispatcherServlet (и тем более контроллера) и после возвращения ответа от него
- До контроллера - обрабатывают входящий HTTP-запрос
- После - могут изменить HTTP-ответ перед отправкой клиенту
Чаще всего для кастомных фильтров используется расширение класса OncePerRequestFilter
37. Что такое ApplicationContext? Как работает под капотом #
ApplicationContext — это центральный интерфейс контейнера Spring, который управляет жизненным циклом бинов, их зависимостями и предоставляет дополнительные возможности для разработки приложений. Это расширение интерфейса BeanFactory, добавляющее множество дополнительных функций, таких как обработка событий, поддержка интернационализации и интеграция с AOP.
Основные функции ApplicationContext
- Управление бин-компонентами:
- Автоматическое создание, связывание и уничтожение бинов.
- Инжекция зависимостей:
- Поддерживает механизмы Dependency Injection (DI).
- Обработка событий:
- Поддерживает публикацию и прослушивание событий (например, ContextRefreshedEvent).
- Интернационализация:
- Предоставляет механизмы для работы с сообщениями и локалями.
- Интеграция с AOP:
- Позволяет внедрять аспекты в приложение.
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class MyApp {
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
MyBean myBean = context.getBean(MyBean.class);
myBean.doSomething();
}
}
читает конфигурацию
↓
регистрирует BeanDefinition
↓
создаёт и настраивает бины
↓
оборачивает в proxy
↓
управляет их жизнью
Расскажи про разницу между ApplicationContext и BeanFactory
BeanFactory — это базовый контейнер Spring для управления объектами (бинами).
Он отвечает за инициализацию, связывание и предоставление бинов по запросу.
Однако BeanFactory менее функционален, чем ApplicationContext, так как предоставляет только базовые возможности управления бинами.
import org.springframework.beans.factory.xml.XmlBeanFactory;
import org.springframework.core.io.ClassPathResource;
public class MyApp {
public static void main(String[] args) {
BeanFactory factory = new XmlBeanFactory(new ClassPathResource("beans.xml"));
MyBean myBean = (MyBean) factory.getBean("myBean");
myBean.doSomething();
}
}
- ApplicationContext — более мощный и функциональный контейнер, подходящий для полноценных приложений.
- BeanFactory — легковесный контейнер, используется редко и преимущественно в ограниченных задачах или для оптимизации ресурсов.
Различия между ApplicationContext и BeanFactory
| Критерий | ApplicationContext | BeanFactory |
|---|---|---|
| Область применения | Полноценный контейнер для разработки современных приложений. | Базовый контейнер для управления бинами. |
| Загрузка бинов | Загружает все бины при инициализации контекста (eager loading). | Загружает бины по запросу (lazy loading). |
| Поддержка событий | Поддерживает публикацию и обработку событий (ApplicationEvent). | Не поддерживает события. |
| Интернационализация | Встроенная поддержка интернационализации (I18N). | Отсутствует. |
| Интеграция с AOP | Поддержка AOP для внедрения аспектов. | Ограниченная интеграция с AOP. |
| Преимущество производительности | Менее производителен при большом количестве бинов (загрузка всех бинов при старте). | Производителен, так как загружает бины только по запросу. |
| Поддержка аннотаций | Полная поддержка аннотаций (например, @Component, @Autowired). | Ограниченная поддержка. |
| Расширенные возможности | Поддержка профилей, автоконфигурации, REST и других функций. | Только базовая работа с бинами. |
Когда использовать
| Ситуация | ApplicationContext | BeanFactory |
|---|---|---|
| Полноценное приложение | Да | Нет |
| Требуется работа с событиями | Да | Нет |
| Экономия памяти при большом количестве бинов | Нет | Да |
| Тестовые приложения | Возможен, но избыточен | Да |
Можно поднять несколько контекстов?
Да, в Spring можно создать несколько контекстов. Это может быть полезно для разделения конфигурации, областей ответственности или работы с вложенными контекстами.
Примеры использования нескольких контекстов
1. Вложенные контексты
Можно создать основной (parent) и дочерний (child) контексты. Основной контекст может предоставлять бины, доступные дочерним контекстам, но не наоборот.
Пример:
ApplicationContext parentContext = new AnnotationConfigApplicationContext(ParentConfig.class);
AnnotationConfigApplicationContext childContext = new AnnotationConfigApplicationContext();
childContext.setParent(parentContext);
childContext.register(ChildConfig.class);
childContext.refresh();
2. Несколько независимых контекстов
Если контексты не связаны друг с другом, они не делятся бинами и настройками.
Пример:
ApplicationContext context1 = new AnnotationConfigApplicationContext(AppConfig1.class);
ApplicationContext context2 = new AnnotationConfigApplicationContext(AppConfig2.class);
Преимущества использования нескольких контекстов
- Разделение конфигурации между модулями приложения.
- Изоляция модулей для тестирования.
- Улучшение модульности и читаемости кода.
Недостатки
- Увеличенная сложность управления.
- Возможные конфликты бинов между контекстами.
38. Зачем нужны аннотации @RequestParam и @PathVariable? #
Эти аннотации используются в Spring MVC для извлечения параметров из HTTP-запросов. Они применяются в методах контроллера
@RequestParam(name) позволяет извлечь из запроса параметр с именем name
@GetMapping("/api/users")
public String getUser(@RequestParam String name, @RequestParam(defaultValue = "0") int page) {
return "Name: " + name + ", Page: " + page;
}
@PathVariable извлекает путь-параметр указанный в маппинге в параметр метода
@GetMapping("/api/users/{id}")
public String getUserById(@PathVariable int id) {
return "User ID: " + id;
}
39. @ConfigurationProperties? ConfigurationProperties vs Value #
@ConfigurationProperties позволяет извлекать свойства из конфигураций типа application.properties, application.yaml в поля класса. Особенно удобен для маппинга многоуровневых свойств
- Привязывает свойства по префиксу к полям класса
- Удобна для работы с группами связанных настроек
- Требует аннотации
@EnableConfigurationPropertiesили компонентов типа@Component
app:
name: MyApp
version: 1.0
@Component
@ConfigurationProperties(prefix = "app")
public class AppProperties {
private String name;
private String version;
// Getters and setters
}
@Component
public class MyService {
private final AppProperties appProperties;
public MyService(AppProperties appProperties) {
this.appProperties = appProperties;
}
public void printConfig() {
System.out.println("App Name: " + appProperties.getName());
System.out.println("Version: " + appProperties.getVersion());
}
}
Cравнение @ConfigurationProperties и @Value
| Критерий | @ConfigurationProperties | @Value |
|---|---|---|
| Привязка групп свойств | Удобна для групп связанных настроек (с префиксом) | Подходит для одиночных свойств |
| Приведение типов | Автоматически поддерживает сложные типы (например, списки) | Поддерживает только базовые типы |
| Работа с файлами YAML | Полностью поддерживается, включая сложные структуры | Менее удобна для сложных структур |
| Гибкость | Удобна для крупных конфигураций. | Быстрее для мелких настроек |
| Пример использования | @ConfigurationProperties(prefix = "app") | @Value("${app.name}") |
| Аннотации | Требует @EnableConfigurationProperties или @Component | Не требует дополнительных аннотаций |
| Тестирование и валидация | Поддерживает валидацию через @Validated | Не поддерживает валидацию |
Когда использовать?
| Ситуация | @ConfigurationProperties | @Value |
|---|---|---|
| Большое количество связанных свойств | Да | Нет |
| Одиночное свойство | Нет | Да |
| Сложные структуры (списки, объекты) | Да | Нет |
| Простые, статические значения | Нет | Да |
40. @Value. Что это? #
@Value - это аннотация Spring, которая позволяет внедрять значения из конфигурационных файлов (application.properties, application.yaml), системных переменных или других внешних источников в поля, методы или конструкторы класса. Под капотом создаёт скалярный бин (т.е. бин, содержащий константу), значение которого и внедряется
app:
name: MyApp
version: 1.0
@Component
public class AppConfig {
@Value("${app.name}")
private String appName;
@Value("${app.version}")
private String appVersion;
@Value("${app.description:Default Description}") // Значение по умолчанию
private String appDescription;
public void printConfig() {
System.out.println("Name: " + appName);
System.out.println("Version: " + appVersion);
System.out.println("Description: " + appDescription);
}
}
41. Как работает DispatcherServlet? #
DispatcherServlet - это основной компонент Spring MVC, который обрабатывает входящие HTTP-запросы, направляет их к соответствующим контроллерам, обрабатывает ответы и возвращает их клиенту. Это центральный обработчик HTTP-запросов в Spring MVC
Основной цикл работы DispatcherServlet
Этап 1. Получение HTTP-запроса - DispatcherServlet обрабатывает все запросы, поступающие в приложение (если настроен как фронт-контроллер в web.xml, через Java Config или автоконфигурацию)
Этапы 2-3. Определение подходящего контроллера - с помощью HandlerMapping находит контроллер, который должен обработать запрос, основываясь на URL и HTTP-методе. С помощью аннотаций контроллера (@RequestMapping, @GetMapping, @PostMapping и др.) определяется, какой метод должен быть вызван
Этапы 4-5. Обработка запроса - DispatcherServlet вызывает контроллер через HandlerAdapter, контроллер выполняет бизнес-логику и возвращает ModelAndView или объект в формате JSON/XML (например, с @ResponseBody)
Этапы 6-7. Обработка ответа - результат передается в компонент ViewResolver, который определяет, какое представление (HTML, JSP, JSON и др.) нужно отрендерить
Этапы 8-9. Рендеринг представления
Этап 10. Возврат клиенту - готовый HTTP-ответ отправляется обратно клиенту

42. Варианты обработки Exceptions в Spring #
1. Методы с аннотацией @ExceptionHandler в контроллере
- используются для обработки исключений на уровне контроллера
- позволяют привязать обработку конкретных исключений к определённому методу в контроллере
@RestController
public class MyController {
...
@ExceptionHandler(IllegalArgumentException.class)
public ResponseEntity<String> handleIllegalArgument(IllegalArgumentException e) {
return ResponseEntity.badRequest().body("Error: " + e.getMessage());
}
}
2. Методы с аннотацией @ExceptionHandler в @ControllerAdvice для централизованной глобальной обработки исключений
@ControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(IllegalArgumentException.class)
public ResponseEntity<String> handleIllegalArgument(IllegalArgumentException e) {
return ResponseEntity.badRequest().body("Global Error: " + e.getMessage());
}
}
3. ResponseStatusException позволяет выбрасывать исключения с указанием HTTP-статуса и сообщения
@GetMapping("/error")
public void error() {
throw new ResponseStatusException(HttpStatus.NOT_FOUND, "Resource not found");
}
4. @ResponseStatus на классе исключения
@ResponseStatus(HttpStatus.NOT_FOUND)
public class ResourceNotFoundException extends RuntimeException {
public ResourceNotFoundException(String message) {
super(message);
}
}
@GetMapping("/resource")
public void getResource() {
throw new ResourceNotFoundException("Resource not found");
}
5. HandlerExceptionResolver
- низкоуровневый подход для полного контроля над обработкой исключений.
- требует реализации интерфейса
HandlerExceptionResolver
@Component
public class CustomExceptionResolver implements HandlerExceptionResolver {
@Override
public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
try {
response.sendError(HttpServletResponse.SC_BAD_REQUEST, ex.getMessage());
} catch (IOException e) {
e.printStackTrace();
}
return new ModelAndView();
}
}
6. RestTemplate и WebClient
Для обработки ошибок при выполнении REST-запросов можно использовать кастомные обработчики ошибок, такие как ResponseErrorHandler для RestTemplate
RestTemplate restTemplate = new RestTemplate();
restTemplate.setErrorHandler(new DefaultResponseErrorHandler() {
@Override
public void handleError(ClientHttpResponse response) throws IOException {
// Обработка ошибки
}
});
Как транзакционная логика обрабатывает исключения?
Spring использует транзакционный менеджер для обработки транзакций. Исключения в транзакционной логике влияют на коммит или откат (rollback) транзакции.
Основные принципы обработки исключений в транзакциях:
Rollback для
RuntimeExceptionиError:- По умолчанию, Spring откатывает транзакцию при возникновении
RuntimeExceptionилиError. - Исключения типа
Checked Exception(например,SQLException) не приводят к откату транзакции, если это явно не указано.
- По умолчанию, Spring откатывает транзакцию при возникновении
Настройка rollback: Можно указать, какие исключения должны приводить к откату.
Пример:
@Transactional(rollbackFor = Exception.class)
public void performTransaction() throws Exception {
// Если выбрасывается Exception, транзакция откатывается
throw new Exception("Transaction failed");
}
Сохранение состояния после исключения: Если транзакция откатывается, изменения, внесённые в базу данных в рамках транзакции, не сохраняются.
Работа с
@Transactional:- Если транзакция завершается с ошибкой, контейнер Spring вызывает метод
rollbackу используемого транзакционного менеджера. - Для методов
privateили вызовов внутри того же класса обработка транзакций может не сработать.
- Если транзакция завершается с ошибкой, контейнер Spring вызывает метод
Пример:
@Transactional
public void process() {
try {
performAction();
} catch (Exception e) {
// Исключение обработано, транзакция откатывается
throw e;
}
}
private void performAction() {
// Действия, которые вызывают исключение
throw new RuntimeException("Error in action");
}
Основные проблемы и решения:
| Проблема | Решение |
|---|---|
Внутренний вызов метода с @Transactional. | Использовать прокси или вызывать метод через другой бин. |
Исключение Checked Exception не приводит к rollback. | Указать rollbackFor в аннотации @Transactional. |
| Повторное использование транзакции. | Указать параметр propagation, например, Propagation.REQUIRES_NEW для открытия новой транзакции. |
43. @Configuration. Является Configuration компонентом? Какую доп логику Configuration реализует? #
Аннотация @Configuration является специализированным компонентом Spring. Она указывает, что класс используется для конфигурации приложения, включая создание бинов. Обрабатывается аналогично аннотации @Component
Дополнительная логика, реализуемая @Configuration
1. Определение бинов (@Bean) - классы с @Configuration позволяют определять методы, помеченные как @Bean, которые возвращают бины
@Configuration
public class AppConfig {
@Bean
public MyService myService() {
return new MyService();
}
}
2. Поддержка прокси (proxyBeanMethods = true) - по умолчанию Spring оборачивает класс с @Configuration в прокси для обеспечения корректного создания и управления зависимостями. Это позволяет ссылаться на один и тот же экземпляр бина
@Configuration
public class AppConfig {
@Bean
public ServiceA serviceA() {
return new ServiceA(serviceB());
}
@Bean
public ServiceB serviceB() {
return new ServiceB();
}
}
При вызове serviceB() внутри метода serviceA() контейнер Spring возвращает тот же экземпляр ServiceB
3. Логика для импорта конфигурации - класс с @Configuration может импортировать другие классы конфигурации через @Import
@Configuration
@Import(AnotherConfig.class)
public class AppConfig {}
** 4. Поддержка дополнительных аннотаций** - в классах с @Configuration можно использовать такие аннотации, как @ComponentScan, @PropertySource, и другие
@Configuration vs @Component
Аннотации @Configuration и @Component в Spring используются для регистрации бинов в контейнере, но они имеют разные цели и поведение
| Критерий | @Configuration | @Component |
|---|---|---|
| Назначение | Используется для создания конфигурационных классов и бинов | Используется для обозначения обычного Spring-компонента |
| Создание бинов | Определяет бины через методы с аннотацией @Bean | Бины создаются автоматически (через сканирование классов) |
| Прокси (proxyBeanMethods) | По умолчанию true, что обеспечивает singleton для бинов | Прокси не используется |
| Семантика | Обозначает, что класс содержит настройки для приложения | Описывает логический компонент приложения |
| Импорт конфигураций | Поддерживает @Import для подключения других конфигураций | Не поддерживает |
| Использование вместе с @Bean | Методы с @Bean гарантируют создание одного экземпляра (singleton) | Методы с @Bean вызываются напрямую стандартным способом |
| Где использовать | В конфигурационных классах для определения настроек и бинов | В служебных классах, сервисах, репозиториях и т.д. |
44. @PostConstruct #
Метод, помеченный аннотацией @PostConstruct определяется спрингом как init-метод для бина, который должен быть выполнен после того, как бин был полностью создан и его зависимости внедрены. Этот метод вызывается один раз для каждого экземпляра бина
Сколько @PostConstruct можно задекларировать в бине?
Можно задекларировать только один метод с @PostConstruct в одном бине. Если попытаться добавить несколько методов с этой аннотацией, Spring выбросит ошибку
45. Dependency management в Spring boot #
В Spring Boot используется механизм Maven или Gradle для автоматического добавления, обновления и совместимости библиотек
- Spring Boot Starter Dependencies - группы зависимостей, которые включают всё необходимое для работы с определённым функционалом
- Spring Boot Dependency Management:
- использует BOM (Bill of Materials) для управления версиями зависимостей
- версии зависимостей определяются в родительском POM (
spring-boot-dependencies), что гарантирует их совместимость
<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>3.0.0</version> </parent>- в Gradle для этого используется плагин
io.spring.dependency-management
- Профили зависимостей - можно включать зависимости в зависимости от среды выполнения (например, dev, test, prod) с помощью профилей Maven
- Явное указание зависимости - Spring Boot позволяет переопределить версию зависимости, если требуется особая
46. Spring Security, как хранить пароль пользователя? #
Для хранения паролей используют хэширование - исходную строку преобразуют в хэш по криптостойким алгоритмам (восстановить оригинальный текст крайне сложно или невозможно), после чего сохраняют в базу данных.
Как хранить пароль в Spring Security?
- Хэширование с помощью BCrypt - Spring Security предоставляет поддержку алгоритма BCrypt, который подходит для хэширования паролей
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
При сохранении пароля:
@Autowired
private PasswordEncoder passwordEncoder;
public void saveUser(String rawPassword) {
String hashedPassword = passwordEncoder.encode(rawPassword);
// Сохранение hashedPassword в базе данных
}
- Проверка пароля - при аутентификации введённый пароль сравнивается с хэшированным значением в базе данных
boolean matches = passwordEncoder.matches(rawPassword, hashedPassword);
Почему BCrypt?
- Защита от атак с помощью “радужных таблиц” - BCrypt автоматически добавляет случайную “соль” к каждому паролю, чтобы одинаковые пароли имели разные хэши
- Замедление - BCrypt есть возможность настройки сложности (work factor), что увеличивает время вычисления хэша, усложняя атаки
Сравнение с другими современными алгоритмами хэширования
| Алгоритм | CPU-hard | Memory-hard | GPU resistance | Популярность |
|---|---|---|---|---|
| BCrypt | Да | Нет | средняя | очень высокая |
| PBKDF2 | Да | Нет | слабее | enterprise/FIPS |
| Argon2 | Да | Нет | высокая | современный best practice |
47. Spring Profiles как с ними работать? #
Профили Spring позволяют загружать разные настройки и компоненты в зависимости от окружения (разработка, тестирование, прод)
Как установить профиль?
- В
application.properties/yml
spring:
profiles:
active: dev
- Через аргументы JVM (
-Dspring.profiles.active=prod) Можно передавать профиль при запуске:
java -jar myapp.jar --spring.profiles.active=prod
Как создать разные настройки для профилей?
Разные файлы
application-{profile}.propertiesapplication-dev.propertiesapplication-prod.propertiesapplication-test.properties
Использование
@Profileв коде
@Component
@Profile("dev")
public class DevDataSource implements DataSourceConfig {
@Override
public void setup() {
System.out.println("DEV database setup");
}
}
@Component
@Profile("prod")
public class ProdDataSource implements DataSourceConfig {
@Override
public void setup() {
System.out.println("PROD database setup");
}
}
Если активен профиль dev, то создастся DevDataSource, иначе - ProdDataSource. Можно комбинировать профили через операторы &, |, !,, например
@Component
@Profile("prod & some-other-profile")
public class ProdDataSource implements DataSourceConfig {
...
}
*Примечание. Существует также аннотация@IfProfileValue`, однако она применима только к тестовым классам или методам тестовых классов. Позволяет включать/выключать выполнение конкретных тестов в зависимости настроек среды, т.е. напрямую не связана с профилями Spring*
@IfProfileValue(name = "java.vendor", value = "Oracle Corporation")
public void testSomething() {
// ...
}
48. Виды proxy в Spring #
Spring использует динамическое проксирование для создания AOP-обёрток над бинами
| Тип Proxy | Описание | Как создается? |
|---|---|---|
| JDK Dynamic Proxy | Использует java.lang.reflect.Proxy | Для интерфейсов |
| CGLIB Proxy | Использует net.sf.cglib.proxy | Для классов |
| AspectJ Proxy | Используется в Spring AOP | Нужно доп. подключение |
JDK Dynamic Proxy (java.lang.reflect.Proxy)
- Используется только для интерфейсов.
- Создает прокси-класс, который реализует интерфейс.
public interface Service {
void doWork();
}
public class ServiceImpl implements Service {
public void doWork() {
...
}
}
Создание динамического прокси вручную:
Service proxy = (Service) Proxy.newProxyInstance(
Service.class.getClassLoader(),
new Class[]{Service.class},
(proxyObj, method, args) -> {
System.out.println("Логирование перед вызовом метода...");
return method.invoke(new ServiceImpl(), args);
}
);
proxy.doWork();
CGLIB Proxy (net.sf.cglib.proxy)
- Используется для классов (даже если нет интерфейса).
- Создает подкласс оригинального класса
class UserService {}
class UserService$$EnhancerBySpring extends UserService {}
Создание CGLIB-прокси вручную:
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(Service.class);
enhancer.setCallback((MethodInterceptor) (obj, method, args, proxy) -> {
System.out.println("Логирование перед вызовом метода...");
return proxy.invokeSuper(obj, args);
});
Service proxy = (Service) enhancer.create();
proxy.doWork();
Нельзя проксировать:
finalклассыfinalметодыprivateметоды
CGLIB vs Dynamic Proxy
| Функция | JDK Dynamic Proxy | CGLIB Proxy |
|---|---|---|
| Где работает? | Только с интерфейсами | Работает с классами |
| Как создается? | java.lang.reflect.Proxy | net.sf.cglib.proxy.Enhancer |
| Наследование | Нельзя наследовать логику класса | Создает подкласс |
| Производительность | Чуть быстрее | Немного медленнее |
Hibernate (а значит и Spring Data JPA), Mockito и сам Spring (в немногочисленных случаях) использует более современный ByteBuddy. Эта библиотека объединяет возможности обоих видов прокси + дополняет их возможность изменения (enhance) уже скомпилированного байткода.
Какое proxy по-умолчанию в Spring? Если проксируемый бин реализует хотя бы один интерфейс, то используется JDK Dynamic Proxy, если нет, то CGLIB.
Когда используется CGLIB?
- Если класс не реализует интерфейс
- Если явно указать:
-
@EnableAspectJAutoProxy(proxyTargetClass = true)-spring.aop.proxy-target-class=true
49. @Lookup #
@Lookup - это аннотация в Spring, позволяющая в singleton-бине получать новый экземпляр prototype-бина при каждом вызове метода. Без @Lookup при внедрении prototype-бина в singleton он создаётся только один раз.
Как это работает
- Помечаешь метод в singleton-классе аннотацией
@Lookup - Spring динамически переопределяет этот метод (через CGLIB)
- При каждом вызове метода Spring запрашивает новый
prototype-бин из контекста.
@Component
@Scope("prototype")
public class PrototypeBean {
public void doSomething() {
System.out.println("PrototypeBean: " + this);
}
}
@Component
public abstract class SingletonBean {
public void process() {
// Каждый раз получаем новый экземпляр PrototypeBean
PrototypeBean prototypeBean = getPrototypeBean();
prototypeBean.doSomething();
}
@Lookup
protected abstract PrototypeBean getPrototypeBean();
}
Зачем нужно?
- Когда в
singleton-бине надо часто создаватьprototype-объекты - Чтобы не использовать вручную
ApplicationContext.getBean() - Для избежания утечек памяти (прототипы не живут вечно внутри синглтона)
50. Как работает BeanPostProcessor. Почему есть before и after initialization #
В Spring BeanPostProcessor - это специальный интерфейс, который позволяет «перехватывать» создаваемые спрингом бины и модифицировать их до и после его инициализации.
public interface BeanPostProcessor {
// Вызывается до выполнения любых init-методов бина (@PostConstruct, InitializingBean.afterPropertiesSet, init-method)
Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException;
// Вызывается после init-методов
Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException;
}
Зачем два метода - before и after?
postProcessBeforeInitializationпозволяет подготовить бин до запуска его собственных методов инициализации (@PostConstruct,afterPropertiesSet,init-method)postProcessAfterInitializationвызывается после того, как бин полностью готов к работе: все свойства установлены, init-методы выполнены. Наиболее частый случай применения - оборачивание бина в прокси (например, для@Transactional,@Async)
Пример своего BeanPostProcessor
@Component
public class LoggingBeanPostProcessor implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String name) {
System.out.println("Before init: " + name);
return bean; // можно вернуть обёртку или другой объект
}
@Override
public Object postProcessAfterInitialization(Object bean, String name) {
System.out.println("After init: " + name);
// Если бин имеет аннотацию @MyTransactional — возвращаем прокси
if (bean.getClass().isAnnotationPresent(MyTransactional.class)) {
return ProxyFactory.getProxy(bean);
}
return bean;
}
}
Все зарегистрированные BeanPostProcessor выполняются в порядке их PriorityOrdered/Ordered или по умолчанию в порядке регистрации.
51. @Lazy #
@Lazy - аннотация Spring, которая позволяет
- откладывать создание бина до момента его первого использования вместо загрузки при старте контекста
- откладывать внедрение зависимости (см. [[#19. Циклическая зависимость]])
Может быть применена:
- на уровне объявления класса-бина
- на уровне метода
@Bean - на уровне точки внедрения
Как это работает под капотом
- При ленивой инициализации Spring создаёт не сам объект, а прокси (proxy).
- Прокси удерживает ссылку на фабрику бинов и при первом вызове любого метода инициализирует реальный объект и делегирует вызов ему.
- В случае
@Lazyнад точкой внедрения бин инициализируется сразу, но внедрение происходит позже
Можно применить @Lazy для всего приложения сразу, тогда все бины будут инициализироваться лениво
spring.main.lazy-initialization=true
52. @ControllerAdvice #
Аннотация для вынесения общей логики аспектов, применяемой ко всем контроллерам.
Основные задачи
- глобальная обработка ошибок (
@ExceptionHandler) - общие данные для всех контроллеров (
@ModelAttribute) - общие биндеры (валидация, конвертация) через
@InitBinder
Особенности
- по умолчанию работает для всех контроллеров, можно ограничить область (
basePackages,assignableTypes) - Для REST-API используют
@RestControllerAdvice(автоматически добавляет@ResponseBody)
53. Как устроен Spring Security #
Фреймворк для аутентификации и авторизации. Работает как цепочка фильтров (Servlet Filters), которая перехватывает каждый запрос.
Основные компоненты
- SecurityFilterChain - набор фильтров, через которые проходит каждый запрос. Примеры фильтров:
-
UsernamePasswordAuthenticationFilter(логин/пароль) -BearerTokenAuthenticationFilter(JWT токен) - Authentication - объект, представляющий пользователя после входа:
-
principal(сам пользователь, чащеUserDetails) -credentials(пароль или токен) -authorities(роли/права) - AuthenticationManager - точка входа для проверки логина/пароля или токена, делегирует проверку в AuthenticationProvider
- AuthenticationProvider - конкретная логика проверки (например, сверка пароля с БД)
- UserDetailsService - загружает данные пользователя (по имени/почте), возвращает объект
UserDetailsс логином, паролем и ролями - Authorization - после входа система проверяет роли/права. Используются аннотации:
-
@PreAuthorize("hasRole('ADMIN')")-@Secured("ROLE_USER")
Поток обработки запроса
- Запрос приходит в приложение
- Проходит через SecurityFilterChain
- Фильтр (например,
UsernamePasswordAuthenticationFilter) пытается аутентифицировать AuthenticationManagerвызываетAuthenticationProviderUserDetailsServiceзагружает пользователя, сверяет пароль- Если успех, создаётся
Authentication, кладётся в SecurityContext - Далее проверяется авторизация (права/роли)
- Контроллер получает запрос
**Пример конфигурации (новый стиль, Spring Security 5.7+)
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(auth -> auth
.requestMatchers("/admin/**").hasRole("ADMIN")
.anyRequest().authenticated()
)
.formLogin(withDefaults()) // логин/пароль форма
.httpBasic(withDefaults()); // базовая авторизация
return http.build();
}
@Bean
public UserDetailsService users() {
UserDetails user = User.withUsername("user")
.password("{noop}password") // {noop} = без шифрования
.roles("USER")
.build();
return new InMemoryUserDetailsManager(user);
}
}