офлайн
офлайн
ГлавнаяSpring Core.md
---
tags: [spring, java]
created: 2026-11-06
updated: 
---

Spring Core

Springspringjava6 мин
Содержание

› Базовые концепции: IoC и DI

  • IoC (Inversion of Control - Инверсия контроля): Архитектурный принцип. В традиционном программировании твой код управляет потоком выполнения и сам создает объекты (new Service()). При IoC ты отдаешь управление фреймворку. Фреймворк сам решает, когда создать объект и когда вызвать его методы.
  • DI (Dependency Injection - Внедрение зависимостей): Это конкретная реализация принципа IoC. Объекты не создают свои зависимости сами, а получают их извне (от IoC-контейнера) через конструктор, сеттер или поле.

› IoC Container: BeanFactory vs ApplicationContext

Контейнер — это ядро Spring, которое хранит объекты и управляет ими.

  • BeanFactory: Базовый, самый легковесный интерфейс контейнера.
    • Использует Lazy Loading (ленивую инициализацию): бин создается только тогда, когда его впервые запрашивают (getBean()).
  • ApplicationContext: Расширенный интерфейс (наследуется от BeanFactory). Именно он используется в 99% современных приложений.
    • Использует Eager Loading: все Singleton-бины создаются сразу при старте приложения.
    • Дополнительные фичи: Управление жизненным циклом, публикация событий (ApplicationEvent), интернационализация (MessageSource), автоматическая регистрация пост-процессоров.

› Bean и BeanDefinition

  • Bean (Бин): Любой Java-объект (POJO), который был создан, настроен и управляется IoC-контейнером Spring.
  • BeanDefinition: Это “рецепт” или “чертеж” бина. Прежде чем создать объект, Spring парсит конфигурацию (XML, Java Config или аннотации) и создает метаданные. BeanDefinition хранит: имя класса, scope, зависимости, методы init/destroy.

› Жизненный цикл Бина

Это процесс от старта контекста до уничтожения бина. Состоит из нескольких этапов:

Этап 1: Инициализация окружения и чертежей (Metadata)

  1. Environment Init: Spring собирает свойства из всех источников в единый объект Environment (application.yml / .properties, переменные окружения ОС, системные аргументы JVM).
  2. BeanDefinitionReader: Spring сканирует пакеты (@ComponentScan), находит классы (@Component, @Service, @Bean в @Configuration, XML) и парсит их метаданные.
  3. BeanDefinitionRegistry: Для каждого найденного класса создается «чертеж» (BeanDefinition) и регистрируется в потокобезопасной мапе Map<String, BeanDefinition> внутри контекста.
  4. BeanFactoryPostProcessor (BFPP): Вступает в работу до создания объектов.
    • Что делает: Модифицирует сами «чертежи» (BeanDefinition).
    • Пример: PropertySourcesPlaceholderConfigurer находит плейсхолдеры ${...} и подставляет реальные значения из Environment (шаг 1).

Этап 2: Bean Lifecycle Pipeline

  1. Instantiation (Инстанцирование): Spring вызывает конструктор класса через Reflection (или библиотеку CGLIB). Создается «сырой» POJO в куче (Heap), зависимости еще не внедрены.
  2. Populate Properties / DI (Внедрение зависимостей): Spring внедряет зависимости и значения в поля (@Autowired, @Value) или через сеттеры.
  3. Aware Interfaces: Spring передает бину инфраструктурные ссылки, если он реализует маркерные интерфейсы (BeanNameAware — отдает имя бина, ApplicationContextAware — ссылку на сам контейнер).
  4. BeanPostProcessor: Before-Init: Метод postProcessBeforeInitialization() обрабатывает бин (например, здесь считывается аннотация @PostConstruct).
  5. Initialization (Инициализация): Вызывается пользовательская логика настройки строго в следующем порядке:
    1. Метод с аннотацией @PostConstruct.
    2. Метод afterPropertiesSet() из интерфейса InitializingBean.
    3. Кастомный метод из @Bean(initMethod = "...").
  6. BeanPostProcessor: After-Init (Генерация Прокси): Метод postProcessAfterInitialization().
    • Ключевой шаг AOP: Если над бином или его методами есть @Transactional, @Async, @Cacheable, BPP оборачивает оригинальный объект в Динамический Прокси (CGLIB / Dynamic Proxy) и возвращает его.
  7. Singleton Pool: Готовый (проксированный) бин сохраняется в кэш одиночек Map<String, Object> внутри IoC-контейнера.

Этап 3: Работа приложения (Runtime)

  1. ContextRefreshed: Публикуется системное событие ContextRefreshedEvent — сигнал о том, что абсолютно все Singleton-бины созданы и настроены.
  2. Runners Execution: Контейнер находит и выполняет стартовые бины, реализующие интерфейсы CommandLineRunner или ApplicationRunner (например, первичное заполнение БД).
  3. Active Serving: Запускается встроенный веб-сервер (Tomcat/Jetty), приложение слушает порт (8080) и обрабатывает входящие запросы.

Этап 4: Завершение работы (Graceful Shutdown)

  1. JVM Shutdown Hook: Процесс получает сигнал остановки (SIGTERM, Ctrl+C) вызывается метод context.close().
  2. Destruction (Методы уничтожения): Для каждого бина вызываются методы очистки в обратном порядке:
    1. Метод с аннотацией @PreDestroy.
    2. Метод destroy() интерфейса DisposableBean.
    3. Кастомный метод из @Bean(destroyMethod = "..."). (На этом этапе закрываются пулы соединений БД HikariCP, сбрасываются буферы на диск, закрываются сетевые сокеты).
  3. Process Terminated: Контекст закрыт, память освобождена, процесс JVM завершается с кодом 0.

› Scopes (Области видимости)

  • Singleton (По умолчанию): Один экземпляр бина на весь IoC-контейнер. Создается при старте.
  • Prototype: Новый экземпляр создается каждый раз, когда его запрашивают (через getBean или при инъекции). Spring не управляет полным жизненным циклом прототипа (не вызывает @PreDestroy).
  • Web Scopes (доступны только в веб-контексте WebApplicationContext):
    • request: Один экземпляр на каждый входящий HTTP-запрос. Уничтожается после завершения запроса.
    • session: Один экземпляр на жизненный цикл конкретной HTTP-сессии пользователя. Уничтожается вместе с сессией.
    • application: Один экземпляр на жизненный цикл ServletContext (разделяется между всеми сессиями веб-приложения).
    • websocket: Один экземпляр на время жизни WebSocket-сессии.

Проблема: Prototype внутри Singleton Если внедрить Prototype-бин в Singleton-бин, то Prototype создастся только один раз (при создании Синглтона) и навсегда останется внутри него. Решение:

  1. Использовать аннотацию @Lookup над геттером прототипа.
  2. Использовать ScopedProxyMode (@Scope(value = "prototype", proxyMode = ScopedProxyMode.TARGET_CLASS)). В синглтон внедрится прокси, который при каждом вызове метода будет запрашивать новый прототип из контекста.

› Способы внедрения зависимостей (DI)

  1. Через конструктор (Рекомендуемый): Позволяет сделать поля final. Гарантирует, что объект не будет создан без обязательных зависимостей. Защищает от циклических зависимостей на этапе старта.
  2. Через сеттер: Используется для опциональных зависимостей.
  3. Через поле (@Autowired над полем): Антипаттерн. Нарушает инкапсуляцию (нельзя написать Unit-тест без поднятия Spring-контекста или рефлексии), скрывает проблему “Божественного объекта” (когда зависимостей слишком много).

› AOP (Aspect-Oriented Programming)

AOP позволяет вынести сквозную логику (логирование, транзакции, безопасность) в отдельные классы, не засоряя бизнес-логику.

Терминология:

  • Aspect (Аспект): Класс, содержащий сквозную логику (помечен @Aspect).
  • Advice (Совет): Сам код, который должен выполниться (@Before, @After, @Around).
  • Pointcut (Срез): Выражение (регулярка), определяющее, где (на каких методах) должен сработать Advice.
  • JoinPoint (Точка соединения): Конкретная точка выполнения программы (вызов метода), где применяется Advice. В @Around используется ProceedingJoinPoint, у которого нужно явно вызвать proceed(), чтобы оригинальный метод отработал.

Как AOP работает под капотом (Proxy): Spring AOP не меняет байт-код твоего класса. Он создает Proxy (Обертку). Когда другой класс вызывает твой метод, он на самом деле вызывает метод Прокси. Прокси выполняет логику Аспекта (например, открывает транзакцию), затем вызывает твой реальный метод, а затем закрывает транзакцию.

Два механизма создания Proxy:

  1. JDK Dynamic Proxy: Используется, если твой класс имплементирует хотя бы один интерфейс. Прокси создается на основе этого интерфейса (стандартная фича Java).
  2. CGLIB: Используется, если класс не имеет интерфейсов. CGLIB динамически генерирует класс-наследник от твоего класса и переопределяет его методы. (Поэтому бины и их методы не должны быть final).

Проблема самовызова (Self-invocation): Если метод A() вызывает метод B() внутри одного и того же класса, и метод B() помечен @Transactional, транзакция не откроется. Вызов происходит через this (внутри реального объекта), минуя Прокси!

› Конфигурация и Разрешение конфликтов

  • Несколько бинов одного типа: Если есть два бина типа Printer, Spring упадет с NoUniqueBeanDefinitionException.
    • Решение 1: Пометить один из бинов @Primary (бин по умолчанию).
    • Решение 2: Использовать @Qualifier("beanName") при инъекции, чтобы указать конкретное имя бина.
  • Иерархия PropertySource: Spring собирает настройки из разных мест. Приоритет (от высшего к низшему):
    1. Параметры запуска JVM (-Dmy.prop=value).
    2. Переменные окружения ОС (Environment variables).
    3. Файл application.properties / application.yml.
    4. Аннотация @PropertySource на конфигурационном классе.

Ссылаются сюда 1

main48 заметок1048 слов6 мин чтения