Home » Шаблон Chain of Responsibility в Java — учебное пособие
Шаблон Chain of Responsibility в Java — учебное пособие

Шаблон Chain of Responsibility в Java — учебное пособие

Сегодня разберём один из самых полезных паттернов проектирования для тех, кто работает с серверным кодом — Chain of Responsibility. Особенно актуально для разработчиков, которые настраивают и обслуживают серверы, работают с веб-приложениями и системами обработки запросов. Этот паттерн позволяет создавать гибкие цепочки обработчиков, где каждый элемент может либо обработать запрос, либо передать его дальше по цепочке. Идеально подходит для создания middleware, систем авторизации, валидации данных и обработки HTTP-запросов.

Представьте ситуацию: у вас есть веб-сервер, который должен обрабатывать входящие запросы через несколько этапов — аутентификацию, авторизацию, логирование, кеширование. Вместо создания монолитного обработчика, Chain of Responsibility позволяет создать элегантную цепочку, где каждый обработчик отвечает только за свою задачу.

Как работает Chain of Responsibility

Паттерн Chain of Responsibility основан на простой идее: создать цепочку обработчиков, где каждый может либо обработать запрос полностью, либо передать его следующему звену. Это как конвейер на заводе — каждый рабочий выполняет свою операцию и передаёт заготовку дальше.

Основные компоненты паттерна:

  • Handler (Обработчик) — абстрактный класс или интерфейс, определяющий методы для обработки запросов
  • ConcreteHandler (Конкретный обработчик) — реализация обработчика для конкретного типа запросов
  • Client (Клиент) — отправляет запрос в цепочку обработчиков

Ключевое преимущество — слабая связанность между отправителем запроса и его обработчиками. Клиент не знает, какой именно обработчик выполнит запрос, а обработчики не знают о существовании друг друга напрямую.

Пошаговая реализация на Java

Давайте создадим практический пример системы обработки HTTP-запросов с аутентификацией, авторизацией и логированием:

// Абстрактный обработчик
public abstract class RequestHandler {
    private RequestHandler nextHandler;
    
    public void setNext(RequestHandler handler) {
        this.nextHandler = handler;
    }
    
    public void handleRequest(HttpRequest request) {
        if (canHandle(request)) {
            processRequest(request);
        }
        
        if (nextHandler != null) {
            nextHandler.handleRequest(request);
        }
    }
    
    protected abstract boolean canHandle(HttpRequest request);
    protected abstract void processRequest(HttpRequest request);
}

// Класс запроса
public class HttpRequest {
    private String path;
    private String method;
    private String authToken;
    private String userRole;
    private boolean authenticated = false;
    private boolean authorized = false;
    
    // Конструкторы, геттеры и сеттеры
    public HttpRequest(String path, String method, String authToken, String userRole) {
        this.path = path;
        this.method = method;
        this.authToken = authToken;
        this.userRole = userRole;
    }
    
    // Геттеры и сеттеры
    public String getPath() { return path; }
    public String getMethod() { return method; }
    public String getAuthToken() { return authToken; }
    public String getUserRole() { return userRole; }
    public boolean isAuthenticated() { return authenticated; }
    public void setAuthenticated(boolean authenticated) { this.authenticated = authenticated; }
    public boolean isAuthorized() { return authorized; }
    public void setAuthorized(boolean authorized) { this.authorized = authorized; }
}

Теперь создадим конкретные обработчики:

// Обработчик аутентификации
public class AuthenticationHandler extends RequestHandler {
    @Override
    protected boolean canHandle(HttpRequest request) {
        return request.getAuthToken() != null && !request.isAuthenticated();
    }
    
    @Override
    protected void processRequest(HttpRequest request) {
        // Имитация проверки токена
        if (isValidToken(request.getAuthToken())) {
            request.setAuthenticated(true);
            System.out.println("✓ Аутентификация прошла успешно для токена: " + request.getAuthToken());
        } else {
            System.out.println("✗ Аутентификация не удалась");
            throw new SecurityException("Invalid authentication token");
        }
    }
    
    private boolean isValidToken(String token) {
        // Простая проверка для примера
        return token.startsWith("Bearer ") && token.length() > 10;
    }
}

// Обработчик авторизации
public class AuthorizationHandler extends RequestHandler {
    @Override
    protected boolean canHandle(HttpRequest request) {
        return request.isAuthenticated() && !request.isAuthorized();
    }
    
    @Override
    protected void processRequest(HttpRequest request) {
        if (hasPermission(request.getUserRole(), request.getPath())) {
            request.setAuthorized(true);
            System.out.println("✓ Авторизация успешна для роли: " + request.getUserRole());
        } else {
            System.out.println("✗ Недостаточно прав для доступа к " + request.getPath());
            throw new SecurityException("Insufficient permissions");
        }
    }
    
    private boolean hasPermission(String role, String path) {
        // Простая проверка прав
        if ("admin".equals(role)) return true;
        if ("user".equals(role) && !path.startsWith("/admin")) return true;
        return false;
    }
}

// Обработчик логирования
public class LoggingHandler extends RequestHandler {
    @Override
    protected boolean canHandle(HttpRequest request) {
        return true; // Логируем все запросы
    }
    
    @Override
    protected void processRequest(HttpRequest request) {
        System.out.println("📝 LOG: " + request.getMethod() + " " + request.getPath() + 
                          " | Auth: " + request.isAuthenticated() + 
                          " | Authorized: " + request.isAuthorized());
    }
}

Теперь создадим основной класс для тестирования:

public class ChainOfResponsibilityDemo {
    public static void main(String[] args) {
        // Создаём цепочку обработчиков
        RequestHandler authHandler = new AuthenticationHandler();
        RequestHandler authzHandler = new AuthorizationHandler();
        RequestHandler logHandler = new LoggingHandler();
        
        // Настраиваем цепочку
        authHandler.setNext(authzHandler);
        authzHandler.setNext(logHandler);
        
        // Тестируем разные сценарии
        System.out.println("=== Тест 1: Успешный запрос администратора ===");
        testRequest(authHandler, "/admin/users", "GET", "Bearer admin_token_123", "admin");
        
        System.out.println("\n=== Тест 2: Обычный пользователь пытается получить доступ к админке ===");
        testRequest(authHandler, "/admin/settings", "POST", "Bearer user_token_456", "user");
        
        System.out.println("\n=== Тест 3: Неавторизованный запрос ===");
        testRequest(authHandler, "/public/info", "GET", "invalid_token", "guest");
    }
    
    private static void testRequest(RequestHandler handler, String path, String method, 
                                   String token, String role) {
        try {
            HttpRequest request = new HttpRequest(path, method, token, role);
            handler.handleRequest(request);
            System.out.println("✓ Запрос обработан успешно");
        } catch (SecurityException e) {
            System.out.println("✗ Ошибка безопасности: " + e.getMessage());
        } catch (Exception e) {
            System.out.println("✗ Общая ошибка: " + e.getMessage());
        }
    }
}

Практические кейсы и примеры использования

Chain of Responsibility особенно полезен в серверных приложениях. Вот несколько реальных сценариев:

Сценарий Обработчики в цепочке Преимущества Недостатки
Web middleware CORS → Auth → Rate Limiting → Logging Модульность, переиспользование Overhead при длинных цепочках
API Gateway Validation → Auth → Transform → Route Гибкость конфигурации Сложность отладки
Event Processing Filter → Enrich → Validate → Store Легко добавлять новые этапы Производительность при больших объёмах

Пример middleware для веб-сервера:

// Middleware для обработки CORS
public class CorsHandler extends RequestHandler {
    @Override
    protected boolean canHandle(HttpRequest request) {
        return "OPTIONS".equals(request.getMethod()) || 
               request.getPath().startsWith("/api/");
    }
    
    @Override
    protected void processRequest(HttpRequest request) {
        System.out.println("🌐 CORS: Добавлены заголовки для " + request.getPath());
        // Здесь бы добавлялись CORS заголовки
    }
}

// Rate Limiting
public class RateLimitHandler extends RequestHandler {
    private final Map requestCounts = new ConcurrentHashMap<>();
    private final int maxRequests = 100;
    
    @Override
    protected boolean canHandle(HttpRequest request) {
        return true;
    }
    
    @Override
    protected void processRequest(HttpRequest request) {
        String clientId = extractClientId(request);
        int currentCount = requestCounts.getOrDefault(clientId, 0);
        
        if (currentCount >= maxRequests) {
            throw new RuntimeException("Rate limit exceeded for " + clientId);
        }
        
        requestCounts.put(clientId, currentCount + 1);
        System.out.println("⏱️ Rate limit: " + (currentCount + 1) + "/" + maxRequests + " for " + clientId);
    }
    
    private String extractClientId(HttpRequest request) {
        // В реальном приложении это был бы IP или API key
        return request.getAuthToken() != null ? request.getAuthToken() : "anonymous";
    }
}

Команды для тестирования и развёртывания

Для тестирования приложения с Chain of Responsibility на сервере:

# Компиляция проекта
javac -cp . *.java

# Запуск основного класса
java ChainOfResponsibilityDemo

# Для Spring Boot приложения
mvn spring-boot:run

# Тестирование с помощью curl
curl -X GET http://localhost:8080/api/users \
  -H "Authorization: Bearer your_token_here" \
  -H "Content-Type: application/json"

# Мониторинг производительности
java -XX:+PrintGCDetails -Xloggc:gc.log -jar your-app.jar

# Профилирование с помощью JProfiler
java -agentpath:/opt/jprofiler/bin/linux-x64/libjprofilerti.so=port=8849 -jar your-app.jar

Для контейнеризации с Docker:

# Dockerfile
FROM openjdk:11-jre-slim
COPY target/chain-app.jar app.jar
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "/app.jar"]

# Сборка и запуск
docker build -t chain-app .
docker run -p 8080:8080 chain-app

# Docker Compose для тестирования
version: '3.8'
services:
  app:
    build: .
    ports:
      - "8080:8080"
    environment:
      - JAVA_OPTS=-Xmx512m
  redis:
    image: redis:alpine
    ports:
      - "6379:6379"

Интеграция с популярными фреймворками

Chain of Responsibility отлично работает с различными Java-фреймворками:

Spring Boot Filter Chain:

@Component
public class CustomAuthFilter implements Filter {
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, 
                        FilterChain chain) throws IOException, ServletException {
        HttpServletRequest httpRequest = (HttpServletRequest) request;
        
        // Аутентификация
        if (authenticate(httpRequest)) {
            chain.doFilter(request, response); // Передаём дальше по цепочке
        } else {
            ((HttpServletResponse) response).setStatus(HttpStatus.UNAUTHORIZED.value());
        }
    }
    
    private boolean authenticate(HttpServletRequest request) {
        String token = request.getHeader("Authorization");
        return token != null && token.startsWith("Bearer ");
    }
}

Netty Pipeline:

// Создание Netty pipeline с обработчиками
public class ServerInitializer extends ChannelInitializer {
    @Override
    protected void initChannel(SocketChannel ch) {
        ChannelPipeline pipeline = ch.pipeline();
        
        pipeline.addLast("decoder", new HttpRequestDecoder());
        pipeline.addLast("encoder", new HttpResponseEncoder());
        pipeline.addLast("auth", new AuthenticationHandler());
        pipeline.addLast("business", new BusinessLogicHandler());
    }
}

Продвинутые техники и оптимизации

Для высоконагруженных серверов важно оптимизировать цепочку обработчиков:

// Асинхронная обработка с CompletableFuture
public abstract class AsyncRequestHandler {
    private AsyncRequestHandler nextHandler;
    
    public void setNext(AsyncRequestHandler handler) {
        this.nextHandler = handler;
    }
    
    public CompletableFuture handleRequestAsync(HttpRequest request) {
        return CompletableFuture.runAsync(() -> {
            if (canHandle(request)) {
                processRequest(request);
            }
        }).thenCompose(v -> {
            if (nextHandler != null) {
                return nextHandler.handleRequestAsync(request);
            }
            return CompletableFuture.completedFuture(null);
        });
    }
    
    protected abstract boolean canHandle(HttpRequest request);
    protected abstract void processRequest(HttpRequest request);
}

// Обработчик с кешированием
public class CachedAuthHandler extends RequestHandler {
    private final Cache authCache = 
        CacheBuilder.newBuilder()
            .maximumSize(10000)
            .expireAfterWrite(5, TimeUnit.MINUTES)
            .build();
    
    @Override
    protected void processRequest(HttpRequest request) {
        String token = request.getAuthToken();
        Boolean cached = authCache.getIfPresent(token);
        
        if (cached != null) {
            request.setAuthenticated(cached);
            System.out.println("🚀 Использован кеш для токена: " + token);
            return;
        }
        
        boolean isValid = validateToken(token);
        authCache.put(token, isValid);
        request.setAuthenticated(isValid);
    }
    
    private boolean validateToken(String token) {
        // Реальная валидация токена
        return token.startsWith("Bearer ") && token.length() > 10;
    }
}

Альтернативные решения и сравнение

Сравним Chain of Responsibility с другими подходами:

Подход Гибкость Производительность Сложность Использование
Chain of Responsibility Высокая Средняя Низкая Middleware, валидация
Strategy Pattern Средняя Высокая Средняя Бизнес-логика
Command Pattern Средняя Высокая Средняя Операции, undo/redo
Observer Pattern Высокая Средняя Высокая События, уведомления

Популярные альтернативы и библиотеки:

  • Apache Camel — для интеграционных цепочек (https://camel.apache.org/)
  • Spring Integration — для enterprise integration patterns
  • Akka — для actor-based обработки сообщений
  • RxJava — для реактивных цепочек обработки

Метрики и мониторинг

Для production-среды важно мониторить производительность цепочки:

// Обработчик с метриками
public class MetricsHandler extends RequestHandler {
    private final Counter processedRequests = Counter.build()
        .name("processed_requests_total")
        .help("Total processed requests")
        .register();
    
    private final Histogram requestDuration = Histogram.build()
        .name("request_duration_seconds")
        .help("Request duration in seconds")
        .register();
    
    @Override
    protected void processRequest(HttpRequest request) {
        Timer.Sample sample = Timer.start();
        
        try {
            // Обработка запроса
            processedRequests.inc();
            System.out.println("📊 Обработан запрос: " + request.getPath());
        } finally {
            sample.stop(requestDuration);
        }
    }
    
    @Override
    protected boolean canHandle(HttpRequest request) {
        return true;
    }
}

Автоматизация и DevOps

Для автоматизации развёртывания приложений с Chain of Responsibility:

# Kubernetes deployment
apiVersion: apps/v1
kind: Deployment
metadata:
  name: chain-app
spec:
  replicas: 3
  selector:
    matchLabels:
      app: chain-app
  template:
    metadata:
      labels:
        app: chain-app
    spec:
      containers:
      - name: app
        image: chain-app:latest
        ports:
        - containerPort: 8080
        env:
        - name: JAVA_OPTS
          value: "-Xmx1g -XX:+UseG1GC"
        livenessProbe:
          httpGet:
            path: /health
            port: 8080
          initialDelaySeconds: 30
          periodSeconds: 10

# CI/CD pipeline (GitLab CI)
stages:
  - build
  - test
  - deploy

build:
  stage: build
  script:
    - mvn clean compile
    - docker build -t $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA .

test:
  stage: test
  script:
    - mvn test
    - java -jar target/chain-app.jar &
    - sleep 10
    - curl -f http://localhost:8080/health

deploy:
  stage: deploy
  script:
    - kubectl apply -f k8s/
    - kubectl set image deployment/chain-app app=$CI_REGISTRY_IMAGE:$CI_COMMIT_SHA

Интересные факты и нестандартные применения

Chain of Responsibility может использоваться не только для обработки HTTP-запросов:

  • Log Processing — цепочка для обработки логов: парсинг → фильтрация → агрегация → сохранение
  • Game Development — обработка игровых событий: коллизии → физика → звук → отрисовка
  • Data Pipeline — ETL процессы: извлечение → трансформация → валидация → загрузка
  • IoT Processing — обработка данных с датчиков: фильтрация → калибровка → анализ → действие

Интересный факт: паттерн Chain of Responsibility используется внутри многих популярных фреймворков — Spring Security, Servlet Filters, Netty Pipeline. Это один из самых “невидимых”, но при этом широко используемых паттернов.

Для работы с высоконагруженными системами рекомендую тестировать на мощных серверах. Можете арендовать VPS или выделенный сервер для полноценного тестирования под нагрузкой.

Заключение и рекомендации

Chain of Responsibility — это мощный паттерн для создания гибких и расширяемых систем обработки запросов. Он особенно полезен в серверных приложениях, где нужно обрабатывать запросы через несколько этапов.

Когда использовать:

  • Нужна гибкая система middleware для веб-приложений
  • Требуется обработка событий или сообщений через несколько этапов
  • Хотите разделить ответственность между различными компонентами
  • Планируете часто изменять или добавлять новые этапы обработки

Когда не использовать:

  • Простые задачи, где достаточно одного обработчика
  • Критично важна максимальная производительность
  • Цепочка может стать слишком длинной (>10 обработчиков)

Лучшие практики:

  • Держите обработчики независимыми и сфокусированными на одной задаче
  • Используйте кеширование для часто вызываемых обработчиков
  • Добавляйте метрики и логирование для мониторинга
  • Рассмотрите асинхронную обработку для I/O операций
  • Тестируйте цепочку как единое целое, а не отдельные обработчики

Chain of Responsibility поможет сделать ваш код более модульным, тестируемым и расширяемым. Это инвестиция в будущее вашего проекта, которая окупится при первом же изменении требований к обработке запросов.


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

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

Leave a reply

Your email address will not be published. Required fields are marked