Паттерн "Цепочка обязанностей" в Java — фильтры и обработчики
Краткий обзор — в поведенческих паттернах. Здесь — практика на Java: как собрать цепочку обработчиков, где паттерн уже встроен в платформу (Filter, FilterChain) и когда цепочка оправдана в прикладном коде.
Задача паттерна
Chain of Responsibility (цепочка обязанностей) — поведенческий паттерн GoF. Запрос передаётся по цепочке обработчиков. Каждый звено решает: выполнить свою часть работы и передать запрос дальше, обработать запрос полностью и остановить цепочку, или отклонить запрос (ошибка, отказ).
Простая аналогия — эскалация тикета в поддержке: L1 → L2 → L3. Уровень либо закрывает инцидент, либо передаёт выше.
Типичный сценарий в вебе — последовательная проверка HTTP-запроса: аутентификация → авторизация → rate limit → бизнес-логика. Каждый шаг — отдельный обработчик; порядок можно менять без правок соседних классов.
Каркас на 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 FilterChain | HTTP-запрос до сервлета |
| 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. Порядок этапов (фильтры → DispatcherServlet → preHandle → контроллер → afterCompletion) — в жизненном цикле запроса Spring Boot. Собственная цепочка оправдана, когда правила домена не укладываются в Security и нужен явный, тестируемый порядок шагов.
Итог
Chain of Responsibility в Java — это и учебная схема "абстрактный обработчик + next", и основа Filter/FilterChain в веб-стеке. Паттерн уменьшает связность между отправителем запроса и конкретными проверками, ценой явного контроля порядка звеньев и терминального обработчика.
См. также
См. также
Другие статьи этого же раздела в боковом меню (как на странице "О разделе"). Паттерн — это повторяющийся шаблон, узор или схема. Паттерны встречаются повсюду — в природе, архитектуре, поведении людей и, конечно, в программировании. Порождающие паттерны проектирования — это группа шаблонов, направленных на решение задач, связанных с созданием объектов. Структурные паттерны — это группа шаблонов проектирования, решающих задачи организации классов и объектов таким образом, чтобы обеспечить гибкую архитектуру программного обеспечения. Поведенческие паттерны — это группа шаблонов проектирования, которые определяют способы взаимодействия объектов и распределения ответственности между ними. Архитектурные паттерны — это проверенные решения для организации структуры программного обеспечения. Интеграция систем — одна из центральных задач в современной разработке программного обеспечения. Паттерны доменного моделирования представляют собой проверенные решения для организации бизнес-логики в программных системах. Паттерн Strategy в C# — классическая реализация через интерфейс, замена на Func и Action, DI и критерии выбора без лишних абстракций. Паттерн Iterator в C# — ручной IEnumerator, генерация итератора компилятором через yield return, ленивость, LINQ и случаи, когда класс писать всё же нужно. Abstract Factory в C# и .NET — классическая схема через интерфейсы, замена через DI-контейнер, фабричный делегат и keyed services в .NET 8. Паттерн Command в C# — классическая схема, делегаты, MediatR, очередь задач, undo и критерии выбора между объектом команды и простым вызовом сервиса. Паттерн Observer в C# — event и делегаты, IObservable IObserver, слабая связанность, отписка и как не поймать утечки памяти в долгоживущих сервисах.Обзор паттернов проектирования
Порождающие паттерны
Структурные паттерны
Поведенческие паттерны
Архитектурные паттерны
Паттерны интеграции внешних систем
Паттерны проектирования доменных моделей
Стратегия в C#
Итератор в C#
Фабрика в C#
Команда в C#
Наблюдатель в C#