Книга доктора Яна Хонга «JAVA и шаблоны» начинается с описания модели цепочки ответственности:
Паттерн цепочки ответственности — это шаблон поведения объекта. В шаблоне цепочки ответственности многие объекты соединяются в цепочку посредством ссылки каждого объекта на своего потомка. Запрос передается вверх по цепочке до тех пор, пока объект в цепочке не решит обработать запрос. Клиент, делающий запрос, не знает, какой объект в цепочке в конечном итоге обрабатывает запрос, что позволяет системе динамически реорганизовывать и распределять обязанности, не затрагивая клиента.
Начиная с барабанного боя и разбрасывания цветов.
Барабанье и передача цветов – это оживленная и напряженная застольная игра. Во время банкета гости садятся по порядку, и один человек играет на барабане. Место, где играют на барабане, и место, где переносят цветы, разделены в целях справедливости. Когда начинается барабанный бой, букеты начинают раздавать по кругу, и как только барабаны упадут, если букет окажется у кого-то в руке, этот человек должен выпить.
Например, Цзя Му, Цзя Ше, Цзя Чжэн, Цзя Баоюй и Цзя Хуан — пять разносчиков цветов, которые участвуют в игре с игрой на барабанах и передачей цветов. Они образуют цепочку. Барабанщик передал цветы Цзя Му и начал игру по передаче цветов. Цветок передался от Цзя Му к Цзя Шэ, от Цзя Шэ к Цзя Чжэну, от Цзя Чжэна к Цзя Баоюю, от Цзя Баоюю к Цзя Хуаню, от Цзя Хуаня к Цзя Му и так далее, как показано на рисунке ниже. Когда барабанный бой прекратится, человек с цветком в руке должен выполнить заказ на питье.
Бить в барабан и разбрасывать цветы — это применение модели цепочки ответственности. Цепочка ответственности может представлять собой прямую линию, цепочку или часть древовидной структуры.
Структура модели цепочки ответственности
Ниже используется простейшая реализация шаблона цепочки ответственности.
В модели цепочки ответственности задействованы следующие роли:
● Роль абстрактного обработчика (Handler): определяет интерфейс для обработки запросов. При необходимости интерфейс может определить метод для установки и возврата ссылки на следующий интерфейс. Эта роль обычно реализуется абстрактным классом Java или интерфейсом Java. Отношение агрегации класса Handler на рисунке выше дает ссылку конкретного подкласса на следующий. Абстрактный метод handleRequest() стандартизирует работу подкласса при обработке запросов.
● Роль конкретного обработчика (ConcreteHandler): после получения запроса конкретный обработчик может выбрать обработку запроса или передачу запроса следующей стороне. Поскольку конкретный процессор содержит ссылку на следующий дом, при необходимости он может получить доступ к следующему дому.
исходный код
роль абстрактного обработчика
Скопируйте код кода следующим образом:
публичный абстрактный класс Handler {
/**
* Содержит объекты ответственности преемника
*/
защищенный преемник обработчика;
/**
* Указывает метод обработки запроса, хотя этот метод не передает параметры
* Однако на самом деле параметры можно передавать. Вы можете выбрать, передавать ли параметры в соответствии с конкретными потребностями.
*/
публичный абстрактный void handleRequest();
/**
* Метод значения
*/
публичный обработчик getSuccessor() {
вернуть преемника;
}
/**
* Метод назначения для установки последующих объектов ответственности.
*/
public void setSuccessor (преемник обработчика) {
this.successor = преемник;
}
}
Особая роль процессора
Скопируйте код кода следующим образом:
общественный класс ConcreteHandler расширяет Handler {
/**
* Метод обработки, вызовите этот метод для обработки запроса.
*/
@Override
общественный недействительный handleRequest () {
/**
* Определить, существует ли преемник ответственного объекта
* Если есть, перенаправить запрос на последующий ответственный объект
* Если нет, обработайте запрос
*/
если (getSuccessor()! = ноль)
{
System.out.println("Отпустить запрос");
getSuccessor().handleRequest();
}еще
{
System.out.println("Обработка запроса");
}
}
}
Класс клиента
Скопируйте код кода следующим образом:
Клиент публичного класса {
public static void main(String[] args) {
//Собираем цепочку ответственности
Обработчик handler1 = новый ConcreteHandler();
Обработчик handler2 = новый ConcreteHandler();
обработчик1.setSuccessor(обработчик2);
//Отправить запрос
обработчик1.handleRequest();
}
}
Видно, что клиент создает два объекта-обработчика и указывает, что следующий объект-обработчик первого объекта-обработчика является вторым объектом-обработчиком, в то время как второй объект-обработчик не имеет следующего домашнего объекта. Затем клиент передает запрос первому объекту-обработчику.
Потому что логика передачи в этом примере очень проста: пока есть следующий владелец, он будет передан для обработки следующему владельцу, если следующего владельца нет, он будет обработан сам; Таким образом, после того, как первый объект-обработчик получит запрос, он передаст запрос второму объекту-обработчику. Поскольку второй объект-обработчик не имеет дома, он обрабатывает запрос самостоятельно. Схема последовательности действий представлена ниже.
Сценарии использования
Давайте рассмотрим такую функцию: подача заявки на управление расходами на званый ужин.
Многие компании имеют такого рода преимущество: команда проекта или отдел могут подать заявку на оплату некоторых расходов на званые обеды от компании, которые используются для организации званых обедов для членов проектной команды или сотрудников отдела.
Общий процесс подачи заявки на расходы на званый обед обычно следующий: заявитель сначала заполняет форму заявки, а затем передает ее руководителю на утверждение. Если заявка одобрена, руководитель уведомляет заявителя о том, что одобрение получено. а затем заявитель пойдет в финансовый отдел, чтобы получить плату. Если он не будет одобрен, руководитель будет уведомлен о том, что одобрение не было получено, и дело будет закрыто.
Руководители разных уровней имеют разные лимиты одобрения. Например, руководитель проекта может утверждать заявки с лимитом в 500 юаней; руководители отделов могут утверждать заявки с лимитом в 1000 юаней, а генеральный менеджер может утверждать заявки на любую сумму;
Другими словами, когда кто-то делает запрос на расходы на званый обед, запрос будет соответствующим образом обработан одним из менеджеров проекта, менеджеров отделов и генеральных менеджеров, но человек, сделавший запрос, не знает, что произойдет в конце. Кто будет обрабатывать его запрос? Как правило, заявитель подает заявку менеджеру проекта, и, возможно, генеральный менеджер в конечном итоге обрабатывает его запрос.
Вы можете использовать модель цепочки ответственности для реализации вышеуказанной функции: когда кто-то делает запрос на расходы на званый ужин, запрос будет передан в цепочку обработки руководства, например, менеджер проекта -> менеджер отдела -> генеральный директор. тот, кто сделал запрос, не знает, кто будет обрабатывать его запрос, каждый руководитель будет решать, обрабатывать ли запрос или передать его руководителю более высокого уровня, исходя из своего круга обязанностей. передача окончена.
Необходимо изолировать обработку каждого лидера и реализовать ее в отдельном объекте обработки ответственности, а затем предоставить им общий абстрактный родительский объект ответственности, чтобы цепочку ответственности можно было динамически объединять на клиенте для достижения различных функциональных требований. .
исходный код
Класс роли абстрактного обработчика
Скопируйте код кода следующим образом:
публичный абстрактный класс Handler {
/**
* Удерживает объект, который обрабатывает следующий запрос
*/
защищенный преемник обработчика = null;
/**
* Метод значения
*/
публичный обработчик getSuccessor() {
вернуть преемника;
}
/**
* Установите следующий объект для обработки запроса
*/
public void setSuccessor (преемник обработчика) {
this.successor = преемник;
}
/**
* Обработка заявок на праздничные расходы
* @param пользователь-заявитель
* @param Fee Заявленная сумма денег
* @return Специальное уведомление об успехе или неудаче.
*/
публичная абстрактная строка handleFeeRequest (пользователь строки, двойная плата);
}
Особая роль процессора
Скопируйте код кода следующим образом:
публичный класс ProjectManager расширяет Handler {
@Override
public String handleFeeRequest (пользователь String, двойная плата) {
Строка ул = "";
//Полномочия руководителя проекта сравнительно невелики и могут находиться в пределах 500
если (комиссия < 500)
{
//Ради тестирования будьте проще и соглашайтесь только на просьбу Чжан Саня.
if("Чжан Сан".equals(пользователь))
{
str = "Успех: Менеджер проекта соглашается оплатить [" + пользователю + "] плату за званый ужин, сумма равна " + плата + "юань";
}еще
{
//Никто больше не согласен
str = "Ошибка: менеджер проекта не согласен с комиссией [" + user + "] за званый обед, сумма равна " + комиссия + "юань";
}
}еще
{
//Если оно превышает 500, продолжайте передавать его на обработку людям более высокого уровня.
если (getSuccessor()! = ноль)
{
return getSuccessor().handleFeeRequest(пользователь, плата);
}
}
вернуть ул;
}
}
Скопируйте код кода следующим образом:
публичный класс DeptManager расширяет Handler {
@Override
public String handleFeeRequest (пользователь String, двойная плата) {
Строка ул = "";
//Авторитет руководителя отдела может быть не более 1000
если (комиссия < 1000)
{
//Ради тестирования будьте проще и соглашайтесь только на просьбу Чжан Саня.
if("Чжан Сан".equals(пользователь))
{
str = "Успех: руководитель отдела соглашается оплатить [" + пользователю + "] плату за званый обед, сумма равна " + плата + "юань";
}еще
{
//Никто больше не согласен
str = "Ошибка: руководитель отдела не согласен с комиссией [" + user + "] за званый ужин, сумма равна " + комиссия + "юань";
}
}еще
{
//Если оно превышает 1000, продолжаем передавать его на обработку людям более высокого уровня.
если (getSuccessor()! = ноль)
{
return getSuccessor().handleFeeRequest(пользователь, плата);
}
}
вернуть ул;
}
}
Скопируйте код кода следующим образом:
общедоступный класс GeneralManager расширяет Handler {
@Override
public String handleFeeRequest (пользователь String, двойная плата) {
Строка ул = "";
//Генеральный менеджер имеет большие полномочия, пока сюда поступает запрос, он может его обработать.
если (комиссия >= 1000)
{
//Ради тестирования будьте проще и соглашайтесь только на просьбу Чжан Саня.
if("Чжан Сан".equals(пользователь))
{
str = "Успех: Генеральный менеджер соглашается оплатить расходы [" + пользователя + "] на ужин, сумма равна " + комиссия + "юань";
}еще
{
//Никто больше не согласен
str = "Ошибка: генеральный менеджер не согласен с комиссией [" + пользователя + "] за званый обед, сумма равна " + комиссия + "юань";
}
}еще
{
//Если есть объекты последующей обработки, продолжаем их передавать
если (getSuccessor()! = ноль)
{
return getSuccessor().handleFeeRequest(пользователь, плата);
}
}
вернуть ул;
}
}
Класс клиента
Скопируйте код кода следующим образом:
Клиент публичного класса {
public static void main(String[] args) {
//Сначала соберите цепочку ответственности
Обработчик h1 = новый GeneralManager();
Обработчик h2 = новый DeptManager();
Обработчик h3 = новый ProjectManager();
h3.setSuccessor(h2);
h2.setSuccessor(h1);
//Начинаем тестирование
String test1 = h3.handleFeeRequest("Чжан Сан", 300);
System.out.println("test1 = " + test1);
String test2 = h3.handleFeeRequest("李思", 300);
System.out.println("test2 = " + test2);
System.out.println("---------------------------------------");
String test3 = h3.handleFeeRequest("Чжан Сан", 700);
System.out.println("test3 = " + test3);
String test4 = h3.handleFeeRequest("李思", 700);
System.out.println("test4 = " + test4);
System.out.println("---------------------------------------");
String test5 = h3.handleFeeRequest("Чжан Сан", 1500);
System.out.println("test5 = " + test5);
String test6 = h3.handleFeeRequest("李思", 1500);
System.out.println("test6 = " + test6);
}
}
Результаты бега следующие:
Чистые и нечистые модели цепочки ответственности
Модель чистой цепочки ответственности требует, чтобы конкретный объект процессора мог выбрать только одно из двух действий: одно — взять на себя ответственность, но передать ответственность следующей стороне. Определенному объекту процессора не разрешается передавать ответственность после принятия на себя некоторых обязанностей.
В чистой модели цепочки ответственности запрос должен быть получен объектом-обработчиком; в нечистой модели цепочки ответственности запрос не может быть получен ни одним объектом-получателем.
Реальные примеры чистой модели цепочки ответственности найти трудно, и обычно встречающиеся примеры представляют собой реализации нечистой модели цепочки ответственности. Некоторые люди думают, что нечистая цепочка ответственности — это вообще не модель цепочки ответственности, и это может иметь смысл. Но в реальных системах сложно найти чистую цепочку ответственности. Если вы настаиваете на том, что цепочка ответственности нечиста, что это не модель цепочки ответственности, то модель цепочки ответственности не будет иметь особого смысла.
Применение модели цепочки ответственности в Tomcat
Как мы все знаем, фильтр в Tomcat использует модель цепочки ответственности. Помимо создания соответствующих конфигураций в файле web.xml, создание фильтра также требует реализации интерфейса javax.servlet.Filter.
Скопируйте код кода следующим образом:
публичный класс TestFilter реализует Filter{
public void doFilter (запрос ServletRequest, ответ ServletResponse,
Цепочка FilterChain) выдает IOException, ServletException {
Chain.doFilter(запрос, ответ);
}
общественный недействительный уничтожить () {
}
public void init(FilterConfig filterConfig) выдает ServletException {
}
}
Результаты, наблюдаемые в режиме DEBUG, следующие:
Фактически, прежде чем фактически выполнить класс TestFilter, он пройдет через множество внутренних классов Tomcat. Кстати, настройка контейнера Tomcat также представляет собой модель цепочки ответственности. Обратите внимание на классы, обведенные красным прямоугольником. От Engine к хосту, к контексту и к Wrapper, запросы передаются через цепочку. В месте, обведенном зеленым прямоугольником, находится класс ApplicationFilterChain. Класс ApplicationFilterChain играет роль абстрактного процессора, а каждый фильтр выполняет определенную роль процессора.
Первый вопрос: где ApplicationFilterChain хранит все фильтры?
Ответ хранится в массиве объектов ApplicationFilterConfig в классе ApplicationFilterChain.
Скопируйте код кода следующим образом:
/**
*Фильтры.
*/
частные фильтры ApplicationFilterConfig[] =
новый ApplicationFilterConfig[0];
Так что же такое объект ApplicationFilterConfig?
ApplicationFilterConfig — это контейнер фильтра. Ниже приведено объявление класса ApplicationFilterConfig:
Скопируйте код кода следующим образом:
/**
* Реализация <code>javax.servlet.FilterConfig</code>, полезная в
* управление экземплярами фильтров, создаваемыми при работе веб-приложения.
* запускается впервые.
*
* @author Крейг Р. МакКланахан
* @version $Id: ApplicationFilterConfig.java 1201569 2011-11-14 01:36:07Z kkolinko $
*/
ApplicationFilterConfig автоматически создается при первом запуске веб-приложения. Он считывает настроенную информацию фильтра из файла web.xml веб-приложения, а затем загружает ее в контейнер.
Я только что увидел, что длина массива ApplicationFilterConfig, созданного в классе ApplicationFilterChain, равна нулю. Когда он был переназначен?
Скопируйте код кода следующим образом:
частные фильтры ApplicationFilterConfig[] =
новый ApplicationFilterConfig[0];
Это происходит при вызове метода addFilter() класса ApplicationFilterChain.
Скопируйте код кода следующим образом:
/**
* Целое число, которое дает текущее количество фильтров в цепочке.
*/
частный интервал n = 0;
public static Final int INCREMENT = 10;
Скопируйте код кода следующим образом:
void addFilter (ApplicationFilterConfig filterConfig) {
// Предотвращаем добавление одного и того же фильтра несколько раз
for (фильтр ApplicationFilterConfig: фильтры)
если (фильтр == FilterConfig)
возвращаться;
if (n == filter.length) {
ApplicationFilterConfig[] newFilters =
новый ApplicationFilterConfig[n + INCREMENT];
System.arraycopy(filters, 0, newFilters, 0, n);
фильтры = новыеФильтры;
}
фильтры[n++] = filterConfig;
}
Переменная n используется для записи количества фильтров в текущей цепочке фильтров. По умолчанию n равно 0, а длина массива объектов ApplicationFilterConfig также равна 0, поэтому при вызове метода addFilter(). в первый раз, если (n == filter .length) истинно и длина массива ApplicationFilterConfig изменена. Затем filter[n++] = filterConfig; поместите переменную filterConfig в массив ApplicationFilterConfig и добавьте 1 к количеству фильтров в текущей цепочке фильтров.
Так где же вызывается метод addFilter() класса ApplicationFilterChain?
Он находится в методе createFilterChain() класса ApplicationFilterFactory.
Скопируйте код кода следующим образом:
общедоступный ApplicationFilterChain createFilterChain
(запрос ServletRequest, оболочка-оболочка, сервлет сервлета) {
// получаем тип диспетчера
Диспетчер DispatcherType = null;
если (request.getAttribute(DISPATCHER_TYPE_ATTR)!= null) {
диспетчер = (DispatcherType) request.getAttribute(DISPATCHER_TYPE_ATTR);
}
Строка requestPath = null;
Атрибут объекта = request.getAttribute(DISPATCHER_REQUEST_PATH_ATTR);
если (атрибут!= ноль){
RequestPath = Attribute.toString();
}
// Если нет сервлета для выполнения, возвращаем ноль
если (сервлет == ноль)
возврат (ноль);
логическая комета = ложь;
// Создаём и инициализируем объект цепочки фильтров
ApplicationFilterChain filterChain = null;
если (запрос экземпляра запроса) {
Запрос req = (Запрос) запрос;
комета = req.isComet();
если (Globals.IS_SECURITY_ENABLED) {
// Безопасность: не перерабатывать
filterChain = новый ApplicationFilterChain ();
если (комета) {
req.setFilterChain(filterChain);
}
} еще {
filterChain = (ApplicationFilterChain) req.getFilterChain();
если (filterChain == null) {
filterChain = новый ApplicationFilterChain ();
req.setFilterChain(filterChain);
}
}
} еще {
//Диспетчер запросов используется
filterChain = новый ApplicationFilterChain ();
}
filterChain.setServlet(сервлет);
filterChain.setSupport
(((StandardWrapper)обертка).getInstanceSupport());
// Получите сопоставления фильтров для этого контекста
Контекст StandardContext = (StandardContext) Wrapper.getParent();
FilterMap filterMaps[] = context.findFilterMaps();
// Если сопоставлений фильтров нет, мы закончили
if ((filterMaps == null) || (filterMaps.length == 0))
возврат (filterChain);
// Получите информацию, которая нам понадобится для сопоставления фильтров
Строка имя_сервлета = оболочка.getName();
// Добавляем соответствующие фильтры с сопоставлением путей в эту цепочку фильтров
for (int i = 0; i <filterMaps.length; i++) {
if (!matchDispatcher(filterMaps[i] ,dispatcher)) {
продолжать;
}
if (!matchFiltersURL(filterMaps[i], requestPath))
продолжать;
ApplicationFilterConfig filterConfig = (ApplicationFilterConfig)
context.findFilterConfig(filterMaps[i].getFilterName());
если (filterConfig == null) {
// FIXME - проблема с настройкой журнала
продолжать;
}
логическое значение isCometFilter = false;
если (комета) {
пытаться {
isCometFilter = filterConfig.getFilter() экземпляр CometFilter;
} catch (Исключение е) {
// Примечание. Улов try присутствует, потому что getFilter имеет много
// объявленные исключения Однако фильтру выделяется много.
// ранее
Throwable t = ExceptionUtils.unwrapInvocateTargetException(e);
ExceptionUtils.handleThrowable(т);
}
если (isCometFilter) {
filterChain.addFilter(filterConfig);
}
} еще {
filterChain.addFilter(filterConfig);
}
}
// Добавляем фильтры, соответствующие второму имени сервлета
for (int i = 0; i <filterMaps.length; i++) {
if (!matchDispatcher(filterMaps[i] ,dispatcher)) {
продолжать;
}
if (!matchFiltersServlet(filterMaps[i], servletName))
продолжать;
ApplicationFilterConfig filterConfig = (ApplicationFilterConfig)
context.findFilterConfig(filterMaps[i].getFilterName());
если (filterConfig == null) {
// FIXME - проблема с настройкой журнала
продолжать;
}
логическое значение isCometFilter = false;
если (комета) {
пытаться {
isCometFilter = filterConfig.getFilter() экземпляр CometFilter;
} catch (Исключение е) {
// Примечание. Улов try присутствует, потому что getFilter имеет много
// объявленные исключения Однако фильтру выделяется много.
// ранее
}
если (isCometFilter) {
filterChain.addFilter(filterConfig);
}
} еще {
filterChain.addFilter(filterConfig);
}
}
// Возвращаем завершенную цепочку фильтров
возврат (filterChain);
}
Приведенный выше код можно разделить на два раздела: первый раздел перед строкой 51 и второй раздел после строки 51.
Основная цель первого пункта — создать объект ApplicationFilterChain и задать некоторые параметры.
Основная цель второго абзаца — получить всю информацию о фильтре из контекста, а затем использовать цикл for для обхода и вызова filterChain.addFilter(filterConfig); поместите filterConfig в массив ApplicationFilterConfig объекта ApplicationFilterChain.
Так где же вызывается метод createFilterChain() класса ApplicationFilterFactory?
Он вызывается в методе вызова() класса StandardWrapperValue.
Поскольку метод вызова() длинный, многие места опущены.
Скопируйте код кода следующим образом:
публичный окончательный недействительный вызов (запрос, ответ)
выдает IOException, ServletException {
...Пропустить промежуточный код // Создаем цепочку фильтров для этого запроса
Фабрика ApplicationFilterFactory =
ApplicationFilterFactory.getInstance();
ApplicationFilterChain filterChain =
Factory.createFilterChain(запрос, оболочка, сервлет);
...опустить промежуточный код
filterChain.doFilter(request.getRequest(), response.getResponse());
...опустить промежуточный код
}
Обычный процесс должен быть таким:
Вызовите метод createFilterChain() класса ApplicationFilterChai в методе ignore() класса StandardWrapperValue ---> вызовите метод addFilter() класса ApplicationFilterChain в методе createFilterChain() класса ApplicationFilterChai ---> вызовите метод createFilterChain() класса ApplicationFilterChai. Метод addFilter() класса ApplicationFilterChain Присвойте значение массиву ApplicationFilterConfig.
Согласно приведенному выше коду видно, что метод вызова() класса StandardWrapperValue продолжит выполнять метод doFilter() класса ApplicationFilterChain после выполнения метода createFilterChain(), а затем метод InternalDoFilter() будет вызывается в методе doFilter().
Ниже приведена часть кода метода InternalDoFilter().
Скопируйте код кода следующим образом:
// Вызов следующего фильтра, если он есть
если (pos < n) {
//Получаем следующий фильтр и перемещаем указатель на одну позицию вниз
//pos он определяет, какой фильтр выполняет текущая ApplicationFilterChain (текущая цепочка фильтров)
ApplicationFilterConfig filterConfig = фильтры[pos++];
Фильтр фильтр = ноль;
пытаться {
//Получаем экземпляр указанного в данный момент фильтра
фильтр = filterConfig.getFilter();
support.fireInstanceEvent(InstanceEvent.BEFORE_FILTER_EVENT,
фильтр, запрос, ответ);
if (request.isAsyncSupported() && "false".equalsIgnoreCase(
filterConfig.getFilterDef().getAsyncSupported())) {
request.setAttribute(Globals.ASYNC_SUPPORTED_ATTR,
Логическое значение.ЛОЖЬ);
}
если (Globals.IS_SECURITY_ENABLED) {
окончательный запрос ServletRequest = запрос;
окончательный ответ ServletResponse = ответ;
Главный директор =
((HttpServletRequest) req).getUserPrincipal();
Object[] args = новый Object[]{req, res, this};
SecurityUtil.doAsPrivilege
(«doFilter», фильтр, classType, args, принципал);
} еще {
//Вызов метода doFilter() фильтра
filter.doFilter(запрос, ответ, это);
}
Здесь filter.doFilter(request, response, this); вызывает метод doFilter() в TestFilter, который мы создали ранее. Метод doFilter() в TestFilter продолжит вызывать метод Chain.doFilter(request, response); и эта цепочка на самом деле является ApplicationFilterChain, поэтому процесс вызова возвращается к вызову dofilter и вызову метода InternalDoFilter, описанного выше, и это выполнение продолжается до тех пор, пока фильтр внутри не выполнит их все.
Если определены два фильтра, результаты отладки будут следующими: