Зарегистрируйтесь, чтобы продолжить обучение

Динамические маршруты Java: Веб-технологии

До сих пор мы встречались только со статическими маршрутами. В таких маршрутах нет изменяемых частей — адрес точно совпадает с маршрутом и не меняется. На практике чаще встречаются динамические маршруты. Для примера проанализируем адреса курсов на Хекслете:

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

Динамические маршруты

Вернемся еще раз к адресам курсов на Хекслете:

Обратите внимание, что в этих адресах прослеживается определенная структура:

/courses/<имя курса>

Для таких адресов создается ровно один маршрут, в котором изменяемая часть попадает внутрь через контекст как параметр:

package org.example.hexlet;

import io.javalin.Javalin;

public class HelloWorld {
    public static void main(String[] args) {
        var app = Javalin.create(config -> {
            config.bundledPlugins.enableDevLogging();
        });

        // Обратите внимание, что id — это не обязательно число
        app.get("/courses/{id}", ctx -> {
            ctx.result("Course ID: " + ctx.pathParam("id"));
        });
        app.get("/users/{id}", ctx -> {
            ctx.result("User ID: " + ctx.pathParam("id"));
        });

        app.start(7070);
    }
}

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

/courses/{id}

В этом маршруте секция {id} означает, что на это место подставляется конкретный идентификатор курса:

curl localhost:7070/courses/132
Course id: 132

curl localhost:7070/courses/php-oop
Course id: php-oop

Имя изменяемой части можно выбирать произвольно — например, вместо {id} можно написать {lala}. Способ записи зависит от конкретного фреймворка. Здесь мы записали имя с обрамляющими фигурными скобками {}, как это принято в Javalin.

Параметры пути

Изменяемая часть маршрута в Javalin называется параметром пути. В примере выше есть только один такой параметр — это id. Доступ к нему мы получаем через метод ctx.pathParam():

// String
var id = ctx.pathParam("id");

Как и в случае с параметрами запроса, возвращаемый тип метода ctx.pathParam() будет равен String. Это подходит не для всех случаев. Поэтому ctx содержит метод, который позволяет автоматически конвертировать данные в нужный тип:

var id = ctx.pathParamAsClass("id", Integer.class);

Откуда берутся конкретные значения в параметрах пути? Конкретные ссылки формируются на страницах сайта. Для примера можно посмотреть на список постов в блоге Хекслета. На каждый пост есть ссылка, которая формируется в коде как обычная строчка с подстановкой:

var url = "/blog/posts/" + post.getId()

Чтобы пользователям было удобнее, разработчики стараются использовать в адресах не числовые идентификаторы, а человекочитаемые названия. Например, вместо /courses/332 показывают /courses/php-mvc. Эту часть адреса называют словом слаг (slug). В каждом случае мы используем уникальный слаг, при этом его формат обязан соответствовать требованиям формирования адресов.

Как правило, такие имена содержат символы латинского алфавита с дефисами между словами:

this-that-other-outre-collection

Подведем промежуточный итог:

  • Понятия «адрес» и «маршрут» обозначают разные вещи
  • Если маршрут статический, то он всегда совпадает с адресом — например, /about
  • Если маршрут динамический, то ему могут соответствовать бесконечное число адресов, даже если таких страниц на сайте нет — например, /courses/:id

Обработка ошибок

Большинство фреймворков позволяют задавать любое значение в качестве параметра. Параметром считается набор символов, который находится между символами слеша / или после последнего слеша. Фреймворк не знает, какие значения подходят конкретно в нашем случае, поэтому он не может принимать решение за программиста.

Предположим, что в нашей базе данных есть 10 курсов c идентификаторами от 1 до 10. Адрес каждого курса формируется так:

/courses/{id}

В таком случае адрес /courses/1 вернет курс с идентификатором 1. А вот адрес /courses/11 выдаст ошибку 404, потому что такого курса не существует. Чтобы программа сработала именно так, мы должны все правильно реализовать. По идее, код должен выполнять два обязательных действия:

  • Проверять наличие данных в базе
  • Выбрасывать исключения в тех случаях, когда данных нет

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

// Дополнительный импорт
import io.javalin.http.NotFoundResponse;

// Обработчик маршрута /courses/{id}
public static void show(Context ctx) {
    var id = ctx.pathParamAsClass("id", Long.class).get();
    // Позже мы разберем эти конструкции подробнее
    var user = UserRepository.find(id) // Ищем пользователя в базе по id
            .orElseThrow(() -> new NotFoundResponse("Entity with id = " + id + " not found"));
}

Множественные параметры пути

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

package org.example.hexlet;

import io.javalin.Javalin;

public class HelloWorld {
    public static void main(String[] args) {
        var app = Javalin.create(config -> {
            config.bundledPlugins.enableDevLogging();
        });

        // Название параметров мы выбрали произвольно
        app.get("/courses/{courseId}/lessons/{id}", ctx -> {
            var courseId = ctx.pathParam("courseId");
            var lessonId =  ctx.pathParam("id");
            ctx.result("Course ID: " + courseId + " Lesson ID: " + lessonId);
        });

        app.start(7070);
    }
}

Порядок определения

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

Посмотрите на этот пример:

app.get("/courses/{id}", ctx -> {
    // Вывод информации о курсе
});
app.get("/courses/build", ctx -> {
    // Вывод формы создания курса
});

При таком порядке программа обработает запрос на страницу /courses/build через маршрут /courses/{id}. Это не то, что мы задумывали. Чтобы исправить, поменяем маршруты местами:

app.get("/courses/build", ctx -> {
    // Вывод формы создания курса
});

app.get("/courses/{id}", ctx -> {
    // Вывод информации о курсе
});

Ситуация с неверным обработчиком повторится, если мы добавим курс, в которой значение id равно build. Чтобы предотвратить эту проблему, мы просто запрещаем создавать такие id.


Самостоятельная работа

  1. Выполните все шаги из урока на своем компьютере
  2. Ориентируясь на примеры из урока, добавьте в приложение обработчик, который будет обрабатывать запросы по пути users/{id}/post/{postId}
  3. Запустите приложение и убедитесь, что все работает
  4. Залейте изменения на GitHub

Дополнительные материалы

  1. Официальная документация
  2. Человекопонятный URL

Аватары экспертов Хекслета

Остались вопросы? Задайте их в разделе «Обсуждение»

Вам ответят команда поддержки Хекслета или другие студенты

Для полного доступа к курсу нужен базовый план

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

Получить доступ
1000
упражнений
2000+
часов теории
3200
тестов

Открыть доступ

Курсы программирования для новичков и опытных разработчиков. Начните обучение бесплатно

  • 130 курсов, 2000+ часов теории
  • 1000 практических заданий в браузере
  • 360 000 студентов
Отправляя форму, вы принимаете «Соглашение об обработке персональных данных» и условия «Оферты», а также соглашаетесь с «Условиями использования»

Наши выпускники работают в компаниях:

Логотип компании Альфа Банк
Логотип компании Aviasales
Логотип компании Yandex
Логотип компании Tinkoff
Рекомендуемые программы
профессия
от 25 000 ₸ в месяц
Разработка приложений на языке Java
10 месяцев
с нуля
Старт 26 декабря

Используйте Хекслет по-максимуму!

  • Задавайте вопросы по уроку
  • Проверяйте знания в квизах
  • Проходите практику прямо в браузере
  • Отслеживайте свой прогресс

Зарегистрируйтесь или войдите в свой аккаунт

Отправляя форму, вы принимаете «Соглашение об обработке персональных данных» и условия «Оферты», а также соглашаетесь с «Условиями использования»