Spring

Spring #

1. Зачем мы используем Spring? Почему его так любят? #

Spring — это один из самых популярных фреймворков для разработки приложений на Java. Его используют благодаря широкому набору возможностей, гибкости и модульности, что упрощает разработку корпоративных приложений.


2. Inversion of Control #

Это принцип, при котором мы передаем управление созданием и настройкой объектов Spring-у


3. Dependency Injection #

Dependency Injection (внедрение зависимостей) — это способ предоставления объекту его зависимостей извне, вместо того чтобы объект создавал их самостоятельно. DI упрощает управление зависимостями и делает код более модульным, тестируемым и легко расширяемым.


Dependency Injection vs dependency lookup

КритерийDependency InjectionDependency Lookup
СутьЗависимости предоставляются извне (контейнером или фреймворком).Объект сам запрашивает зависимости (чаще из контекста).
Простота тестированияЛегче тестировать, зависимости можно заменять mock-объектами.Требует мокирования контекста или фабрики для тестов.
Уровень контроляКонтейнер управляет зависимостями, объект их не создаёт.Объект сам решает, как получить зависимости.
ПримерSpring внедряет зависимость через аннотации или XML-конфигурацию.Вручную вызов метода ApplicationContext.getBean(...).

Какие способы внедрения зависимостей знаешь?

Существует три способа внедрения зависимостей:

  • Через конструктор
  • Через сеттер
  • Через поля
СпособОписаниеПлюсыМинусыПримеры использования
Через конструкторЗависимости передаются при создании объекта.- Гарантия неизменяемости. - Обязательные зависимости явно видны. - Высокая тестируемость.- Неудобно для большого числа зависимостей. - Не подходит для опциональных зависимостей.Когда зависимости обязательны и не изменяются после создания.
Через setterЗависимости устанавливаются с помощью метода после создания объекта.- Подходит для опциональных зависимостей. - Зависимости можно изменять в runtime.- Возможна ситуация, когда объект используется без установленной зависимости. - Менее явный.Когда зависимости опциональны или могут изменяться.
Через поля (field)Зависимости внедряются напрямую в поля объекта, обычно с помощью аннотаций.- Минимум кода. - Простота при использовании фреймворков (Spring, CDI).- Сложнее тестировать. - Нарушение принципа явности зависимостей. - Отсутствие контроля.Быстрое внедрение в Spring через @Autowired.

Почему лучше использовать конструктор?

  • Неизменяемость: Зависимости устанавливаются один раз при создании объекта, и их невозможно изменить.
  • Обязательность зависимостей: Устанавливает чёткое требование, что объект не может существовать без переданных зависимостей.
  • Тестируемость: Проще создавать mock-объекты для тестов.
  • Явность: Видно, какие зависимости требуются для работы объекта.

Когда пригодится способ внедрения через setter?

  • Опциональные зависимости: Зависимость не обязательна для работы объекта и используется только в некоторых сценариях.
  • Динамическое изменение зависимостей: Когда требуется сменить зависимость во время выполнения программы.
  • Библиотеки или старый код: Для интеграции с кодом, где нельзя контролировать процесс создания объектов.

4. @Autowired #

Автоматически внедряет бины. Аннотацией @Autowired помечают:

  • Сеттер
  • Поле
  • Конструктор

5. @Qualifier vs @Primary #

Если есть два одинаковых бина (по типу и имени) Spring не знает какой именно использовать и выдает exception. Если над одним из этих бинов установлена @Primary, то его использовать предпочтительнее. Но если нам нужно использовать в работе оба этих бина, можно над каждым поставить @Qualifier и задать имя, для идентификации этих бинов (работает только с @Autowired)

АннотацияОписаниеКогда использовать
@PrimaryУказывает бин по умолчанию при выборе среди нескольких.Когда нужен бин, который используется чаще всего.
@QualifierЯвно указывает, какой бин использовать среди нескольких.Когда нужно внедрить конкретный бин, несмотря на наличие @Primary.

6. Что такое Bean? #

Bean в Spring — это объект, управляемый контейнером Spring IoC. Это основной строительный блок приложения, который регистрируется и конфигурируется контейнером.


@Bean

Аннотация @Bean используется для указания метода, который возвращает объект(бин) и эти бины в дальнейшем можно внедрять в другие компоненты.

Аннотация @Bean используется для явного объявления бина в Java-конфигурации (в классе, помеченном как @Configuration).

Также можно использовать методы по умолчанию для определения бинов. Это позволяет создавать конфигурации бинов путем реализации интерфейсов с определениями бинов в методах по умолчанию.

public interface BaseConfig {
    @Bean
    default TransferServiceImpl transferService() {
        return new TransferServiceImpl();
    }
}
@Configuration
public class AppConfig implements BaseConfig {
}

Что такое BeanDefinition?

BeanDefinition — это метаданные, описывающие, как должен быть создан, конфигурирован и управляем конкретный бин.

Он содержит:

  • Класс бина.
  • Его скоуп (например, singleton или prototype).
  • Зависимости.
  • Методы инициализации и уничтожения.

Как Spring создает бины?

  • Чтение конфигурации:
    • Spring считывает конфигурацию из @Configuration, XML или аннотаций на классах.
  • Создание BeanDefinition:
    • На основе конфигурации Spring формирует BeanDefinition для каждого бина.
  • Инициализация бина:
    • Вызывается конструктор или фабричный метод.
    • Выполняется внедрение зависимостей (через конструктор, поля или сеттеры).
  • Обработка бина:
    • Постобработка через BeanPostProcessor (например, для работы с @Autowired или @PostConstruct).
  • Добавление в контейнер:
    • Готовый бин помещается в ApplicationContext.

Как конфигурируется бин?

  1. Аннотации (@Bean, @Component, @Value, @Scope).
  2. XML-конфигурацию.
  3. Java-код (в классе с @Configuration).
  4. Средства типа @PostConstruct и @PreDestroy для управления жизненным циклом.

В чем проблема того, что мы не можем final поле сделать в Bean-е?

Если в бине есть final поле, то оно должно быть инициализировано в момент создания объекта. Spring может не знать значения для этого поля на этапе создания объекта, так как внедрение зависимостей происходит после вызова конструктора (например, через @Autowired).


Можно вставить Bean в статическое поле?

Нет, Spring не поддерживает внедрение зависимостей в статические поля напрямую. Решение: Используйте вручную сеттер или вызовите контекст через ApplicationContext.


Как сделать ленивую инициализацию бина?

Используйте аннотацию @Lazy.


Какие есть способы донастройки бина?

Spring предоставляет несколько способов для настройки (customization) бинов перед их использованием в приложении.

1️⃣ Способы донастройки бина

МетодОписание
@PostConstructМетод вызывается после создания бина и внедрения зависимостей
@PreDestroyМетод вызывается перед удалением бина
InitializingBean#afterPropertiesSet()Метод вызывается после установки свойств
DisposableBean#destroy()Метод вызывается перед уничтожением бина
@Bean(initMethod, destroyMethod)Указывает методы инициализации и удаления
BeanPostProcessorПозволяет изменять бины до и после инициализации
ApplicationContextAwareДает доступ к ApplicationContext внутри бина

📌 1. @PostConstruct и @PreDestroy

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

@Component
public class MyBean {
    @PostConstruct
    public void init() {
        System.out.println("Бин создан!");
    }

    @PreDestroy
    public void destroy() {
        System.out.println("Бин будет удален!");
    }
}

📌 Вывод:

Бин создан!
Бин будет удален!

📌 2. Реализация InitializingBean и DisposableBean

Интерфейсы, которые позволяют управлять жизненным циклом бина.

@Component
public class MyBean implements InitializingBean, DisposableBean {
    @Override
    public void afterPropertiesSet() {
        System.out.println("Бин инициализирован!");
    }

    @Override
    public void destroy() {
        System.out.println("Бин уничтожается!");
    }
}

Используется редко, так как @PostConstruct и @PreDestroy удобнее.

📌 3. @Bean(initMethod, destroyMethod)

В @Configuration можно указать методы инициализации и уничтожения бина.

@Configuration
public class AppConfig {
    @Bean(initMethod = "init", destroyMethod = "cleanup")
    public MyBean myBean() {
        return new MyBean();
    }
}

class MyBean {
    public void init() {
        System.out.println("Инициализация через initMethod");
    }

    public void cleanup() {
        System.out.println("Удаление через destroyMethod");
    }
}

📌 Вывод:

Инициализация через initMethod
Удаление через destroyMethod

📌 4. BeanPostProcessor

Позволяет изменять бины до и после их инициализации.

@Component
public class CustomBeanPostProcessor implements BeanPostProcessor {
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) {
        System.out.println("Before init: " + beanName);
        return bean;
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) {
        System.out.println("After init: " + beanName);
        return bean;
    }
}

📌 Вывод:

Before init: myBean
After init: myBean

📌 5. ApplicationContextAware

Дает доступ к ApplicationContext внутри бина.

@Component
public class MyBean implements ApplicationContextAware {
    private ApplicationContext applicationContext;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) {
        this.applicationContext = applicationContext;
        System.out.println("ApplicationContext установлен!");
    }
}

✅ Полезно для получения других бинов внутри класса.


Как создать несколько бинов одного типа Singleton?

По умолчанию в Spring бины создаются в единственном экземпляре (Singleton). Однако, можно создать несколько бинов одного типа.

📌 1. Разные имена в @Bean

@Configuration
public class AppConfig {
    @Bean("bean1")
    public MyBean myBean1() {
        return new MyBean("Первый бин");
    }

    @Bean("bean2")
    public MyBean myBean2() {
        return new MyBean("Второй бин");
    }
}

class MyBean {
    private String name;

    public MyBean(String name) {
        this.name = name;
    }
}

📌 Теперь можно получить два разных бина:

@Autowired
@Qualifier("bean1")
private MyBean myBean1;

📌 2. Использование @Primary и @Qualifier

Если есть несколько бинов одного типа, можно указать приоритетный бин с @Primary.

@Component
@Primary
public class FirstService implements MyService {}

@Component
public class SecondService implements MyService {}

📌 Теперь @Autowired будет использовать FirstService.
Если нужен SecondService, то используем @Qualifier:

@Autowired
@Qualifier("secondService")
private MyService myService;

📌 3. Использование @Profile

Можно загружать разные бины в зависимости от профиля окружения.

@Component
@Profile("dev")
public class DevBean implements MyBean {}

@Component
@Profile("prod")
public class ProdBean implements MyBean {}

📌 Запуск с -Dspring.profiles.active=dev создаст DevBean, а с -Dspring.profiles.active=prodProdBean.


Где хранятся бины?

📌 Контейнер Spring – ApplicationContext

Spring создает и хранит бины в контейнере IoC (ApplicationContext).
📌 Когда Spring загружается, он:

  1. Сканирует классы с аннотациями @Component, @Service, @Repository.
  2. Создает бины и помещает их в ApplicationContext.
  3. Управляет их жизненным циклом (инициализация, использование, удаление).

📌 Как получить бин из ApplicationContext?

ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
MyBean myBean = context.getBean(MyBean.class);

📌 Как вывести все бины?

String[] beanNames = context.getBeanDefinitionNames();
for (String beanName : beanNames) {
    System.out.println(beanName);
}

✅ Выведет все бины, зарегистрированные в контейнере.


7. Скоуп бинов. Web скоуп #

  • Singleton

    • Для каждого бина создается только один экземпляр на весь контекст приложения и переиспользуется везде, где он требуется
    • Используется по-умолчанию
    • Жизненный цикл бина начинается при загрузке контекста, и заканчивается при его завершении
  • Prototype

    • Каждый раз, когда запрашивается бин, создается новый экземпляр
    • Бины со скоупом prototype не уничтожаются Spring, так как они выходят из его контроля сразу после создания
    • НЕ ХРАНЯТСЯ в контексте Spring-а

Web скоуп:

СкоупОписаниеЖизненный цикл
requestСоздаётся один экземпляр бина на каждый HTTP-запрос.Бин существует только в рамках одного HTTP-запроса.
sessionСоздаётся один экземпляр бина на каждую HTTP-сессию.Бин живёт в течение одной HTTP-сессии.
applicationСоздаётся один экземпляр бина на весь ServletContext (аналогично синглтону, но для веб-приложения).Бин существует в рамках всего времени работы веб-приложения.
websocketСоздаётся один экземпляр бина на каждую WebSocket-сессию.Бин живёт в течение времени существования WebSocket-сессии.

Как создать свой Скоуп

  1. Реализуйте интерфейс 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);
    }
    // Реализуйте другие методы...
}
  1. Зарегистрируйте его в контексте:
@Bean
public static CustomScopeConfigurer customScopeConfigurer() {
    CustomScopeConfigurer configurer = new CustomScopeConfigurer();
    configurer.addScope("customScope", new CustomScope());
    return configurer;
}

8. Жизненный цикл Bean. Вопросы связанные с классами входящими в жизненный цикл бина #

Spring управляет созданием, инициализацией и уничтожением бинов в рамках контейнера IoC. Этот жизненный цикл можно разделить на несколько этапов.

Создание бина -> Внедрение зависимостей -> Постобработка -> Инициализация -> Использование -> Уничтожение

  • Сначала мы предоставляем наши Bean Definitions IoC контейнеру
  • IoC-контейнер сортирует Bean Definitions чтобы сначала создавать те, которые не имеют зависимостей, и затем те, которые зависят от других бинов
  • Затем итерируется по всем Bean Definitions и инициализирует их
  • Далее вызываются PostConstruct методы
  • Перед и после инициализации вызываются методы BeanPostProcessor
  • На выходе получаем готовый бин. Если у этого бина скоуп singleton - храним его в ассоциативном массиве IoC контейнера. Если же другой скоуп - бин сразу же возвращается
  • Для всех бинов, которые находятся в IoC контейнере, перед его завершением вызывается @PreDestroy метод
ЭтапОписаниеМетоды/Аннотации
Создание бинаSpring создает экземпляр бина, используя конструктор или фабричный метод.Конструктор бина (или фабричный метод).
Внедрение зависимостейПосле создания, Spring внедряет все необходимые зависимости (через конструктор, поля, или сеттеры).@Autowired, @Inject, конструкторы, сеттеры.
ПостобработкаБин проходит через постобработчики перед инициализацией. Это этап, на котором можно изменить состояние бина.BeanPostProcessorpostProcessBeforeInitialization, postProcessAfterInitialization.
ИнициализацияЕсли бин реализует интерфейсы InitializingBean или использует аннотации @PostConstruct, вызываются методы инициализации.afterPropertiesSet(), @PostConstruct, пользовательские методы инициализации через @Bean(initMethod).
ИспользованиеБин доступен для использования в приложении. Это этап, когда бин полностью готов для работы.Использование бина через @Autowired или прямые вызовы методов.
УничтожениеКогда контейнер Spring уничтожает бин (особенно для синглтонов), вызываются методы очистки.destroy(), @PreDestroy, пользовательские методы уничтожения через @Bean(destroyMethod).

9. @ComponentScan #

Аннотация @ComponentScan указывает Spring где искать классы, помеченные аннотацией @Component или его производной (@RestController, @Controller, @Repository, @Service и т.д)


10. @Conditional в Spring #

Аннотация @Conditional в Spring используется для того, чтобы условно подключать бины или выполнять конфигурации только при соблюдении определённых условий. Она позволяет гибко управлять поведением приложения, включая или исключая определенные компоненты на основе внешних или внутренних условий.


Что такое @ConditionalOnBean, @ConditionalOnProperty?

1. @ConditionalOnBean

Аннотация @ConditionalOnBean указывает, что бин или конфигурация должны быть зарегистрированы в контексте только если определённый бин уже существует.

Основное использование

Вы хотите зарегистрировать бин или выполнить конфигурацию только в том случае, если в контексте приложения уже есть бин определённого типа или с определённым именем.

@Configuration
public class MyConfig {

    @Bean
    public MyService myService() {
        return new MyService();
    }

    @Bean
    @ConditionalOnBean(MyService.class)
    public DependentService dependentService() {
        return new DependentService();
    }
}
  • Если бин MyService существует в контексте, будет создан бин DependentService.
  • Если бина MyService нет, DependentService не будет создан.

2. @ConditionalOnProperty

Аннотация @ConditionalOnProperty указывает, что бин или конфигурация должны быть зарегистрированы в контексте только если определённое свойство в файле конфигурации имеет нужное значение.

Основное использование

Вы хотите активировать или деактивировать бин в зависимости от свойства, определённого в файле конфигурации (application.properties или application.yml).

# 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();
    }
}
  • Если feature.enabled=true в конфигурации, бин FeatureService будет зарегистрирован.
  • Если feature.enabled=false или свойство отсутствует, бин не будет создан (если matchIfMissing=false).
АннотацияУсловие регистрации бинаТип проверки
@ConditionalOnBeanПроверяет наличие другого бина в контекстеПроверка наличия бина
@ConditionalOnPropertyПроверяет значение свойства из конфигурации (application.properties или application.yml)Проверка значения свойства

11. @Service, @Repository, @Component #

АннотацияОписаниеИспользованиеСемантика
@ComponentОбщая аннотация для обозначения компонента, который будет управляться контейнером Spring.Для обычных бинов.Может быть использована для любых типов компонентов. Обычно используется, когда нет более точного типа.
@ServiceСпециализация @Component, указывающая, что бин представляет собой сервисный слой.Для сервисов.Используется для классов, реализующих бизнес-логику. По сути, это более семантическое обозначение, чем @Component.
@RepositoryСпециализация @Component, указывающая, что бин представляет собой слой доступа к данным (DAO).Для классов, работающих с данными.Используется для классов, которые инкапсулируют логику доступа к данным (например, через JDBC или JPA). Аннотация также может активировать обработку исключений, специфичных для DAO.

Singleton-антипаттерн. Если мы на класс поставим @Service, это будет Singleton?

Если вы ставите аннотацию @Service, Spring создаёт бин с по умолчанию синглтон. Однако это не является Singleton-антипаттерном, поскольку Spring управляет жизненным циклом и внедрением зависимостей, что решает многие проблемы, присущие обычному Singleton.


@Bean vs @Component

1️⃣ @Bean

  • Используется в конфигурационных классах (@Configuration).
  • Определяет бин программно (через метод).
  • Позволяет настраивать бин перед его созданием.

📌 Пример:

@Configuration
public class AppConfig {
    @Bean
    public MyService myService() {
        return new MyServiceImpl();
    }
}

Когда использовать?

  • Если создание бина требует сложной логики.
  • Если бин не может быть помечен @Component (например, библиотечный класс).

2️⃣ @Component

  • Используется для автоматического сканирования компонентов (@ComponentScan).
  • Позволяет Spring автоматически обнаруживать бины.

📌 Пример:

@Component
public class MyServiceImpl implements MyService {
}

Когда использовать?

  • Если класс управляется Spring и его можно автоматически найти.

🔹 Основные отличия

Характеристика@Bean@Component
Где используется?В @Configuration классеНа классе
Как создается бин?ПрограммноАвтоматически (@ComponentScan)
Гибкость настройки?Высокая (можно передавать параметры)Меньшая
Где применяется?Когда нужен сложный бинКогда класс управляется Spring

12. @Controller и @RestController #

  • @Controller В основном используется для возврата веб-страниц. Если нужно вернуть данные (например, JSON), нужно добавить @ResponseBody к методу
  • @RestContoller Это комбинация @Controller и @ResponseBody. Используется в REST. Всегда возвращает данные (например, JSON или XML) в теле HTTP-ответа. Нет необходимости добавлять @ResponseBody к каждому методу.

13. @ResponseBody vs ResponseEntity #

  • ResponseEntity — это объект, который содержит как тело ответа, так и HTTP-статус (например, 200 OK, 404 Not Found) и заголовки. Используется для более гибкого управления HTTP-ответами
  • @ResponseBody Аннотация, которая указывает, что результат метода контроллера должен быть возвращен как тело HTTP-ответа (например, JSON, XML)

14. Spring MVC. Что это? Какие проблемы решает? #

Spring MVC — это один из модулей Spring Framework, предназначенный для разработки веб-приложений на основе шаблона Model-View-Controller. Решает следующие проблемы

  • Чёткое разделение логики. Разбиваем логику на MVC
  • Поддержка различных шаблонизаторов. JSP, Thymeleaf и т.д
  • Упрощает обработку HTTP-запросов через контроллеры

15. Парадигма AOP #

AOP - это способ добавить в программу дополнительные функции, не трогая основной код программы. Например, у нас есть метод, который что-то делает. Но нам нужно, чтобы перед началом и в конце работы этого метода писался лог. Вместо того чтобы добавлять этот код логирования в каждый метод руками - можно вынести его в отдельное место (аспект), а Spring сам “вставит” его туда, где нужно, без изменений основного кода.


16. Циклическая зависимость #

Циклическая зависимость - это когда два класса взаимно ссылаются друг на друга через аннотацию @Autowired. Это может вызвать проблему циклической зависимости, которую Spring Framework не сможет разрешить. Для решения этой проблемы можно использовать один из следующих подходов:

  • Внедрять зависимости через set-ер
  • Использование @Lazy. Аннотация @Lazy откладывает создание бина до момента его использования, что позволяет разорвать круг
@Component
public class BeanA {
    private final BeanB beanB;

    @Autowired
    public BeanA(@Lazy BeanB beanB) {
        this.beanB = beanB;
    }
}

@Component
public class BeanB {
    private final BeanA beanA;

    @Autowired
    public BeanB(@Lazy BeanA beanA) {
        this.beanA = beanA;
    }
}

17. Spring Boot #

Spring Boot — это фреймворк на основе Spring, который позволяет быстрее и проще создавать готовые к работе приложения. Ключевые преимущества:

  • Стартеры — это готовые наборы библиотек (модулей), которые включают все необходимые зависимости для работы с конкретными технологиями
  • Встроенный сервер - В Spring Boot сервер встроен прямо в приложение. Например, когда вы добавляете стартер spring-boot-starter-web, Spring Boot автоматически включает Tomcat или Jetty
  • Автоматическая конфигурация - Spring Boot использует механизм автоматической конфигурации, который анализирует, какие библиотеки находятся в вашем проекте, и автоматически настраивает необходимые компоненты.
  • Упрощение сборки - В обычном Spring для развертывания приложения вам нужно собрать WAR файл и развернуть его на сервере. В Spring Boot приложение собирается в исполняемый JAR файл, который содержит все зависимости и сервер.

Spring Boot vs Spring

  • Spring — это основной фреймворк с полной конфигурацией и гибкостью.
  • Spring Boot — это расширение Spring, которое делает процесс разработки проще, автоматизируя конфигурацию и позволяя быстро запускать приложение с минимальной настройкой.
КритерийSpringSpring Boot
ЦельМощный фреймворк для создания Java-приложений.Упрощение разработки с использованием Spring. Автоматизация конфигурации.
КонфигурацияТребуется вручную настроить XML или Java-конфигурацию.Автоконфигурация, минимальные настройки для старта проекта.
ЗапускНужно вручную настроить веб-сервер (например, Tomcat).Встроенные серверы (Tomcat, Jetty, Undertow) для простого запуска.
ГибкостьБольшая гибкость, полная настройка каждого компонента.Меньше гибкости, но намного быстрее и проще для старта проекта.
СложностьБолее высокая сложность настройки и конфигурации.Упрощенная настройка и конфигурация.
ИспользованиеИспользуется, когда нужна полная настройка и контроль.Используется для быстрого старта и простоты разработки.
Конфигурационные файлыИспользует XML или Java-конфигурацию для настройки бинов.Использует application.properties или application.yml для настройки.

Как Spring Boot работает под капотом

  1. Автоконфигурация (Auto Configuration):

    • Spring Boot анализирует классы и зависимости в вашем проекте и пытается настроить приложение так, чтобы оно работало “из коробки”.
    • Например, если в проекте есть зависимость от spring-boot-starter-web, то Spring Boot автоматически настраивает DispatcherServlet, который обрабатывает HTTP-запросы.
  2. Сканирование компонентов:

    • При запуске приложения Spring Boot сканирует пакеты для поиска аннотированных классов (например, с аннотациями @Component, @Service, @Repository, @Controller и т. д.), чтобы зарегистрировать бины.
    • Это сканирование начинается с класса, аннотированного @SpringBootApplication, который обычно находится в корне проекта. Пакеты и подпакеты, расположенные ниже этого класса, будут сканироваться на наличие компонентов.
  3. Встроенный сервер:

    • Spring Boot может использовать встроенные сервера, такие как Tomcat, Jetty или Undertow, что позволяет запустить приложение как самодостаточный исполнимый JAR или WAR файл.
  4. Процесс инициализации:

    • Когда приложение запускается, Spring Boot автоматически запускает и настраивает все необходимые компоненты и зависимости. Весь процесс инициализации централизован и автоматизирован.
  5. Основной класс с @SpringBootApplication:

    • Этот класс является точкой входа в приложение. Он выполняет несколько задач:
      • Включает автоматическое конфигурирование с помощью @EnableAutoConfiguration.
      • Разрешает сканирование компонентов с помощью @ComponentScan.
      • Включает настройку конфигурации Spring с помощью @Configuration.

Где Spring Boot ищет бины?

  1. Основной класс с @SpringBootApplication:

    • Spring Boot начинает сканировать пакеты с местоположения класса, аннотированного @SpringBootApplication.
    • Все классы в этом пакете и его подпакетах будут автоматически обработаны на наличие аннотаций для создания бинов (например, @Component, @Service, @Repository и т. д.).
  2. Пакет с @SpringBootApplication:

    • По умолчанию Spring Boot сканирует только текущий пакет и его подпакеты. Поэтому важно, чтобы основной класс с аннотацией @SpringBootApplication находился в корне пакета, чтобы гарантировать, что все компоненты будут найдены.
  3. Дополнительные настройки сканирования:

    • Можно явно указать, какие пакеты нужно сканировать, с помощью аннотации @ComponentScan:
@SpringBootApplication
@ComponentScan(basePackages = "com.example.myapp")
public class MyApplication {
    public static void main(String[] args) {
        SpringApplication.run(MyApplication.class, args);
    }
}

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

  1. Использование профилей:

    • Если в проекте используются разные профили (например, для разных сред), Spring Boot будет также искать компоненты, соответствующие активному профилю, при условии что они аннотированы как @Profile.

18. Spring JDBC #

Spring JDBC — это модуль Spring, который упрощает взаимодействие с базами данных, используя JDBC (Java Database Connectivity). Он предлагает упрощённый подход к выполнению SQL-запросов, обработке результатов и управлению ресурсами.


19. Spring Data JPA #

Spring Data JPA — это модуль Spring, который упрощает работу с JPA (Java Persistence API) и предоставляет удобные способы доступа к данным. Он позволяет разработчикам сосредоточиться на бизнес-логике, не заботясь о низкоуровневых деталях доступа к данным.


20. Блокировка по умолчанию в Spring #

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


21. @Transactional. Как работает? #

@Transactional — это аннотация, которая позволяет автоматически управлять транзакциями. Когда метод аннотирован @Transactional, Spring берет на себя начало, коммит или откат транзакции


Что можно пометить аннотацией @Transactional?

  • Класс. Все его методы станут @Transactional
  • Метод

Дефолтный propagation

Propagation.REQUIRED


Атрибут propagation у @Transactional

  • REQUIRED — применяется по умолчанию. При входе в @Transactional метод будет использована уже существующая транзакция или создана новая транзакция, если никакой еще нет
  • REQUIRES_NEW — для внутреннего метода создается своя отдельная транзакция. Пока выполняется внутренний метод, внешняя транзакция приостанавливается. Результат выполнения внутренней - не повлияет на результат выполнения внешней
  • NESTED — создаёт под транзакцию внутри основной транзакции. Эта под транзакция может быть откатана отдельно от основной, но зависит от успешного завершения основной транзакции. Если основная транзакция откатится, под транзакция тоже откатится, даже если она завершилась успешно
  • MANDATORY — требует внешнюю транзакцию, а иначе выбрасывается исключение
  • SUPPORTS — метод будет выполняться в рамках транзакции, если она существует. Если ее нет, метод выполнится без транзакции
  • NOT_SUPPORTED — метод всегда выполняется вне транзакции, даже если существует текущая транзакция. Текущая транзакция приостанавливается
  • NEVER — запрещает выполнение метода в транзакции

Какие способы управления транзакциями в Spring вы знаете?

  • Аннотация @Transactional
  • TransactionManager
  • TransactionTemplate

TransactionManager и TransactionTemplate

  • Когда требуется точный контроль над транзакциями (например, управлять несколькими транзакциями в одном потоке)

@Transactional

  • Когда нужно простое управление транзакциями
  • Когда транзакция не требует сложной логики управления

@Transactional повесить на private метод?

Если вы поместите аннотацию @Transactional на private метод, то транзакция не будет создана или применена, потому что Spring использует прокси-механизм для управления транзакциями.


Какие есть атрибуты у @Transactional?

Аннотация @Transactional управляет транзакциями в Spring. Она может быть установлена на классе или методе.

📌 Основные атрибуты

АтрибутОписаниеЗначение по умолчанию
propagationКак ведет себя транзакция (создавать новую или использовать существующую)REQUIRED
isolationУровень изоляции транзакции (как управлять конкурентным доступом)DEFAULT
readOnlyТолько для чтения (не позволяет изменять данные)false
timeoutМаксимальное время выполнения (в секундах)-1 (бесконечно)
rollbackForПеречисляет исключения, при которых делается rollbackПусто
noRollbackForИсключения, при которых rollback не делаетсяПусто

📌 1. propagation – управление транзакциями

📌 Варианты:

ЗначениеОписание
REQUIREDИспользует существующую транзакцию или создает новую (по умолчанию)
REQUIRES_NEWВсегда создает новую транзакцию
SUPPORTSИспользует существующую, но если ее нет — выполняется без транзакции
NOT_SUPPORTEDВсегда выполняется без транзакции
MANDATORYИспользует существующую, если ее нет — выбрасывает ошибку
NEVERНикогда не выполняется в транзакции

📌 Пример:

@Transactional(propagation = Propagation.REQUIRES_NEW)
public void saveUser() {
    // Эта транзакция создаст новую, даже если уже есть другая
}

📌 2. isolation – уровень изоляции

📌 Варианты:

ЗначениеОписание
DEFAULTИспользует уровень БД
READ_UNCOMMITTEDПозволяет читать неподтвержденные изменения (грязное чтение)
READ_COMMITTEDНе позволяет читать неподтвержденные изменения
REPEATABLE_READНе позволяет изменять данные, которые уже были прочитаны
SERIALIZABLEПолная изоляция (максимальная защита, но низкая производительность)

📌 Пример:

@Transactional(isolation = Isolation.REPEATABLE_READ)
public void processOrder() {
    // Защита от "фантомных" чтений
}

📌 3. readOnly – транзакция только для чтения

📌 Если метод только читает данные (например, findById), то readOnly = true ускорит работу.

@Transactional(readOnly = true)
public User getUser(Long id) {
    return userRepository.findById(id).orElseThrow();
}

Преимущества:

  • Не блокирует ресурсы для обновления
  • Повышает производительность

📌 4. rollbackFor – откат при ошибке

По умолчанию Spring откатывает транзакции только при RuntimeException, но можно указать другие классы исключений.

@Transactional(rollbackFor = Exception.class)
public void saveData() throws Exception {
    throw new Exception("Ошибка!"); // Теперь произойдет rollback
}

@Transaction Что будет если вызвать метод из метода в одном классе?

Если вы вызовете метод, помеченный @Transactional, из другого метода того же класса, то транзакция не начнётся или не будет соблюдена, если вызов происходит напрямую.

Почему?

Вызов внутри класса не проходит через прокси, который отвечает за управление транзакцией. Таким образом, аннотация @Transactional не будет обработана.

@Service
public class MyService {

    @Transactional
    public void transactionalMethod() {
        // Транзакция активируется, если вызов через прокси
    }

    public void callTransactionalMethod() {
        transactionalMethod(); // Прямой вызов, транзакция НЕ активируется
    }
}

Как исправить?

1. Используйте вызов через другой бин

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

@Service
public class MyService {

    @Transactional
    public void transactionalMethod() {
        // Транзакция активируется
    }
}

@Service
public class AnotherService {

    @Autowired
    private MyService myService;

    public void callMethod() {
        myService.transactionalMethod(); // Вызов через прокси, транзакция активируется
    }
}

2. Используйте self-injection

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

@Service
public class MyService {

    @Autowired
    private MyService self;

    @Transactional
    public void transactionalMethod() {
        // Транзакция активируется
    }

    public void callTransactionalMethod() {
        self.transactionalMethod(); // Вызов через прокси, транзакция активируется
    }
}

22. @Async #

Аннотация @Async используется в Spring для выполнения методов асинхронно, то есть в отдельном потоке, чтобы не блокировать основной поток выполнения.

Как работает @Async

  1. Асинхронное выполнение:

    • Метод, помеченный аннотацией @Async, выполняется в отдельном потоке.
    • Возвращаемый тип может быть void, Future, или CompletableFuture.
  2. Spring AOP:

    • Подобно @Transactional, @Async работает через прокси.
    • Вызов асинхронного метода должен проходить через Spring контейнер (бин), чтобы @Async обработалась.
  3. Пул потоков:

    • Асинхронные вызовы используют пул потоков, определённый в настройках. По умолчанию используется пул SimpleAsyncTaskExecutor, но вы можете настроить свой.

23. Starter в Spring. Плюсы и минусы #

Starters в Spring — это специальные артефакты, которые облегчают конфигурацию и настройку приложений. Они предоставляют преднастроенные зависимости и настройки для различных функциональностей, что позволяет разработчикам быстро и просто начать работу с определёнными аспектами Spring-приложения.

Плюсы:

  • Упрощение конфигурации
  • Согласованность
  • Сокращение времени разработки
  • Модульность
  • Документация и поддержка

Минусы:

  • Избыточные зависимости
  • Меньшая гибкость

Какие отличия стартера от библиотеки?

📌 1. Что такое starter?

Spring Boot Starter – это набор зависимостей, который включает все необходимые библиотеки для работы с определенной технологией.

Пример:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

Этот starter включает:

  • Spring MVC
  • Tomcat
  • Jackson

📌 Преимущества starter:
✔ Упрощает конфигурацию
✔ Автоматически подключает все нужные зависимости

📌 2. Чем отличается от обычной библиотеки?

ХарактеристикаStarterLibrary
СоставВключает набор библиотекОдиночная библиотека
НастройкаАвтоматическаяТребует ручной настройки
Примерspring-boot-starter-data-jpahibernate-core

📌 Пример библиотеки:

<dependency>
    <groupId>org.hibernate</groupId>
    <artifactId>hibernate-core</artifactId>
</dependency>

Starter включает библиотеку + нужные настройки.


24. Spring Criteria API #

Spring Criteria API (часть Hibernate) предоставляет гибкий способ построения запросов к базе данных. Она основана на использовании объектно-ориентированных принципов, позволяя составлять SQL-запросы с использованием Java-классов вместо ручного написания SQL.

Ключевые особенности Criteria API

  1. Динамическое построение запросов:

    • Удобно для ситуаций, когда структура запроса может меняться в зависимости от условий.
  2. Безопасность типов:

    • Ошибки в синтаксисе запросов обнаруживаются на этапе компиляции.
  3. Ленивость:

    • Запрос выполняется только при обращении к результатам.
  4. Поддержка сложных запросов:

    • Подзапросы, группировки, сортировки, фильтрации и агрегатные функции.

Пример базового использования

  1. Установка сущности
@Entity
public class Employee {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String name;

    private double salary;

    // getters и setters
}
  1. Использование Criteria API
@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();
    }
}

25. @Scheduled #

Аннотация @Scheduled используется для выполнения методов по расписанию. Она позволяет запускать задачи с фиксированным интервалом времени, фиксированной задержкой или на основе cron-выражений.


26. Понятие Controller и Servlet в Java. Это одно и то же? Если разное, в чем отличия? #

Servlet и Controller — это разные концепции, хотя оба они используются для обработки HTTP-запросов в Java веб-приложениях. Однако их роли и области применения различаются.

Servlet — это Java-класс, который обрабатывает HTTP-запросы (HTTP GET, POST и т.д.) и возвращает HTTP-ответы.
Он является основой для создания серверных приложений в Java.

Controller — это часть архитектуры MVC (Model-View-Controller). В контексте Java это обычно класс, аннотированный, например, @Controller в Spring MVC, который обрабатывает HTTP-запросы и управляет их маршрутизацией.

Контроллер абстрагирует разработчика от низкоуровневых деталей (например, работы с HTTP-запросами и ответами), позволяя сосредоточиться на бизнес-логике.

Основные отличия Servlet и Controller

КритерийServletController
Область примененияНизкоуровневая обработка HTTP-запросов.Высокоуровневый компонент архитектуры MVC.
Ручная работаТребуется больше ручной работы для маршрутизации и обработки данных.Маршрутизация и обработка запросов автоматизированы.
ИнструментыИспользует контейнер сервлетов (например, Tomcat).Использует веб-фреймворки (например, Spring MVC).
ГибкостьПолный контроль над HTTP-запросами и ответами.Сосредоточен на бизнес-логике, делегируя детали фреймворку.
Уровень абстракцииНизкий: работа напрямую с HttpServletRequest и HttpServletResponse.Высокий: аннотации и абстрагирование низкоуровневой логики.
ПримерHttpServlet, GenericServlet.@Controller в Spring, @RestController.

Когда использовать?

СитуацияServletController
Вам нужен полный контроль над HTTP-запросамиДаНет
Вы используете современные фреймворкиРедкоДа
Требуется простота разработкиНетДа
Вы пишете сложное веб-приложениеНепрактичноПредпочтительно

27. Что такое объект Filter? В какой момент вызывается? #

Filter — это интерфейс из Java Servlet API, который позволяет перехватывать и изменять HTTP-запросы и ответы. Фильтры часто используются для задач, которые необходимо выполнить до или после обработки запроса в сервлете, например:

  • Аутентификация и авторизация.
  • Логирование запросов.
  • Сжатие ответов.
  • Кэширование.
  • Добавление или изменение заголовков запросов и ответов.

Когда вызывается фильтр?

Фильтры вызываются до попадания HTTP-запроса к сервлету (или контроллеру) и после возвращения ответа от сервлета. Они служат промежуточным уровнем между клиентом и сервлетом.

  • До сервлета: Обрабатывают входящий HTTP-запрос.
  • После сервлета: Могут изменить HTTP-ответ перед отправкой клиенту.

28. Что такое ApplicationContext? #

ApplicationContext — это центральный интерфейс контейнера Spring, который управляет жизненным циклом бинов, их зависимостями и предоставляет дополнительные возможности для разработки приложений. Это расширение интерфейса BeanFactory, добавляющее множество дополнительных функций, таких как обработка событий, поддержка интернационализации и интеграция с AOP.

Основные функции ApplicationContext

  1. Управление бин-компонентами:
    • Автоматическое создание, связывание и уничтожение бинов.
  2. Инжекция зависимостей:
    • Поддерживает механизмы Dependency Injection (DI).
  3. Обработка событий:
    • Поддерживает публикацию и прослушивание событий (например, ContextRefreshedEvent).
  4. Интернационализация:
    • Предоставляет механизмы для работы с сообщениями и локалями.
  5. Интеграция с 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();
    }
}

Расскажи про разницу между 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

КритерийApplicationContextBeanFactory
Область примененияПолноценный контейнер для разработки современных приложений.Базовый контейнер для управления бинами.
Загрузка биновЗагружает все бины при инициализации контекста (eager loading).Загружает бины по запросу (lazy loading).
Поддержка событийПоддерживает публикацию и обработку событий (ApplicationEvent).Не поддерживает события.
ИнтернационализацияВстроенная поддержка интернационализации (I18N).Отсутствует.
Интеграция с AOPПоддержка AOP для внедрения аспектов.Ограниченная интеграция с AOP.
Преимущество производительностиМенее производителен при большом количестве бинов (загрузка всех бинов при старте).Производителен, так как загружает бины только по запросу.
Поддержка аннотацийПолная поддержка аннотаций (например, @Component, @Autowired).Ограниченная поддержка.
Расширенные возможностиПоддержка профилей, автоконфигурации, REST и других функций.Только базовая работа с бинами.

Когда использовать

СитуацияApplicationContextBeanFactory
Полноценное приложениеДаНет
Требуется работа с событиямиДаНет
Экономия памяти при большом количестве биновНетДа
Тестовые приложенияВозможен, но избыточенДа

Можно поднять несколько контекстов?

Да, в 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);

Преимущества использования нескольких контекстов

  • Разделение конфигурации между модулями приложения.
  • Изоляция модулей для тестирования.
  • Улучшение модульности и читаемости кода.

Недостатки

  • Увеличенная сложность управления.
  • Возможные конфликты бинов между контекстами.

29. Зачем нужны аннотации @RequestParam и @PathVariable? #

Эти аннотации используются в Spring MVC для извлечения параметров из HTTP-запросов. Они применяются в контроллерах для получения входных данных.

@RequestParam

Аннотация, которая позволяет извлекать параметры запроса (Query Parameters) из URL.

  • Используется для параметров, указанных после ? в строке запроса.
  • Может задавать значения по умолчанию.

Пример: Запрос:
GET /api/users?name=John&page=1

@GetMapping("/api/users")
public String getUser(@RequestParam String name, @RequestParam(defaultValue = "0") int page) {
    return "Name: " + name + ", Page: " + page;
}

// Результат: Name: John, Page: 1

@PathVariable

Аннотация, которая позволяет извлекать значения из пути запроса (Path Variables).

  • Используется для параметров, включенных в шаблон URL.

Пример: Запрос:
GET /api/users/42

@GetMapping("/api/users/{id}")
public String getUserById(@PathVariable int id) {
    return "User ID: " + id;
}

// Результат: User ID: 42

30. @ConfigurationProperties? ConfigurationProperties vs Value #

Аннотация @ConfigurationProperties используется для привязки свойств из файла конфигурации (например, application.properties или application.yml) к Java-классу.

Как работает @ConfigurationProperties?

  • Привязывает свойства по префиксу к полям класса.
  • Удобна для работы с группами связанных настроек.
  • Требует аннотации @EnableConfigurationProperties или компонентов типа @Component.
app:
  name: MyApp
  version: 1.0

Класс конфигурации:

@ConfigurationProperties(prefix = "app")
@Component
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());
    }
}

Сравнение @ConfigurationProperties и @Value

Критерий@ConfigurationProperties@Value
Привязка групп свойствУдобна для групп связанных настроек (с префиксом).Подходит для одиночных свойств.
Приведение типовАвтоматически поддерживает сложные типы (например, списки).Поддерживает только базовые типы.
Работа с файлами YAMLПолностью поддерживается, включая сложные структуры.Менее удобна для сложных структур.
ГибкостьУдобна для крупных конфигураций.Быстрее для мелких настроек.
Пример использования@ConfigurationProperties(prefix = "app")@Value("${app.name}")
АннотацииТребует @EnableConfigurationProperties или @Component.Не требует дополнительных аннотаций.
Тестирование и валидацияПоддерживает валидацию через @Validated.Не поддерживает валидацию.

Когда использовать?

Ситуация@ConfigurationProperties@Value
Большое количество связанных свойствДаНет
Одиночное свойствоНетДа
Сложные структуры (списки, объекты)ДаНет
Простые, статические значенияНетДа

31. @Value. Что это? #

@Value — это аннотация Spring, которая позволяет внедрять значения из конфигурационных файлов (application.properties, application.yml), системных переменных или других внешних источников в поля, методы или конструкторы класса.

Основное назначение: получить значение переменной или настроек из конфигурации.

Как использовать @Value?

app.name=MyApp
app.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);
    }
}

// Результат:
// Name: MyApp 
// Version: 1.0 
// Description: Default Description

32. Как работает DispatcherServlet? #

DispatcherServlet — это основной компонент Spring MVC, который обрабатывает входящие HTTP-запросы, направляет их к соответствующим контроллерам, обрабатывает ответы и возвращает их клиенту.

Это центральный обработчик HTTP-запросов в Spring MVC.

Основной цикл работы DispatcherServlet

  1. Получение HTTP-запроса:

    • DispatcherServlet обрабатывает все запросы, поступающие в приложение (если настроен как фронт-контроллер в web.xml или через Java Config).
  2. Определение подходящего контроллера:

    • С помощью HandlerMapping находит контроллер, который должен обработать запрос, основываясь на URL и методе HTTP.
  3. Выбор метода обработчика:

    • С помощью аннотаций контроллера (@RequestMapping, @GetMapping, @PostMapping и др.) определяется, какой метод должен быть вызван.
  4. Обработка запроса:

    • Контроллер выполняет бизнес-логику и возвращает ModelAndView или объект в формате JSON/XML (например, с @ResponseBody).
  5. Обработка ответа:

    • Результат передается в компонент ViewResolver, который определяет, какое представление (HTML, JSP, JSON и др.) нужно отрендерить.
  6. Возврат клиенту:

    • Готовый HTTP-ответ отправляется обратно клиенту.

Компоненты в работе DispatcherServlet

КомпонентОписание
HandlerMappingОпределяет, какой обработчик (контроллер) будет вызван для конкретного запроса.
HandlerAdapterОпределяет, как вызывать метод обработчика (например, метод контроллера).
ViewResolverОпределяет, какое представление (JSP, Thymeleaf, JSON) нужно использовать для ответа.
ExceptionResolverОбрабатывает исключения и может возвращать пользовательские ответы в случае ошибок.
LocaleResolverОбеспечивает поддержку локализации, определяя текущий язык и регион пользователя.

33. Варианты обработки Exceptions в Spring #

1. @ExceptionHandler

  • Используется для обработки исключений на уровне контроллера.
  • Позволяет привязать обработку конкретных исключений к определённому методу в контроллере.

Пример:

@RestController
public class MyController {

    @GetMapping("/test")
    public String test() {
        throw new IllegalArgumentException("Invalid input");
    }

    @ExceptionHandler(IllegalArgumentException.class)
    public ResponseEntity<String> handleIllegalArgument(IllegalArgumentException e) {
        return ResponseEntity.badRequest().body("Error: " + e.getMessage());
    }
}

2. @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

  • Устанавливает HTTP-статус для указанного исключения.

Пример:

@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) транзакции.

Основные принципы обработки исключений в транзакциях:

  1. Rollback для RuntimeException и Error:

    • По умолчанию, Spring откатывает транзакцию при возникновении RuntimeException или Error.
    • Исключения типа Checked Exception (например, SQLException) не приводят к откату транзакции, если это явно не указано.
  2. Настройка rollback: Можно указать, какие исключения должны приводить к откату.

Пример:

@Transactional(rollbackFor = Exception.class)
public void performTransaction() throws Exception {
    // Если выбрасывается Exception, транзакция откатывается
    throw new Exception("Transaction failed");
}
  1. Сохранение состояния после исключения: Если транзакция откатывается, изменения, внесённые в базу данных в рамках транзакции, не сохраняются.

  2. Работа с @Transactional:

    • Если транзакция завершается с ошибкой, контейнер Spring вызывает метод rollback у используемого транзакционного менеджера.
    • Для методов private или вызовов внутри того же класса обработка транзакций может не сработать.

Пример:

@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 для открытия новой транзакции.

34. @Configuration. Является Configuration компонентом? Какую доп логику Configuration реализует? #

Аннотация @Configuration является специализированным компонентом Spring. Она указывает, что класс используется для определения конфигурации приложения, включая создание бинов. Этот класс обрабатывается так же, как и класс с аннотацией @Component.

Дополнительная логика, реализуемая @Configuration

  1. Определение бинов (@Bean): Классы с @Configuration позволяют определять методы, помеченные как @Bean, которые возвращают объекты, управляемые контейнером Spring.

    Пример:

    @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.
Где использоватьВ конфигурационных классах для определения настроек и бинов.В служебных классах, сервисах, репозиториях и т. д.

1. @Configuration

Аннотация @Configuration обозначает, что класс содержит определения бинов и может быть использован Spring-контейнером для генерации и управления этими бинами.

  • Используется для конфигурации приложения.
  • Прокси: Spring создает прокси для обеспечения правильного вызова методов внутри класса конфигурации.
@Configuration
public class AppConfig {

    @Bean
    public ServiceA serviceA() {
        return new ServiceA(serviceB());
    }

    @Bean
    public ServiceB serviceB() {
        return new ServiceB();
    }
}

Если метод serviceA() вызывает serviceB(), Spring использует прокси, чтобы вернуть тот же экземпляр ServiceB, созданный контейнером.

2. @Component

Аннотация @Component регистрирует класс как бин в Spring-контейнере. Она более общего назначения и применяется для различных типов классов (сервисы, репозитории, контроллеры и т. д.).

  • Используется для описания компонентов, которые выполняют определенную логику.
  • Не создает прокси: методы внутри класса с @Component вызываются напрямую
@Component
public class AppConfig {

    @Bean
    public ServiceA serviceA() {
        return new ServiceA(serviceB());
    }

    @Bean
    public ServiceB serviceB() {
        return new ServiceB();
    }
}

В данном случае метод serviceB() вызывается каждый раз напрямую, и создается новый экземпляр ServiceB.

Когда использовать @Configuration и @Component?

  • @Configuration:

    • Если нужно определить сложные зависимости.
    • Когда требуется управление экземплярами через прокси.
    • Для работы с бинами, определенными вручную через @Bean.
  • @Component:

    • Для обозначения служебных классов, выполняющих конкретные задачи.
    • Для автоматического сканирования компонентов.

35. @PostConstruct #

Аннотация @PostConstruct из пакета javax.annotation указывает метод, который должен быть выполнен после того, как бин был полностью создан и его зависимости внедрены (инициализация). Этот метод вызывается один раз для каждого экземпляра бина.

Цель: Выполнение логики, требующей полностью сконфигурированный бин (например, начальная настройка).


Сколько @PostConstruct можно задекларировать в бине?

Можно задекларировать только один метод с @PostConstruct в одном бине. Если попытаться добавить несколько методов с этой аннотацией, Spring выбросит ошибку.


36. Dependency management в Spring boot #

Dependency Management — это управление зависимостями приложения. В Spring Boot используется механизм Maven или Gradle для автоматического добавления, обновления и совместимости библиотек.

1. Spring Boot Starter Dependencies:

  • Группы зависимостей (например, spring-boot-starter-web), которые включают всё необходимое для работы.
  • Упрощают настройку: вместо добавления каждой библиотеки вручную, добавляется “стартер”.
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

2. 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>

3. Профили зависимостей: Можно включать зависимости в зависимости от среды выполнения (например, dev, test, prod) с помощью профилей Maven.

4. Эксплицитные зависимости: Spring Boot позволяет переопределить версию зависимости, если требуется особая версия.


37. Spring Security, как хранить пароль пользователя? #

Правила безопасного хранения паролей:

  1. Не хранить пароли в виде открытого текста.
  2. Использовать хэширование: Пароль перед сохранением в базе данных должен быть преобразован в хэш (необратимое преобразование).

Как хранить пароль в Spring Security?

  1. Хэширование с помощью 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 в базе данных
    }
    
  2. Проверка пароля: При аутентификации введённый пароль сравнивается с хэшированным значением в базе данных:

    boolean matches = passwordEncoder.matches(rawPassword, hashedPassword);
    

Где хранить пароли?

  1. В базе данных:

    • Пароли сохраняются в виде хэшей, используя безопасные алгоритмы (например, BCrypt, PBKDF2, Argon2).
    • Используйте столбцы с ограничением по длине, чтобы избежать уязвимости типа переполнения.
  2. Пример схемы таблицы:

    CREATE TABLE users (
        id BIGINT AUTO_INCREMENT PRIMARY KEY,
        username VARCHAR(50) NOT NULL,
        password VARCHAR(255) NOT NULL,
        role VARCHAR(20) NOT NULL
    );
    

Почему BCrypt?

  • Солирование: BCrypt автоматически добавляет случайную “соль” к каждому паролю, чтобы одинаковые пароли имели разные хэши.
  • Замедление: У BCrypt есть возможность настройки сложности (work factor), что увеличивает время вычисления хэша, защищая от атак перебором.

38. Spring Profiles как с ними работать? #

Spring Profiles позволяют загружать разные настройки в зависимости от окружения (разработка, тестирование, прод).

1️⃣ Как задать профиль?

📌 1. В application.properties/yml

📌 Указываем профиль в application.properties:

spring.profiles.active=dev

📌 Или в application.yml:

spring:
  profiles:
    active: dev

📌 2. Через аргументы JVM (-Dspring.profiles.active=prod)

Можно передавать профиль при запуске:

java -jar myapp.jar --spring.profiles.active=prod

2️⃣ Как создать разные настройки для профилей?

📌 Способ 1: Разные файлы application-{profile}.properties

  • application-dev.properties (локальная разработка)
  • application-prod.properties (продакшен)
  • application-test.properties (тестирование)

Пример application-dev.properties:

server.port=8080
spring.datasource.url=jdbc:mysql://localhost:3306/dev_db

Пример application-prod.properties:

server.port=80
spring.datasource.url=jdbc:mysql://prod-db:3306/prod_db

📌 Способ 2: Использование @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.

📌 Способ 3: Использование @IfProfileValue
Этот метод работает только для простых значений (не динамические профили).

@IfProfileValue(name = "spring.profiles.active", notValue = "prod")
public void runOnlyInDev() {
    System.out.println("Этот код выполняется НЕ в проде!");
}

39. Виды proxy в Spring #

Spring использует динамическое проксирование (Proxy) для создания AOP-оберток над бинами.

Тип ProxyОписаниеКак создается?
JDK Dynamic ProxyИспользует java.lang.reflect.ProxyДля интерфейсов
CGLIB ProxyИспользует net.sf.cglib.proxyДля классов
AspectJ ProxyИспользуется в Spring AOPНужно доп. подключение

1️⃣ JDK Dynamic Proxy (java.lang.reflect.Proxy)

  • Используется только для интерфейсов.
  • Создает прокси-класс, который реализует интерфейс.

📌 Пример:

public interface Service {
    void doWork();
}

public class ServiceImpl implements Service {
    public void doWork() {
        System.out.println("Работа выполнена!");
    }
}

Создание динамического прокси вручную:

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();

📌 Вывод:

Логирование перед вызовом метода...
Работа выполнена!

2️⃣ CGLIB Proxy (net.sf.cglib.proxy)

  • Используется для классов (даже если нет интерфейса).
  • Создает подкласс оригинального класса.

📌 Пример:

public class Service {
    public void doWork() {
        System.out.println("Работа выполнена!");
    }
}

Создание 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();

📌 Вывод:

Логирование перед вызовом метода...
Работа выполнена!

Spring cglib vs dynamic proxy

ФункцияJDK Dynamic ProxyCGLIB Proxy
Где работает?Только с интерфейсамиРаботает с классами
Как создается?java.lang.reflect.Proxynet.sf.cglib.proxy.Enhancer
НаследованиеНельзя наследовать логику классаСоздает подкласс
ПроизводительностьЧуть быстрееНемного медленнее

Какое proxy по-умолчанию в Spring?

📌 Spring использует JDK Dynamic Proxy по умолчанию.

Когда используется CGLIB?

  • Если класс не реализует интерфейс.
  • Если явно указать:
@EnableAspectJAutoProxy(proxyTargetClass = true)

📌 Как проверить, какой Proxy используется?

ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
Object proxy = context.getBean(MyService.class);
System.out.println(proxy.getClass());

✅ Выведет com.sun.proxy.$Proxy... для JDK Dynamic Proxy или MyService$$EnhancerByCGLIB$$... для CGLIB.


40. @Lookup #

@Lookup — это аннотация в Spring, позволяющая в singleton-бине (одиночке) получать новый экземпляр prototype-бина при каждом вызове метода. Без @Lookup при внедрении prototype-бина в singleton он создаётся только один раз.

Как это работает

  1. Ты помечаешь метод в singleton-классе аннотацией @Lookup.
  2. Spring динамически переопределяет этот метод (через CGLIB).
  3. При каждом вызове метода 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();
}

Что здесь происходит?

  • SingletonBean — бин с scope = singleton (по умолчанию).
  • PrototypeBean — бин с scope = prototype.
  • Метод getPrototypeBean() аннотирован @Lookup, и Spring подменяет его реализацию: при вызове getPrototypeBean() возвращается новый PrototypeBean.

Зачем нужно

  • Когда в singleton-бине надо часто создавать prototype-объекты.
  • Чтобы не использовать вручную ApplicationContext.getBean().
  • Для избежания утечек памяти (прототипы не живут вечно внутри синглтона).