Основы разработки на Java Spring

Этот курс предназначен для изучения фундаментальных принципов фреймворка Spring, включая создание веб-приложений и работу с базами данных. Вы освоите Spring Boot, научитесь строить REST API и обеспечивать безопасность ваших сервисов.

1. Введение в экосистему Spring: Inversion of Control и Dependency Injection

Введение в экосистему Spring: Inversion of Control и Dependency Injection

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

В этой первой статье мы не будем сразу бросаться писать веб-контроллеры или подключать базы данных. Сначала нам нужно понять фундамент, на котором стоит весь Spring. Этот фундамент держится на двух ключевых понятиях: Inversion of Control (IoC) и Dependency Injection (DI).

Проблема: Жесткая связность (Tight Coupling)

Чтобы понять решение, нужно сначала осознать проблему. Представьте, что вы пишете приложение для интернет-магазина. У вас есть класс OrderService (сервис заказов), который должен отправлять уведомления пользователям после покупки. Для этого он использует класс EmailService.

В классическом процедурном или наивном объектно-ориентированном подходе код мог бы выглядеть так:

Взгляните на конструктор OrderService. Мы создаем экземпляр EmailService прямо внутри него, используя ключевое слово new. Это называется жесткой связностью (Tight Coupling).

Почему это плохо?

  • Сложность тестирования. Если вы захотите протестировать OrderService, вы не сможете сделать это изолированно. При создании теста для OrderService всегда будет создаваться реальный EmailService, который может пытаться отправить реальные письма.
  • Сложность изменений. Представьте, что завтра бизнес решит отправлять уведомления не по Email, а через SMS. Вам придется зайти в класс OrderService (и во все другие классы, где используется EmailService) и переписать код, заменив new EmailService() на new SmsService(). Вы нарушаете принцип открытости/закрытости (Open/Closed Principle).
  • Нарушение Single Responsibility Principle. Класс OrderService теперь отвечает не только за логику заказов, но и за управление жизненным циклом своих зависимостей.
  • !Сравнение жесткой связности, где объекты создают друг друга, и слабой связности, где объекты соединяются через интерфейсы.

    Решение: Inversion of Control (IoC)

    Inversion of Control (Инверсия управления) — это принцип проектирования, при котором поток выполнения программы контролируется не самим кодом приложения, а внешним фреймворком или контейнером.

    В контексте создания объектов это означает передачу ответственности за создание и связывание объектов от самого объекта кому-то другому.

    Часто этот принцип описывают фразой, известной как «Принцип Голливуда»:

    > «Не звоните нам, мы сами вам позвоним».

    В нашем случае это означает: «Не создавай свои зависимости сам, мы (контейнер) сами тебе их дадим».

    Реализация: Dependency Injection (DI)

    Если IoC — это абстрактный принцип (философия), то Dependency Injection (Внедрение зависимостей) — это конкретный паттерн проектирования, реализующий этот принцип.

    Идея проста: объекты не создают свои зависимости, они объявляют, что им нужно, а кто-то внешний (в нашем случае Spring) «внедряет» эти зависимости в них.

    Давайте перепишем наш пример, используя DI. Сначала выделим интерфейс для отправки сообщений, чтобы снизить связность:

    Теперь изменим OrderService:

    Заметьте разницу: в OrderService больше нет слова new. Класс говорит: «Мне нужен кто-то, кто умеет отправлять уведомления (NotificationService). Дайте мне его в конструктор, и я буду с ним работать».

    Теперь мы можем легко подменить EmailService на SmsService или на заглушку (Mock) для тестов, не меняя ни строчки кода в самом OrderService.

    Виды Dependency Injection

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

  • Constructor Injection (Внедрение через конструктор). Зависимости передаются при создании объекта. Это рекомендуемый способ в Spring, так как он гарантирует, что объект не может быть создан в невалидном состоянии (без обязательных зависимостей).
  • Setter Injection (Внедрение через сеттер). Зависимости передаются через методы set.... Полезно для опциональных зависимостей, которые можно менять в процессе работы.
  • Field Injection (Внедрение через поля). Прямая запись в приватные поля (обычно с помощью аннотации @Autowired). Это выглядит лаконично, но не рекомендуется, так как усложняет тестирование (сложно подсунуть мок в приватное поле без рефлексии) и скрывает зависимости класса.
  • Spring IoC Container

    Кто же тот самый «волшебник», который создает EmailService, создает OrderService и передает первый во второй? Это Spring IoC Container.

    Контейнер — это ядро Spring Framework. Он отвечает за:

  • Создание объектов.
  • Настройку объектов.
  • Сборку зависимостей (связывание объектов друг с другом).
  • Управление жизненным циклом объектов (от создания до уничтожения).
  • В мире Spring объекты, которыми управляет контейнер, называются Бинами (Beans).

    Как это работает на практике?

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

    !Процесс превращения обычных Java-классов в управляемые Spring Beans через конфигурацию и контейнер.

    Пример современной конфигурации:

    Аннотация @Component ставит метку на классе: «Эй, Spring, это твой клиент, управляй им». Когда приложение запускается, Spring сканирует классы, находит помеченные аннотациями, создает их экземпляры и связывает их.

    ApplicationContext

    Интерфейс ApplicationContext — это главное представление Spring IoC контейнера для разработчика. Именно через него мы (или чаще сам фреймворк при запуске веб-приложения) получаем доступ к бинам.

    Упрощенно запуск выглядит так:

    Вам больше не нужно писать new. Вы просите контекст дать вам объект, и контекст возвращает полностью настроенный экземпляр со всеми внедренными зависимостями.

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

    Подводя итог, использование IoC и DI в Spring дает нам:

    * Слабая связность (Loose Coupling). Компоненты системы знают друг о друге минимум необходимого (обычно только интерфейсы). * Легкость тестирования. Вы можете легко заменять реальные базы данных и сервисы на заглушки при написании Unit-тестов. * Управление жизненным циклом. Spring берет на себя создание и уничтожение объектов (например, открытие и закрытие соединений с БД). * Чистота кода. Бизнес-логика отделена от логики настройки и связывания компонентов.

    Заключение

    Мы разобрали фундамент Spring. Inversion of Control — это передача управления созданием объектов фреймворку, а Dependency Injection — это способ, которым фреймворк доставляет зависимости внутрь объектов. Поняв это, вы поняли 50% магии Spring.

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

    Полезные ссылки

    * Официальная документация Spring Framework: IoC Container

    2. Работа с данными: интеграция баз данных через Spring Data JPA и Hibernate

    Работа с данными: интеграция баз данных через Spring Data JPA и Hibernate

    В предыдущей статье мы разобрали фундамент Spring Framework — принципы Inversion of Control (IoC) и Dependency Injection (DI). Мы научились создавать слабосвязанные компоненты и передавать управление их жизненным циклом контейнеру Spring. Однако, большинство корпоративных приложений бесполезны без возможности сохранять и извлекать данные.

    Сегодня мы переходим к одной из самых важных тем в разработке бэкенда — работе с базами данных. Мы узнаем, как Spring превращает рутинную работу с SQL в элегантное манипулирование Java-объектами, используя мощную связку технологий: JPA, Hibernate и Spring Data.

    Эволюция доступа к данным: от JDBC к ORM

    Чтобы оценить удобство современных инструментов, нужно вспомнить, как это делалось раньше (и иногда делается до сих пор для специфических задач). Стандартный способ работы с БД в Java — это JDBC (Java Database Connectivity).

    Проблема JDBC

    Представьте, что у вас есть класс User с полями id, name и email. Чтобы сохранить такого пользователя в базу данных через «голый» JDBC, вам пришлось бы написать примерно такой код:

    В этом подходе есть несколько проблем:

  • Много шаблонного кода (Boilerplate). Открытие соединений, обработка исключений, закрытие ресурсов.
  • SQL внутри Java. SQL-запросы пишутся как обычные строки. Если вы опечатаетесь в названии колонки, вы узнаете об этом только во время выполнения программы, а не при компиляции.
  • Ручной маппинг. Вам нужно вручную перекладывать данные из объекта в запрос и из результата запроса (ResultSet) обратно в объект.
  • Решение: ORM (Object-Relational Mapping)

    ORM (Объектно-реляционное отображение) — это технология, которая связывает объектную модель (классы в Java) с реляционной моделью (таблицы в БД).

    Идея проста: вместо того чтобы писать SQL-запросы, вы работаете с Java-объектами, а ORM-фреймворк сам генерирует нужный SQL и выполняет его.

    !Визуализация принципа работы ORM: автоматическое преобразование между объектами кода и таблицами базы данных.

    Разбираемся в терминах: JPA, Hibernate и Spring Data

    Новички часто путают эти три понятия. Давайте разложим их по полочкам, используя аналогию с автомобилестроением.

    1. JPA (Java Persistence API)

    Это спецификация (стандарт). Это набор интерфейсов и правил, описывающих, как Java-приложения должны взаимодействовать с ORM. JPA — это как чертеж автомобиля или ГОСТ. Сам по себе чертеж ехать не может.

    2. Hibernate

    Это реализация спецификации JPA. Это конкретная библиотека, которая написана по правилам JPA. Это готовый двигатель и шасси, собранные по чертежам. Hibernate — самый популярный ORM-фреймворк в мире Java.

    3. Spring Data JPA

    Это абстракция над абстракцией. Это библиотека от команды Spring, которая упрощает работу с JPA (и Hibernate). Если Hibernate — это двигатель, то Spring Data JPA — это автопилот. Он позволяет вам вообще не писать код реализации методов доступа к данным, генерируя их на лету.

    Настройка сущностей (Entities)

    Чтобы магия заработала, нам нужно объяснить фреймворку, как наш класс соответствует таблице в базе данных. Для этого используются аннотации JPA.

    Создадим простой класс Product:

    Разберем аннотации: * @Entity: Говорит Hibernate, что этот класс нужно сохранять в базе данных. * @Table(name = "products"): Указывает имя таблицы. Если не указать, имя будет взято из названия класса. * @Id: Указывает, какое поле является первичным ключом (Primary Key). * @GeneratedValue: Говорит, что ID генерируется автоматически (например, через автоинкремент в БД). * @Column: Позволяет настроить параметры колонки (имя, обязательность, длину). Если имя поля совпадает с именем колонки, аннотацию можно опустить.

    Магия Spring Data: Репозитории

    В классическом использовании Hibernate нам пришлось бы создавать класс ProductDao, внедрять туда EntityManager и писать методы для сохранения и поиска. Spring Data JPA избавляет нас от этого.

    Нам достаточно создать интерфейс, который наследуется от JpaRepository:

    Это всё! Нам не нужно писать реализацию этого интерфейса.

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

    При запуске приложения Spring сканирует интерфейсы-наследники JpaRepository. Используя механизм Dynamic Proxy, он создает класс-реализацию на лету.

  • Методы вроде save(), findAll(), deleteById() уже есть в JpaRepository, и Spring знает, как их выполнять.
  • Для методов вроде findByName(String name) Spring парсит название метода. Он видит префикс find, видит By, видит поле Name и автоматически генерирует SQL-запрос: SELECT * FROM products WHERE name = ?.
  • !Иерархия вызовов при работе с данными: от интерфейса репозитория до реальной базы данных.

    Подключение и конфигурация

    Чтобы все это заработало, нужно добавить зависимости в pom.xml (для Maven) и настроить подключение к базе.

    Зависимости: * spring-boot-starter-data-jpa * Драйвер базы данных (например, h2 для тестов или postgresql для продакшена).

    Пример настройки в application.properties (для базы данных H2, которая хранится в памяти):

    Параметр ddl-auto=update — очень мощная вещь. При запуске Hibernate посмотрит на ваши классы с аннотацией @Entity, сравнит их с таблицами в базе и, если нужно, создаст таблицы или добавит недостающие колонки. Важно: в продакшене (на реальных серверах) обычно используют значение validate или none, чтобы случайно не повредить данные, а изменения схемы делают через инструменты миграции (Liquibase или Flyway).

    Использование в сервисе

    Теперь объединим знания из предыдущего урока (DI) и текущего. Внедрим репозиторий в сервис.

    Транзакции

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

    В Spring управление транзакциями осуществляется декларативно с помощью аннотации @Transactional.

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

    Заключение

    Spring Data JPA и Hibernate кардинально меняют подход к разработке. Вместо написания сотен строк SQL-кода и ручного маппинга, мы оперируем объектами и интерфейсами.

    Ключевые выводы:

  • JPA — это стандарт, Hibernate — реализация, Spring Data — удобная обертка.
  • Аннотация @Entity превращает класс в таблицу.
  • Интерфейс JpaRepository предоставляет готовые методы CRUD (Create, Read, Update, Delete).
  • Spring умеет генерировать SQL-запросы на основе имен методов (Derived Query Methods).
  • В следующей статье мы рассмотрим, как создать REST API контроллеры, чтобы наш сервис мог принимать запросы из внешнего мира и отдавать данные, которые мы сегодня научились сохранять.

    Полезные ссылки

    * Официальная документация Spring Data JPA * Руководство по Hibernate ORM

    3. Разработка веб-приложений: архитектура Spring MVC и создание RESTful сервисов

    Разработка веб-приложений: архитектура Spring MVC и создание RESTful сервисов

    Мы прошли долгий путь. В первой части курса мы изучили, как Spring управляет объектами через Dependency Injection. Во второй — научились сохранять эти объекты в базу данных с помощью Spring Data JPA. Но пока наше приложение похоже на вещь в себе: оно работает, но никто из внешнего мира не может с ним взаимодействовать.

    Сегодня мы откроем двери нашего приложения. Мы разберем архитектуру Spring MVC и научимся создавать современные RESTful API, которые позволяют фронтенду (сайту), мобильным приложениям или другим сервисам общаться с нашим бэкендом.

    Что такое Spring MVC?

    MVC (Model-View-Controller) — это фундаментальный паттерн проектирования пользовательских интерфейсов. Он разделяет приложение на три компонента, чтобы каждый занимался своим делом:

  • Model (Модель) — это данные и бизнес-логика. В нашем случае это сущности (Entity), репозитории и сервисы, которые мы писали в прошлых уроках.
  • View (Представление) — это то, как данные отображаются пользователю. В классических сайтах это HTML-страницы. В современных REST-сервисах это чаще всего JSON-ответы.
  • Controller (Контроллер) — это дирижер. Он принимает запрос от пользователя, обращается к Модели за данными и передает их Представлению.
  • Архитектура запроса: DispatcherServlet

    В центре Spring MVC стоит специальный компонент, называемый DispatcherServlet. Это реализация паттерна Front Controller. Представьте, что вы пришли в большой отель. Вы не бегаете по коридорам в поисках горничной или повара. Вы идете на ресепшн.

    DispatcherServlet — это ресепшн вашего приложения.

    !Схема обработки HTTP-запроса: от клиента к DispatcherServlet, затем к контроллеру и обратно.

    Жизненный цикл запроса выглядит так:

  • HTTP-запрос (например, GET /products) прилетает на сервер.
  • DispatcherServlet перехватывает его.
  • Он спрашивает у компонента HandlerMapping: «Кто у нас отвечает за адрес /products?».
  • HandlerMapping указывает на конкретный метод в конкретном классе-контроллере.
  • Контроллер обрабатывает запрос (обычно вызывая Service) и возвращает результат.
  • REST: Язык современного веба

    Раньше серверы часто генерировали HTML-код целиком. Сейчас стандартом стала архитектура REST (Representational State Transfer).

    В REST-архитектуре сервер не занимается отрисовкой кнопочек и таблиц. Он просто отдает «сырые» данные, обычно в формате JSON (JavaScript Object Notation). А уже браузер или мобильное приложение решают, как эти данные красиво показать.

    Основные принципы REST

    REST построен вокруг ресурсов (Resources) и HTTP-методов. Если мы работаем с товарами (products), то действия маппятся на методы протокола HTTP так:

    * GET — получить данные (чтение). * POST — создать новые данные. * PUT — обновить существующие данные целиком. * PATCH — обновить часть данных. * DELETE — удалить данные.

    Создание первого контроллера

    Давайте создадим API для управления товарами, используя наш ProductService из прошлого урока.

    В Spring Boot для создания REST-контроллеров используется аннотация @RestController. Это составная аннотация, которая включает в себя @Controller (говорит, что это бин-контроллер) и @ResponseBody (говорит, что ответы методов нужно сериализовать в JSON, а не искать HTML-шаблон).

    Разберем аннотации: * @RequestMapping("/api/products"): Задает базовый адрес для всех методов в этом классе. * @GetMapping: Говорит, что метод обрабатывает HTTP-запросы типа GET.

    Когда вы запустите приложение и перейдете в браузере по адресу http://localhost:8080/api/products, Spring:

  • Вызовет метод getAllProducts.
  • Получит список Java-объектов Product.
  • С помощью библиотеки Jackson (встроена в Spring Boot) превратит этот список в JSON-строку.
  • Вернет её вам.
  • Передача параметров

    Часто нам нужно получить не всё сразу, а что-то конкретное. В REST есть два основных способа передачи параметров.

    1. Path Variable (Переменная пути)

    Используется, когда параметр является частью адреса ресурса, например, ID товара: GET /api/products/5.

    Аннотация @PathVariable связывает {id} из URL с аргументом метода Long id.

    2. Request Param (Параметр запроса)

    Используется для фильтрации или сортировки. Это то, что идет после знака вопроса: GET /api/products/search?minPrice=100.

    Создание и обновление данных (POST, PUT)

    Для отправки данных на сервер (например, создание нового товара) мы используем метод POST. Данные при этом передаются не в URL, а в теле запроса (Request Body) в формате JSON.

    Чтобы Spring превратил JSON от клиента обратно в Java-объект, используется аннотация @RequestBody.

    Управление ответами: ResponseEntity

    В примерах выше мы возвращали просто объекты (Product, List<Product>). По умолчанию Spring возвращает HTTP-статус 200 OK. Но что, если мы создали объект? По стандартам REST нужно вернуть 201 Created. А если товар не найден? Нужно вернуть 404 Not Found.

    Для тонкой настройки ответа используется класс-обертка ResponseEntity.

    Это делает ваш API профессиональным и предсказуемым для клиентов.

    Слоистая архитектура: Итог

    Теперь, когда мы изучили все три компонента, давайте взглянем на полную картину архитектуры типичного Spring-приложения.

  • Controller Layer (@RestController):
  • * Принимает HTTP-запросы. * Валидирует входные данные. * Вызывает методы сервиса. * Формирует HTTP-ответ (статус, заголовки, JSON). Здесь не должно быть бизнес-логики!*

  • Service Layer (@Service):
  • * Содержит бизнес-логику (расчеты, проверки, сложные алгоритмы). * Управляет транзакциями (@Transactional). * Оркестрирует вызовы репозиториев.

  • Data Access Layer (@Repository):
  • * Просто выполняет запросы к базе данных (сохранить, найти, удалить).

    Такое разделение (Separation of Concerns) позволяет легко тестировать каждый слой отдельно и менять их реализацию, не ломая остальную систему.

    Заключение

    Spring MVC — это мощный инструмент, который берет на себя всю рутинную работу по обработке HTTP-протокола. Вам не нужно парсить строки запросов или вручную формировать JSON. Вы просто пишете методы, которые работают с Java-объектами, и расставляете аннотации.

    Мы научились: * Понимать роль DispatcherServlet. * Создавать REST-контроллеры. * Принимать данные через @PathVariable, @RequestParam и @RequestBody. * Управлять статусами ответов через ResponseEntity.

    В следующей статье мы поговорим о безопасности: как защитить наши API, настроить аутентификацию и авторизацию с помощью Spring Security.

    Полезные ссылки

    * Официальная документация Spring MVC * Руководство по созданию RESTful Web Service

    4. Основы безопасности: аутентификация и авторизация с использованием Spring Security

    Основы безопасности: аутентификация и авторизация с использованием Spring Security

    В предыдущих статьях мы создали полноценное веб-приложение: настроили базу данных, написали REST-контроллеры и научились обрабатывать HTTP-запросы. Но сейчас наше приложение похоже на магазин с открытыми дверями, где нет ни охраны, ни замков. Любой желающий может зайти и, используя наш API, удалить все товары или изменить цены.

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

    Два кита безопасности: Аутентификация и Авторизация

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

  • Аутентификация (Authentication) — это ответ на вопрос: «Кто ты?».
  • Это процесс проверки личности пользователя. В реальной жизни это предъявление паспорта на проходной. Если фото в паспорте совпадает с вашим лицом — вы аутентифицированы.

  • Авторизация (Authorization) — это ответ на вопрос: «Что тебе можно делать?».
  • Это проверка прав доступа. Даже если вы прошли проходную (аутентификация), это не значит, что вам можно заходить в кабинет директора или в серверную. У вас должен быть соответствующий допуск.

    Как работает Spring Security: Цепочка фильтров

    Вспомните архитектуру Spring MVC из прошлого урока. Запрос попадает в DispatcherServlet, который направляет его в контроллер. Spring Security встраивается в этот процесс до того, как запрос достигнет DispatcherServlet.

    Он создает «стену» из специальных фильтров (Servlet Filters). Каждый запрос должен пройти через эту стену. Если у запроса нет нужных прав или токенов, фильтр разворачивает его обратно, и контроллер даже не узнает, что кто-то пытался к нему обратиться.

    !Визуализация работы Security Filter Chain: фильтры перехватывают запрос до попадания в бизнес-логику.

    Подключение и базовая настройка

    Чтобы начать, нужно добавить зависимость в pom.xml:

    Как только вы добавите эту зависимость и перезапустите приложение, Spring Security автоматически закроет все ваши эндпоинты. При попытке сделать запрос вы получите ошибку 401 Unauthorized или увидите форму входа.

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

    Настройка правил доступа: SecurityFilterChain

    Чтобы настроить поведение (например, разрешить просмотр товаров всем, а удаление — только админам), мы должны создать конфигурационный бин. В современном Spring Security (версии 6.0 и выше) это делается через определение бина SecurityFilterChain.

    Здесь мы используем requestMatchers, чтобы указать шаблоны путей, и методы permitAll() (доступно всем) или authenticated() (только для вошедших).

    Хранение паролей: Никогда не храните текст!

    Представьте, что вы храните пароли пользователей в базе данных как обычный текст. Если злоумышленник получит доступ к вашей базе (через SQL-инъекцию или бэкап), он узнает пароли всех пользователей. Поскольку люди часто используют одни и те же пароли везде, хакер получит доступ к их почтам и соцсетям.

    Поэтому пароли хранят в виде хешей. Хеширование — это необратимое математическое преобразование.

    Формула безопасного хранения пароля выглядит так:

    Где — итоговый хеш, который мы запишем в базу данных, — криптографически стойкая функция (например, BCrypt), — исходный пароль пользователя, — соль (случайный набор данных, уникальный для каждого пользователя).

    В Spring Security за это отвечает интерфейс PasswordEncoder. Стандартом индустрии является BCrypt.

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

    Связь с базой данных: UserDetailsService

    Spring Security ничего не знает о вашей сущности User или таблице users. Он работает со своим интерфейсом UserDetails. Наша задача — научить Spring превращать наших пользователей из БД в тех, кого он понимает.

    Для этого нужно реализовать интерфейс UserDetailsService. Это мост между базой данных и системой безопасности.

    Предположим, у нас есть репозиторий UserRepository. Реализация сервиса будет выглядеть так:

    Теперь, когда кто-то пытается войти, Spring Security:

  • Вызывает ваш метод loadUserByUsername.
  • Получает объект с хешем пароля из БД.
  • Сверяет введенный пароль с полученным хешем.
  • Если все верно — пускает пользователя.
  • Авторизация на уровне методов

    Настраивать доступ только по URL (в SecurityFilterChain) не всегда удобно. Часто логика прав доступа зависит от бизнес-правил. Spring позволяет ставить защиту прямо над методами сервисов или контроллеров с помощью аннотаций.

    Для этого нужно включить эту возможность в конфигурации:

    Теперь мы можем защищать конкретные методы:

    Аннотация @PreAuthorize принимает выражение на языке SpEL (Spring Expression Language). Это очень гибкий инструмент. Вы можете писать условия вроде hasRole('ADMIN') or hasRole('MANAGER').

    Заключение

    Безопасность — это не просто «логин и пароль». Это комплекс мер по защите данных и функционала.

    Сегодня мы узнали:

  • Разницу между аутентификацией (проверка личности) и авторизацией (проверка прав).
  • Как Security Filter Chain охраняет входы в наше приложение.
  • Зачем нужно хешировать пароли и формулу .
  • Как подключить пользователей из базы данных через UserDetailsService.
  • Как защищать отдельные методы с помощью @PreAuthorize.
  • В следующем уроке мы рассмотрим более продвинутые темы: обработку ошибок, валидацию данных и логирование, чтобы сделать наше приложение по-настоящему готовым к продакшену.

    5. Магия Spring Boot: автоматическая конфигурация, профилирование и тестирование приложений

    Магия Spring Boot: автоматическая конфигурация, профилирование и тестирование приложений

    Мы прошли большой путь: от понимания того, как Spring управляет зависимостями (IoC), до создания защищенных REST API с базой данных. Возможно, вы заметили одну странность: мы писали очень мало конфигурационного кода. Мы просто добавляли зависимости в pom.xml, ставили пару аннотаций, и всё «просто работало».

    Как Spring узнал, что нужно подключить именно базу данных H2? Как он понял, что нужно запустить веб-сервер Tomcat на порту 8080? Почему нам не пришлось вручную настраивать DispatcherServlet?

    Ответ кроется в магии Spring Boot. Сегодня мы заглянем под капот этой магии, научимся управлять настройками для разных окружений (профилирование) и, наконец, сделаем наши приложения надежными с помощью тестов.

    Автоматическая конфигурация: Как это работает?

    В старых версиях Spring (до появления Boot) начало нового проекта было настоящим испытанием. Разработчикам приходилось писать огромные XML-файлы или Java-классы, явно указывая каждый бин, каждый контроллер и каждое соединение с базой данных. Это называлось «Configuration Hell».

    Spring Boot принес новую философию: Convention over Configuration (Соглашение важнее конфигурации).

    Аннотация @SpringBootApplication

    Взгляните на главный класс вашего приложения:

    Аннотация @SpringBootApplication — это на самом деле «матрешка», которая объединяет в себе три другие важные аннотации:

  • @Configuration: Помечает класс как источник определений бинов.
  • @ComponentScan: Говорит Spring искать компоненты (@Component, @Service, @RestController) в текущем пакете и всех вложенных пакетах.
  • @EnableAutoConfiguration: Это и есть та самая «волшебная палочка».
  • Механизм @EnableAutoConfiguration

    Когда приложение запускается, Spring Boot сканирует ваш classpath (все подключенные библиотеки jar). Он задает себе вопросы:

    * «Вижу ли я библиотеку H2 в зависимостях?» -> Да. -> «Значит, нужно создать бин DataSource для H2». * «Вижу ли я spring-webmvc?» -> Да. -> «Значит, нужно настроить DispatcherServlet и запустить встроенный Tomcat». * «Пользователь определил свой собственный DataSource?» -> Нет. -> «Тогда использую стандартный».

    Этот процесс управляется набором условий @Conditional....

    !Визуализация процесса принятия решений Spring Boot: проверка наличия классов в classpath и автоматическое создание бинов.

    Пример того, как это выглядит внутри самого Spring Boot (упрощенно):

    Благодаря этому вы можете переопределить любую автоматическую настройку. Просто создайте свой бин, и Spring Boot отступит, увидев @ConditionalOnMissingBean.

    Профилирование (Profiles)

    При разработке реальных приложений мы сталкиваемся с проблемой разных окружений.

    * Локально (DEV): Вы используете базу данных H2 (в памяти) и выводите все логи в консоль. * Продакшен (PROD): Вы используете мощную PostgreSQL, пароли к которой нельзя хранить в коде, и пишете логи в файл.

    Менять код каждый раз перед деплоем — плохая идея. Здесь на помощь приходят Профили Spring.

    Файлы свойств

    Spring Boot ищет настройки в файле application.properties (или application.yml). Вы можете создать отдельные файлы для каждого профиля:

    * application.properties (общие настройки) * application-dev.properties (настройки для разработки) * application-prod.properties (настройки для боевого сервера)

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

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

    Активация профиля

    Сказать приложению, какой профиль использовать, можно несколькими способами:

  • В файле application.properties: spring.profiles.active=dev
  • Через аргумент командной строки при запуске jar:
  • java -jar myapp.jar --spring.profiles.active=prod

    Также можно помечать бины, которые должны создаваться только в определенном профиле:

    Тестирование Spring приложений

    «Код без тестов — это легаси код с момента написания». В экосистеме Spring тестирование возведено в абсолют. Благодаря spring-boot-starter-test мы получаем мощный набор инструментов «из коробки»: JUnit, Mockito, AssertJ и Hamcrest.

    Мы рассмотрим два основных уровня тестирования: модульное (Unit) и интеграционное.

    1. Модульное тестирование (Unit Testing)

    Здесь мы тестируем один класс в изоляции. Если мы тестируем UserService, нам не нужна реальная база данных или веб-сервер. Нам нужно убедиться, что бизнес-логика работает верно.

    Для подмены зависимостей используется библиотека Mockito.

    Здесь нет Spring Context. Тест работает мгновенно.

    2. Интеграционное тестирование с @SpringBootTest

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

    Обратите внимание на @MockBean. Это аннотация Spring Boot, которая заменяет реальный бин в контексте на Mockito-мок. Это позволяет изолировать контроллер от сервиса и базы данных.

    !Пирамида тестирования, показывающая соотношение скорости и изоляции различных типов тестов в Spring.

    Заключение

    Spring Boot — это не просто фреймворк, это платформа, которая берет на себя рутину конфигурации, позволяя вам сосредоточиться на бизнес-логике.

    Сегодня мы разобрали три столпа профессиональной разработки:

  • Автоконфигурация избавляет от ручной настройки инфраструктуры.
  • Профили позволяют безопасно управлять настройками для разных сред (Dev/Prod).
  • Тестирование (Unit и Integration) гарантирует, что ваше приложение не сломается при внесении изменений.
  • Этим мы завершаем цикл статей «Основы разработки на Java Spring». Вы прошли путь от понимания DI-контейнера до создания готового к продакшену, протестированного REST-сервиса. Дальше вас ждет углубленное изучение микросервисов, реактивного программирования и облачных технологий, но фундамент у вас уже есть.

    Полезные ссылки

    * Официальная документация Spring Boot Features * Руководство по тестированию в Spring Boot