JavaRush /Курсы /Spring Boot /Static resources и welcome page

Static resources и welcome page

Spring Boot
11 уровень , 4 лекция
Открыта

1. Роль статики в API‑сервисе

Когда мы говорим «backend-сервис», в голове легко появляется картинка: только JSON, только хардкор, только терминал. Но в реальной жизни маленькому сервису почти всегда полезно иметь минимальную «точку входа для человека»: страничку, где написано, что это за сервис, что он жив, и куда можно ткнуть, чтобы увидеть результат. Причём без шаблонизаторов, без фронтенд-сборок и без превращения курса в “React на максималках”.

До этого мы настраивали request-side поведение MVC: как параметры запроса превращаются в LocalDate, CourseTrack и другие типы. Но у web-слоя есть ещё одна спокойная поверхность, где Boot уже многое умеет сам, — статика и welcome page. Для обычного index.html, CSS и пары ссылок WebMvcConfigurer вообще не нужен: достаточно положить файлы в standard location и не мешать defaults работать.

Статические ресурсы в Spring Boot — это как наклейка на коробке. Коробка (ваш сервис) может быть технически идеальной, но если на ней нет ни названия, ни инструкции «открывать тут», любому новичку (и даже вам через неделю) будет грустно. Небольшой index.html помогает решать очень практичную задачу: сделать сервис “самообъяснимым” при ручном открытии в браузере.

Важно, что мы не строим UI как отдельный продукт. Мы делаем то, что в учебном проекте особенно ценно: быстрый способ “пощупать” сервис глазами и руками, не вспоминая точные URL и параметры. У вас уже есть GET /api/catalog/courses и GET /api/catalog/featured. Браузеру всё равно, что это «API»: он откроет ссылку и покажет JSON. И это нормально для учебного read-only сервиса.

Ещё один плюс статических файлов — они не требуют участия Spring MVC как «бизнес-уровня». Да, технически они обслуживаются внутри MVC-механики, но ваш код при этом вообще не вызывается. То есть, если вы хотите страницу-справку, вам не нужно писать контроллер, который возвращает HTML-строку (это выглядит как «попытка написать фронтенд на аннотациях»).

2. Локации статики и classpath

Когда новичок впервые слышит «статические файлы», он часто по привычке вспоминает древний Java‑веб: webapp/, JSP, какие-то WAR‑архивы и “деплой на сервер приложений”. У Spring Boot (в режиме executable jar, который мы используем в курсе) мышление другое: большинство ресурсов лежит в src/main/resources и попадает в classpath. Это означает простую штуку: «всё, что положили в resources, потом окажется внутри jar и будет доступно приложению».

Spring Boot по умолчанию умеет раздавать статические ресурсы из нескольких стандартных classpath‑локаций. Важно не пытаться запомнить это как заклинание, а увидеть смысл: Boot даёт вам несколько “полок”, куда можно положить HTML/CSS/картинки, и он сам подключит обработчик, который их раздаёт.

Ниже — табличка, которую стоит один раз увидеть и дальше просто помнить: «у меня есть static/ и этого достаточно почти всегда».

Classpath‑локация внутри resources Что туда обычно кладут Примечание
src/main/resources/static/ index.html, styles.css, favicon.ico, картинки Самая популярная и понятная для jar‑проекта
src/main/resources/public/ То же самое Альтернатива static, разница чаще историческая
src/main/resources/resources/ То же самое Встречается реже
src/main/resources/META-INF/resources/ Статика библиотек, “webjars”-стиль Чаще нужно авторам библиотек, нам почти не надо

Для нашего catalog-service самый простой и «учебно правильный» путь — src/main/resources/static/. Там вы и будете держать index.html.

Чтобы закрепить mental model, полезно представить это так: папка static/ — это “мини‑публичная витрина”, которую Boot может показывать напрямую. И чем меньше мы её усложняем, тем лучше.

3. Приоритет: контроллер и файл для /

После появления статических ресурсов возникает тонкий момент, который почти всегда ловит новичок: “Я положил index.html, но он не открывается на /”. И тут начинается шаманство: кеш, браузер виноват, Tomcat заколдован… На самом деле обычно виноват очень простой факт: у вас уже есть явный маршрут в контроллере.

Чтобы не гадать, давайте взглянем на ситуацию схематично. Запрос в Boot‑приложении (в servlet MVC) проходит примерно такой маршрут:

flowchart TD
    %% Упрощённая схема: показываем только ключевые этапы и выбор обработчика
    A[HTTP запрос] --> B[Tomcat]
    B --> C[DispatcherServlet]
    C --> D{Кто обработает путь?}
    D -->|"Наш @Controller / @RestController"| E[Controller method]
    D -->|Static resource handler| F[Файл из classpath]
    E --> G[HTTP response]
    F --> G[HTTP response]

Ключевая мысль: Spring MVC не «угадывает» по настроению. Он ищет подходящий handler. Если есть ваш контроллер с @GetMapping("/"), то для запроса / он почти наверняка будет выбран раньше, чем “welcome page”. А welcome page как раз и задуман как fallback: “если корень не занят, давайте покажем index”.

Поэтому в рамках курса мы держим очень удобное разделение: API живёт под /api/..., а человеческая точка входа — на /. Тогда конфликтов меньше, а проект читабельнее.

4. Welcome page через index.html

Есть приятная магия Spring Boot, которую важно воспринимать правильно: как “удобство платформы”, а не как “ну оно как-то само”. Если в одной из стандартных static‑локаций Boot находит index.html, он делает две вещи. Во-первых, этот файл становится доступен как обычный статический ресурс (например, по /index.html). Во-вторых, он может стать welcome page, то есть открываться на / — но только если вы не заняли / явным маршрутом.

Эта механика полезна именно потому, что не требует Java-кода. Никакого HomeController, никаких return "...", никаких аннотаций. Вам нужно просто положить файл в правильное место.

Для ясности — минимальная структура, которую мы хотим получить в проекте:

src/main/resources/
|-- static/
|   `-- index.html

И теперь важный антипример: если вы где-то сделали явный маршрут /, welcome page перестанет быть “главной”.

package com.example.catalogservice.catalog.web;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class HomeController {

    // Явно занимаем корень приложения: после этого welcome page (index.html) на "/" уже не сработает
    @GetMapping("/")
    public String home() {
        // Для примера возвращаем простой текст, чтобы было видно, что ответ пришёл из контроллера
        return "explicit home route";
    }
}

Если такой контроллер есть, запрос GET / попадёт сюда, а не в index.html. Это не баг и не “Spring глючит”, это нормальная логика роутинга: явное поведение важнее неявного.

В catalog-service welcome page обычно выгоднее, потому что нам не нужен умный HTML, зависящий от данных. Нам нужна “визитка”: название и ссылки. То есть мы выбираем максимально простой и честный вариант.

5. index.html для catalog-service

Когда сервис уже отдаёт JSON, следующая маленькая победа — сделать так, чтобы человек мог открыть http://localhost:8080/ и буквально за 10 секунд понять, “куда тыкать”. Эта страница не должна превращаться в UI. Мы не делаем админку, не делаем формы, не делаем template engine. Мы делаем “навигационную табличку” — как список ссылок в README, только прямо в сервисе.

Создадим файл:

`src/main/resources/static/index.html`

<!doctype html>
<html lang="ru">
<head>
  <meta charset="UTF-8">
  <!-- Заголовок вкладки в браузере -->
  <title>catalog-service</title>
</head>
<body>
  <h1>catalog-service</h1>
  <p>Мини‑витрина для ручной проверки API.</p>

  <!-- Эти ссылки ведут прямо на API-эндпоинты и в браузере покажут JSON -->
  <a href="/api/catalog/courses">Все курсы</a><br/>
  <a href="/api/catalog/featured">Featured</a><br/>
  <a href="/api/catalog/courses/spring-boot">Курс по slug: spring-boot</a><br/>
</body>
</html>

Здесь есть важная “учебная честность”: браузер откроет ссылки и покажет JSON как есть. Для сервисов без UI это нормальная практика. Да, это не красиво как приложение. Зато это практично как инструмент обучения.

Теперь давайте сделаем страницу чуть полезнее и свяжем её с тем, что мы уже умеем из предыдущих кусков: query‑параметры, конвертеры, форматирование LocalDate по ISO. То есть мы можем дать ссылки “с фильтрами” прямо на landing page.

<!doctype html>
<html lang="ru">
<head>
  <meta charset="UTF-8">
  <title>catalog-service</title>
</head>
<body>
  <h1>catalog-service</h1>
  <p>Ссылки для ручной проверки фильтров.</p>

  <h3>Каталог</h3>
  <!-- Пример query-параметров: limit -->
  <a href="/api/catalog/courses?limit=5">Первые 5 курсов</a><br/>
  <!-- Пример булевого фильтра -->
  <a href="/api/catalog/courses?featuredOnly=true">Только featured</a><br/>
  <!-- Пример “человеческого” значения, которое конвертер превратит в enum -->
  <a href="/api/catalog/courses?track=java-backend">Track: java-backend</a><br/>
  <!-- Пример ISO-даты (удобно, если вы включили глобальный ISO-формат) -->
  <a href="/api/catalog/courses?launchedAfter=2026-01-01">Запуск после 2026‑01‑01</a><br/>
</body>
</html>

Обратите внимание на две вещи. Во-первых, launchedAfter=2026-01-01 — это как раз тот ISO‑формат, который удобно поддерживать глобально через spring.mvc.format.date: iso. Во-вторых, track=java-backend — это тот случай, когда Converter<String, CourseTrack> превращает “человеческий” вариант в enum внутри Java.

Если хотите добавить чуть эстетики (но не превращать это в дизайн‑систему), можно положить простой CSS рядом — и это будет хороший пример того, что Spring Boot отдаёт не только HTML.

/* Базовая типографика: чтобы страница читалась как страница, а не как сырой вывод */
body {
  font-family: sans-serif;
  max-width: 720px;
  margin: 40px auto;
  line-height: 1.5;
}

/* Чуть “воздуха” между ссылками */
a { display: inline-block; margin: 4px 0; }

И подключить его в index.html (важно: путь абсолютный от корня, потому что файл лежит в static‑локации и раздаётся напрямую):

<link rel="stylesheet" href="/styles.css">

Да, это по-прежнему “страница для инженера”, а не UI‑продукт. Но теперь она хотя бы выглядит как страница, а не как сообщение об ошибке.

6. Полезные нюансы статики

spring.mvc.static-path-pattern и /assets/**

Иногда хочется, чтобы статические файлы жили не “как попало” в корне (/styles.css, /logo.png), а в отдельном пространстве имён, например /assets/.... Причины обычно банальные и практичные: вы не хотите случайно пересечься с будущими маршрутами контроллеров, вы хотите более очевидную структуру URL, или вам проще настроить внешнее кеширование по одному префиксу.

В Spring Boot это можно сделать настройкой spring.mvc.static-path-pattern. Она меняет публичный URL‑шаблон для статики, но не заставляет вас переносить файлы в другую папку. То есть файлы остаются в src/main/resources/static/, а снаружи доступны по другому пути.

Пример настройки в application.yaml:

spring:
  mvc:
    # Все статические ресурсы будут доступны по URL, начинающемуся с /assets/
    static-path-pattern: "/assets/**"

После этого ваш CSS уже не будет доступен по /styles.css. Он станет доступен по /assets/styles.css, и HTML нужно будет подправить:

<link rel="stylesheet" href="/assets/styles.css">

С welcome page (/) здесь лучше не гадать по памяти или старым статьям. После изменения static-path-pattern проверьте в своей версии Boot отдельно два пути: корень приложения / и путь под новым префиксом, например /assets/index.html. Если вам не нужен явный URL‑префикс для статики, для учебного catalog-service проще оставить дефолтное поведение и не добавлять себе лишнюю развилку.

Для учебного catalog-service чаще всего достаточно дефолтного поведения. Менять static-path-pattern имеет смысл только если у вас реально есть конфликт или вы сознательно хотите единый URL‑префикс для всех статических файлов. В противном случае это превращается в «настройку ради настройки» — а таких в Boot лучше избегать, иначе в один момент вы проснётесь среди YAML‑лабиринта и не вспомните, зачем он был построен.

src/main/webapp в jar‑проекте

У многих, кто видел старые учебники по Java EE, есть рефлекс: “HTML должен лежать в webapp”. В Spring Boot jar‑подходе это обычно не нужно и только добавляет путаницы. Ваш проект собирается как executable jar, а значит ресурсы должны быть доступны на classpath. Самый прямой и понятный путь — src/main/resources/static.

Когда Gradle собирает приложение, всё из src/main/resources попадает внутрь артефакта как ресурсы. Это означает, что ваш index.html реально будет жить внутри jar и раздаваться оттуда. Мы ещё отдельно будем говорить о запуске “вне IDE”, но уже сейчас полезно держать в голове: если ресурс лежит в resources, шанс, что он корректно поедет вместе с приложением, сильно выше, чем если он лежит где-то “сбоку”.

Если коротко: static/ — это не “временная папка для разработки”, а нормальная часть приложения, которая переживёт сборку и запуск.

7. Типичные ошибки при работе со статикой

Ошибка №1: файл положили “почти туда”, но не туда.
Классика: index.html лежит в src/main/resources/templates (потому что где-то слышали про шаблоны), или в src/main/resources/ рядом с application.yaml, или вообще в src/main/java. Spring Boot не телепат: welcome page он будет искать в стандартных static‑локациях. Для нашего курса самый надёжный путь — src/main/resources/static/index.html.

Ошибка №2: welcome page не работает, потому что уже есть @GetMapping("/").
Это выглядит как мистика: “я добавил index.html, но на / всё равно какой-то текст/JSON”. На самом деле это просто приоритет явного маршрута. Если вы уже сделали HomeController с /, то он победит. Либо освобождайте / под статику, либо принимайте, что welcome page будет недоступна как корень.

Ошибка №3: поменяли spring.mvc.static-path-pattern, но забыли обновить ссылки.
После переноса статики под /assets/** старые ссылки вида href="/styles.css" перестают работать, и вы получаете “сломанный дизайн” (а иногда и впечатление, что сервис “не видит файл”). В таких настройках нет магии: URL меняется — ссылки тоже должны поменяться.

Ошибка №4: пытаются сделать landing page “динамической” и тащат логику в HTML.
Иногда хочется, чтобы index.html показывал название из spring.application.name или количество курсов. Это уже динамика, и статическая страница сама по себе этого не умеет. В рамках курса лучше принять ограничение и держать landing page простой. Иначе вы очень быстро случайно уедете в шаблонизаторы, фронтенд-сборки и прочие темы, которые не являются целью текущего блока.

Ошибка №5: “Я поменял index.html, но браузер показывает старую версию”.
Это может быть обычный кеш браузера. Особенно заметно со стилями (styles.css): вы поправили файл, а визуально ничего не изменилось. В таких случаях помогает жёсткая перезагрузка (hard reload) или отключение кеша в devtools на время. Это не специфичная проблема Spring Boot — это нормальная “радость” веба.

1
Задача
Spring Boot, 11 уровень, 4 лекция
Недоступна
`index.html` как welcome page
`index.html` как welcome page
1
Задача
Spring Boot, 11 уровень, 4 лекция
Недоступна
Статические файлы под URL-префиксом `/assets/**`
Статические файлы под URL-префиксом `/assets/**`
1
Опрос
Spring MVC, 11 уровень, 4 лекция
Недоступен
Spring MVC
Конвертация и статические ресурсы
Комментарии
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ