Следвайте веригата на отговорността

Наскоро преминах към Mac OS X от Windows и съм развълнуван от резултатите. Но след това прекарах само кратък петгодишен престой на Windows NT и XP; преди това бях строго разработчик на Unix в продължение на 15 години, най-вече на машини Sun Microsystems. Също така имах късмета да разработя софтуер под Nextstep, буйния Unix-базиран предшественик на Mac OS X, така че съм малко пристрастен.

Освен красивия си потребителски интерфейс Aqua, Mac OS X е Unix, може би най-добрата съществуваща операционна система. Unix има много страхотни функции; една от най-известните е тръбата , която ви позволява да създавате комбинации от команди, като извеждате изхода на една команда към входа на друга. Да предположим например, че искате да изброите изходни файлове от дистрибуцията на източници Struts, които извикват или дефинират име с име execute(). Ето един начин да направите това с тръба:

grep "изпълнение (" `намери $ STRUTS_SRC_DIR-име" * .java "` | awk -F: '{печат}'

В grepкоманда търси файлове за регулярни изрази; тук го използвам, за да намеря поява на низа execute(във файлове, открити от findкомандата. grepизходът се извежда в тръбопровода awk, който отпечатва първия жетон - разграничен с двоеточие - във всеки ред от grepизхода (вертикална лента означава тръба). Този маркер е име на файл, така че в крайна сметка получавам списък с имена на файлове, които съдържат низа execute(.

Сега, когато имам списък с имена на файлове, мога да използвам друга тръба, за да сортирам списъка:

grep "изпълнение (" `намери $ STRUTS_SRC_DIR -име" * .java "` | awk -F: '{печат}' | сортиране

Този път направих списъка с имената на файловете sort. Ами ако искате да знаете колко файлове съдържат низа execute(? Лесно е с друга тръба:

 grep "изпълнение (" `намери $ STRUTS_SRC_DIR -име" * .java "` | awk -F: '{печат}' | сортиране -u | wc -l 

В wcзаповедта се брои думи, линии и байтове. В този случай посочих -lопцията за броене на редове, по един ред за всеки файл. Също така добавих -uопция, за sortда осигуря уникалност за всяко име на файл ( -uопцията филтрира дубликати).

Тръбите са мощни, защото ви позволяват да съставяте динамично верига от операции. Софтуерните системи често използват еквивалент на тръби (например, имейл филтри или набор от филтри за сървлет). В основата на тръбите и филтрите лежи моделът на проектиране: Chain of Responsibility (CoR).

Забележка: Можете да изтеглите изходния код на тази статия от ресурси.

Въведение в КР

Моделът Chain of Responsibility използва верига от обекти за обработка на заявка, която обикновено е събитие. Обектите във веригата препращат заявката по веригата, докато един от обектите обработи събитието. Обработката спира след обработка на събитие.

Фигура 1 илюстрира как моделът на КР обработва заявките.

В Design Patterns авторите описват модела Chain of Responsibility по следния начин:

Избягвайте да свързвате подателя на заявка с нейния получател, като давате възможност на повече от един обект да обработи заявката. Верига на приемащите обекти и предавайте заявката по веригата, докато обектът я обработи.

Моделът „Верига на отговорността“ е приложим, ако:

  • Искате да отделите подателя и получателя на заявката
  • Множество обекти, определени по време на изпълнение, са кандидати за обработка на заявка
  • Не искате да посочвате изрично манипулатори във вашия код

Ако използвате модела на КР, не забравяйте:

  • Само един обект във веригата обработва заявка
  • Някои заявки може да не бъдат обработени

Тези ограничения, разбира се, са за класическо прилагане на КР. На практика тези правила са изкривени; например, сервлет филтрите са изпълнение на CoR, което позволява на множество филтри да обработват HTTP заявка.

Фигура 2 показва диаграма на класа на модела на КР.

Обикновено обработчиците на заявки са разширения на базовия клас, който поддържа препратка към следващия манипулатор във веригата, известен като successor. Базовият клас може да реализира handleRequest()по следния начин:

публичен абстрактен клас HandlerBase {... public void handleRequest (SomeRequestObject sro) {if (naslednik! = null) successor.handleRequest (sro); }}

Така че по подразбиране манипулаторите предават заявката на следващия манипулатор във веригата. Конкретно разширение на HandlerBaseможе да изглежда така:

публичен клас SpamFilter разширява HandlerBase {public void handleRequest (SomeRequestObject mailMessage) {if (isSpam (mailMessage)) {// Ако съобщението е спам // предприемете действия, свързани със спам. Не препращайте съобщение. } else {// Съобщението не е спам. super.handleRequest (mailMessage); // Предаваме съобщение на следващия филтър във веригата. }}}

На SpamFilterдръжките заявката (вероятно получаване на нов имейл), ако съобщението е спам, и поради това, искането не отива по-нататък; в противен случай надеждни съобщения се предават на следващия манипулатор, вероятно друг имейл филтър, който иска да ги премахне. В крайна сметка последният филтър във веригата може да съхранява съобщението, след като премине през събиране, като се премести през няколко филтъра.

Обърнете внимание, че обсъдените по-горе хипотетични имейл филтри се взаимно изключват: В крайна сметка само един филтър обработва заявка. Можете да изберете да обърнете това отвътре навън, като оставите множество филтри да обработват една заявка, което е по-добра аналогия с тръбите на Unix. Така или иначе, основният двигател е моделът на КР.

В тази статия обсъждам две реализации на модела Chain of Responsibility: сервлетни филтри, популярна реализация на КР, която позволява множество филтри да обработват заявка, и оригиналния модел на събитие на абстрактния прозорец (AWT), непопулярна класическа реализация на КР, която в крайна сметка беше оттеглена .

Сервлетни филтри

В ранните дни на платформата Java 2, Enterprise Edition (J2EE), някои контейнери за сървлети осигуряват удобна функция, известна като верига на сървлети, при която човек може по същество да приложи списък с филтри към сървлет. Сервлет филтрите са популярни, защото са полезни за сигурност, компресиране, регистриране и др. И, разбира се, можете да съставите верига от филтри, за да направите някои или всички тези неща в зависимост от условията на изпълнение.

С появата на Java Servlet Specification версия 2.3, филтрите се превърнаха в стандартни компоненти. За разлика от класическия CoR, сервлет филтрите позволяват на множество обекти (филтри) във верига да обработват заявка.

Сервлет филтрите са мощно допълнение към J2EE. Също така, от гледна точка на дизайнерските модели, те осигуряват интересен обрат: Ако искате да промените заявката или отговора, вие използвате модела на декоратор в допълнение към CoR. Фигура 3 показва как работят сервлет филтрите.

Прост филтър за сървлети

Трябва да направите три неща, за да филтрирате сървлета:

  • Внедрете сървлет
  • Внедрете филтър
  • Свържете филтъра и сървлета

Примери 1-3 изпълняват и трите последователни стъпки:

Пример 1. Сервлет

импортиране на java.io.PrintWriter; импортиране на javax.servlet. *; импортиране на javax.servlet.http. *; публичен клас FilteredServlet разширява HttpServlet {public void doGet (HttpServletRequest заявка, HttpServletResponse отговор) хвърля ServletException, java.io.IOException {PrintWriter out = response.getWriter (); out.println ("Извикан филтриран сървлет"); }}

Пример 2. Филтър

импортиране на java.io.PrintWriter; импортиране на javax.servlet. *; импортиране на javax.servlet.http.HttpServletRequest; публичният клас AuditFilter реализира Filter {private ServletContext app = null; публична невалидна init (FilterConfig config) {app = config.getServletContext (); } public void doFilter (ServletRequest заявка, ServletResponse отговор, FilterChain верига) хвърля java.io.IOException, javax.servlet.ServletException {app.log (((HttpServletRequest) заявка) .getServletPath ()); chain.doFilter (заявка, отговор); } публична невалидност унищожи () {}}

Пример 3. Дескриптор за разполагане

    auditFilter AuditFilter < filter-mapping > auditFilter / filteredServlet < / filter-mapping > filteredServlet FilteredServlet filteredServlet / filteredServlet ...  

Ако отворите сървлета с URL адреса /filteredServlet, той auditFilterполучава пукнатина при заявката преди сървлета. AuditFilter.doFilterзаписва в регистрационния файл на контейнера на сървлета и призовава chain.doFilter()да препрати заявката. За извикване не са необходими филтри за сървлети chain.doFilter(); ако не го направят, заявката не се препраща. Мога да добавя още филтри, които ще бъдат извикани в реда, в който са декларирани в предходния XML файл.

След като видяхте прост филтър, нека разгледаме друг филтър, който модифицира HTTP отговора.

Филтрирайте отговора с модела на декоратора

Unlike the preceding filter, some servlet filters need to modify the HTTP request or response. Interestingly enough, that task involves the Decorator pattern. I discussed the Decorator pattern in two previous Java Design Patterns articles: "Amaze Your Developer Friends with Design Patterns" and "Decorate Your Java Code."

Example 4 lists a filter that performs a simple search and replace in the body of the response. That filter decorates the servlet response and passes the decorator to the servlet. When the servlet finishes writing to the decorated response, the filter performs a search and replace within the response's content.

Example 4. A search and replace filter

import java.io.*; import javax.servlet.*; import javax.servlet.http.*; public class SearchAndReplaceFilter implements Filter { private FilterConfig config; public void init(FilterConfig config) { this.config = config; } public FilterConfig getFilterConfig() { return config; } public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws java.io.IOException, javax.servlet.ServletException { StringWrapper wrapper = new StringWrapper((HttpServletResponse)response); chain.doFilter(request, wrapper); String responseString = wrapper.toString(); String search = config.getInitParameter("search"); String replace = config.getInitParameter("replace"); if(search == null || replace == null) return; // Parameters not set properly int index = responseString.indexOf(search); if(index != -1) { String beforeReplace = responseString.substring(0, index); String afterReplace=responseString.substring(index + search.length()); response.getWriter().print(beforeReplace + replace + afterReplace); } } public void destroy() { config = null; } } 

The preceding filter looks for filter init parameters named search and replace; if they are defined, the filter replaces the first occurrence of the search parameter value with the replace parameter value.

SearchAndReplaceFilter.doFilter() wraps (or decorates) the response object with a wrapper (decorator) that stands in for the response. When SearchAndReplaceFilter.doFilter() calls chain.doFilter() to forward the request, it passes the wrapper instead of the original response. The request is forwarded to the servlet, which generates the response.

When chain.doFilter() returns, the servlet is done with the request, so I go to work. First, I check for the search and replace filter parameters; if present, I obtain the string associated with the response wrapper, which is the response content. Then I make the substitution and print it back to the response.

Example 5 lists the StringWrapper class.

Example 5. A decorator

import java.io.*; import javax.servlet.*; import javax.servlet.http.*; public class StringWrapper extends HttpServletResponseWrapper { StringWriter writer = new StringWriter(); public StringWrapper(HttpServletResponse response) { super(response); } public PrintWriter getWriter() { return new PrintWriter(writer); } public String toString() { return writer.toString(); } } 

StringWrapper, which decorates the HTTP response in Example 4, is an extension of HttpServletResponseWrapper, which spares us the drudgery of creating a decorator base class for decorating HTTP responses. HttpServletResponseWrapper ultimately implements the ServletResponse interface, so instances of HttpServletResponseWrapper can be passed to any method expecting a ServletResponse object. That's why SearchAndReplaceFilter.doFilter() can call chain.doFilter(request, wrapper) instead of chain.doFilter(request, response).

Сега, след като разполагаме с филтър и обвивка за отговор, нека свържем филтъра с шаблон на URL и да посочим шаблони за търсене и замяна: