Перейти к основному содержимому

Паттерн "Цепочка обязанностей" в Java — фильтры и обработчики

Разработчику Архитектору

Краткий обзор — в поведенческих паттернах. Здесь — практика на Java: как собрать цепочку обработчиков, где паттерн уже встроен в платформу (Filter, FilterChain) и когда цепочка оправдана в прикладном коде.

Загрузка редактора схем…

Задача паттерна

Chain of Responsibility (цепочка обязанностей) — поведенческий паттерн GoF. Запрос передаётся по цепочке обработчиков. Каждый звено решает: выполнить свою часть работы и передать запрос дальше, обработать запрос полностью и остановить цепочку, или отклонить запрос (ошибка, отказ).

Простая аналогия — эскалация тикета в поддержке: L1 → L2 → L3. Уровень либо закрывает инцидент, либо передаёт выше.

Типичный сценарий в вебе — последовательная проверка HTTP-запроса: аутентификация → авторизация → rate limit → бизнес-логика. Каждый шаг — отдельный обработчик; порядок можно менять без правок соседних классов.

Загрузка ArchiStyler…

Каркас на Java

Общий контракт — абстрактный обработчик со ссылкой на следующее звено и методом setNext для сборки цепочки в fluent-стиле.

public abstract class RequestHandler {
private RequestHandler next;

public RequestHandler setNext(RequestHandler next) {
this.next = next;
return next;
}

public void handle(HttpRequest request) {
if (next != null) {
next.handle(request);
}
}
}

Конкретные обработчики вызывают super.handle(request) после успешной проверки, чтобы запрос дошёл до конца цепочки.

public class AuthenticationHandler extends RequestHandler {
@Override
public void handle(HttpRequest request) {
if (request.getToken() == null) {
throw new SecurityException("Нет токена");
}
System.out.println("Аутентификация пройдена");
super.handle(request);
}
}

public class RateLimitHandler extends RequestHandler {
private final Map<String, Integer> counters = new ConcurrentHashMap<>();

@Override
public void handle(HttpRequest request) {
int count = counters.merge(request.getIp(), 1, Integer::sum);
if (count > 100) {
throw new RuntimeException("429 Too Many Requests");
}
System.out.println("Rate limit OK");
super.handle(request);
}
}

public class BusinessLogicHandler extends RequestHandler {
@Override
public void handle(HttpRequest request) {
System.out.println("Бизнес-логика выполнена");
}
}

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

RequestHandler chain = new AuthenticationHandler();
chain.setNext(new RateLimitHandler())
.setNext(new BusinessLogicHandler());

chain.handle(request);

Для учебного примера достаточно простого DTO:

public final class HttpRequest {
private final String ip;
private final String token;

public HttpRequest(String ip, String token) {
this.ip = ip;
this.token = token;
}

public String getIp() { return ip; }
public String getToken() { return token; }
}

Где паттерн уже есть в Java-экосистеме

Servlet API — javax.servlet.Filter

Классический пример из экосистемы Java EE и Jakarta EE: каждый Filter — звено цепочки. Контейнер вызывает FilterChain.doFilter(), и фильтры по очереди решают, пропустить запрос дальше или оборвать обработку.

Тот же приём в Spring MVC/Spring Boot: фильтры регистрируются в контейнере или через FilterRegistrationBean.

Spring Security — SecurityFilterChain

В Spring Security десятки внутренних фильтров выстроены в упорядоченную цепочку (SecurityFilterChain). Конфигурация в Security Basic и обзоре Spring — это промышленная версия Chain of Responsibility: аутентификация, CSRF, авторизация по URL и т.д.

Другие примеры

МестоРоль цепочки
Jakarta EE / Servlet FilterChainHTTP-запрос до сервлета
Spring SecurityБезопасность до DispatcherServlet
Логгеры (уровни DEBUG → INFO → WARN)Сообщение доходит до подходящего аппендера
Согласование (approval)Заявка по сумме — менеджер → директор → CFO

Когда применять

СитуацияПочему подходит цепочка
Набор и порядок обработчиков меняются в рантайме или по профилюЗвенья подключаются без правки клиентского кода
Неизвестно заранее, кто обработает запросОтправитель знает только вход в цепочку
Несколько независимых политик над одним запросомКаждая политика — отдельный класс (SRP)
Middleware-пайплайнОдин контракт handle, единый порядок

Риски и ограничения

Запрос может пройти всю цепочку и остаться без обработки, если финальное звено только "пропускает" дальше, а обработчика с бизнес-логикой нет. Длинную цепочку сложнее отлаживать: нужны логи с именем класса-обработчика и порядком вызова.

Финальный обработчик

Добавляйте терминальное звено, которое либо выполняет работу, либо явно бросает исключение / пишет в лог "запрос не обработан". Иначе ошибки теряются молча.


Сравнение с соседними паттернами

ПаттернОтличие
НаблюдательОдно событие рассылается всем подписчикам параллельно
Цепочка обязанностейЗапрос идёт последовательно, пока звено не обработает или не передаст дальше
КомандаДействие инкапсулируется в объект; цепочка — про маршрутизацию запроса
СтратегияВыбирается один алгоритм; в цепочке может сработать несколько проверок подряд

Чек-лист

ВопросВыбор
Нужен фиксированный пайплайн проверок над запросомЦепочка обработчиков или Servlet/Spring filters
Нужно уведомить много подписчиков об одном событииObserver / domain events
Нужно упаковать одно действие с undo/очередьюCommand
Один из нескольких алгоритмов на выборStrategy
Практика

В новом Spring Boot REST-сервисе часто достаточно встроенных фильтров и HandlerInterceptor, без собственной иерархии RequestHandler. Порядок этапов (фильтры → DispatcherServletpreHandle → контроллер → afterCompletion) — в жизненном цикле запроса Spring Boot. Собственная цепочка оправдана, когда правила домена не укладываются в Security и нужен явный, тестируемый порядок шагов.


Итог

Chain of Responsibility в Java — это и учебная схема "абстрактный обработчик + next", и основа Filter/FilterChain в веб-стеке. Паттерн уменьшает связность между отправителем запроса и конкретными проверками, ценой явного контроля порядка звеньев и терминального обработчика.


См. также

См. также

Другие статьи этого же раздела в боковом меню (как на странице "О разделе").