Ikut Rantai Tanggungjawab

Saya baru-baru ini beralih ke Mac OS X dari Windows dan saya gembira dengan hasilnya. Tetapi sekali lagi, saya hanya menghabiskan masa lima tahun untuk Windows NT dan XP; sebelum itu saya adalah pembangun Unix selama 15 tahun, kebanyakannya menggunakan mesin Sun Microsystems. Saya juga cukup bernasib baik untuk membangunkan perisian di bawah Nextstep, pendahulu Unix yang berasaskan Mac OS X, jadi saya agak berat sebelah.

Selain dari antara muka pengguna Aqua yang indah, Mac OS X adalah Unix, boleh dikatakan sistem operasi terbaik yang ada. Unix mempunyai banyak ciri menarik; salah satu yang paling terkenal adalah paip , yang membolehkan anda membuat kombinasi perintah dengan memasukkan output satu perintah ke input yang lain. Sebagai contoh, andaikan anda mahu menyenaraikan fail sumber dari pengedaran sumber Struts yang memanggil atau menentukan kaedah yang dinamakan execute(). Inilah salah satu cara untuk melakukannya dengan paip:

grep "execute (" `cari $ STRUTS_SRC_DIR -name" * .java "` | awk -F: '{print}'

The greparahan mencari fail untuk ungkapan biasa; di sini, saya menggunakannya untuk mencari kejadian rentetan execute(dalam fail yang digali oleh findperintah. grepOutput disalurkan ke awk, yang mencetak token pertama - dibatasi oleh titik dua - di setiap baris grepoutput (bar menegak menandakan paip). Token itu adalah nama fail, jadi saya berakhir dengan senarai nama fail yang mengandungi rentetan execute(.

Sekarang saya mempunyai senarai nama fail, saya boleh menggunakan paip lain untuk menyusun senarai:

grep "execute (" `cari $ STRUTS_SRC_DIR -name" * .java "` | awk -F: '{print}' | urutkan

Kali ini, saya memasukkan senarai nama fail ke sort. Bagaimana jika anda ingin mengetahui berapa banyak fail yang mengandungi rentetan execute(? Sangat mudah dengan paip lain:

 grep "execute (" `cari $ STRUTS_SRC_DIR -name" * .java "` | awk -F: '{print}' | sort -u | wc -l 

The wcarahan mengira perkataan, baris dan bait. Dalam kes ini, saya menentukan -lpilihan untuk mengira baris, satu baris untuk setiap fail. Saya juga menambahkan -upilihan untuk sortmemastikan keunikan bagi setiap nama fail ( -upilihan menyaring pendua).

Paip kuat kerana mereka membolehkan anda menyusun rangkaian operasi secara dinamik. Sistem perisian sering menggunakan setara dengan paip (contohnya, penapis e-mel atau sekumpulan penapis untuk servlet). Di tengah paip dan penapis terdapat corak reka bentuk: Rantai Tanggungjawab (CoR).

Catatan: Anda boleh memuat turun kod sumber artikel ini dari Sumber.

Pengenalan CoR

Corak Rantai Tanggungjawab menggunakan rantai objek untuk menangani permintaan, yang biasanya merupakan peristiwa. Objek dalam rantai meneruskan permintaan di sepanjang rantai sehingga salah satu objek menangani acara. Pemprosesan berhenti setelah sesuatu acara dikendalikan.

Rajah 1 menggambarkan bagaimana corak CoR memproses permintaan.

Dalam Corak Reka Bentuk , penulis menerangkan corak Rantai Tanggungjawab seperti ini:

Elakkan menghubungkan pengirim permintaan dengan penerima dengan memberi lebih dari satu objek peluang untuk menangani permintaan tersebut. Rantai objek penerima dan lulus permintaan di sepanjang rantai sehingga objek menanganinya.

Corak Rantai Tanggungjawab berlaku sekiranya:

  • Anda mahu mencabut pengirim dan penerima permintaan
  • Pelbagai objek, ditentukan pada waktu berjalan, adalah calon untuk menangani permintaan
  • Anda tidak mahu menentukan pengendali secara eksplisit dalam kod anda

Sekiranya anda menggunakan corak CoR, ingat:

  • Hanya satu objek dalam rantai yang menangani permintaan
  • Beberapa permintaan mungkin tidak dapat ditangani

Sekatan itu, tentu saja, adalah untuk pelaksanaan CoR klasik. Dalam praktiknya, peraturan tersebut dibengkokkan; sebagai contoh, servlet filter adalah implementasi CoR yang membolehkan beberapa filter memproses permintaan HTTP.

Rajah 2 menunjukkan gambarajah kelas corak CoR.

Biasanya, Pengendali permintaan adalah sambungan daripada kelas asas yang mengekalkan sebutan mengenai pengendali seterusnya dalam rantaian, yang dikenali sebagai successor. Kelas asas mungkin dilaksanakan handleRequest()seperti ini:

kelas abstrak awam HandlerBase {... public void handleRequest (SomeRequestObject sro) {if (pengganti! = null) pengganti.handleRequest (sro); }}

Oleh itu, secara lalai, pengendali menyampaikan permintaan tersebut kepada pengendali seterusnya dalam rantai. Sambungan konkrit HandlerBasemungkin kelihatan seperti ini:

kelas awam SpamFilter meluaskan HandlerBase {public void handleRequest (SomeRequestObject mailMessage) {if (isSpam (mailMessage)) {// Sekiranya mesej itu spam // lakukan tindakan berkaitan spam. Jangan hantar mesej. } lain {// Mesej bukan spam. super.handleRequest (mailMessage); // Kirimkan mesej ke penapis seterusnya dalam rantai. }}}

Yang SpamFiltermengendalikan permintaan itu (mungkin penerimaan e-mel baru) jika mesej spam, dan oleh itu, permintaan itu pergi lagi; jika tidak, mesej yang dipercayai boleh dihantar ke pengendali seterusnya, mungkin penapis e-mel lain ingin menyingkirkannya. Akhirnya, penapis terakhir dalam rantai mungkin menyimpan mesej setelah melewati pengumpulan dengan bergerak melalui beberapa penapis.

Perhatikan penapis e-mel hipotetikal yang dibincangkan di atas adalah saling eksklusif: Pada akhirnya, hanya satu penapis yang menangani permintaan. Anda mungkin memilih untuk mengubahnya keluar dengan membiarkan beberapa penapis menangani satu permintaan, yang merupakan analogi yang lebih baik untuk paip Unix. Bagaimanapun, enjin yang mendasari adalah corak CoR.

Dalam artikel ini, saya membincangkan dua implementasi corak Rantai Tanggungjawab: saringan servlet, pelaksanaan CoR yang popular yang membolehkan banyak penapis menangani permintaan, dan model peristiwa Abstrak Alat Jendela Abstrak (AWT) asli, pelaksanaan CoR klasik yang tidak popular yang akhirnya tidak digunakan lagi .

Penapis servlet

Di Java 2 Platform, Edisi Perusahaan (J2EE) pada masa awal, beberapa wadah servlet menyediakan fitur yang berguna yang dikenali sebagai servlet chaining, di mana seseorang pada dasarnya dapat menerapkan daftar penapis pada servlet. Penapis servlet popular kerana ia berguna untuk keselamatan, pemampatan, pembalakan dan banyak lagi. Dan, tentu saja, anda boleh menyusun rangkaian penapis untuk melakukan beberapa atau semua perkara bergantung kepada keadaan waktu operasi.

Dengan munculnya Java Servlet Spesifikasi versi 2.3, penapis menjadi komponen standard. Tidak seperti CoR klasik, servlet filter membenarkan pelbagai objek (filter) dalam rantai untuk menangani permintaan.

Penapis servlet adalah tambahan yang kuat untuk J2EE. Juga, dari sudut corak reka bentuk, mereka memberikan kelainan yang menarik: Sekiranya anda ingin mengubah permintaan atau tindak balas, anda menggunakan corak Dekorasi sebagai tambahan kepada CoR. Rajah 3 menunjukkan bagaimana penapis servlet berfungsi.

Penapis servlet ringkas

Anda mesti melakukan tiga perkara untuk menapis servlet:

  • Laksanakan servlet
  • Laksanakan penapis
  • Kaitkan penapis dan servlet

Contoh 1-3 melakukan ketiga-tiga langkah berturut-turut:

Contoh 1. servlet

import java.io.PrintWriter; import javax.servlet. *; import javax.servlet.http. *; kelas awam FilteredServlet meluaskan HttpServlet {public void doGet (permintaan HttpServletRequest, respons HttpServletResponse) melemparkan ServletException, java.io.IOException {PrintWriter out = Respons.getWriter (); out.println ("Servlet Disaring dipanggil"); }}

Contoh 2. Penapis

import java.io.PrintWriter; import javax.servlet. *; import javax.servlet.http.HttpServletRequest; AuditFilter kelas awam melaksanakan Filter {private ServletContext app = null; init kekosongan awam (FilterConfig config) {app = config.getServletContext (); } public void doFilter (permintaan ServletRequest, respons ServletResponse, rantai FilterChain) melemparkan java.io.IOException, javax.servlet.ServletException {app.log ((((HttpServletRequest) permintaan) .getServletPath ()); chain.doFilter (permintaan, tindak balas); } kekosongan awam memusnahkan () {}}

Contoh 3. Penerangan penyebaran

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

Sekiranya anda mengakses servlet dengan URL /filteredServlet, auditFiltermendapat retakan pada permintaan sebelum servlet. AuditFilter.doFiltermenulis ke fail log kontena servlet dan panggilan chain.doFilter()untuk meneruskan permintaan. Penapis servlet tidak diperlukan untuk memanggil chain.doFilter(); jika tidak, permintaan itu tidak akan diteruskan. Saya dapat menambahkan lebih banyak penapis, yang akan dipanggil mengikut urutan yang dinyatakan dalam fail XML sebelumnya.

Sekarang setelah anda melihat penapis sederhana, mari kita lihat penapis lain yang mengubah tindak balas HTTP.

Tapis tindak balas dengan corak Penghias

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).

Sekarang kita mempunyai penapis dan pembungkus respons, mari kaitkan penapis dengan corak URL dan tentukan corak carian dan ganti: