Spring Boot — создание современных веб-приложений на Java
Spring Boot — это фреймворк на основе Spring, который превращает создание production-готовых приложений на Java в несколько кликов. В этом уроке мы разберёмся от создания проекта до деплоя в Docker.
1. Что такое Spring и Spring Boot
Spring — это мощный, но сложный фреймворк для Java, который даёт инструменты для зависимого внедрения, доступа к данным, веб-слоя и многого другого. Его слабое место — огромное количество ручной конфигурации: XML-файлы, фабрики бинов, подключение компонентов вручную.
Spring Boot — это надстройка над Spring, которая упрощает запуск приложений благодаря трём ключевым идеям:
- Авто-конфигурация — Spring Boot сам настраивает зависимости по их наличию в classpath.
- Opinionated defaults — разумные настройки «по умолчанию», которые можно переопределить.
- Embedded-сервер — встроенный Tomcat, не нужен внешний сервер приложений.
Что потребуется для старта:
- Java 17+ — установить JDK и настроить переменную окружения.
- Maven — инструмент сборки и управления зависимостями.
Сравнение: Spring «без Boot» против Spring Boot
| Аспект | Spring (без Boot) | Spring Boot |
|---|---|---|
| Запуск приложения | Разворачивать WAR на внешнем Tomcat | java -jar с встроенным сервером |
| Конфигурация | XML и JavaConfig вручную | Авто-конфигурация и file application.properties |
| Настройка БД | DataSource, JPA, транзакции вручную | Достаточно указать URL подключения |
| Тестирование | Сложная настройка контекста | spring-boot-starter-test из коробки |
2. Создание проекта
Самый простой способ создать проект — онлайн-генератор Spring Initializr на сайте start.spring.io. Выбери:
- Build tool: Maven, язык: Java.
- Версию Java (17 или выше) и версию Spring Boot.
- Заполни Group и Artifact.
Необходимые зависимости для нашего урока:
| Зависимость | Зачем нужна |
|---|---|
| Spring Web | REST-контроллеры и встроенный Tomcat |
| Spring Data JPA | Работа с базой данных через репозитории |
| PostgreSQL Driver | Драйвер для подключения к PostgreSQL (либо H2 для тестов) |
| Spring Boot DevTools | Автоперезагрузка приложения при изменении кода |
Нажми Generate, скачай zip-архив и распакуй его. Открой проект в IntelliJ IDEA, и структура будет такой:
demo/
├── pom.xml
└── src/main/
├── java/com/example/demo/
│ ├── DemoApplication.java
│ └── ...
└── resources/
├── application.properties
└── static/3. Структура приложения и первый запуск
Главный класс помечается аннотацией @SpringBootApplication. Он содержит метод main, который запускает Spring-контекст:
package com.example.demo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}@SpringBootApplication объединяет @Configuration, @EnableAutoConfiguration и @ComponentScan — то есть включает авто-конфигурацию и сканирование бинов.Запустить приложение можно любым способом:
mvn spring-boot:runили собрать jar и запустить его:
mvn package
java -jar target/demo-0.0.1-SNAPSHOT.jarПосле запуска Spring Boot поднимет встроенный Tomcat на порту 8080. В консоли появится строка Tomcat started on port 8080. Открой http://localhost:8080 — пока увидишь страницу ошибки, потому что мы ещё не создали ни одного эндпоинта.
4. REST контроллеры
Контроллер — это класс, который обрабатывает HTTP-запросы. Аннотация @RestController объединяет @Controller и @ResponseBody: всё, что возвращает метод, автоматически превращается в JSON.
package com.example.demo.web;
import java.util.Map;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/api")
public class HelloController {
@GetMapping("/hello")
public Map<String, String> hello() {
return Map.of("message", "Привет, мир!");
}
@GetMapping("/hello/{name}")
public Map<String, String> greet(@PathVariable String name) {
return Map.of("message", "Привет, " + name);
}
@GetMapping("/sum")
public int sum(@RequestParam(defaultValue = "0") int a,
@RequestParam(defaultValue = "0") int b) {
return a + b;
}
@PostMapping("/hello")
public Map<String, String> create() {
return Map.of("status", "создано");
}
}@GetMapping/@PostMapping— привязка к методу HTTP.@PathVariable— берёт значение из пути URL.@RequestParam— берёт значение из query-параметра.
GET /api/hello/Анна вернёт {"message":"Привет, Анна"}. JSON-сериализацией занимается Jackson, входящий в состав Spring Web.5. Сервисы и внедрение зависимостей
Логику приложения лучше выносить в отдельный слой — сервисы. Для этого приложение делится на слои: контроллер → сервис → репозиторий.
package com.example.demo.service;
import com.example.demo.web.HelloController;
import org.springframework.stereotype.Service;
@Service
public class GreetingService {
public String greet(String name) {
return "Привет, " + name + "!";
}
}Теперь внедрим сервис в контроллер. Лучший способ — внедрение через конструктор:
package com.example.demo.web;
import com.example.demo.service.GreetingService;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class GreetingController {
private final GreetingService service;
public GreetingController(GreetingService service) {
this.service = service;
}
@GetMapping("/greet/{name}")
public String greet(@PathVariable String name) {
return service.greet(name);
}
}Это и есть инверсия управления (IoC): не мы создаём объекты через new, а Spring-контейнер создаёт бины и передаёт их туда, где они нужны.
| Аннотация | Назначение |
|---|---|
| @Component | общий бин-компонент |
| @Service | бин бизнес-логики (сервис) |
| @Repository | бин доступа к данным |
| @Autowired | внедрение зависимости (по полю или конструктору) |
@Autowired над конструктором можно не ставить. Использование поля с @Autowired затрудняет тестирование — предпочитай внедрение через конструктор.6. Работа с базой данных
Spring Data JPA — это подмодуль Spring Data, который позволяет работать с базой данных через сущности и репозитории, не пиша SQL вручную. Сначала создадим сущность Author:
package com.example.demo.model;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
@Entity
public class Author {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}Репозиторий — это интерфейс, расширяющий JpaRepository. Spring сам сгенерирует его реализацию:
package com.example.demo.repository;
import com.example.demo.model.Author;
import java.util.Optional;
import org.springframework.data.jpa.repository.JpaRepository;
public interface AuthorRepository extends JpaRepository<Author, Long> {
Optional<Author> findByName(String name);
boolean existsByName(String name);
}Интерфейс JpaRepository расширяет CrudRepository и добавляет возможности пагинации и сортировки. Базовые CRUD-методы уже готовы:
| Метод | Действие |
|---|---|
| save(entity) | INSERT или UPDATE |
| findById(id) | поиск по первичному ключу |
| findAll() | все записи |
| deleteById(id) | удаление по id |
| count() | количество записей |
findByName превращается в WHERE name = ?.7. Модели и DTO
Часто сущность нельзя отдавать наружу целиком из-за связей или лишних полей. Чтобы не тащить сущность в API, используют DTO (Data Transfer Object) — объекты для передачи данных.
Для связей между сущностями используй @JsonIgnore, чтобы избежать бесконечной сериализации (Book ↔ Author):
package com.example.demo.model;
import com.fasterxml.jackson.annotation.JsonIgnore;
import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
import jakarta.persistence.Id;
import jakarta.persistence.ManyToOne;
@Entity
public class Book {
@Id
private Long id;
private String title;
@ManyToOne(fetch = FetchType.LAZY)
@JsonIgnore
private Author author;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public Author getAuthor() {
return author;
}
public void setAuthor(Author author) {
this.author = author;
}
}@JsonIgnore на поле author запрещает Jackson сериализовать эту связь, иначе при чтении Book будет загружаться Author, который ссылается на Book — получится бесконечный цикл.DTO с валидацией. Валидация объявляется аннотациями, а активируется @Valid в контроллере:
package com.example.demo.dto;
import jakarta.validation.constraints.NotBlank;
public class AuthorRequest {
@NotBlank(message = "Имя не может быть пустым")
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}Использование в контроллере вместе с обработкой ошибок через @ControllerAdvice:
package com.example.demo.web;
import com.example.demo.dto.AuthorRequest;
import jakarta.validation.Valid;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class AuthorController {
@PostMapping("/api/authors")
public AuthorRequest create(@Valid @RequestBody AuthorRequest request) {
return request;
}
}В отдельном классе @RestControllerAdvice перехватывает исключения валидации и возвращает аккуратный ответ с кодом 400 вместо краша:
package com.example.demo.web;
import java.util.Map;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(MethodArgumentNotValidException.class)
public Map<String, String> handleValidation(MethodArgumentNotValidException ex) {
HttpStatus status = HttpStatus.BAD_REQUEST;
return Map.of("error", ex.getBindingResult().getFieldError().getDefaultMessage());
}
}8. Конфигурация через application.properties
Все настройки лежат в файле src/main/resources/application.properties:
server.port=8080
spring.datasource.url=jdbc:postgresql://localhost:5432/demo
spring.datasource.username=postgres
spring.datasource.password=secret
spring.jpa.hibernate.ddl-auto=update
spring.jpa.show-sql=true| Ключ | Назначение |
|---|---|
| server.port | порт встроенного Tomcat |
| spring.datasource.url | URL подключения к базе |
| spring.datasource.username / password | логин и пароль БД |
| spring.jpa.hibernate.ddl-auto | создание/обновление схемы (create, update, validate, none) |
| spring.jpa.show-sql | вывод SQL в консоль |
Для разных окружений используй профили. Файл application-dev.properties активируется профилем dev:
# application-prod.properties
server.port=8080
spring.datasource.url=${DB_URL}
spring.datasource.username=${DB_USER}
spring.datasource.password=${DB_PASSWORD}
spring.jpa.hibernate.ddl-auto=validatejava -jar app.jar --spring.profiles.active=prod${ИМЯ_ПЕРЕМЕННОЙ} — они задаются на сервере и не попадают в git.9. Тестирование
Зависимость spring-boot-starter-test уже включает JUnit 5, Mockito и MockMvc. Интеграционный тест со ссылкой на весь контекст пишется аннотацией @SpringBootTest:
package com.example.demo.web;
import static org.hamcrest.Matchers.containsString;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.web.servlet.MockMvc;
@SpringBootTest
@AutoConfigureMockMvc
class GreetingControllerTest {
@Autowired
private MockMvc mockMvc;
@Test
void greetsByName() throws Exception {
mockMvc.perform(get("/greet/Иван"))
.andExpect(status().isOk())
.andExpect(content().string(containsString("Иван")));
}
}Для теста сервиса изолированно используют @MockBean или чистый Mockito. Тестируем логику без поднятия всего приложения:
package com.example.demo.service;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import com.example.demo.repository.AuthorRepository;
import org.junit.jupiter.api.Test;
class AuthorServiceTest {
@Test
void findsAuthorByName() {
AuthorRepository repository = mock(AuthorRepository.class);
when(repository.existsByName("Анна")).thenReturn(true);
boolean exists = repository.existsByName("Анна");
assertEquals(true, exists);
}
}10. Миграции и базы
Настройка ddl-auto=update удобна в разработке, но опасна в проде: она молча меняет схему и не даёт истории изменений. Решение — инструменты миграций:
- Flyway — миграции в SQL-файлах вида
V1__create_authors.sql. - Liquibase — миграции в XML/YAML/SQL, поддерживают откат.
Добавь зависимость Flyway в pom.xml:
<dependency>
<groupId>org.flywaydb</groupId>
<artifactId>flyway-core</artifactId>
</dependency>Файл миграции в src/main/resources/db/migration/V1__create_authors.sql:
CREATE TABLE author (
id BIGSERIAL PRIMARY KEY,
name VARCHAR(255) NOT NULL
);ddl-auto=validate — тогда Hibernate лишь проверит, что схема совпадает с сущностями, а всеми изменениями занимается Flyway.11. Spring Boot + Docker
Обычная практика — собирать Docker-образ с multi-stage build: сначала компиляция в Maven-образие, затем лёгкий runtime-образ с JRE. Файл Dockerfile:
FROM maven:3.9-eclipse-temurin-17 AS build
WORKDIR /app
COPY pom.xml .
RUN mvn dependency:go-offline -B
COPY src ./src
RUN mvn package -DskipTests
FROM eclipse-temurin:17-jre
WORKDIR /app
COPY --from=build /app/target/*.jar app.jar
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "app.jar"]Сборка и запуск:
docker build -t demo-app .
docker run -p 8080:8080 demo-app12. Деплой и прод
Spring Boot упаковывается в два формата:
| Формат | Описание |
|---|---|
| jar (по умолчанию) | самодостаточный исполняемый файл со встроенным Tomcat |
| war | для развёртывания на внешнем сервере приложений |
Для деплоя в прод обязательно:
- Не храни пароли в свойствах — используй переменные окружения (см. раздел 8).
- Добавь зависимость Actuator для проверки состояния:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>Actuator отдаёт готовые production-эндпоинты. Проверка живости приложения:
curl http://localhost:8080/actuator/healthОтвет {"status":"UP"} означает, что приложение работает. Именно этот эндпоинт используется в health check'ах Docker и оркестраторов:
# в docker-compose.yml
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8080/actuator/health"]
interval: 30s
timeout: 5s
retries: 3DB через management.endpoint.health.show-details=always — healthcheck покажет, доступна ли база данных.13. Управление конфигурациями окружений
Профили Spring Boot позволяют держать один код под разные окружения: локально, тесты, прод. Активация профиля меняет настройки и бины без правки кода.
# application.yml — общее
app:
name: shop-service
retry: 3# application-prod.yml
spring:
datasource:
url: jdbc:postgresql://prod-db:5432/shop
username: ${DB_USER}
password: ${DB_PASSWORD}java -jar shop.jar \
--spring.profiles.active=prod
# или:
SPRING_PROFILES_ACTIVE=prod java -jar shop.jar- профили активируют выражениями:
ENV=devили!test; - данные для секретов берите из переменных окружения;
- Spring Cloud Config/Vault — для конфигурации в микросервисах.
test обычно поднимает H2 in-memory БД: тесты не зависят от внешней базы и выполняются мгновенно.14. Spring Security: JWT-аутентификация
Практичные API требуют аутентификации. Простой и современный вариант — токен JWT: подписанный JSON с идентификатором пользователя и сроком жизни.
spring-boot-starter-security
jjwt-api, jjwt-impl, jjwt-jackson
POST /api/login { "email": "...", "password": "..." }
→ Authorization: Bearer eyJhbGciOiJIUzI1NiJ9...@Configuration
@EnableWebSecurity
class SecurityConfig {
@Bean
SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
return http
.csrf(AbstractHttpConfigurer::disable)
.authorizeHttpRequests(auth -> auth
.requestMatchers("/api/login", "/api/register").permitAll()
.anyRequest().authenticated())
.sessionManagement(sm ->
sm.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.build();
}
}- пароли храните через
BCryptPasswordEncoder; - JWT подписывается секретом сервера и проверяется на каждом запросе;
- добавьте срок жизни токена и refresh-токен для UX.
15. Мониторинг и Actuator
Приложение в проде обязано рассказывать о здоровье. Spring Boot Actuator открывает метрики и проверки состояния через специальные эндпоинты.
implementation 'org.springframework.boot:spring-boot-starter-actuator'# применить в application.yml
management:
endpoints:
web:
exposure:
include: health,info,metrics,prometheus| Эндпоинт | Данные |
|---|---|
| /actuator/health | статус приложения и зависимостей (БД, mq) |
| /actuator/info | произвольная информация (версия, git) |
| /actuator/metrics | JVM-, HTTP- и бизнес-метрики |
| /actuator/prometheus | метрики в формате Prometheus |
exclude: /actuator/loggers без защиты — дыра.16. Логирование: SLF4J и Logback
Хорошие логи — единственный способ понять, что происходило в проде до падения. Стандарт Spring — SLF4J (абстракция) поверх Logback (реализация по умолчанию).
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@Service
public class OrderService {
private static final Logger log =
LoggerFactory.getLogger(OrderService.class);
public Order create(CreateOrderRequest req) {
log.info("Создание заказа: user={}, total={}",
req.userId(), req.total());
try {
return doCreate(req);
} catch (PaymentException e) {
log.error("Оплата не прошла: order={}, reason={}",
req.orderId(), e.getMessage(), e);
throw e;
}
}
}| Уровень | Когда использовать |
|---|---|
| DEBUG | детали разработки, в проде выключено |
| INFO | значимые события: старт, создание, вход |
| WARN | нештатные, но обрабатываемые ситуации |
| ERROR | исключения, требующие вмешательства |
# src/main/resources/logback-spring.xml (фрагмент)
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender"/>
<root level="INFO">
<appender-ref ref="CONSOLE"/>
</root>17. Планировщики: @Scheduled
Очистка кэша в полночь, выгрузка отчётов каждый час, проверка неоплаченных заказов — фоновые задачи в Spring Boot запускаются по расписанию.
@Configuration
@EnableScheduling
public class SchedulerConfig { }
@Service
public class ReportsJob {
private final ReportService reports;
@Scheduled(cron = "0 0 2 * * *") // ежедневно в 02:00
public void nightlyReport() { reports.buildDaily(); }
@Scheduled(fixedDelay = 60_000) // через 60 с после конца
public void cleanupExpired() { ... }
}| Свойство | Смысл |
|---|---|
| fixedRate | каждые N мс от начала запуска |
| fixedDelay | каждые N мс после завершения |
| cron | расписание в формате cron |
| zone | часовой пояс расписания |
18. Документация API: springdoc OpenAPI
Документация API не должна жить отдельно от кода — её генерируют из дескрипторов OpenAPI по самим контроллерам.
// build.gradle / pom.xml
implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui'@Operation(summary = "Создать заказ",
description = "Создаёт заказ и резервирует товар")
@ApiResponses({
@ApiResponse(responseCode = "201", description = "Заказ создан"),
@ApiResponse(responseCode = "400", description = "Невалидные данные")
})
@PostMapping("/orders")
public OrderDto create(@Valid @RequestBody CreateOrderRequest req) { ... }Интерактивная спецификация появляется на /swagger-ui.html, JSON-схема — на /v3/api-docs. Её импортируют в Postman и используют для генерации клиента.
19. Testcontainers: тесты с реальной базой
Интеграционные тесты на H2 «почти совпадают» с проду, но отличия диалекта способны обмануть: часть функций не поддерживается, типы ведут себя иначе. Testcontainers поднимает настоящие PostgreSQL, Redis и Kafka в Docker прямо в тесте.
@Testcontainers
@SpringBootTest
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
class OrderRepositoryTest {
@Container
static PostgreSQLContainer<?> postgres =
new PostgreSQLContainer<>("postgres:16")
.withDatabaseName("test")
.withUsername("test")
.withPassword("test");
@DynamicPropertySource
static void props(DynamicPropertyRegistry r) {
r.add("spring.datasource.url", postgres::getJdbcUrl);
r.add("spring.datasource.username", postgres::getUsername);
r.add("spring.datasource.password", postgres::getPassword);
}
@Test
void findsByEmail() { ... }
}Контейнер поднимается один раз на тестовый класс, а реальные зависимости дают уверенность, что миграции из раздела 10 применились именно к той СУБД, что работает в проде.
20. JSON на свой вкус: настройка Jackson
По умолчанию Jackson сериализует JSR-310 даты как массивы [2026, 9, 19] — а фронтенду нужна строка. Тонкая настройка JSON — базовая задача каждой команды, работающей через REST.
# application.yml
spring.jackson:
ser.write-dates-as-timestamps: false # ISO-8601 строкой
default-property-inclusion: non_null # не отдавать null-поля
property-naming-strategy: SNAKE_CASE # поля без camelCase- для
LocalDate/Instantзадайте формат через@JsonFormat(pattern="yyyy-MM-dd"); - records из коробки сериализуются без сеттеров;
- при десериализации лишние JSON-ключи не обрушивают маппинг — лишнее игнорируется;
| Настройка | Эффект |
|---|---|
| write-dates-as-timestamps: false | даты строками ISO |
| property-naming-strategy | имена полей в JSON |
| default-property-inclusion | null/empty пропуск |
21. Пагинация, сортировка и фильтры
Реальный список в REST не бывает «просто все строки». Spring Data делает пагинацию и сортировку почти бесплатными, а фильтрацию наращивают Specification-ами.
@GetMapping("/products")
public Page<ProductDto> list(@PageableDefault(size=20) Pageable pageable) {
return repo.findAll(pageable).map(ProductDto::from);
}// SPECIFICATION: фильтр динамический
Specification<Product> spec = (root, q, cb) ->
cb.and(cb.equal(root.get("category"), "tech"),
cb.greaterThan(root.get("price"), 1000));
repo.findAll(spec, pageable);| Ключ запроса | Назначение |
|---|---|
| page, size | номер и размер страницы |
| sort=price,desc | сортировка по полю |
| filter по полям | условия (Specification) |
- лимит размера страницы — защита от «отдай мне 10 млн строк»;
- для глубоких страниц предпочитайте keyset (по срезу) вместо offset — на больших таблицах быстрее;
- имена полей для sort привязаны к сущности — валидируйте, иначе SQL-инъекция через сортировку.
22. Жизненный цикл при деплое: liveness и readiness
При развороте в Kubernetes критично различать два состояния: приложение живо (JVM работает) и приложение готово принимать трафик (подключились БД, внешние сервисы). Actuator отдаёт оба эндпоинта.
management:
endpoint.health.probes.enabled: true
health.livenessstate.enabled: true
health.readinessstate.enabled: trueПоды кластера стучатся в /actuator/health/liveness и /actuator/health/readiness. Liveness «прожил» → перезапустили deadlock; readiness не готов → просто не направляем трафик, приложение лечится само.
- в readiness включите проверки БД/Redis — app не готово без них;
- graceful shutdown позволяет закончить текущие запросы:
server.shutdown: graceful; - при деплое дождитесь ready-сигнала, а не «процесс поднялся».
server.shutdown: graceful
spring.lifecycle.timeout-per-shutdown-phase: 20s| Эндпоинт | Ответ |
|---|---|
| /health/liveness | UP / DOWN |
| /health/readiness | ACCEPTING_TRAFFIC / REFUSING_TRAFFIC |
23. Spring Boot 3/4: виртуальные потоки и native image
Spring Boot 3 открыл два направления производительности: виртуальные потоки (JVM 21+) снимают лимит «поток на запрос», а GraalVM native image сокращает старт до миллисекунд.
# application.yml
spring.threads.virtual.enabled: true| Подход | Старт | Память | Время компиляции |
|---|---|---|---|
| классический JVM | 1–3 сек | средне | мгновенно |
| native image | ~50 мс | низкая | минуты |
- виртуальные потоки подходят для блокирующих IO без переписывания;
- native image выигрывает в serverless/edge-окружениях;
- в native нет отражений по умолчанию — регистрируйте их конфигом и пишите код без классических рефлексий.
mvn -Pnative native:compile # требуется GraalVM + native-build-toolsЧто дальше
Ты прошёл путь от пустого проекта до готового к деплою приложения. Рекомендуемые следующие шаги: глубже разобрать Spring и JUnit, изучить безопасность (Spring Security), а затем перейти к работе с Docker и базами данных PostgreSQL.