Spring Flashcards

(14 cards)

1
Q

✅ Что такое IoC и DI? Как они реализованы в Spring?

A

1. Проблема
В традиционном Java-коде управление зависимостями (создание и связывание объектов) выполняется вручную:

class OrderService {
    private PaymentService paymentService = new PaymentService(); // Жёсткая связь
}

Недостатки:
- Тесная связанность (tight coupling), усложняющая тестирование и замену реализаций.
- Нарушение принципа Single Responsibility (класс управляет не только логикой, но и своими зависимостями).

2. Решение → Inversion of Control (IoC) и Dependency Injection (DI)
IoC — принцип инверсии управления, при котором контейнер (Spring) управляет жизненным циклом объектов и их зависимостями.
DI — частный случай IoC, реализующий внедрение зависимостей.

Как реализовано в Spring:
1. Контейнер IoC:
- ApplicationContext — центральный интерфейс, управляющий бинами.
- Бины объявляются через аннотации (@Component, @Service) или конфигурации (@Bean).

  1. Способы внедрения зависимостей:
    • Через конструктор (рекомендуется для неизменяемых зависимостей):
      ```
      @Service
      public class OrderService {
      private final PaymentService paymentService;
      @Autowired // Опционально (начиная с Spring 4.3)
      public OrderService(PaymentService paymentService) {
          this.paymentService = paymentService;
      } } ```  
    • Через сеттеры/поля (для опциональных зависимостей).
  2. Типы бинов:
    • Singleton (по умолчанию), Prototype, Request, Session.

3. Пример/Опыт
Кейс из практики:
В проекте с микросервисной архитектурой нужно было обеспечить замену реализации PaymentService для разных окружений (например, MockPaymentService для тестов).

Решение:
1. Использовали DI через конструктор:
```
@Service
public class OrderService {
private final PaymentService paymentService;

   public OrderService(PaymentService paymentService) {
       this.paymentService = paymentService;
   }    }    ```   2. Настроили конфигурацию для подмены реализации:      ```     @Profile("test")    @Configuration    public class TestConfig {
   @Bean
   public PaymentService mockPaymentService() {
       return new MockPaymentService();
   }    }    ```   **Результат:**   - Упростили тестирование (Mockito легко подменяет зависимости).   - Избежали жёсткой связности — переход на новую платежную систему (например, Stripe → PayPal) потребовал только изменения конфигурации.  

4. Важные детали для Senior
- Как работает под капотом:
Spring создает прокси для бинов, управляя их жизненным циклом через BeanFactory. Для DI используется Reflection + CGLIB (если нет интерфейсов).
- Потенциальные проблемы:
- Циклические зависимости (решение: @Lazy или рефакторинг).
- Неоднозначность бинов (решение: @Qualifier).

Итог:
IoC/DI в Spring — это не просто «аннотации для внедрения зависимостей», а архитектурный подход, который:
✅ Уменьшает связанность.
✅ Упрощает тестирование.
✅ Позволяет гибко конфигурировать систему.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
2
Q

✅ Что такое Spring Context? Как он создается?

A

Проблема
В enterprise-разработке ручное управление зависимостями и жизненным циклом объектов становится непрактичным при масштабировании. Возникают сложности с:
- Тестированием компонентов
- Управлением конфигурацией
- Внедрением зависимостей
- Поддержкой различных окружений

Решение
Spring Context (ApplicationContext) — центральный контейнер IoC, который:
1. Управляет созданием и жизненным циклом бинов
2. Обеспечивает механизм внедрения зависимостей
3. Предоставляет единую точку конфигурации приложения
4. Обрабатывает события и профили

Создание контекста
1. Загрузка конфигурации:
- Через аннотации (@Configuration, @ComponentScan)
- Через XML (в legacy-проектах)
- Программно через Java Config

  1. Инициализация:
    • Сканирование компонентов
    • Построение графа зависимостей
    • Решение конфликтов (@Primary, @Qualifier)
  2. Постобработка:
    • Применение BeanPostProcessor
    • Инициализация прокси (AOP, транзакции)
    • Вызов callback-методов (@PostConstruct)

Опыт
В распределенной системе с 50+ микросервисами мы столкнулись с проблемой медленного старта из-за монолитного контекста. Решение включало:
1. Разделение на иерархию контекстов
2. Ленивую инициализацию неключевых компонентов
3. Оптимизацию @ComponentScan
В результате время запуска уменьшилось на 65%, а потребление памяти — на 40%.

Ключевые аспекты для Senior
1. Типы контекстов:
- AnnotationConfigApplicationContext
- ClassPathXmlApplicationContext
- WebApplicationContext

  1. Жизненный цикл:
    • refresh() → создание бинов → post-processing → готовность
    • close() → уничтожение бинов
  2. Производительность:
    • Влияние количества бинов на время инициализации
    • Оптимизация через ленивую загрузку
    • Кеширование конфигурации
  3. Расширяемость:
    • Кастомные BeanFactoryPostProcessor
    • Программная регистрация бинов
    • Динамическое обновление конфигурации

Понимание внутренней работы Spring Context критически важно для проектирования масштабируемых Spring-приложений и решения сложных проблем в production-средах.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
3
Q

✅ Что такое BeanPostProcessor?

A

Проблема
В сложных Spring-приложениях стандартного механизма внедрения зависимостей часто недостаточно, когда требуется:
- Модифицировать экземпляры бинов после их создания
- Добавлять дополнительное поведение к определенным классам
- Реализовывать кастомную логику инициализации
- Интегрировать сторонние библиотеки с Spring-контейнером

Решение
BeanPostProcessor — это ключевой расширяемый интерфейс Spring, позволяющий вмешиваться в процесс создания бинов. Он предоставляет две точки расширения:
1. postProcessBeforeInitialization — вызывается после создания экземпляра, но до вызова методов инициализации
2. postProcessAfterInitialization — вызывается после завершения всей инициализации бина

Опыт
В высоконагруженном микросервисе потребовалось реализовать автоматическое логирование вызовов методов для компонентов, помеченных специальной аннотацией. Использование BeanPostProcessor позволило:
- Создать динамические прокси для нужных бинов
- Добавить унифицированное логирование без модификации исходного кода
- Сохранить производительность за счет избирательного применения только к аннотированным классам

Ключевые аспекты для Senior
- BeanPostProcessor лежит в основе многих встроенных возможностей Spring (AOP, транзакции, кэширование)
- Порядок выполнения процессоров критически важен и контролируется через Ordered/PriorityOrdered
- Чрезмерное использование может негативно сказаться на производительности при старте приложения
- Альтернативы для сложных сценариев: BeanFactoryPostProcessor, FactoryBean, AspectJ

Практическое применение
- Создание AOP-прокси
- Реализация кастомных аннотаций
- Интеграция сторонних библиотек
- Добавление перехватчиков вызовов
- Валидация бинов после инициализации

Понимание работы BeanPostProcessor необходимо для создания сложных расширений Spring и диагностики проблем в enterprise-приложениях.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
4
Q

✅ Опишите жизненный цикл бина в Spring.

A

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

  1. Инстанциирование
    Контейнер создает экземпляр бина через конструктор по умолчанию или указанный фабричный метод. Для singleton-бинов это происходит при запуске контекста, для prototype - по требованию.
  2. Популяция свойств
    Spring внедряет зависимости через:
    - Автовайринг (@Autowired)
    - Явное указание (@Resource, @Value)
    - XML-конфигурацию (в legacy-системах)
  3. Предварительная инициализация
    - Вызов Aware-интерфейсов (BeanNameAware, ApplicationContextAware)
    - Обработка BeanPostProcessor.postProcessBeforeInitialization
  4. Инициализация
    Выполняется в строгом порядке:
    1) Методы с @PostConstruct
    2) afterPropertiesSet() (из InitializingBean)
    3) Кастомные init-методы (указанные в initMethod @Bean)
  5. Использование
    Полностью инициализированный бин готов к работе. Может быть:
    - Проксирован для AOP
    - Инжектирован в другие компоненты
    - Использован для обработки бизнес-логики
  6. Уничтожение (только для singleton)
    Перед закрытием контекста:
    1) @PreDestroy методы
    2) destroy() (из DisposableBean)
    3) Кастомные destroy-методы (destroyMethod в @Bean)

Ключевые моменты для Senior-разработчика:

  1. Порядок выполнения критичен для:
    - Транзакционных прокси
    - Кеширующих механизмов
    - Интеграций с внешними системами
  2. Особенности scope:
    - prototype-бины не получают destroy-хуков
    - request/session-бины имеют дополнительный жизненный цикл
  3. Производительность:
    - Тяжелые init-методы замедляют старт
    - Избыточные BeanPostProcessor влияют на время инициализации
  4. Обработка ошибок:
    - Исключения на этапе инициализации приводят к недоступности бина
    - Проблемы при уничтожении могут вызвать утечки ресурсов
  5. Расширяемость:
    - BeanPostProcessor позволяют модифицировать стандартное поведение
    - CustomScope обеспечивает гибкое управление жизненным циклом

Глубокое понимание этих механизмов необходимо для построения надежных enterprise-приложений и решения сложных проблем в production-средах.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
5
Q

Что такое @Profile? Как его использовать?

A
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
6
Q

✅ Почему рекомендуется внедрение бина через конструктор?

A

Проблема 1:
Неявные зависимости и невалидное состояние объекта
При использовании сеттеров или field injection зависимости становятся опциональными - класс может быть создан без обязательных зависимостей. Это приводит к:
- Runtime ошибкам (NullPointerException), когда код пытается использовать неинициализированную зависимость
- Нарушению инвариантов класса, так как объект может существовать в частично инициализированном состоянии
- Сложностям в понимании, какие зависимости действительно необходимы для работы класса
- Проблемам при рефакторинге, так как зависимости не видны в публичном API класса

Проблема 2:
Нарушение принципов иммутабельности и thread-safety

Field injection и сеттеры:
- Не позволяют объявлять зависимости как final, что делает объект изменяемым
- Создают риск перезаписи зависимостей после инициализации объекта
- Усложняют работу в многопоточной среде, так как зависимости могут быть заменены в runtime
- Нарушают принцип “закрытости для модификации” (Open-Closed principle), позволяя изменять внутреннее состояние объекта после создания

Решение
Внедрение через конструктор решает эти проблемы следующим образом:
1. Явно декларирует все обязательные зависимости в сигнатуре конструктора
2. Гарантирует, что объект будет создан в валидном состоянии
3. Гарантирует полную инициализацию объекта перед использованием
4. Позволяет делать зависимости final, обеспечивая неизменяемость
4. Упрощает тестирование (можно передавать моки напрямую)

Опыт
В крупном проекте с микросервисной архитектурой мы столкнулись с проблемами:
1. Сервисы могли работать с частично инициализированными зависимостями
2. Возникали NPE в runtime из-за неявных зависимостей
3. Тесты были хрупкими и сложными в поддержке

После перехода на внедрение через конструктор:
1. Код стал более предсказуемым и надежным
2. Упростилось тестирование (на 40% сократилось количество mock-объектов)
3. Улучшилась читаемость и поддерживаемость кода
4. Исчезли runtime-ошибки, связанные с зависимостями

Ключевые аспекты для Senior
1. Thread-safety: final-поля, инициализированные через конструктор, безопасны для многопоточной среды
2. Циклические зависимости: легче выявляются на этапе компиляции
3. Совместимость с современными подходами: хорошо работает с реактивными стеками и immutable-объектами
4. Инверсия управления: явно показывает зависимость класса от абстракций, а не реализаций
5. Интеграция с фреймворками: Spring (начиная с 4.3) и другие DI-контейнеры оптимально работают с constructor injection

Constructor injection стал де-факто стандартом в современной Spring-разработке, так как обеспечивает надежность, предсказуемость и удобство поддержки enterprise-приложений.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
7
Q

✅ Что такое Spring Boot? Чем он отличается от Spring Framework?

A

Проблема
Разработка на чистом Spring Framework требует значительных усилий по настройке инфраструктуры:
- Ручное объявление бинов в XML или JavaConfig
- Необходимость явного подключения совместимых версий зависимостей
- Трудоемкая настройка сервера приложений и интеграция с ним
- Большое количество шаблонного кода для стандартных задач

Решение
Spring Boot — это расширение Spring Framework, которое:
- Предоставляет готовые конфигурации (auto-configuration) на основе анализа classpath
- Включает встроенные серверы (Tomcat, Jetty, Undertow)
- Упрощает подключение зависимостей через starter-пакеты
- Добавляет production-фичи: мониторинг, health checks, externalized configuration

Ключевые отличия

  1. Подход к конфигурации
    • Spring Framework: Требует явного объявления бинов через XML, аннотации (@Bean, @ComponentScan) или JavaConfig. Разработчик должен вручную настраивать инфраструктурные компоненты (DataSource, TransactionManager и др.).
    • Spring Boot: Использует автоматическую конфигурацию (@EnableAutoConfiguration), которая активирует бины на основе:
      • Наличия классов в classpath (например, DataSource → автоматически настраивает JdbcTemplate)
      • Свойств в application.properties/application.yml
      • Условий (@ConditionalOnClass, @ConditionalOnProperty)
  2. Управление зависимостями
    • Spring Framework: Разработчик самостоятельно подбирает совместимые версии библиотек (Spring MVC, Hibernate, Jackson и др.), что может приводить к конфликтам.
    • Spring Boot: Предоставляет starter-пакеты (например, spring-boot-starter-web, spring-boot-starter-data-jpa), которые:
      • Автоматически подключают совместимые версии всех зависимостей
      • Включают готовые настройки для типовых сценариев (например, embedded сервер + Spring MVC)
  3. Запуск и развертывание
    • Spring Framework: Приложение требует внешнего сервера приложений (Tomcat, WildFly) и деплоится как WAR.
    • Spring Boot:
      • Создает самодостаточные (self-contained) JAR-файлы со встроенным сервером
      • Предоставляет SpringApplication для удобного запуска (main() метод)
      • Поддерживает executable JAR для production-развертывания
  4. Работа с окружением
    • Spring Framework: Настройки окружения (профили, свойства) требуют ручной конфигурации (например, через PropertyPlaceholderConfigurer).
    • Spring Boot:
      • Упрощает управление конфигурацией через application.properties/application.yml
      • Поддерживает профили (spring.profiles.active)
      • Позволяет переопределять настройки через внешние источники (env vars, VM options, config серверы)
  5. Готовые решения для production
    • Spring Framework: Нет встроенных инструментов для мониторинга и администрирования.
    • Spring Boot: Добавляет Actuator — REST-эндпоинты для:
      • Проверки здоровья приложения (/actuator/health)
      • Просмотра метрик (/actuator/metrics)
      • Анализа автоматических конфигураций (/actuator/conditions)

Опыт
При переходе с Spring Framework на Spring Boot в enterprise-проекте:
- Время настройки инфраструктуры сократилось на 60-70% за счет auto-configuration
- Количество конфигурационных файлов уменьшилось в 3 раза
- Упростился процесс CI/CD благодаря единому формату исполняемых JAR
- Стандартизировались подходы к мониторингу (Actuator + Prometheus/Grafana)

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

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
8
Q

Как создать кастомный Starter?
https://struchkov.dev/blog/ru/create-spring-boot-starter
https://www.baeldung.com/spring-boot-custom-starter

A

Проблема
1. В распределенных системах с множеством микросервисов повторяются одинаковые интеграции
2. Ручная настройка в каждом сервисе приводит к:
- Несогласованным конфигурациям
- Тонким ошибкам
- Трудностям масштабирования
3. Изменения требований требуют правок во всех сервисах
4. Новые разработчики тратят время на изучение нюансов вместо бизнес-логики

Решение
1. Инкапсуляция всей конфигурации в reusable компонент
2. Автоматическая настройка по best practices
3. Единый подход для:
- Базовой конфигурации
- Retry-логики
- Circuit breakers
- Сбора метрик
4. Гибкая настройка через application.properties
5. Соответствие принципам Spring Boot AutoConfiguration

Практический опыт
1. Кейс: интеграция 30+ микросервисов с платежной системой
2. Реализация:
- Pre-configured REST client
- Resilience4j для retry/circuit breaker
- Кастомные Prometheus метрики
- Детализированное логирование
3. Результаты:
- Время подключения сократилось с дней до 20 минут
- Централизованное управление изменениями
- Устранение класса ошибок
- Экономия сотен часов работы

Ключевые преимущества
1. Стандартизация подходов
2. Ускорение разработки
3. Упрощение поддержки
4. Гибкость конфигурации
5. Соблюдение best practices

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
9
Q

Что такое N+1 проблема? Как ее решить (@EntityGraph, FETCH JOIN)?

A
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
10
Q

Как работают @Transactional, Propagation и Isolation? Как работает @Transactional под капотом?

A
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
11
Q

Как работает аутентификация и авторизация в Spring Security?

A
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
12
Q

Какие есть способы аутентификации (Basic, JWT, OAuth2)?

A
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
13
Q

Как реализовать кастомный SecurityFilter?

A
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
14
Q

Как реализовать distributed transactions (Saga, Outbox Pattern)?

A
How well did you know this?
1
Not at all
2
3
4
5
Perfectly