# Технологии программирования на Java
**Курс:** java
**Лекций:** 14
**Обработка:** убраны YAML, Hugo shortcodes, конвертирован Obsidian-синтаксис
# Лекция 1. Вводная лекция: Java и JVM
## О чём курс
Курс охватывает следующие темы:
- Устройство виртуальной машины Java
- Типы данных и ООП на Java
- Обработка ошибок, Generics
- Java коллекции и функциональное программирование
- Работа с базами данных
- Средства сборки и тестирование
- Spring и микросервисы
## Историческая справка: предпосылки появления Java
### Эволюция языков программирования
**40-е годы — перфокарты.** Суперкомпьютер того времени мог принимать на ввод около 125 карт/минуту и выдавать 100 карт/минуту на вывод.
**60-е годы.** Появляются FORTRAN, BASIC, COBOL, Pascal, Ассемблер. Зарождаются основные парадигмы программирования. Разбиение на множество концепций (ПП, СП, ООП, ФП). Главный недостаток того времени — ни один язык не удовлетворял одновременно критериям:
- простота использования
- предоставляемые возможности
- безопасность
- эффективность
- устойчивость
- расширяемость
**70-е годы.** Появляется **Си** — современный этап развития языков. Удовлетворял всем критериям и был создан программистами для программистов (в отличие от академических или бюрократических предшественников).
**80-е годы — период консолидации.** Появляется **C++**, объединивший черты объектно-ориентированного и системного программирования. ООП решает проблему размера программ, разбивая их на классы и модули.
**90-е годы.** Массовая домашняя компьютеризация. Появляются требования к переносимости — возможности запуска одного и того же кода на различных платформах.
### Рождение Java
Из «хотелки» переносимости родилась Java — язык, который теоретически мог бы запуститься на любом холодильнике, микроволновке или мобильном телефоне. Одновременно росла всемирная паутина, и вопрос платформонезависимости стал ещё острее.
В результате появился ЯП, который позволял запускать **единожды скомпилированную программу на любой системной архитектуре** и соответствовал высоким стандартам C++ (хотя в некоторых аспектах уступал ему).
## Managed vs Unmanaged Code
Высокоуровневые языки обычно компилируют код не в машинный, а в промежуточный язык. Этот код потом подаётся в финальный компилятор, который создаёт объектный модуль или машинный код. Делается это для облегчения оптимизации и увеличения портируемости.
## Байт-код и JVM
**Байт-код Java** — набор инструкций, исполняемых виртуальной машиной Java. Каждый код операции — один байт; из 256 возможных значений используются не все (51 зарезервирован для будущего).
Знание байт-кода не обязательно для программирования на Java, но понимание его генерации помогает Java-программисту так же, как знание ассемблера помогает C/C++-программисту.
### Три кита Java-платформы
| Аббревиатура | Расшифровка | Назначение |
|--------------|-------------|------------|
| **JVM** | Java Virtual Machine | Виртуальная машина, исполняющая байт-код |
| **JRE** | Java Runtime Environment | Среда выполнения: библиотеки классов, загрузчик, JVM |
| **JDK** | Java Development Kit | Средства разработки. Основной — OpenJDK |
### Три части JVM
1. **Спецификация** — набор правил, описывающий как должна быть реализована JVM. По сути сводится к «JVM должна правильно запускать программы, написанные на Java».
2. **Реализация** — реальная программа, которая запускает Java-код. Существует много реализаций (как коммерческих, так и open-source).
3. **Экземпляр** — оболочка над вашим кодом, которая его исполняет. Например, Minecraft — это программа на Java, но запускается она в экземпляре JVM со своими ресурсами.
## Пример: Java-код и его байт-код
**Java-код:**
```java
package ru.butenko.springdatatest;
import java.time.LocalDate;
public class Dog {
public String name;
private LocalDate birthdate;
public int calculateAge() {
return LocalDate.now().getYear() - this.birthdate.getYear();
}
}
```
**Соответствующий байт-код:**
```
public class ru/butenko/springdatatest/Dog {
public Ljava/lang/String; name
private Ljava/time/LocalDate; birthdate
public calculateAge()I
INVOKESTATIC java/time/LocalDate.now ()Ljava/time/LocalDate;
INVOKEVIRTUAL java/time/LocalDate.getYear ()I
ALOAD 0
GETFIELD ru/butenko/springdatatest/Dog.birthdate
INVOKEVIRTUAL java/time/LocalDate.getYear ()I
ISUB
IRETURN
}
```
## Основы синтаксиса Java
### Типы данных, операции, управляющие конструкции
- **Целочисленные:** `byte`, `short`, `int`, `long`
- **С плавающей точкой:** `float`, `double`
- **Логический:** `boolean`
- **Символьный:** `char`
- **Объекты и массивы:** `Object`, `int[] x`
Поддерживаются: арифметические и логические операции, операции отношения, `if`/`switch`, `while`/`do-while`/`for`.
### Синтаксис классов
**Наследование:**
```java
public class Dog extends Animal {
// код класса
}
```
**Имплементация интерфейса:**
```java
public class Cat implements Eatable {
// код класса
}
```
## Исключения
Важно понимать ключевое различие:
- Проверка на **checked-исключения** происходит в момент **компиляции** (compile-time checking).
- **Перехват** исключений (`catch`) происходит в момент **выполнения** (runtime checking).
```java
public class App {
// "пугаем" компилятор тем, что можем выбросить Exception
public static void main(String[] args) throws Exception {
throw new Exception();
}
}
```
## JavaDoc
JavaDoc — встроенный инструмент документирования. Поддерживает теги `@param`, `@return`, `@throws` и другие.
```java
/**
* Класс, описывающий объект "Собака".
*/
public class Dog {
public String name;
private LocalDate birthdate;
/**
* Метод, который вычисляет возраст собаки на основе её даты рождения.
*
* @return int возраст собаки.
*/
public int calculateAge() {
return LocalDate.now().getYear() - this.birthdate.getYear();
}
/**
* Метод, который задаёт кличку собаки.
*
* @param name - кличка, которая будет дана собаке.
*/
public void setName(String name) {
this.name = name;
}
/**
* Метод, который выбрасывает исключение.
*
* @throws Exception - базовое исключение для демонстрации тега.
*/
public void doException() throws Exception {
throw new Exception("Exception!");
}
}
```
---
## Что такое сборка мусора?
**Сборка мусора** — это процесс восстановления заполненной памяти среды выполнения путём уничтожения неиспользуемых объектов.
## Проблема утечек памяти в unmanaged-языках
В таких языках, как C и C++, программист отвечает и за создание, и за уничтожение объектов. Иногда программист может забыть уничтожить бесполезный объект, и память не освобождается. В результате приложение страдает от **утечек памяти**, и в конечном итоге падает с `OutOfMemoryError`.
В C++ для освобождения памяти используется `delete`, в C — `free()`. В managed-языках сборка мусора происходит **автоматически**.
### Почему утечки памяти — серьёзная проблема
Не допускать утечек памяти на первый взгляд несложно — нужно «класть на место всё, что взяли». Но на практике это сильно осложняется:
- хитрой архитектурой
- нелинейным порядком выполнения (из-за исключений)
- человеческим фактором
Помимо утечек важна и противоположная проблема — **обращение к уже высвобожденной памяти**. Если указатель остался, а память освобождена, обращение по нему ведёт к `Segmentation fault`.
## Автоматическое управление памятью
Java-код транслируется в байт-код, который исполняет JVM. Таким образом, **JVM играет ключевую роль** и предоставляет важнейшую возможность — автоматическое управление памятью.
Инструмент, освобождающий неиспользуемую память, называется **Garbage Collector (GC)**. У GC две задачи:
1. **Обнаружение** мусора
2. **Очистка** мусора
> «Мусор» — это структура данных (объект) в памяти, к которому из программного кода больше невозможно получить доступ.
## Подходы к обнаружению мусора
### 1. Reference Counting (подсчёт ссылок)
Каждый объект имеет счётчик ссылок. Когда ссылка уничтожается — счётчик уменьшается. Если счётчик равен нулю — объект считается мусором.
**Используется в:** Python и других языках.
**Плюсы:**
- Простой
- Не требует долгих пауз для сборки
**Минусы:**
- Плохо сочетается с многопоточностью
- Сложно выявлять **циклические зависимости** — когда два объекта ссылаются друг на друга, но никто живой на них не ссылается; в итоге утечка
- Влияет на производительность — каждое чтение/запись ссылки требует обновления счётчика
### 2. Tracing (отслеживание)
Подход вводит понятие **GC Root**.
> Живые объекты — это те, до которых мы можем добраться от корня (GC Root). Всё остальное — мусор. Всё, что доступно с живого объекта — также живое.
Если представить все объекты и ссылки как дерево, нужно пройти от корневых узлов по всем достижимым. До чего не дошли — мусор. **Проблема циклических зависимостей решается автоматически.**
### Типы GC Root в Java 8
- Локальные переменные и параметры методов
- Потоки Java
- Статические переменные
- Ссылки из JNI (Java Native Interface — механизм для запуска кода из unmanaged-языков)
Даже самое простое Java-приложение имеет GC Root:
- Локальные переменные внутри `main`
- Статические переменные класса `main`
- Параметры `main` (`args`)
- Поток, выполняющий `main`
### Scope жизни GC Root
Компилятор вычисляет для каждой переменной **live range** — путь от определения переменной до последнего использования. Если переменная вне своего live range, она перестаёт быть корнем для GC.
## Алгоритмы очистки памяти
### 1. Копирующая сборка
Память делится на области **from-space** и **to-space**. Все объекты создаются в from-space. Когда место заканчивается, происходит **stop-the-world** — все «живые» объекты копируются в to-space, from-space очищается полностью, и области меняются местами.
### Stop-the-World
Простые сборщики полностью останавливают выполнение программы во время сборки. Это гарантирует, что новые объекты не выделяются и старые не становятся внезапно недоступными.
**Преимущества паузы:**
- Проще определять достижимость (граф объектов заморожен)
- Проще перемещать объекты в куче
Для задач с критичными к паузам системами существует **инкрементальная сборка** — много кратковременных пауз вместо одной длинной.
### 2. Mark-and-Sweep
Похож на копирующий, но с улучшениями:
1. Приложение полностью останавливается
2. Проходим по всем объектам и помечаем (**mark**) «живые»
3. Делаем **sweep** — чистим всё непомеченное
**Минус:** память становится фрагментированной (остаются «дыры»).
### 3. Mark-and-Sweep Compact
Улучшение Mark-and-Sweep:
1. Ищем «мёртвые» объекты и помечаем их для переноса **параллельно работе приложения**
2. Останавливаем приложение для очистки
3. Делаем **compact** — дефрагментируем память, объекты сдвигаются к более близким адресам
**Плюсы:**
- Нет фрагментации памяти
- Эффективно при большом количестве «живых» объектов
**Минусы:**
- Плохо работает при большом количестве «мёртвых» объектов
- Compact — дорогостоящая операция
## Сборка мусора на поколениях
### «Слабая гипотеза о поколениях»
Анализ работающих систем выявил две закономерности:
1. **Большинство объектов живут либо очень долго, либо очень недолго.** Долгоживущих очень мало.
2. **Между «старыми» и «новыми» объектами мало связей.**
Отсюда — идея разделения объектов на **поколения**:
- **Young Generation** (молодое поколение)
- **Old Generation** (старое поколение)
Сборки тоже делятся:
- **Minor GC** — затрагивает только младшее поколение, выполняется часто
- **Full GC** — затрагивает оба поколения, выполняется редко
## Характеристики сборщиков мусора
При оценке GC учитываются три фактора:
1. **Максимальная задержка** (STW pause) — максимальное время, на которое сборщик приостанавливает выполнение программы.
2. **Пропускная способность** — отношение общего времени работы к времени простоя из-за сборки.
3. **Потребляемые ресурсы** — объём CPU и дополнительной памяти, потребляемых сборщиком.
> Достичь идеала по всем трём показателям одновременно — невозможно. При выборе реализации сосредотачиваются на двух конкретных характеристиках.
## Структура памяти JVM
### Поколения в Heap
- **Young Generation** — здесь создаются новые объекты. Делится на три части:
- **Eden** — область, где изначально создаются объекты через `new Object()`
- **Survivor S0 / From Space** — куда попадают пережившие «изгнание из Эдема»
- **Survivor S1 / To Space** — последнее место перед переходом в Tenured
- **Old Generation (Tenured)** — здесь хранятся давно живущие объекты
### Метаданные
До Java 8 существовал **PermGen** — раздел для метаданных классов, интернированных строк и т.д. Был сложен в настройке размера, часто давал `OutOfMemoryError`.
Начиная с Java 8, эта область убрана. Информация переехала:
- Интернированные строки — в heap
- Остальные метаданные — в область **Metaspace** в native memory
Максимальный размер Metaspace по умолчанию ограничен только объёмом нативной памяти.
## Типы сборок
### Minor сборка
- Очищает только Young Generation (Eden + Survivor)
- «Живые» молодые объекты, пережившие достаточное число циклов, перемещаются в Tenured
- Остальные «живые» отправляются в пустую Survivor-область
- Eden и очищенная Survivor могут быть переиспользованы
- **Происходит часто, быстро, уничтожает много мусора**
### Major сборка
- Очищает Old Generation
- Тесно связана с minor, поэтому обычно рассматривается в составе full
### Full сборка
- Очищается и Young, и Old Generation
- Запускается, когда minor не может перенести объект (недостаточно места)
- **Происходит редко, но занимает много времени**
Сборщики, работающие с поколениями, называются **Generational Garbage Collection**.
## Реализации GC в HotSpot VM
| Сборщик | Версия Java | JVM-аргумент | Назначение |
|---------|-------------|--------------|------------|
| **Serial GC** | Все | `-XX:+UseSerialGC` | Небольшие приложения, однопоточные среды |
| **Parallel GC** | По умолчанию | `-XX:+UseParallelGC` | Средние/большие данные, многопоточное оборудование |
| **CMS GC** | До Java 9 | `-XX:+UseConcMarkSweepGC` | Веб-приложения, минимальные паузы |
| **G1 GC** | Java 9+ default | `-XX:+UseG1GC` | Крупный размер кучи (>4 ГБ) |
| **Epsilon GC** | Java 11+ | `-XX:+UseEpsilonGC` | Эксперименты, сверхнизкая задержка |
| **Shenandoah GC** | Java 15+ | `-XX:+UseShenandoahGC` | Параллельная сборка, минимальные паузы |
| **ZGC** | Java 15+ | `-XX:+UseZGC` | Низкая задержка (<10 мс), терабайтные кучи |
### Краткие характеристики
- **Serial GC** — все события сборки в одном потоке, всегда вызывает stop-the-world.
- **Parallel GC** — несколько потоков для minor GC, один для major. Также вызывает stop-the-world. Подходит для пакетных задач.
- **CMS** — Concurrent Mark and Sweep. Работает одновременно с приложением, минимизируя паузы. Потребляет больше CPU. В CMS не выполняется уплотнение.
- **G1** — Garbage First. Разбивает кучу на области (1–32 МБ), сканирует их параллельно. Имеет дополнительные области: **Humongous** (для объектов >50% размера кучи) и **Available** (неиспользуемое пространство).
- **Epsilon** — не реализует никакого реального механизма сборки. Как только куча исчерпана — JVM завершает работу. Используется для приложений, чувствительных к сверхвысокой задержке, или для тестирования.
- **Shenandoah** — большая часть цикла выполняется параллельно с приложением, объекты перемещаются на лету. Сильнее нагружает процессор.
- **ZGC** — паузы менее 10 мс даже на терабайтных кучах. Только для больших серверных решений.
## Заключение
> **Отдавайте предпочтение настройкам по умолчанию.** Если у вас небольшое автономное Java-приложение, скорее всего, настраивать GC не нужно. Это не то, на что должен ежедневно обращать внимание разработчик.
---
# Лекция 3. Collection Framework и Stream API
## Packages — пакеты в Java
Классы в Java объединяются в **пакеты**. Пакеты позволяют логически организовать классы и избежать конфликтов имён. Java имеет встроенные пакеты: `java.lang`, `java.util`, `java.io` и т.д.
Если для класса пакет не определён, он находится в пакете «по умолчанию». Чтобы использовать классы из других пакетов, нужно их подключить через `import`. **Исключение** — `java.lang` (`String`, `Object` и др.) подключается автоматически.
## Java Editions
| Edition | Описание |
|---------|----------|
| **Java SE** | Standard Edition. Стандартная редакция для консольных, GUI, апплет-приложений. |
| **Java EE** | Enterprise Edition. Для распределённых приложений масштаба предприятий. Включает EJB, JPA, Servlets, JMS. |
| **Java ME** | Micro Edition. Для микрокомпьютеров и мобильных платформ. |
Спецификации формируются через [Java Community Process](https://ru.wikipedia.org/wiki/Java_Community_Process).
## Java Collection Framework
**Collection Framework** — иерархия интерфейсов и реализаций, которая является частью JDK и предоставляет «из коробки» большое количество структур данных.
Все коллекции наследуются от интерфейса `java.util.Collection`. Он же наследуется от `Iterable` — поэтому всё, что является коллекцией, может использоваться в циклах `for-each`.
### Иерархия Collection Framework
```
Iterable
└── Collection
├── List
│ ├── ArrayList
│ ├── LinkedList
│ └── Vector → Stack
├── Queue
│ └── Deque
│ └── ArrayDeque
└── Set
├── HashSet
└── TreeSet
Map (отдельная иерархия)
├── HashMap
└── TreeMap
```
## List — списки
### ArrayList vs LinkedList
| Характеристика | ArrayList | LinkedList |
|----------------|-----------|------------|
| Основа | Массив | Связанный список (Entry хранит data + next + previous) |
| Тип доступа | Произвольный (Random Access) — по индексу | Последовательный (Sequential) |
| Рост | В 1.5 раза при заполнении | По одной Entry |
> Массив сам по себе не может изменять размер. Когда в `ArrayList` место заканчивается, создаётся новый массив **в 1.5 раза больше** и старые элементы копируются.
### Vector — предшественник ArrayList
`java.util.Vector` отличается от `ArrayList` тем, что методы для работы синхронизированы — один поток работает с коллекцией, остальные ждут.
- Растёт **в 2 раза** (не в 1.5, как ArrayList)
- В JavaDoc прямым текстом рекомендуется использовать `ArrayList`, если потокобезопасность не нужна
- Vector имеет наследника — `java.util.Stack` (LIFO-структура с методами `push`, `pop`, `peek`)
### Потокобезопасность коллекций
- **Synchronized** — потокобезопасные. Только один поток одновременно работает с коллекцией. Гарантируют целостность, но снижают производительность.
- **Non-Synchronized** — не потокобезопасные. Множество потоков могут работать одновременно.
Потокобезопасные версии коллекций лежат в пакете `java.util.concurrent`.
## Queue — очереди
Очередь (`java.util.Queue`) — FIFO-структура (first in — first out).
### Deque — двусторонняя очередь
`java.util.Deque` — двусторонняя очередь, позволяет использовать структуру с обоих концов. Реализация — `ArrayDeque`.
### Блокирующие очереди
- **BlockingQueue** реализуют: `PriorityBlockingQueue`, `SynchronousQueue`, `ArrayBlockingQueue`, `DelayQueue`, `LinkedBlockingQueue`
- **BlockingDeque** реализует: `LinkedBlockingDeque`
Каждая блокирующая очередь предназначена для определённых задач многопоточного программирования.
## Set — множества
Set отличается от очереди и списка большей абстракцией — «мешок с предметами», где порядок не определён.
- Сам интерфейс `Set` не добавляет новых методов к `Collection`, а лишь **уточняет требования** (отсутствие дубликатов)
- Получение элементов — только через `Iterator`
- Основные реализации:
- **HashSet** — на основе хэш-кода
- **TreeSet** — на основе дерева
## Map — карты
`java.util.Map` — структура «ключ — значение». Аналог словарей из C#.
### Основные методы
- `keySet()` — возвращает набор ключей (Set, т.к. ключи уникальны)
- `values()` — возвращает коллекцию значений (Collection, т.к. значения могут повторяться)
- `entrySet()` — возвращает набор пар «ключ — значение»
## Сторонние библиотеки коллекций
При необходимости можно создать собственную реализацию или использовать готовые сторонние решения:
- **Google Guava**
- **Apache Commons Collections**
Эти библиотеки выступают как boost для Java-коллекций (аналог Boost для C++).
## Stream API — «LINQ в Java»
Стримы и коллекции **похожи, но разные по назначению:**
- **Коллекции** — для эффективного доступа к одиночным объектам
- **Стримы** — для параллельных и последовательных агрегаций через цепочки методов
### Получение стрима
```java
list.stream() // из коллекции
Stream.empty() // пустой
Stream.of("1", "2", "3") // из указанных элементов
```
### Операторы Stream API
**Промежуточные (intermediate, lazy)** — обрабатывают элементы и возвращают новый стрим. Их в цепочке может быть много.
**Терминальные (terminal, eager)** — обрабатывают элементы и завершают работу стрима. В цепочке только один.
### Важные моменты
1. **Обработка не начнётся до вызова терминального оператора.**
2. **Экземпляр стрима нельзя использовать более одного раза** (в отличие от `IEnumerable` в C#).
3. Каждый раз для работы с коллекцией нужно открывать **новый поток**.
### Пример использования
```java
list.stream()
.filter(x -> x.toString().length() == 3)
.forEach(System.out::println);
list.stream().forEach(x -> System.out.println(x));
```
### Сравнение с LINQ (C#)
| C# (LINQ) | Java (Stream API) |
|-----------|-------------------|
| `.Where(predicate)` | `.filter(predicate)` |
| `.Select(...)` | `.map(...)` |
| `.SelectMany(...)` | `.flatMap(...)` |
| `Enumerable.Range` | `IntStream.range` |
| `.Take()` | `.limit()` |
| `.Skip()` | `.skip()` |
| `.Distinct()` | `.distinct()` |
| `.OrderBy(...)` | `.sorted(comparator)` |
| `.Count()` | `.count()` |
| `.Aggregate()` | `.reduce()` |
| `.First()` | `.findFirst()` |
| `.Any()` | `.anyMatch()` |
| `.Any()` с отрицанием | `.noneMatch()` |
| `.ToList()` | `.toList()` |
| `String.Join()` | `.joining(delimiter, prefix, suffix)` |
| `.FirstOrDefault(..., 5)` | `.map(...).orElse(5)` |
| `.Single(...)` | `.filter(...).orElseThrow()` |
---
# Лекция 4. Системы сборки
## Зачем нужны системы сборки
Технический подход в разработке заключается в том, что программы и библиотеки разбивают на части — пакеты и модули. В сообществе Java популярно написание библиотек на все случаи жизни и выкладывание их в общий доступ.
Дополнительный стимул — Java получила большую популярность на бэкенде. У серверного ПО более высокие требования к надёжности, и использование проверенных временем библиотек предпочтительнее написания своего кода.
Легко встретить бэкенд-проект из нескольких десятков модулей и сотни библиотек. **Описывать (и изменять!) сценарии сборки таких проектов вручную — чрезвычайно трудно.**
## Что такое автоматизация сборки?
**Автоматизация сборки** — это процесс автоматизации задач, необходимых для создания, выполнения и тестирования программ.
Исторически такие задачи решались с помощью **make-файлов**. Сегодня — с помощью средств автоматизации сборки или серверов сборки. Термины «автоматизация сборки» и «система сборки» используются как синонимы.
## Эволюция инструментов сборки в Java
В начале был **Make**, потом **GNU Make**. Сегодня в экосистеме JVM доминируют три инструмента:
1. **Apache Ant + Apache Ivy** — «Муравей с плющом»
2. **Apache Maven** — «Знаток/Эксперт»
3. **Google Gradle** — «Доктор Грейдл»
## Apache Ant + Ivy
### Ant
**Ant** был выпущен в 2000 году и быстро стал популярным. Имеет низкую кривую обучения, основан на **процедурном программировании**.
**Недостатки:**
- XML как формат скриптов сборки — иерархический по природе, плохо подходит для процедурного подхода
- XML становится неуправляемо большим во всех проектах, кроме очень маленьких
- Изначально не имел управления зависимостями — позже принял Apache Ivy
### Ivy
**Apache Ivy** — менеджер зависимостей для Java-проектов с поддержкой **транзитивных зависимостей** (возможность использовать зависимости других зависимостей).
Для работы Ivy нужны метаданные о ваших модулях — обычно их определяют в **Ivy-файлах**. Артефакты зависимостей (.jar) ищутся в настраиваемых репозиториях.
### Пример конфигурации Ant с Ivy
```xml
```
### Структура Ant build.xml
В Ant фазы сборки называются **целями** (``). Типичный набор:
1. **clean** — отчистка артефактов предыдущей сборки
2. **compile** — запуск `javac`
3. **jar** — упаковка class-файлов в Java Archive
4. **run** — запуск архива в JVM
## Apache Maven
**Maven** выпущен в 2004 году как усовершенствование Ant. Это инструмент сборки и **менеджер проектов** на основе XML.
**Ключевое отличие от Ivy:** Apache Maven — это не только менеджер зависимостей, но и инструмент управления проектом и его сборкой. Ivy же — только инструмент управления зависимостями.
### Maven Central
Репозиторий по умолчанию — **Maven Central**. Де-факто репозиторий для всех Java-библиотек (аналог nuget.org из мира .NET).
### POM — объектная модель проекта
Проекты Maven описываются файлами **POM** (Project Object Model) — `pom.xml`. До Maven у каждой IDE был свой формат project-файла (зачастую бинарный). Maven предложил универсальный открытый стандарт.
**В `pom.xml` хранятся:**
- Информация о версии стандарта Maven-проекта
- Информация о самом проекте
- Информация об используемых библиотеках (зависимостях)
### Структура pom.xml
```xml
...
```
### Артефакт в Maven
В стандарте Maven всё (программа, проект, модуль, библиотека) называется **артефактом**. Артефакт описывается тремя параметрами:
- **groupId** — пакет, к которому принадлежит приложение, обычно с именем домена
- **artifactId** — уникальный строковый ключ (id проекта)
- **version** — версия проекта
Эти три параметра однозначно описывают любой артефакт. Например: `org.junit.jupiter:junit-jupiter-api:5.9.2`.
### Зависимости
**Зависимость** в терминологии Maven — это какой-либо артефакт, который необходим проекту для корректной работы. Артефактом может быть обычный Java-проект, собранный и распространённый с помощью Maven.
### Архетипы
**Архетипы** — стандартизированные шаблоны проектов. Стартовая структура папок (`src`, `java`, `test` и т.д.) задаётся архетипом. Список архетипов можно получить командой:
```bash
mvn archetype:generate
```
### Жизненный цикл Maven
Основное преимущество Maven — его жизненный цикл. Пока проект следует определённым стандартам, через него можно легко пройти полный цикл.
| № | Фаза | Описание |
|---|------|----------|
| 1 | **validate** | Проверяет корректность метаинформации о проекте |
| 2 | **compile** | Компилирует исходники |
| 3 | **test** | Прогоняет тесты |
| 4 | **package** | Упаковывает классы в артефакт: jar, war, zip |
| 5 | **verify** | Проверяет корректность артефакта и соответствие требованиям качества |
| 6 | **install** | Кладёт артефакт в локальный репозиторий |
| 7 | **deploy** | Заливает артефакт на production-сервер или удалённый репозиторий |
### Maven-плагины
Жизненный цикл можно расширить с помощью **плагинов**. Плагины — самая обычная вещь в Maven: чтобы задать нюансы сборки, нужно подключить плагин.
Плагины описываются почти так же, как зависимости:
- `dependencies` → `plugins`
- `dependency` → `plugin`
- `repositories` → `pluginRepositories`
### Список популярных Maven-плагинов
| Плагин | Назначение |
|--------|------------|
| `maven-compiler-plugin` | Управляет Java-компиляцией |
| `maven-resources-plugin` | Управляет включением ресурсов в сборку |
| `maven-source-plugin` | Управляет включением исходного кода |
| `maven-dependency-plugin` | Управляет копированием библиотек зависимостей |
| `maven-jar-plugin` | Создание итогового jar-файла |
| `maven-war-plugin` | Создание итогового war-файла |
| `maven-surefire-plugin` | Управляет запуском тестов |
| `buildnumber-maven-plugin` | Генерирует номер сборки |
## Gradle
**Gradle** выпущен в 2008 году как преемник Maven. Главное отличие — вместо XML использует **DSL** (Domain Specific Language) на основе **Groovy** (один из языков JVM).
### Преимущества Gradle
- Скрипты сборки **намного короче и понятнее**, чем Ant/Maven
- Меньше boilerplate-кода
- DSL предназначен для решения конкретной задачи — продвижение ПО через жизненный цикл
### Gradle и репозитории
Gradle **не имеет собственных репозиториев** — использует Maven и Ivy репозитории как источники зависимостей. Интерфейс работы с ними на базовом уровне одинаков.
### gradlew — Gradle Wrapper
Существует два варианта работы с Gradle:
1. **Глобально** через `gradle`, установленный в системе
2. **Через `gradlew`** — Wrapper (обёртка)
**Преимущество Wrapper:**
- Версия Gradle одинакова у всей команды
- Никому не нужно вручную качать дистрибутив
- Wrapper установит нужную версию инструментов **локально для проекта**
---
# Лекция 5. Работа с базами данных (JDBC и JPA)
## Архитектура Java EE приложений
Java EE приложения разделены по функциональному принципу на изолированные модули. Обычно делятся на **три уровня** (как у Фаулера):
1. **Клиентский уровень**
2. **Промежуточный уровень (BLL)** — Business Logic Layer
3. **Уровень доступа к данным (DAL)**
### Технологии уровня доступа к данным
- **JDBC** — Java Database Connectivity API
- **JPA** — Java Persistence API
- **Java EE Connector Architecture**
- **JTA** — Java Transaction API
## JDBC — Java Database Connectivity
**Низкоуровневое API** для доступа к данным в хранилищах. Типичное использование — написание SQL-запросов к конкретной базе.
### JDBC Driver Manager
Java-приложение общается с БД через **JDBC Driver** — набор классов, реализующих JDBC API для конкретной СУБД.
Драйвер выбирается через **DriverManager**. Можно использовать In-Memory-DB, NoSQL-DB или базу, встроенную в Android-приложение — Java-разработчика эти нюансы не касаются, DriverManager сам выберет подходящий драйвер.
### Connection String
Для подключения протокола JDBC API:
```
jdbc:mysql://localhost:3306/db_scheme
```
- `mysql` — протокол работы с сервером
- `localhost` — имя хоста в сети
- `3306` — порт
- `db_scheme` — имя схемы (БД)
### Установка JDBC Driver
Драйвер для конкретной БД нужно установить через систему сборки:
```xml
mysql
mysql-connector-java
8.0.29
```
### Statements
SQL-запросы делятся на две группы:
| Группа | Операторы | Метод |
|--------|-----------|-------|
| **Получение данных** | `SELECT` | `executeQuery()` — возвращает `ResultSet` |
| **Изменение данных** | `INSERT`, `UPDATE`, `DELETE` | `executeUpdate()` — возвращает число изменённых строк |
### Callable Statements
**CallableStatement** используется для вызова хранимых процедур в БД. Хранимая процедура похожа на функцию класса, но находится в базе данных. Тяжёлые операции с БД могут выиграть в производительности при выполнении в том же пространстве памяти, что и сервер БД.
### DataSource и ConnectionPoolDataSource
Интерфейсы из пакета `javax.sql`, реализуемые поставщиками JDBC-классов.
**Основное назначение:** предоставить возможность получения соединения с БД **абстрагируясь от местоположения сервера СУБД и типа драйвера**.
Объекты `DataSource` используются для получения **физического соединения** с БД и являются **альтернативой DriverManager** — нет необходимости регистрировать драйвер. Достаточно установить параметры соединения и вызвать `getConnection()`.
## JTA — Java Transaction API
**API для определения и управления транзакциями**, включая распределённые транзакции, а также транзакции, затрагивающие множество хранилищ данных.
## JPA — Java Persistence API
**JPA** — спецификация Java EE и Java SE, описывающая систему управления **сохранением Java-объектов в таблицы реляционных БД**. Гораздо более высокоуровневое API по сравнению с JDBC — скрывает всю его сложность.
### JDO vs JPA
**JDO** (Java Data Objects) — более общая спецификация ORM для любых баз и хранилищ. **JPA** можно рассматривать как часть JDO, специализированную на реляционных базах.
### Hibernate
**Hibernate** — самая популярная open-source реализация JPA (де-факто стандарт). JPA только описывает правила и API, а Hibernate реализует эти описания. Hibernate имеет дополнительные возможности, не описанные в JPA (и не переносимые на другие реализации).
## JPA Entity
**Entity** — это легковесный хранимый объект бизнес-логики (persistent domain object). Основная программная сущность — entity-класс. Может использовать дополнительные вспомогательные классы.
### Жизненный цикл Entity
У Entity четыре статуса:
1. **new** — объект создан, ещё нет сгенерированных первичных ключей, не сохранён в БД
2. **managed** — объект создан, управляется JPA, имеет первичные ключи
3. **detached** — объект был создан, но не управляется (или больше не управляется) JPA
4. **removed** — объект создан, управляется JPA, но будет удалён после коммита транзакции
## JPA EntityManager
**EntityManager** — главный интерфейс для работы с JPA. Описывает API для всех основных операций.
### Группы операций EntityManager
**1. Операции над Entity:**
- `persist` — сохранение нового объекта
- `merge` — слияние с существующим
- `remove` — удаление
- `refresh` — обновление из БД
- `detach` — отсоединение от управления
- `lock` — блокировка
**2. Получение данных:**
- `find` — поиск по ключу
- `createQuery`, `createNamedQuery`, `createNativeQuery` — создание запросов
- `contains` — проверка наличия
- `createNamedStoredProcedureQuery`, `createStoredProcedureQuery` — вызов процедур
**3. Получение других сущностей JPA:**
- `getTransaction` — транзакция
- `getEntityManagerFactory` — фабрика
- `getCriteriaBuilder` — построитель критериев
- `getMetamodel` — метамодель
- `getDelegate` — делегат
**4. Работа с EntityGraph:**
- `createEntityGraph`, `getEntityGraph`
**5. Общие операции:**
- `close`, `isOpen`, `getProperties`, `setProperty`, `clear`
---
# Лекция 6. Spring Framework
## Что такое Spring Framework
**Spring Framework** — open-source фреймворк (платформа) для языков семейства JVM:
- Java
- Kotlin
- Groovy
- Scala
- C# (форк)
### Spring и Java EE
Spring является **альтернативой Java EE (Jakarta EE)** и по сути его расширением:
- Spring совместим с Java EE (но не обратное)
- Имеет множество расширений (MVC, Data и т.п.)
- Активно поддерживается сообществом
## Inversion of Control (IoC) — инверсия управления
**IoC-контейнер** — центральная часть фреймворка. Предоставляет средства конфигурирования и управления Java-объектами с помощью **рефлексии**.
IoC-контейнер отвечает за:
- Управление **жизненным циклом** объекта
- Создание объектов
- Вызов методов инициализации
- Связывание объектов между собой
Объекты, создаваемые контейнером, называются **бинами (Beans)**.
### Два способа реализации IoC
1. **Поиск зависимостей (Dependency Lookup)** — вызывающий объект запрашивает у контейнера экземпляр объекта с определённым именем или типом
2. **Внедрение зависимостей (Dependency Injection)** — контейнер передаёт экземпляры объектов другим объектам по их имени
### Способы внедрения зависимостей
- **Через конструктор** (Constructor Injection)
- **Через set-метод** (Setter Injection)
- **Через свойства** (Field Injection)
## Spring изнутри: жизненный цикл бина
### 1. Парсинг конфигурации и создание BeanDefinition
Существует три способа конфигурации Spring:
| Способ | Класс контекста |
|--------|-----------------|
| **XML-конфигурация** | `ClassPathXmlApplicationContext("context.xml")` |
| **Аннотации со сканированием пакета** | `AnnotationConfigApplicationContext("package.name")` |
| **JavaConfig** (через `@Configuration`) | `AnnotationConfigApplicationContext(JavaConfig.class)` |
### XML-конфигурация
Использует класс `XmlBeanDefinitionReader`, реализующий интерфейс `BeanDefinitionReader`. Каждый элемент XML обрабатывается, и если он является бином — создаётся `BeanDefinition`. Все `BeanDefinition` помещаются в `Map`, хранящийся в `DefaultListableBeanFactory`.
### Конфигурация через аннотации и JavaConfig
Используется класс `AnnotationConfigApplicationContext`. Внутри два важных поля:
- **ClassPathBeanDefinitionScanner** — сканирует указанный пакет на наличие классов с аннотацией `@Component`. Найденные классы парсируются, для них создаются `BeanDefinition`.
- **AnnotatedBeanDefinitionReader** — работает в несколько этапов:
1. Регистрирует все `@Configuration` для дальнейшего парсинга
2. Регистрирует специальный `BeanFactoryPostProcessor` (а именно `BeanDefinitionRegistryPostProcessor`), который парсит JavaConfig через `ConfigurationClassParser` и создаёт `BeanDefinition`
### 2. Настройка созданных BeanDefinition
После первого этапа `BeanDefinition` хранятся в `Map`. Архитектура Spring позволяет повлиять на бины **ещё до их фактического создания** — у нас есть доступ к метаданным класса.
Для этого существует интерфейс **`BeanFactoryPostProcessor`**. Реализовав его, мы получаем доступ к созданным `BeanDefinition` и можем их изменять.
Метод `postProcessBeanFactory` принимает `ConfigurableListableBeanFactory`. Через `getBeanDefinitionNames` можем получить все имена, а затем по конкретному имени — конкретный `BeanDefinition` для обработки метаданных.
### 3. FactoryBean
**FactoryBean** — generic-интерфейс, которому можно делегировать процесс создания бинов определённого типа. Был особенно нужен в эпоху XML-конфигурации, когда требовался механизм управления процессом создания бинов.
### 4. Создание экземпляров бинов
На этом этапе Spring создаёт сами экземпляры бинов согласно `BeanDefinition`.
### 5. Настройка созданных бинов через BeanPostProcessor
Интерфейс **`BeanPostProcessor`** позволяет вклиниться в процесс настройки бинов **до того, как они попадут в контейнер**. Содержит два метода:
- `postProcessBeforeInitialization` — вызывается **до** init-метода
- `postProcessAfterInitialization` — вызывается **после** init-метода
> **Важно:** на этом этапе экземпляр бина уже создан и идёт его донастройка. Если хотите сделать прокси над объектом — делайте это в `postProcessAfterInitialization`.
## Scope бинов
Spring поддерживает несколько scope для бинов:
- **`SCOPE_SINGLETON`** — инициализация **один раз** на этапе поднятия контекста (по умолчанию)
- **`SCOPE_PROTOTYPE`** — инициализация **каждый раз** по запросу
(Также существуют request, session, application и websocket для веб-приложений.)
---
# Лекция 7. Spring AOP
## Аспектно-ориентированное программирование
**АОП** — парадигма программирования, являющаяся дальнейшим развитием процедурного и объектно-ориентированного программирования. Идея АОП заключается в выделении так называемой **сквозной функциональности** — кода, который повторяется в разных местах приложения (логирование, безопасность, транзакции, кэширование).
## Ключевые понятия Spring AOP
### Join Point — точка соединения
**Join point** — точки наблюдения, присоединения к коду, где планируется введение функциональности. Это конкретные места в программе, где может быть применён аспект (вызовы методов, обращения к полям и т.д.).
### Pointcut — срез
**Pointcut** — срез, запрос точек присоединения. Может содержать одну или несколько точек.
Правила запросов очень разнообразные: запрос по аннотации на методе, по конкретному методу и т.д. Правила можно объединять через `&&`, `||`, `!`.
### Advice — совет
**Advice** — набор инструкций, выполняемых на точках среза (Pointcut). Инструкции можно выполнять по событиям разных типов:
| Тип Advice | Когда выполняется |
|------------|-------------------|
| **Before** | Перед вызовом метода |
| **After** | После вызова метода |
| **After returning** | После возврата значения из функции |
| **After throwing** | В случае exception |
| **After finally** | В случае выполнения блока finally |
| **Around** | Можно сделать пред- и пост-обработку, а также вообще обойти вызов метода |
На один `Pointcut` можно «повесить» несколько `Advice` разного типа.
### Aspect — аспект
**Aspect** — модуль, в котором собраны описания Pointcut и Advice. По сути, это класс-контейнер для всей логики, связанной с одной сквозной функциональностью.
> *«If you want your code to be easy to write, make it easy to read.» — Robert C. Martin, Clean Code*
## Производительность Spring AOP
Всё происходит в **рантайме**, если не используются AspectJ-компиляторы, которые могут сгенерировать бины заранее (это называется **Weaving**, ткачество).
## Реализация прокси в Spring AOP
Spring AOP использует два механизма прокси:
| Механизм | Когда используется |
|----------|-------------------|
| **JDK Dynamic Proxy** | По умолчанию. Используется, если целевой объект реализует хотя бы один **интерфейс**. Позволяет проксировать любой интерфейс (или их набор). |
| **CGLIB Proxy** | По умолчанию используется, если бизнес-объект **не реализует ни одного интерфейса**. |
---
# Лекция 8. Spring MVC
## Что такое Spring MVC
**Spring MVC** — модуль, обеспечивающий архитектуру паттерна **Model — View — Controller** при помощи слабосвязанных готовых компонентов.
Паттерн MVC разделяет аспекты приложения и обеспечивает свободную связь между ними:
| Компонент | Назначение |
|-----------|------------|
| **Model** (Модель) | Инкапсулирует данные приложения. Обычно — POJO («Plain Old Java Objects» или бины) |
| **View** (Отображение) | Отвечает за отображение данных Модели, обычно генерируя HTML |
| **Controller** (Контроллер) | Обрабатывает запрос пользователя, создаёт Модель и передаёт её в View |
## DispatcherServlet — сердце Spring MVC
Spring MVC построен вокруг центрального сервлета — **DispatcherServlet**, который:
- Распределяет запросы по контроллерам
- Полностью интегрирован в Spring IoC-контейнер
- Имеет доступ ко всем возможностям Spring
### Базовый алгоритм работы DispatcherServlet
1. Получает HTTP-запрос
2. Обращается к **HandlerMapping** для определения, какой контроллер вызвать
3. Отправляет запрос в нужный контроллер
4. Контроллер вызывает соответствующий метод (на основе GET/POST), формирует Модель и возвращает имя View
5. **ViewResolver** определяет, какой View использовать на основе полученного имени
6. После создания View, DispatcherServlet отправляет данные Модели в виде атрибутов в View, который отображается в браузере
## Аннотация @Controller
`DispatcherServlet` отправляет запрос **контроллерам**. Аннотация **`@Controller`** указывает, что класс является контроллером. **`@RequestMapping`** связывает класс или метод с URL.
## Специальные бины Spring MVC
`DispatcherServlet` делегирует специальные компоненты для обработки запросов:
| Бин | Назначение |
|-----|------------|
| **HandlerMapping** | Сопоставляет запрос с обработчиком (handler-объектом) и списком интерсепторов |
| **HandlerAdapter** | Помогает DispatcherServlet вызвать обработчик независимо от того, как он реализован |
| **HandlerExceptionResolver** | Стратегия разрешения исключений: сопоставление с обработчиками, представлениями ошибок |
| **ViewResolver** | Преобразует логические имена View в фактические View |
| **LocaleResolver / LocaleContextResolver** | Определяет «локаль» клиента и часовой пояс для интернационализированных представлений |
| **ThemeResolver** | Определяет темы веб-приложения |
| **MultipartResolver** | Абстракция для разбора multipart-запросов (загрузка файлов) |
| **FlashMapManager** | Хранит и извлекает «входную» и «выходную» FlashMap для передачи атрибутов между запросами (через redirect) |
### Реализации HandlerMapping
Основные реализации:
- **RequestMappingHandlerMapping** — поддерживает аннотированные методы `@RequestMapping`
- **SimpleUrlHandlerMapping** — поддерживает явную регистрацию шаблонов пути URI к обработчикам
- **BeanNameUrlHandlerMapping**
### Реализации HandlerAdapter
- **HttpRequestHandlerAdapter** — поддерживает классы, реализующие `HttpRequestHandler`
- **SimpleControllerHandlerAdapter** — поддерживает классы, реализующие `Controller`
- **RequestMappingHandlerAdapter** — поддерживает контроллеры с `@RequestMapping`
## Полный путь HTTP-запроса в Spring MVC
### Шаг 1. Поиск обработчика
После получения HTTP-запроса `DispatcherServlet` перебирает доступные `HandlerMapping`. Один из них определит, какой метод какого контроллера должен быть вызван.
- HandlerMapping по `HttpServletRequest` находит **handler-объект** (например, `HandlerMethod`)
- Каждый HandlerMapping может иметь несколько реализаций `HandlerInterceptor` — для кастомизации пред- и пост-обработки запроса
- Список `HandlerInterceptor` + handler-объект → формируют `HandlerExecutionChain`
### Шаг 2. Выбор HandlerAdapter
Для выбранного обработчика определяется соответствующий `HandlerAdapter`.
### Шаг 3. Предобработка (preHandle)
Вызывается `applyPreHandle` на `HandlerExecutionChain`:
- Если вернул `true` — все интерсепторы выполнили предобработку, переходим к основному обработчику
- Если вернул `false` — один из интерсепторов взял обработку ответа на себя
### Шаг 4. Вызов handle
HandlerAdapter извлекается из `HandlerExecutionChain`. Через метод `handle` принимаются объекты запроса и ответа, а также метод-обработчик.
### Шаг 5. Возврат ModelAndView
Метод-обработчик возвращает в `DispatcherServlet` объект **ModelAndView**. Через `ViewResolver` определяется, какой View использовать.
**Для REST-контроллеров:**
- Вместо `ModelAndView` возвращается `null`
- `ViewResolver` не используется
- Ответ полностью содержится в теле `HttpServletResponse`
- Контроллер аннотируется `@RestController` или методы — `@ResponseBody`
### Шаг 6. Постобработка (postHandle)
Перед завершением вызывается `applyPostHandle` для постобработки с помощью интерсепторов.
### Шаг 7. Обработка исключений
Если в процессе обработки выбрасывается исключение, обрабатывается через одну из реализаций `HandlerExceptionResolver`:
- **ExceptionHandlerExceptionResolver** — обрабатывает исключения из методов с `@ExceptionHandler`
- **ResponseStatusExceptionResolver** — отображает исключения с `@ResponseStatus` в HTTP-статусы
- **DefaultHandlerExceptionResolver** — отображает стандартные исключения Spring MVC в HTTP-статусы
### Шаг 8. Рендеринг View
Для классических контроллеров — `DispatcherServlet` отправляет данные в виде атрибутов в View, который записывается в `HttpServletResponse`. Для REST — данная логика не вызывается, ответ уже в `HttpServletResponse`.
## HttpMessageConverter
Когда HTTP-запрос приходит с заголовком `Accept`, Spring MVC перебирает доступные `HttpMessageConverter`, пока не найдёт того, кто сможет конвертировать из POJO в указанный тип `Accept`.
**HttpMessageConverter работает в обоих направлениях:**
- Тела входящих запросов конвертируются в Java-объекты
- Java-объекты конвертируются в тела HTTP-ответов
Spring Boot определяет довольно обширный набор реализаций `HttpMessageConverter`. Можно добавлять собственные реализации или переопределять существующие.
---
# Лекция 9. Spring Boot
## Что такое Spring Boot
**Spring Boot** — дополнение к Spring, которое облегчает и ускоряет работу с ним. Представляет собой набор утилит, автоматизирующих настройки фреймворка.
### Зачем нужен Spring Boot
Сам по себе Spring имеет:
- Большое количество зависимостей
- Множество сложных конфигураций (часто в XML)
У рядового разработчика это вызывает проблемы при знакомстве с фреймворком. Spring Boot — самый быстрый и популярный способ запуска Spring-проектов.
### Типовые шаги старта без Spring Boot
Создавая каждое Spring-приложение, приходилось:
1. Импортировать Spring-модули в зависимости от типа приложения
2. Импортировать библиотеку web-контейнеров (для web)
3. Импортировать сторонние библиотеки совместимых версий (Hibernate, Jackson)
4. Конфигурировать DAO-компоненты: DataSource, transaction management
5. Определить класс для загрузки всех конфигураций
### Основные цели Spring Boot
- Обеспечить **быстрый старт** для любых разработок на Spring
- Возможность кастомизировать стандартное поведение
- Предоставлять **нефункциональные возможности**: встроенные серверы, безопасность, метрики, проверка работоспособности, тестирование и конфигурация
- Уйти от старых подходов с **XML-конфигурациями**
## За счёт чего достигаются цели Spring Boot
Согласно [spring.io](https://spring.io/projects/spring-boot):
- **'starter' зависимости**, облегчающие конфигурацию
- **Автоматическая конфигурация** различных библиотек
- **Преднастроенный Application Server** (Apache Tomcat)
- **Готовые рецепты** для широко используемых подходов
## Starter Packages
**Starter-пакеты** — набор удобных дескрипторов зависимостей. Включают в проект универсальное решение для конкретной технологии, избавляя от поиска совместимых версий.
### Примеры стартеров
- **`spring-boot-starter-data-jpa`** — подтянет всё для работы с БД (драйверы, Hibernate)
- **`spring-boot-starter-web`** — подтянет всё для веб-приложений: `spring-webmvc`, `jackson-json`, `validation-api`, Tomcat
- **`spring-boot-starter-test`** — набор тестовых библиотек
> Spring Boot собирает все общие зависимости и определяет их в одном месте, что позволяет разработчикам **просто использовать их**, а не «изобретать колесо» при создании каждого нового приложения.
## Application Server (Apache Tomcat)
**Apache Tomcat** — комплект серверных программ от Apache Software Foundation для тестирования, отладки и исполнения веб-приложений на Java. Обычно называется **контейнером сервлетов**.
`DispatcherServlet` из Spring крутится **внутри Tomcat**. Spring Boot включает Tomcat «из коробки».
## AutoConfiguration — автоматическая конфигурация
Spring Boot пытается автоматически настроить приложение **на основе добавленных jar-зависимостей**.
### Как работает AutoConfiguration
- Краеугольным камнем являются **AutoConfiguration-классы**, которые Spring Boot находит при запуске
- Аннотация **`@EnableAutoConfiguration`** сообщает Spring Boot, что нужно «угадать» как настроить Spring
- Если в classpath найдены `spring-boot-starter-web` (с Tomcat и Spring MVC) — настраивается веб-приложение
- Если используется `spring-boot-starter-jdbc` — автоматически регистрируются `DataSource`, `EntityManagerFactory`, `TransactionManager`, читается информация из `application.properties`
- Если БД не указана — настраивается резидентная база в памяти (если есть H2 или HSQL Driver)
### Конфигурация в @SpringBootApplication
Аннотация `@SpringBootApplication` объединяет несколько других:
- `@EnableAutoConfiguration` — включает автоконфигурацию
- `@ComponentScan` — сканирует компоненты
- `@SpringBootConfiguration` — помечает класс как конфигурацию
### Постепенная замена автоконфигурации
Автоконфигурация работает **неагрессивно** — в любой момент можно начать определять свою конфигурацию, чтобы заменить определённые её части.
Если добавить свой `DataSource` — поддержка встроенной БД отключается.
Для отладки автоконфигурации запустите приложение с параметром:
```
--debug
```
Это активирует отладочные журналы и выведет отчёт об условиях на консоль.
### Отключение автоконфигурации
Если определённые классы автоконфигурации не нужны, используйте атрибут `exclude` в `@SpringBootApplication`.
## Spring Initializr
[Spring Initializr](https://start.spring.io) — сервис, предоставляющий интерфейс для генерации заготовки проекта Spring со стандартными зависимостями.
- Можно конфигурировать зависимости
- Можно выбрать предпочитаемый сборщик
- IntelliJ IDEA имеет встроенную интеграцию
## Spring Beans и внедрение зависимостей
При использовании `@ComponentScan` (или `@SpringBootApplication`, которая её включает) все компоненты (`@Component`, `@Service`, `@Repository`, `@Controller` и др.) **автоматически регистрируются как бины Spring**.
## Инструменты разработчика (DevTools)
`spring-boot-devtools` предоставляет инструменты для разработки. **Автоматически деактивируются** при запуске «упакованного» приложения.
Если приложение запускается через `java -jar` или специальный загрузчик — оно считается **Production-сборкой**. Управлять можно через `spring.devtools.restart.enabled`.
> ⚠️ Использовать DevTools в production **нельзя** — это угроза безопасности.
### Автоматический перезапуск (Hot Module Reload)
Приложения с `spring-boot-devtools` **автоматически перезапускаются** при изменении файлов в classpath. Очень полезная функция при работе в IDE — обеспечивает быструю обратную связь.
### Технология перезапуска
Spring Boot использует **два загрузчика классов**:
- **Основной загрузчик** — для неизменяемых классов (сторонние jar)
- **Перезапускающий загрузчик** — для активно разрабатываемых классов
При перезапуске единовременно используется перезапускающий, после чего создаётся новый. Поэтому перезапуск **гораздо быстрее холодного запуска** — основной загрузчик уже доступен и заполнен.
## Цикл жизни Spring Application
События приложения отправляются в следующем порядке:
| № | Событие | Описание |
|---|---------|----------|
| 1 | `ApplicationStartingEvent` | В начале выполнения, перед обработкой (только регистрация слушателей) |
| 2 | `ApplicationEnvironmentPreparedEvent` | Когда `Environment` известно, но контекст ещё не создан |
| 3 | `ApplicationContextInitializedEvent` | `ApplicationContext` подготовлен, инициализаторы вызваны, но определения бинов ещё не загружены |
| 4 | `ApplicationPreparedEvent` | Перед обновлением, после загрузки определений бинов |
| 5 | `ApplicationStartedEvent` | После обновления контекста, перед вызовом командных средств |
| 6 | `AvailabilityChangeEvent (LivenessState.CORRECT)` | Приложение считается работающим |
| 7 | `ApplicationReadyEvent` | После вызова всех средств выполнения |
| 8 | `AvailabilityChangeEvent (ReadinessState.ACCEPTING_TRAFFIC)` | Приложение готово к обработке запросов |
| ❌ | `ApplicationFailedEvent` | Если при запуске возникло исключение |
### Дополнительные события
Между `ApplicationPreparedEvent` и `ApplicationStartedEvent` также публикуются:
- **WebServerInitializedEvent** — после готовности WebServer (`ServletWebServerInitializedEvent` или `ReactiveWebServerInitializedEvent`)
- **ContextRefreshedEvent** — при обновлении `ApplicationContext`
## Тестирование Spring приложений
Spring Boot предусматривает утилиты и аннотации, упрощающие тестирование. Поддержка обеспечивается двумя модулями:
- **`spring-boot-test`** — основные элементы
- **`spring-boot-test-autoconfigure`** — автоконфигурация тестов
Большинство разработчиков используют стартер **`spring-boot-starter-test`**, импортирующий оба модуля + полезные библиотеки.
### Что внутри spring-boot-starter-test
| Библиотека | Назначение |
|------------|------------|
| **JUnit 5** | Стандарт де-факто для модульного тестирования |
| **Spring Test, Spring Boot Test** | Утилиты для тестирования Spring Boot |
| **AssertJ** | Библиотека текучих утверждений |
| **Hamcrest** | Библиотека объектов-сопоставителей (matchers) |
| **Mockito** | Java-фреймворк для мокирования объектов |
| **JSONassert** | Библиотека утверждений для JSON |
| **JsonPath** | XPath для JSON |
### Преимущества DI для тестирования
Одно из главных преимуществ DI — облегчает модульное тестирование. Можно создавать объекты через `new`, не вовлекая Spring. Можно использовать mock-объекты вместо реальных зависимостей.
### Аннотация @SpringBootTest
Spring Boot предоставляет `@SpringBootTest` — альтернативу стандартной `@ContextConfiguration`.
**Поиск конфигурации:**
- Аннотации `@*Test` ищут вашу первичную конфигурацию автоматически
- Поиск начинается с пакета, содержащего тест
- Идёт до класса с `@SpringBootApplication` или `@SpringBootConfiguration`
### MockMvc
По умолчанию `@SpringBootTest` **не запускает сервер**, а создаёт имитационное окружение для тестирования конечных веб-точек.
В Spring MVC можно запрашивать веб-точки с помощью **MockMvc**.
---
# Лекция 10. Spring Data JPA
## Что такое Spring Data
**Spring Data** — дополнительный удобный механизм для взаимодействия с сущностями БД, их организации в репозитории, извлечения и изменения данных. В некоторых случаях достаточно объявить **интерфейс с методом без реализации** — Spring сгенерирует её сам.
## Spring Repository
Основное понятие в Spring Data — **репозиторий**. Это интерфейсы, использующие JPA Entity для взаимодействия с базой.
### Базовый интерфейс — CrudRepository
```java
public interface CrudRepository
extends Repository
```
Обеспечивает основные **CRUD-операции:**
| Буква | Операция |
|-------|----------|
| **C** | Create — создание |
| **R** | Read — чтение |
| **U** | Update — обновление |
| **D** | Delete — удаление |
### Другие абстракции репозиториев
- **PagingAndSortingRepository** — добавляет пагинацию и сортировку
- **JpaRepository** — JPA-специфичный, добавляет flush и batch-операции
### Расширение базового интерфейса
Если предоставленных методов достаточно — расширяете базовый интерфейс для своей сущности и добавляете свои методы запросов.
## Spring Data и сканирование сущностей
Традиционно сущности JPA задаются в `persistence.xml`. **В Spring Boot этот файл не требуется** — используется **Entity Scanning**. По умолчанию поиск выполняется во всех пакетах ниже основного конфигурационного класса (с `@EnableAutoConfiguration` или `@SpringBootApplication`).
## Подключение Spring Data JPA
Через стартер:
```groovy
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
```
### Application properties
DataSource по умолчанию использует H2 — встроенную in-memory БД:
```properties
spring.datasource.url=jdbc:h2:mem:db;DB_CLOSE_DELAY=-1
spring.datasource.username=sa
spring.datasource.password=sa
```
### Включение Spring Data
```java
@Configuration
@EnableJpaRepositories(basePackages = "org.example.data")
class JpaConfig {}
```
## Naming Conventions для методов репозитория
Spring Data JPA генерирует SQL-запросы **на основе имён методов**. Например:
- `findByNameAndAge(String name, int age)` — `SELECT ... WHERE name = ? AND age = ?`
- `findByLastNameOrderByFirstNameAsc(String lastName)` — `SELECT ... WHERE last_name = ? ORDER BY first_name ASC`
Полная документация: [Spring Data JPA Query Methods](https://docs.spring.io/spring-data/jpa/reference/jpa/query-methods.html)
## Аннотация @Query
Если name conventions не подходят — можно явно указать запрос через `@Query`.
```java
@Query("SELECT u FROM User u WHERE u.email = ?1")
User findByEmail(String email);
```
Также поддерживаются native SQL-запросы.
## Явная реализация репозиториев
Если нужно явно реализовать репозитории — это тоже возможно.
> **По умолчанию Spring Data будет «генерировать» реализацию только тех методов, которые не переопределены в классах с постфиксом `Impl`.**
То есть, если есть интерфейс `UserRepository` — создаём класс `UserRepositoryImpl`, и Spring Data будет использовать его реализации для пересекающихся методов.
## Что делать с DAO-слоем?
Spring Data JPA менеджит большую часть рутины:
- Генерирует SQL-запросы
- Производит маппинг
- Управляет транзакциями (JTA)
- … и многое другое
> Старые явные реализации DAO как будто бы и не нужны…
## Полезные инструменты
**JPA-Buddy plugin** для IntelliJ IDEA — упрощает создание Entity, репозиториев и других классов. Но не все фичи доступны в бесплатной версии.
Полезные ссылки для изучения:
- [Baeldung: Spring Data](https://www.baeldung.com/spring-data)
- [Baeldung: Persistence with Spring](https://www.baeldung.com/persistence-with-spring-series)
---
---
# Лекция 11. Spring Security
## Что такое Spring Security
**Spring Security** — Java/Java EE фреймворк, предоставляющий механизмы построения систем **аутентификации и авторизации**, а также другие возможности обеспечения безопасности для корпоративных приложений на Spring Framework.
## Архитектура: цепочка фильтров
Spring Security использует паттерн **«Цепочка обязанностей» (Chain of Responsibility)**. При выполнении HTTP-запроса происходит **поэтапная обработка фильтрами**, которые производят валидацию запроса.
## Основные фильтры Spring Security
### WebAsyncManagerIntegrationFilter
Интегрирует `SecurityContext` с `WebAsyncManager`, который ответственен за асинхронные запросы.
### SecurityContextPersistenceFilter
- Ищет `SecurityContext` в сессии
- Заполняет `SecurityContextHolder`, если находит
- По умолчанию используется `ThreadLocalSecurityContextHolderStrategy`, хранящий `SecurityContext` в `ThreadLocal`-переменной
### HeaderWriterFilter
Просто добавляет заголовки в response.
### LogoutFilter
Проверяет совпадает ли URL с паттерном (`/logout` POST по умолчанию) и запускает процедуру логаута:
1. Удаляется CSRF-токен
2. Завершается сессия
3. Чистится `SecurityContextHolder`
### BasicAuthenticationFilter
- Проверяет, есть ли заголовок `Authorization: Basic ...`
- Если находит — извлекает логин/пароль и передаёт их в `AuthenticationManager`
### RequestCacheAwareFilter
Восстанавливает оригинальный запрос пользователя после логина:
1. Пользователь заходит на защищённый URL
2. Его перекидывает на страницу логина
3. После успешной авторизации — возвращает на исходную страницу
Внутри проверяет, есть ли сохранённый запрос, и подменяет им текущий. Запрос сохраняется в сессии.
### AnonymousAuthenticationFilter
Если к моменту выполнения этого фильтра `SecurityContextHolder` пуст (т.е. аутентификации не произошло), фильтр заполняет его **анонимной аутентификацией** — `AnonymousAuthenticationToken` с ролью `ROLE_ANONYMOUS`.
Это гарантирует, что в `SecurityContextHolder` всегда будет объект — можно не бояться `NullPointerException` и более гибко настраивать доступ для неавторизованных пользователей.
### SessionManagementFilter
На этом этапе производятся действия, связанные с сессией:
- Смена идентификатора сессии
- Ограничение количества одновременных сессий
- Сохранение `SecurityContext` в `securityContextRepository`
В обычном случае:
1. `HttpSessionSecurityContextRepository` сохраняет `SecurityContext` в сессию
2. Вызывается `sessionAuthenticationStrategy.onAuthentication`
3. По умолчанию включена защита от **session fixation attack** — после аутентификации меняется ID сессии
4. Если был передан CSRF-токен — генерируется новый
### ExceptionTranslationFilter
К этому моменту `SecurityContext` должен содержать анонимную или нормальную аутентификацию. Этот фильтр прокидывает запрос/ответ по filter chain и обрабатывает возможные ошибки авторизации.
### FilterSecurityInterceptor
На **последнем этапе** происходит **авторизация** на основе URL запроса.
- Наследуется от `AbstractSecurityInterceptor`
- Решает, имеет ли текущий пользователь доступ к URL
- Существует другая реализация — `MethodSecurityInterceptor` — для допуска к методам (при использовании `@Secured` / `@PreAuthorize`)
- Внутри вызывается `AccessDecisionManager`
- Несколько стратегий принятия решения, по умолчанию: **AffirmativeBased**
## AuthenticationManager
**AuthenticationManager** — интерфейс, который принимает `Authentication` и возвращает `Authentication`. Это центральная точка процесса аутентификации.
Типичная реализация `Authentication` — **`UsernamePasswordAuthenticationToken`**.
## ProviderManager
**ProviderManager** — стандартная реализация `AuthenticationManager`. Содержит список `AuthenticationProvider`.
## AuthenticationProvider
Когда мы передаём объект `Authentication` в `ProviderManager`, он:
1. **Перебирает** существующие `AuthenticationProvider`-ы
2. **Проверяет**, поддерживает ли `AuthenticationProvider` эту имплементацию `Authentication`
3. Внутри `AuthenticationProvider.authenticate` мы приводим переданный `Authentication` к нужной реализации
4. Извлекаем credentials
5. **Если аутентификация не удалась** — выбрасываем исключение
6. **ProviderManager** ловит исключение и пробует следующий провайдер
7. Если **ни один** провайдер не вернул успешную аутентификацию — ProviderManager пробрасывает последнее пойманное исключение
После успешной аутентификации `BasicAuthenticationFilter` сохраняет полученный `Authentication`:
```java
SecurityContextHolder.getContext().setAuthentication(authResult);
```
Если выбрасывается `AuthenticationException` — контекст сбрасывается, вызывается `AuthenticationEntryPoint`.
---
---
# Лекция 12. Современная аутентификация и авторизация: Keycloak
## План лекции
1. **Keycloak** — что это и зачем
2. **OAuth 2.0 и OpenID Connect** — теория, JWT
3. **Интеграция со Spring Boot** — `oauth2Login` vs `oauth2ResourceServer`
4. **Магия AutoConfiguration** — какие бины создаются автоматически
5. **Практика и лучшие практики** — реальные репозитории и шпаргалка
## Что такое Keycloak
**Keycloak** — open-source решение для управления идентификацией и доступом (**IAM** — Identity and Access Management), поддерживаемое Red Hat. Берёт на себя весь комплекс задач по аутентификации, оставляя вашему приложению только авторизацию.
### Ключевые возможности
| Возможность | Описание |
|-------------|----------|
| **SSO и Sign-Out** | Бесшовный переход между приложениями без повторного ввода пароля |
| **Identity Brokering** | Аутентификация через Google, GitHub, LDAP, Active Directory |
| **Открытые стандарты** | OpenID Connect, OAuth 2.0, SAML 2.0 — всё из коробки |
| **2FA и Login Flows** | Двухфакторная аутентификация, кастомизация тем и гибкие потоки |
## Быстрый старт с Docker
```yaml
services:
keycloak:
container_name: keycloak.openid-provider
image: quay.io/keycloak/keycloak:26.0
command:
- start-dev
- --import-realm # Импортируем готовый realm при старте
ports:
- 8080:8080
volumes:
- ./keycloak/:/opt/keycloak/data/import/
environment:
KEYCLOAK_ADMIN: admin
KEYCLOAK_ADMIN_PASSWORD: ${KEYCLOAK_ADMIN_PASSWORD}
```
> **Совет:** Флаг `--import-realm` позволяет версионировать конфигурацию безопасности вместе с кодом — Realm-файл хранится в Git наравне с исходниками.
## Realm — изолированный домен безопасности
**Realm** — изолированный домен безопасности внутри Keycloak. Это контейнер верхнего уровня, инкапсулирующий уникальный набор пользователей, клиентов, ролей и конфигураций.
> **Аналогия:** Keycloak — многоквартирный дом. Каждый Realm — отдельная квартира со своим замком, жильцами и правилами.
### Ключевые свойства Realm
| Свойство | Описание |
|----------|----------|
| **Изоляция** | Пользователи и настройки одного Realm недоступны в другом. Можно разделить dev и prod |
| **Управление** | Realm управляет пользователями, их учётными данными, ролями и группами |
| **Наследование** | Мастер-администратор может создавать несколько Realms и управлять ими из единой точки |
## Сущности Realm: Клиенты (Clients)
**Client** — приложение или сервис, взаимодействующий с Keycloak для аутентификации и авторизации. Каждый клиент имеет уникальный **Client ID** и тип доступа.
### Типы клиентов
| Тип | Описание | Поток |
|-----|----------|-------|
| **Confidential** | Серверные приложения (бэкенд), могут безопасно хранить client-secret. Пример: Spring Boot API | Authorization Code Flow |
| **Public** | Клиентские приложения (SPA, мобильные), не могут безопасно хранить секреты | Authorization Code Flow with PKCE |
**Создание клиента:** `Clients` → `Create client` → указать Client ID → выбрать тип → `Save`.
## Сущности Realm: Пользователи и Роли
### 👤 Users (Пользователи)
Учётная запись конечного пользователя, аутентифицирующегося в системе.
1. `Users` → `Add user`
2. Заполнить Username, Email, First/Last Name
3. Вкладка `Credentials` → задать пароль, отключить флаг Temporary
### 🎭 Roles (Роли)
Именованный набор прав (**RBAC** — Role-Based Access Control). Keycloak поддерживает два типа ролей:
| Тип ролей | Описание |
|-----------|----------|
| **Realm Roles** | Глобальные роли, доступные всем клиентам в Realm. Подходят для `admin`, `user`, `manager` |
| **Client Roles** | Роли, специфичные для конкретного клиента. Когда разрешения уникальны для одного приложения |
### Назначение ролей и экспорт Realm
**Назначение роли:**
1. `Users` → выбрать пользователя
2. Вкладка `Role mapping` → `Assign role`
3. Выбрать нужные Realm- или Client-роли → `Assign`
**Экспорт Realm:**
1. `Realm Settings` → `Action` → `Partial Export` → JSON-файл
2. Сохранить JSON в репозиторий
3. Импорт: Docker `--import-realm` + смонтированная директория
## OAuth 2.0 и OpenID Connect
**OAuth 2.0** — протокол **авторизации**. Позволяет приложению получить доступ к ресурсам пользователя без передачи пароля.
**OIDC** (OpenID Connect) — слой **аутентификации** поверх OAuth 2.0, добавляющий **ID Token** — ответ на вопрос «кто этот пользователь?».
### Роли в архитектуре OAuth 2.0 / OIDC
| Роль | Кто это | Что делает |
|------|---------|------------|
| **Authorization Server** | Keycloak | Выдаёт токены после успешной аутентификации |
| **Resource Server** | Spring Boot API | Принимает и проверяет Access Token |
| **Access Token** | JWT | Доступ к защищённым эндпоинтам API |
| **ID Token** | JWT | Данные о пользователе (OIDC-специфичный) |
| **Refresh Token** | Долгоживущий токен | Обновление Access Token без участия пользователя |
## JWT — JSON Web Token
**JWT** (RFC 7519) — открытый стандарт, компактный и самодостаточный способ безопасной передачи информации между сторонами в виде JSON-объекта.
### Три ключевых свойства
1. **Компактный** — передаётся в URL, POST-параметрах или HTTP-заголовке
2. **Самодостаточный** — содержит всю информацию о пользователе и его правах
3. **Подписанный** — подпись гарантирует целостность данных
### Формат токена
JWT состоит из **трёх частей**, разделённых точками:
```
xxxxx.yyyyy.zzzzz
Header.Payload.Signature
```
Каждая часть кодируется в Base64Url. Итоговый токен передаётся в заголовке:
```
Authorization: Bearer eyJhbGci...
```
### Структура JWT
| Часть | Содержимое |
|-------|------------|
| **Header** | `typ: "JWT"`, алгоритм подписи `alg: "RS256"`. Кодируется в Base64Url |
| **Payload (Claims)** | Зарегистрированные (`iss`, `exp`, `sub`) и кастомные (`realm_access.roles`, `resource_access`). Кодируется в Base64Url |
| **Signature** | `HMAC(Base64Url(header) + "." + Base64Url(payload), privateKey)`. Проверяет целостность и подлинность |
### JWT Claims в Keycloak
Keycloak добавляет специфические claims, на которых строится авторизация в Spring Security:
**realm_access — Realm Roles:**
```json
{
"realm_access": {
"roles": ["user", "admin"]
}
}
```
**resource_access — Client Roles:**
```json
{
"resource_access": {
"my-backend-api": {
"roles": ["client_user"]
}
}
}
```
> **Важно:** Именно эти claims являются основой для авторизации в Spring Security. Без кастомного конвертера Spring «не увидит» роли в JWT от Keycloak.
## Преимущества Stateless JWT-подхода
| Преимущество | Описание |
|--------------|----------|
| **Горизонтальное масштабирование** | Любой экземпляр сервера обработает запрос — состояние не хранится на сервере |
| **Упрощение архитектуры** | Отпадает необходимость в Spring Session |
| **Универсальный токен** | Один JWT используется для доступа к нескольким микросервисам одновременно |
## Миграция от сессий к JWT
### Старый код (Stateful)
```java
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.sessionManagement(session -> session
.sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED))
.formLogin(Customizer.withDefaults())
.authorizeHttpRequests(auth -> auth.anyRequest().authenticated());
return http.build();
}
```
### Новый код (Stateless JWT)
```java
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.sessionManagement(session -> session
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) // ①
.csrf(csrf -> csrf.disable()) // ②
.authorizeHttpRequests(auth -> auth
.requestMatchers("/api/public/**").permitAll()
.anyRequest().authenticated())
.oauth2ResourceServer(oauth2 ->
oauth2.jwt(Customizer.withDefaults())); // ③
return http.build();
}
```
### Три ключевых изменения
1. **STATELESS** — `SessionCreationPolicy.STATELESS` полностью отключает создание HTTP-сессий. Каждый запрос аутентифицируется заново по токену.
2. **`csrf().disable()`** — для stateless API защита от CSRF не требуется: браузер не отправляет cookies с токеном автоматически.
3. **`oauth2ResourceServer().jwt()`** — настраиваем Spring на приём и проверку JWT. Spring Boot автоматически создаст `JwtDecoder` на основе `issuer-uri`.
## Два подхода интеграции Spring Boot + Keycloak
| Характеристика | `oauth2Login` (Stateful) | `oauth2ResourceServer` (Stateless) |
|----------------|--------------------------|-------------------------------------|
| **Назначение** | Серверные веб-приложения с UI (MVC, Thymeleaf) | REST API, микросервисы |
| **Состояние** | Есть (HTTP-сессии) | Нет (Bearer-токен в каждом запросе) |
| **Защита от CSRF** | Требуется | Не требуется |
| **Тип объекта аутентификации** | `OAuth2AuthenticationToken` | `JwtAuthenticationToken` |
| **Масштабирование** | Нужна репликация сессий (Redis) | Легко, stateless |
| **Аутентификация** | Редирект на страницу логина | Bearer-токен в заголовке |
> ⚠️ **Правило:** Никогда не смешивайте `oauth2Login` и `oauth2ResourceServer` в одном `SecurityFilterChain`!
## Настройка Stateless-клиента
### application.yml
Минимальная конфигурация — **одна строка**:
```yaml
spring:
security:
oauth2:
resourceserver:
jwt:
issuer-uri: http://localhost:8080/realms/baeldung-keycloak
```
Указав `issuer-uri`, Spring Boot **автоматически:**
1. Запросит конфигурацию OIDC-провайдера
2. Получит публичные ключи (JWKS)
3. Создаст `JwtDecoder` — без единой строки Java-кода
### SecurityFilterChain: настройка доступа
```java
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(auth -> auth
.requestMatchers("/api/public/**").permitAll() // открыто всем
.requestMatchers("/api/admin/**").hasRole("ADMIN") // только ADMIN
.anyRequest().authenticated()) // остальное — авторизованным
.oauth2ResourceServer(oauth2 ->
oauth2.jwt(Customizer.withDefaults()));
return http.build();
}
```
> **Важно:** Метод `.hasRole("ADMIN")` автоматически ищет `ROLE_ADMIN` в `GrantedAuthorities`. Необходим кастомный конвертер!
## JwtAuthenticationConverter — извлечение ролей из JWT
По умолчанию Spring Security не знает, где в JWT от Keycloak лежат роли. Нужен кастомный конвертер:
```java
@Bean
public JwtAuthenticationConverter jwtAuthenticationConverter() {
JwtGrantedAuthoritiesConverter grantedAuthoritiesConverter =
new JwtGrantedAuthoritiesConverter();
// Префикс, который Spring Security ожидает для hasRole()
grantedAuthoritiesConverter.setAuthorityPrefix("ROLE_");
// Путь к ролям в JWT-структуре Keycloak
grantedAuthoritiesConverter.setAuthoritiesClaimName("realm_access.roles");
JwtAuthenticationConverter jwtAuthenticationConverter =
new JwtAuthenticationConverter();
jwtAuthenticationConverter.setJwtGrantedAuthoritiesConverter(grantedAuthoritiesConverter);
return jwtAuthenticationConverter;
}
```
Регистрация в SecurityFilterChain:
```java
.oauth2ResourceServer(oauth2 ->
oauth2.jwt(jwt ->
jwt.jwtAuthenticationConverter(jwtAuthenticationConverter())))
```
## Получение данных пользователя в контроллере
### Способ 1: @AuthenticationPrincipal (предпочтительно)
```java
@GetMapping("/api/user")
public Map getUserInfo(@AuthenticationPrincipal Jwt jwt) {
return Map.of(
"username", jwt.getClaimAsString("preferred_username"),
"email", jwt.getClaimAsString("email"),
"roles", jwt.getClaimAsStringList("realm_access.roles")
);
}
```
### Способ 2: SecurityContextHolder
Используется вне контроллера — в сервисах и компонентах, где нет доступа к параметрам запроса.
```java
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
Jwt jwt = (Jwt) auth.getPrincipal();
String username = jwt.getClaimAsString("preferred_username");
```
## Как Spring проверяет подпись JWT: JWKS
Для верификации каждого входящего токена бэкенду нужны публичные ключи Keycloak. Spring Boot получает их **автоматически и кэширует**:
1. **Конфигурация** — Spring читает `issuer-uri` из `application.yml`
2. **Discovery** — `GET /.well-known/openid-configuration`
3. **Извлечение** — берёт `jwks_uri` из JSON-ответа
4. **Загрузка** — скачивает JWKS с certs endpoint
> Keycloak периодически ротирует ключи. Spring Boot автоматически обновляет кэш при получении JWT с неизвестным `kid` (Key ID), обеспечивая бесперебойную работу.
## Stateful-клиент: oauth2Login
Если бэкенд рендерит страницы (Thymeleaf, JSP) и управляет сессиями — используйте `oauth2Login`. Spring Security полностью берёт на себя редирект и обработку callback.
### SecurityFilterChain
```java
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(auth -> auth
.requestMatchers("/public/**").permitAll()
.anyRequest().authenticated())
.oauth2Login(oauth2 -> oauth2.defaultSuccessUrl("/home", true))
.logout(logout -> logout
.logoutSuccessHandler(oidcLogoutSuccessHandler()));
return http.build();
}
```
### application.yml
```yaml
spring:
security:
oauth2:
client:
registration:
keycloak:
client-id: ${KC_CLIENT_ID}
client-secret: ${KC_SECRET}
authorization-grant-type: authorization_code
scope: openid, profile, email
provider:
keycloak:
issuer-uri: ${KC_ISSUER_URI}
user-name-attribute: preferred_username
```
### Важные нюансы oauth2Login
- **RP-Initiated Logout** — используйте `OidcClientInitiatedLogoutSuccessHandler` для корректного выхода. Без него пользователь останется залогиненным в Keycloak.
- **Back-Channel Logout** — Keycloak может инициировать logout на всех клиентах. Зарегистрируйте специальный URL — приложение получит POST от Keycloak.
- **User Info и OAuth2UserService** — для получения дополнительной информации настройте `OAuth2UserService` или `OidcUserService`.
## Когда что использовать
### ✅ oauth2ResourceServer (Stateless)
- Разрабатываете REST API или микросервис
- Бэкенд не управляет сессиями
- Планируете горизонтальное масштабирование
- Клиент — SPA, мобильное приложение или другой сервис
👉 **Выбор по умолчанию для большинства современных бэкенд-сервисов**
### ✅ oauth2Login (Stateful)
- Разрабатываете веб-приложение с серверным рендерингом (MVC)
- Нужны сессии на сервере
- Хотите, чтобы Spring полностью управлял процессом логина/логаута
- Нет нужды в масштабировании без состояния
## Ошибка: смешивание двух подходов
### ❌ Так делать нельзя
```java
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.oauth2Login(Customizer.withDefaults())
.oauth2ResourceServer(oauth2 ->
oauth2.jwt(Customizer.withDefaults()));
return http.build();
}
```
**Почему нельзя?** Один компонент использует сессии, другой — stateless JWT. Они **конфликтуют** в обработке аутентификации.
### ✅ Правильное решение
Создайте **два отдельных бина** `SecurityFilterChain`, разграничив их по URL с помощью `securityMatcher`:
```java
@Bean
@Order(1)
public SecurityFilterChain uiChain(HttpSecurity http) throws Exception {
http.securityMatcher("/ui/**").oauth2Login(...);
return http.build();
}
@Bean
@Order(2)
public SecurityFilterChain apiChain(HttpSecurity http) throws Exception {
http.securityMatcher("/api/**").oauth2ResourceServer(...);
return http.build();
}
```
## AutoConfiguration в Spring Boot
### Какие стартеры нужны
| Стартер | Назначение |
|---------|------------|
| **`spring-boot-starter-oauth2-resource-server`** | Для REST API. Поддержка JWT и Opaque токенов, библиотека Nimbus JOSE+JWT |
| **`spring-boot-starter-oauth2-client`** | Для серверных веб-приложений. Authorization Code Flow и управление сессиями |
| **`spring-boot-starter-security`** | Базовый стартер, обязателен в обоих случаях |
> ⚠️ Устаревший **Keycloak Adapter больше не используется** — только стандартные стартеры Spring Security OAuth2.
### Бины oauth2-resource-server
Когда обнаружены стартер и `issuer-uri`, Spring Boot создаёт три ключевых бина:
1. **JwtDecoder** — реализация `NimbusJwtDecoder`. Декодирует и верифицирует подпись, проверяет `exp`, `nbf`, `iss`.
2. **JwtAuthenticationConverter** — стандартный извлекает `scope`. Почти всегда нужно переопределять для маппинга ролей Keycloak.
3. **BearerTokenAuthenticationFilter** — перехватывает запрос, извлекает токен из `Authorization: Bearer ...`, делегирует проверку `JwtDecoder` и `JwtAuthenticationProvider`.
### Бины oauth2-client (Stateful)
1. **ClientRegistrationRepository** — хранилище конфигураций OAuth2-клиентов из `application.yml`
2. **OAuth2AuthorizedClientService** — управляет токенами доступа. По умолчанию — in-memory
3. **OAuth2AuthorizationRequestRedirectFilter** — перенаправляет на страницу логина Keycloak
4. **OAuth2LoginAuthenticationFilter** — обрабатывает callback после успешного логина
## NimbusJwtDecoder: цепочка проверок
1. Получает публичный ключ (JWK) из кэша или JWKS-эндпоинта Keycloak
2. Проверяет подпись с помощью публичного ключа
3. Проверяет `exp` (не просрочен) и `nbf` (already valid)
4. Проверяет, что `iss` совпадает с `issuer-uri`
5. Возвращает объект `Jwt` или бросает исключение
### Кастомизация JwtDecoder
```java
@Bean
public JwtDecoder jwtDecoder() {
NimbusJwtDecoder decoder = NimbusJwtDecoder.withJwkSetUri(jwksUri).build();
decoder.setJwtValidator(
new DelegatingOAuth2TokenValidator<>(
JwtValidators.createDefaultWithIssuer(issuer),
new CustomBlacklistValidator()
)
);
return decoder;
}
```
## Полная конфигурация: oauth2ResourceServer
### application.yml
```yaml
spring:
security:
oauth2:
resourceserver:
jwt:
issuer-uri: http://localhost:8080/realms/baeldung-keycloak
```
### SecurityConfig.java
```java
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.sessionManagement(s -> s.sessionCreationPolicy(STATELESS))
.csrf(AbstractHttpConfigurer::disable)
.authorizeHttpRequests(auth -> auth
.requestMatchers("/api/public/**").permitAll()
.anyRequest().authenticated())
.oauth2ResourceServer(oauth2 -> oauth2
.jwt(jwt -> jwt.jwtAuthenticationConverter(jwtAuthenticationConverter())));
return http.build();
}
@Bean
public JwtAuthenticationConverter jwtAuthenticationConverter() {
JwtGrantedAuthoritiesConverter conv = new JwtGrantedAuthoritiesConverter();
conv.setAuthorityPrefix("ROLE_");
conv.setAuthoritiesClaimName("realm_access.roles");
JwtAuthenticationConverter converter = new JwtAuthenticationConverter();
converter.setJwtGrantedAuthoritiesConverter(conv);
return converter;
}
}
```
> Это **полный минимум** для production-ready Resource Server с поддержкой Realm Roles из Keycloak.
## Лучшие практики и частые ошибки
### ✅ Делайте так
- Всегда указывайте `issuer-uri` — это источник правды для автоконфигурации
- Используйте `preferred_username` как `user-name-attribute`
- Храните секреты в переменных окружения или Vault (HashiCorp, AWS Secrets Manager)
- Версионируйте Realm-конфигурацию в Git через `--import-realm`
### ❌ Не делайте так
- Не используйте устаревший Keycloak Adapter — только стандартный Spring Security OAuth2
- Не смешивайте `oauth2Login` и `oauth2ResourceServer` в одном `SecurityFilterChain`
- Не забывайте про префикс `ROLE_`: `.hasRole("ADMIN")` ищет `ROLE_ADMIN`
- Не используйте `.hasAuthority("ROLE_ADMIN")` и `.hasRole("ADMIN")` вперемешку без понимания разницы
## Cheat Sheet
| Тема | Главное |
|------|---------|
| **Keycloak иерархия** | `Realm` → `Client` → `Users` → `Roles`. Экспортируйте Realm в JSON и храните в Git |
| **OAuth2 / OIDC** | Keycloak = Authorization Server, выдаёт Access Token (JWT). Spring Boot API = Resource Server, проверяет токен |
| **JWT структура** | `Header.Payload.Signature`. Роли Keycloak в `realm_access.roles`. Используйте кастомный `JwtAuthenticationConverter` |
| **Stateless vs Stateful** | `oauth2ResourceServer` + STATELESS для REST API. `oauth2Login` + сессии для MVC. Не смешивать! |
| **Ключевые бины** | `JwtDecoder` (подпись и claims), `JwtAuthenticationConverter` (маппит роли), `BearerTokenAuthenticationFilter` (перехватывает запросы) |
| **Минимальный старт** | Стартер `spring-boot-starter-oauth2-resource-server` + одна строка `issuer-uri` + кастомный конвертер ролей |
---
# Лекция 13. Введение в межсервисное взаимодействие
## Микросервисная архитектура
**Микросервисная архитектура** позволяет:
- Разделять сервис на отдельные функции
- Независимо масштабировать отдельные части
- Обеспечивать повышенную устойчивость к сбоям
- Использовать разные технологии под разные задачи
Но переход от монолита к микросервисам — сложный процесс. Самый трудный этап — **изменение механизма взаимодействия внутренних компонентов**.
## Монолит vs Микросервисы: взаимодействие
| Архитектура | Взаимодействие |
|-------------|----------------|
| **Монолитная** | Компоненты обращаются друг к другу на уровне кода или функции в рамках одного процесса |
| **Микросервисная** | Модули взаимодействуют через **сеть** по протоколонезависимой технологии |
Прямое преобразование внутрипроцессных вызовов в удалённые **не подойдёт** распределённым средам.
## Сложность перехода на микросервисы
Унифицированного решения нет — для каждой ситуации нужно своё. Варианты:
1. **Изоляция бизнес-значимых микросервисов.** Внутренние микросервисы взаимодействуют асинхронно. Вызовы группируются, данные агрегируются и только потом отправляются клиенту.
2. **Архитектура с частично зависимыми, но согласованными микросервисами.** Каждый микросервис имеет свои данные и логику.
## Типы связи: синхронная vs асинхронная
| Тип | Описание | Пример |
|-----|----------|--------|
| **Асинхронный** | Отправитель не ждёт ответ. Сообщения передаются в очередь брокера | **AMQP** (Advanced Message Queue Protocol) |
| **Синхронный** | При отправке клиент **ждёт ответ**. Задачи выполняются только после ответа сервера | **HTTP** |
## Брокеры сообщений: ActiveMQ и Kafka
Книги в этой области сравнивают и противопоставляют две популярные технологии:
- **Apache ActiveMQ**
- **Apache Kafka**
## Система обмена сообщениями
Для общения двух приложений нужно:
1. **Определить интерфейс**:
- Выбрать транспорт/протокол
- Согласовать форматы сообщений
2. **Стандартизировать** через схему (XML, JSON) или неформальное соглашение
Пока **формат сообщений и порядок их отправки согласованы** — внутренности систем могут меняться. Эти системы **расцеплены** интерфейсом.
### Роль посредника
Системы обмена сообщениями обычно используют **посредника (брокера)** между взаимодействующими системами для дальнейшего расцепления отправителя от получателя. Отправитель отправляет сообщение, **не зная**, где получатель, активен ли он, сколько его экземпляров.
## Модель Point-to-Point (точка-точка)
**Аналогия:** Александра идёт на почту, отправляет посылку Адаму. Сотрудник забирает посылку и выдаёт квитанцию. Адаму не нужно быть дома. Александра уверена, что посылка будет доставлена, и продолжает дела.
Эта модель реализуется через **очереди**:
- Очередь — буфер **FIFO**
- На неё может подписаться один или несколько потребителей
- **Каждое сообщение доставляется только одному** из подписанных потребителей
- Сообщения распределяются справедливо между потребителями
### Надёжность vs Персистентность
- **Надёжность (durability)** — система сохраняет сообщения **при отсутствии подписчиков** до тех пор, пока потребитель не подпишется
- **Персистентность (persistence)** — система **записывает сообщение в хранилище** между получением и отправкой потребителю
Эти термины часто путают, но они выполняют разные функции.
### Когда использовать Point-to-Point
Когда требуется **однократное действие**: внесение средств на счёт, выполнение заказа. Очереди обеспечивают в лучшем случае **доставку хотя бы один раз** (at-least-once).
## Модель Publisher-Subscriber (Pub/Sub)
**Аналогия:** Габриэлла набирает номер конференции. Пока она подключена — слышит всё, что говорит спикер. Когда отключается — пропускает. При повторном подключении продолжает слышать.
Эта модель реализуется через **топики**:
- Сообщение, отправленное в топик, распределяется **по всем подписанным** пользователям
- Топики обычно **ненадёжные (nondurable)** — подписчики пропускают сообщения, отправленные пока они оффлайн
- Гарантия: **доставка не более одного раза** (at-most-once) для каждого потребителя
### Когда использовать Pub/Sub
Когда сообщения **информационные**, и потеря одного — не критична. Пример: температурные датчики передают показания раз в секунду. Если одно сообщение пропущено — следующее придёт скоро.
## Гибридные модели
Сценарии часто требуют **совмещения** моделей:
- Несколько систем нуждаются в копии сообщения (как Pub/Sub)
- Но требуется надёжность и персистентность (как Point-to-Point)
**Решение:** адресат (общий термин для очередей и топиков) распределяет сообщения **как топик**, но каждая система может иметь несколько потребителей, как в очереди. Гарантия — **один раз на каждую заинтересованную сторону**.
Гибридные модели применяются:
- В **ActiveMQ** — через виртуальные или составные адресаты
- В **Kafka** — неявно, как фундаментальное свойство дизайна адресата
## ActiveMQ
**ActiveMQ** — классическая система обмена сообщениями, написанная в 2004 году. Восполняла потребность в open-source брокере сообщений.
### ActiveMQ и JMS
ActiveMQ разработана как реализация спецификации **JMS** (Java Message Service):
- JMS описывает абстракции для отправки/получения сообщений в **асинхронном режиме**
- JMS включает чёткие указания по обязанностям клиента и брокера
- Сама **связь клиент-брокер** **исключена** из спецификации — чтобы существующие брокеры могли стать JMS-совместимыми
ActiveMQ свободна в определении своего протокола — **OpenWire**. Используется в реализациях JMS, .NET (NMS) и C++ (CMS).
## Протоколы
### AMQP (Advanced Message Queue Protocol)
ISO/IEC 19464:2014. Не путать с предшественником 0.x, который реализован в **RabbitMQ** (0.9.1).
AMQP 1.0:
- Двоичный протокол общего назначения
- Нет понятий клиентов или брокеров
- Поддерживает управление потоками, транзакции, QoS:
- Не более одного раза (at-most-once)
- Не менее одного раза (at-least-once)
- Точно один раз (exactly-once)
### MQTT (Message Queuing Telemetry Transport)
ISO/IEC 20922:2016. Лёгкий Pub/Sub-протокол. Используется для:
- Приложений «Машина-Машина» (M2M)
- Интернет вещей (IoT)
ActiveMQ поддерживает оба протокола.
## JMS API
JMS определяет набор программных интерфейсов:
```
ConnectionFactory → Connection → Session
│
├── MessageProducer
├── MessageConsumer
└── Message
```
### ConnectionFactory
- Интерфейс верхнего уровня для установления соединений
- В типичном приложении — единственный экземпляр (**Singleton**)
- В ActiveMQ — `ActiveMQConnectionFactory`
- Сообщает местонахождение брокера и низкоуровневые детали
### Connection
- Долгоживущий объект, похожий на TCP-соединение
- Существует в течение всего жизненного цикла приложения
- **Потокобезопасный** — может работать с несколькими потоками одновременно
- Через него создаются объекты `Session`
### Session
- Дескриптор потока при взаимодействии с брокером
- **НЕ потокобезопасный** — не может быть доступен нескольким потокам одновременно
- Основной транзакционный дескриптор: `commit` и `rollback`
- Через него создаются `Message`, `MessageConsumer`, `MessageProducer`, получаются ссылки на `Topic` и `Queue`
### MessageProducer и MessageConsumer
- **MessageProducer** — отправляет сообщения адресату
- **MessageConsumer** — получает сообщения, два механизма:
- **MessageListener** — реализуемый интерфейс обработчика, последовательно обрабатывает сообщения по мере поступления (один поток)
- **Polling** — опрос через метод `receive()`
### Message
**Message** — самая важная структура, переносит данные. Состоит из двух частей:
1. **Метаданные** — заголовки и свойства
2. **Тело сообщения**
**Заголовки** — хорошо известные элементы, определённые спецификацией JMS: `JMSDestination`, `JMSTimestamp` и т.д.
**Свойства** — произвольные фрагменты информации, упрощающие обработку или маршрутизацию без чтения тела.
**Типы тела сообщения:**
- **TextMessage** — для строк
- **BytesMessage** — для двоичных данных
## Модель обмена сообщениями в ActiveMQ
Полезная (хоть и неточная) модель — **«две половинки мозга»**:
- Одна часть отвечает за **приём сообщений от продюсера**
- Другая отправляет сообщения потребителям
В реальности отношения сложнее из-за оптимизаций производительности, но модель достаточна для базового понимания.
### Процесс отправки сообщения
1. **Маршалинг.** Отправляющий поток вызывает клиентскую библиотеку, маршализирует сообщение в нужный формат. Сообщение отправляется брокеру.
2. **Запись в хранилище.** Брокер записывает сообщение в своё внутреннее хранилище.
3. **Подтверждение записи.** Персистенс-адаптер получает подтверждение записи. Это **самая медленная часть** взаимодействия.
4. **Ответ клиенту.** Брокер отправляет клиенту подтверждение. Поток клиента может продолжить работу.
### Publisher Confirmations
Если у брокера закончилась память или диск:
- Брокер **приостанавливает операцию отправки**, заставляя продюсер ждать (**Producer Flow Control**)
- ИЛИ отправляет **негативное подтверждение** продюсеру (через исключение)
В простом сценарии используется паттерн **Fire-and-forget** — записали и забыли.
### Buffering
Когда брокер записывает данные на диск, он взаимодействует с **файловой системой**. Файловая система **буферизует** данные — минимизирует количество операций записи.
> Именно поэтому компьютер ругается на небезопасное извлечение USB — файлы могли не быть записаны.
Уровни кэширования:
1. **Буферный кэш файловой системы**
2. **Кэш контроллера диска** (аппаратный уровень)
**ActiveMQ** включает свой буфер записи — потоки записывают свои сообщения, ожидая завершения предыдущей записи. Буфер пишется одним действием, максимизируя пропускную способность.
## Процесс получения сообщений
1. Потребитель выражает готовность принимать сообщения (через `MessageListener` или `receive()`).
2. ActiveMQ постранично читает (pages) сообщения из хранилища в память.
3. Сообщения перенаправляются (dispatched) консумеру, часто частями для снижения сетевого взаимодействия.
4. Сообщения помещаются в **prefetch buffer** (буфер предварительной выборки) у консумера — выравнивает поток сообщений.
5. Логика приложения вычитывает сообщения из буфера и отправляет подтверждение брокеру.
6. После получения подтверждения брокером сообщение **удаляется** из памяти и хранилища.
> Термин «удаление» вводит в заблуждение — в журнал записывается запись о подтверждении и увеличивается указатель в индексе. **Фактическое удаление** выполняется **сборщиком мусора** в фоновом потоке.
### Курсорный механизм
В действительности, ActiveMQ не просто постранично читает данные, а использует **механизм курсора** между принимающей и перенаправляющей частями брокера для минимизации взаимодействия с хранилищем.
> Используемый **протокол согласования (coherency)** — значительная часть того, что делает механизм диспетчеризации ActiveMQ **отличным от Kafka**.
---
# Лекция 14. Apache Kafka
## Что такое Kafka
**Apache Kafka** — distributed event streaming platform (распределённая платформа потоковой обработки событий).
### Назначение
- **High-performance data pipelines** — высокопроизводительные пайплайны данных
- **Streaming analytics** — потоковая аналитика
- **Data integration** — интеграция данных
- **Mission-critical applications** — критически важные приложения
### Ключевые свойства Kafka
- **Распределённость**
- **Отказоустойчивость**
- **Высокая доступность**
- **Надёжность и согласованность данных**
- **Высокая производительность** (пропускная способность)
- **Горизонтальное масштабирование**
- **Интегрируемость**
## Немного истории
- Разработана в **LinkedIn**
- Программный код опубликован в **2011 году**
- С **2012** под крылом Apache
- Реализована на **Scala и Java**
- Название придумал Jay Kreps в честь **Franz Kafka** («известный писатель»)
- Девиз: *«Kafka — a system optimized for writing»*
## Задача, которую решает Kafka
**Без Kafka** (прямые соединения между сервисами):
Сложности при количестве сервисов:
- Надёжность и гарантия доставки
- Подключение новых получателей
- Отправители знают получателей
- Техническая поддержка
- Интеграция разных стеков
**С Kafka** (брокер посередине):
Удобства:
- Надёжность и гарантия доставки
- Подключение новых получателей
- Отправители не знают получателей
- Техническая поддержка
- Интеграция разных стеков
## Основные сущности Kafka
1. **Broker** — сервер Kafka
2. **Zookeeper** — координатор кластера
3. **Message (Record)** — сообщение
4. **Topic / Partition** — топик и его партиции
5. **Producer** — отправитель
6. **Consumer** — получатель
## Kafka Broker (Kafka Server / Kafka Node)
### Функции брокера
- ✅ Приём сообщений
- ✅ Хранение сообщений
- ✅ Выдача сообщений
### Kafka-кластер
- **Масштабирование** — несколько брокеров работают вместе
- **Репликация** — данные дублируются между брокерами
## Zookeeper
**Zookeeper** — координатор кластера Kafka. Его функции:
- ✅ Состояние кластера
- ✅ Конфигурация
- ✅ Адресная книга (data)
- ✅ Выбор Controller
- ✅ Обеспечение **консистентности**
### Controller
Один из брокеров кластера становится **Controller** — отвечает за управление и принятие решений в кластере. Выбирается через Zookeeper.
## Kafka Message (Record / Event)
Сообщение представляет собой **key-value пару** с дополнительными полями:
| Поле сообщения | Описание |
|----------------|----------|
| **Key** | Ключ (опциональный). Используется для распределения сообщений по кластеру |
| **Value** | Содержимое сообщения — массив байт |
| **Timestamp** | Время сообщения (от эпохи). Устанавливается при отправке или обработке внутри кластера |
| **Headers** | Набор key-value пар с пользовательскими атрибутами сообщения |
## Kafka Topic — поток данных (stream of data)
**Topic** — это поток данных, через который сообщения проходят от продюсеров к консумерам.
### Свойства Topic
- ✅ **FIFO** — гарантирует порядок (ordering support)
- Сообщения сохраняются последовательно
### Partitions — разделение топика
Топик разделяется на **партиции** для:
- ✅ **Ускорения чтения/записи данных** (параллелизация)
**FIFO per partition** — порядок гарантируется **внутри партиции**, но не во всём топике (если партиций больше одной).
## Topic placement by Brokers (в кластере)
Партиции топика **размещаются на разных брокерах**:
```
Topic A: partition 0, 1, 2
Broker 1: P0
Broker 2: P1
Broker 3: P2
```
> ⚠️ Возможна **несбалансированность** размещения партиций топика — учитывается количество всех партиций всех топиков на брокере.
При наличии нескольких топиков (B, C, D и т.д.) кафка пытается балансировать нагрузку между брокерами.
## Storage for Kafka Topic — где хранятся данные
Данные топика хранятся в **файловой системе брокера**, в каталоге `/logs`:
- Для каждой партиции — отдельная директория
- Внутри директории несколько файлов:
- **`.log`** — сами сообщения
- **`.index`** — индекс по offset
- **`.timeindex`** — индекс по timestamp
### Segments — сегменты файлов
Файлы партиции разбиваются на **сегменты**:
- Имя файла соответствует **start offset** сегмента
- В **timeindex** хранится **max message timestamp** сегмента
Это нужно для:
- Эффективного управления хранением
- Быстрого поиска по offset и timestamp
- Удаления старых данных
## Data removing from Kafka Topic — удаление данных
> ⚠️ **Операция удаления данных не поддерживается!**
✅ Поддерживается **автоматическое удаление данных по TTL (time-to-live):**
- Удаляются целиком **сегменты** партиций, не отдельные сообщения
- Условие: `segment timestamp expired → to delete`
## Data Replication — надёжность данных и отказоустойчивость
Если **один брокер падает** — данные его партиций **потеряны!**
### Решение: replication-factor
```
Set replication-factor (>1)
```
- Каждая партиция реплицируется на **несколько брокеров**
- При падении одного брокера — данные **сохраняются на других**
> Но просто с репликацией не всё так просто. Есть нюансы.
## Master-Slave — гарантия согласованности данных
При `replication-factor: 3` — у каждой партиции есть 3 копии.
### Leader и Followers
- **Kafka Controller назначает Leader-реплики**
- ⚠️ **Операции чтения и записи производятся ТОЛЬКО с Leader-репликой**
```
producer → Leader → consume
```
Остальные реплики (Followers) только синхронизируются с Leader.
### Несбалансированность Leaders
> ⚠️ Возможна проблема несбалансированности Leaders и Followers — все запросы к одному брокеру, остальные «отдыхают».
Kafka должна перебалансировать Leader'ов между брокерами.
## Data sync between Leader and Followers
### Followers must fetch data from Leader
Followers **периодически** запрашивают данные у Leader (`fetch periodically`).
### Проблема: что если Leader упал?
> Кто теперь Leader? И все ли данные у него есть?
Если новым Leader станет Follower с **отстающими** данными — данные могут быть **потеряны**. Это **ненадёжно**!
## Решение — In-Sync Replicas (ISR)
**ISR** — реплики, которые синхронизированы с Leader.
Настройка: `min.insync.replicas = 3`
- Запись считается успешной только когда она прошла на **минимум N ISR**
- **ISR Follower** — надёжный кандидат на Leader
```
Если запись прошла в Leader + ISR(1) + ISR(2) — данные надёжны.
Если новый Leader выбирается из ISR — данные не теряются.
```
## Kafka Producer — отправка сообщений
**Kafka Producer** — высокопроизводительный отправитель сообщений.
### acks — гарантии доставки
Параметр **`acks`** определяет, чего ждёт продюсер:
| acks | Что происходит | Гарантия | Производительность |
|------|----------------|----------|---------------------|
| **`0`** | Producer не ждёт подтверждения отправки | (Самый ненадёжный — могут теряться сообщения) | Самый быстрый |
| **`1`** | Producer ждёт подтверждения только от Leader-реплики | Компромиссный — могут теряться сообщения, если лидер упал до репликации | Средний |
| **`-1` (all)** | Producer ждёт подтверждения от **всех ISR-реплик**, включая Leader | Надёжный — сообщения не теряются | Самый медленный |
### Delivery semantic support
Kafka поддерживает три семантики доставки:
- **At most once** — не более одного раза (сообщение может потеряться)
- **At least once** — хотя бы один раз (сообщение может дублироваться)
- **Exactly once** — ровно один раз (через **idempotence**)
## Конвейер обработки сообщения Producer'ом
Что происходит при `send message`:
1. **fetch metadata** — Producer получает метаданные от Zookeeper:
- `cluster state`
- `topic placement`
- ⚠️ **Expensive operation** — блокирует `send()` до получения метаданных (или таймаут 60 сек)
2. **serialize message** — сериализация ключа и значения через `key.serializer` и `value.serializer` (например, `StringSerializer`)
3. **define partition** — определение партиции:
- `explicit partition` — явное указание
- `round-robin` — по очереди
- `key-defined` — `key_hash % n`
4. **accumulate batch** — накопление в батч (контролируется `batch.size` и `linger.ms`)
5. **compress message** — сжатие
6. **send to Broker** — отправка на брокер
> Producer пытается **группировать сообщения** в батчи для эффективности, чтобы лучше использовать сеть и хранилище.
## Kafka Consumer — получение сообщений
**Kafka Consumer** — высокопроизводительный получатель.
### Принцип работы
Consumer **сам** запрашивает сообщения у брокера (`poll messages`). В отличие от ActiveMQ — Kafka не пушит сообщения консумерам.
### Подключение Consumer
Consumer подключается к **Leader-репликам всех партиций** топика.
> ⚠️ В один поток это может быть медленно, если партиций много.
### Consumer Group
Чтобы распараллелить чтение — Consumer'ы объединяются в **Consumer Group** через ``:
- Каждая партиция читается **одним Consumer'ом** в группе
- Сообщения распределяются между Consumer'ами группы
- При добавлении/удалении Consumer'а — происходит **rebalance**
Пример:
- Topic с 4 партициями
- Consumer Group из 4 Consumer'ов
- Каждый Consumer читает свою партицию
## Kafka Consumer Offset
**Offset** — last consumed message by Group (последнее прочитанное сообщение группой).
### Что хранится
Для каждой Consumer Group хранится:
| Field | Value |
|-------|-------|
| **Partition** | A/0 (топик/партиция) |
| **Group** | имя группы |
| **Offset** | номер последнего обработанного сообщения |
Эта информация хранится в **специальном системном топике**: `__consumer_offsets`.
### Зачем offsets
- Если Consumer перезапускается — он продолжает с последнего обработанного offset
- Если приходит **новая** группа — она может начать с **начала топика** или с **последнего offset**
## Kafka Consumer Offset commit
После обработки сообщения нужно **закоммитить offset**.
### Типы коммитов
| Тип | Семантика | Особенность |
|-----|-----------|-------------|
| **Auto commit** | **At most once** | Сообщения могут пропускаться. Коммит происходит автоматически, до обработки |
| **Manual commit** | **At least once** | Сообщения могут дублироваться. Коммит после обработки — если упали, прочитаем снова |
### А как же exactly once?
**Custom offset management** — управление offset вручную, в той же транзакции, что и обработка. Тогда:
- Обработка и commit в одной транзакции
- При неуспехе — откат всего
- **Exactly once** — не пропустили, не дублировали
## Kafka Consumer Offset missing
Что если Consumer Group **неактивна** долгое время?
### Параметры
- **`max inactive period`** — `offsets.retention.minutes` (по умолчанию **7 дней**)
- После истечения — offsets **удаляются** из `__consumer_offsets`
### После активации группы
После повторной активации **используется параметр `auto.offset.reset`**:
| Значение | Что делает |
|----------|-----------|
| **`earliest`** | Читать с самого начала топика |
| **`latest`** | Читать только новые сообщения |
## Kafka performance — почему так быстро?
| Свойство | Описание |
|----------|----------|
| **Scalable architecture** | Масштабируемая архитектура — партиции, брокеры, consumer groups |
| **Sequential write and read** | Последовательная запись и чтение (быстрее случайных операций) |
| **No random read** | Нет случайных чтений |
| **Zero-copy** | Передача данных от диска в сеть без копирования в user-space |
| **Huge amount of settings** | Множество настроек для разных кейсов |
## Итоги: Apache Kafka
- ✅ Kafka — **очень популярна**
- ✅ Kafka — **надёжное проверенное решение**
- ✅ Kafka — **высокопроизводительный инструмент**
- ✅ Kafka — **широковещательная с упорядочиванием** (FIFO per partition)
- ✅ Kafka — **интегрируема со всеми тулами**
- ✅ Kafka — **гибкая в конфигурации**
- ⚠️ Kafka — **нужно понимать нюансы** (acks, ISR, offset management, rebalance)