Spring Boot — способ собрать Java-сервис без ручной настройки сервера приложений: встроенный Tomcat, автоконфигурация, готовые стартеры и запуск одним java -jar. В 2026 году актуальная ветка требует Java 17 и выше, а Java 21 LTS остаётся безопасным выбором для нового кода. Ниже — путь от пустого каталога до работающего HTTP-API с валидацией, тестами, Actuator и контейнерным образом.

Что понадобится

Нужны JDK 21 и Maven или Gradle. Maven-обёртка mvnw уже лежит в сгенерированном проекте и скачает нужную версию сама — отдельная установка Maven не обязательна. Проверьте окружение:

java -version
curl -s https://start.spring.io/metadata/client | head -c 300
mkdir -p ~/projects && cd ~/projects

Если java -version показывает старую версию, переключитесь через update-alternatives, SDKMAN или переменную JAVA_HOME. Для сборки контейнера понадобится Docker, но для запуска и тестов он не нужен.

Генерация проекта

Spring Initializr отдаёт архив с готовой структурой. Параметры передаются query-строкой; версию Boot не фиксируем, чтобы получить актуальную стабильную:

curl -G https://start.spring.io/starter.tgz   -d type=maven-project -d language=java -d javaVersion=21   -d groupId=su.linuxmint -d artifactId=demo   -d dependencies=web,validation,actuator -o demo.tgz
mkdir demo && tar xzf demo.tgz -C demo
cd demo && ./mvnw -q test

В архиве будет pom.xml, класс DemoApplication и тест контекста. Стартеры web, validation и actuator подтягивают Tomcat, Hibernate Validator и эндпоинты мониторинга. Не добавляйте зависимости «на будущее»: каждый стартер увеличивает поверхность обновлений.

Первый контроллер и сервис

Разделим HTTP-слой и логику. Контроллер принимает запрос, проверяет входные данные и отдаёт DTO, сервис хранит состояние. Для примера хватит памяти, но структура останется той же при переходе на базу:

@RestController
@RequestMapping("/api/tasks")
class TaskController {
    private final TaskService service;

    TaskController(TaskService service) {
        this.service = service;
    }

    @GetMapping
    List<Task> list() {
        return service.findAll();
    }

    @PostMapping
    @ResponseStatus(HttpStatus.CREATED)
    Task create(@Valid @RequestBody CreateTask body) {
        return service.create(body.title());
    }
}

record CreateTask(@NotBlank @Size(max = 200) String title) {}
record Task(long id, String title, boolean done) {}

Конструктор вместо поля с @Autowired делает зависимости явными и упрощает тесты. Сервис можно оставить простым:

@Service
class TaskService {
    private final Map<Long, Task> store = new ConcurrentHashMap<>();
    private final AtomicLong seq = new AtomicLong();

    List<Task> findAll() {
        return List.copyOf(store.values());
    }

    Task create(String title) {
        long id = seq.incrementAndGet();
        var task = new Task(id, title, false);
        store.put(id, task);
        return task;
    }
}

Аннотация @Valid запускает валидацию, а ошибка превращается в ответ 400. Чтобы формат ошибок был единым, добавьте @RestControllerAdvice с обработчиком MethodArgumentNotValidException. Не отдавайте клиенту stack trace: это утечка внутренних деталей.

Конфигурация и профили

Настройки храните в application.yml, а секреты — в переменных окружения или внешнем хранилище. Профили позволяют держать разные значения для локального запуска и production:

server:
  port: 8080
spring:
  application:
    name: demo
management:
  endpoints:
    web:
      exposure:
        include: health,info,metrics
  endpoint:
    health:
      probes:
        enabled: true

Запуск с профилем: java -jar target/demo-0.0.1-SNAPSHOT.jar --spring.profiles.active=prod. Переменные вида SPRING_DATASOURCE_URL переопределяют YAML без пересборки. Дополнительный каталог подключите через spring.config.additional-location=file:/etc/demo/ и монтируйте его в контейнер. Пароли не коммитьте даже в тестовые профили: история Git остаётся навсегда.

Тесты

Минимум — проверить, что контекст поднимается и API отвечает. @SpringBootTest со случайным портом поднимает настоящее приложение, а TestRestTemplate шлёт реальные HTTP-запросы:

@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
class TaskApiTest {
    @Autowired
    TestRestTemplate rest;

    @Test
    void createsTask() {
        var response = rest.postForEntity(
            "/api/tasks", new CreateTask("check"), Task.class);

        assertThat(response.getStatusCode()).isEqualTo(HttpStatus.CREATED);
        assertThat(response.getBody().title()).isEqualTo("check");
    }
}

Для модульных тестов сервиса не нужен Spring: создайте объект и проверьте поведение. Держите быстрые тесты отдельно от интеграционных, иначе сборка начнёт занимать минуты. В CI запускайте ./mvnw verify — эта команда прогонит тесты и соберёт jar.

Сборка и запуск

Обычный запуск и упаковка:

./mvnw -q verify
./mvnw -q spring-boot:run
./mvnw -q package -DskipTests
java -jar target/demo-0.0.1-SNAPSHOT.jar

Для контейнера удобно использовать buildpacks: плагин соберёт образ без Dockerfile, с правильным слоем зависимостей и непривилегированным пользователем по умолчанию:

./mvnw -q spring-boot:build-image -Dspring-boot.build-image.imageName=registry.example.com/demo:1.0
docker run --rm -p 8080:8080 registry.example.com/demo:1.0

Образ всё равно нужно сканировать и обновлять базовый слой. Не запускайте контейнер от root и передавайте секреты через переменные окружения или монтируемые файлы, а не через аргументы командной строки. В контейнере задайте -XX:MaxRAMPercentage=75, чтобы доля кучи была предсказуемой при лимите cgroup, и включите server.shutdown=graceful: балансировщик успеет вывести под из пула. Логи пишите в stdout, а не в файл внутри контейнера.

Actuator и диагностика

После старта проверьте /actuator/health: он покажет состояние приложения и, если включены probes, отдельные liveness и readiness. Метрики доступны на /actuator/metrics, но не открывайте все эндпоинты в интернет. Для отладки автоконфигурации запустите приложение с --debug и посмотрите отчёт о том, какие бины созданы и почему.

  • curl -s localhost:8080/actuator/health — базовая проверка;
  • journalctl -u demo -n 100 — логи при запуске через systemd;
  • jcmd <pid> Thread.print — зависшие потоки;
  • jcmd <pid> GC.heap_info — состояние памяти.

Частые проблемы: порт занят, несовпадение версии Java, отсутствующий бин из-за сканирования пакетов и закрытый Actuator в цепочке прокси. Читайте первую причину в stack trace, а не последнюю строку.

Чек-лист

  • Java 21 LTS и зафиксированная версия Spring Boot.
  • Зависимости добавляются только под задачу.
  • Валидация входа и единый формат ошибок.
  • Секреты вне репозитория, профили разделены.
  • Есть тест контекста и тест ключевого сценария.
  • Health-пробы и метрики подключены, лишние эндпоинты закрыты.

Итог

Spring Boot экономит время на инфраструктуре, но не отменяет проектирования. Начните с Initializr, сделайте один контроллер и сервис, добавьте валидацию и тест, затем упакуйте в образ. Такой скелет легко расширяется базой данных, очередями и внешней конфигурацией, а обновление зависимостей остаётся управляемым процессом.

Насколько публикация полезна?

Нажмите на звезду, чтобы оценить!

Средняя оценка 0 / 5. Количество оценок: 0

Оценок пока нет. Поставьте оценку первым.

Поделиться: Telegram ВКонтакте