Προγραμματισμός

Ακολουθήστε την αλυσίδα ευθύνης

Πρόσφατα άλλαξα στο Mac OS X από τα Windows και είμαι ενθουσιασμένος με τα αποτελέσματα. Αλλά και πάλι, πέρασα μια σύντομη πενταετή περίοδο στα Windows NT και XP. πριν από αυτό ήμουν αυστηρά προγραμματιστής της Unix για 15 χρόνια, κυρίως σε μηχανήματα της Sun Microsystems. Ήμουν επίσης αρκετά τυχερός για την ανάπτυξη λογισμικού στο Nextstep, τον πλούσιο προκάτοχο που βασίζεται στο Unix στο Mac OS X, οπότε είμαι λίγο προκατειλημμένος

Εκτός από την όμορφη διεπαφή χρήστη του Aqua, το Mac OS X είναι το Unix, αναμφισβήτητα το καλύτερο λειτουργικό σύστημα που υπάρχει. Το Unix έχει πολλά δροσερά χαρακτηριστικά. ένα από τα πιο γνωστά είναι το σωλήνας, που σας επιτρέπει να δημιουργήσετε συνδυασμούς εντολών διοχετεύοντας την έξοδο μιας εντολής στην είσοδο μιας άλλης. Για παράδειγμα, ας υποθέσουμε ότι θέλετε να παραθέσετε αρχεία προέλευσης από τη διανομή πηγής Struts που επικαλούνται ή καθορίζουν μια μέθοδο που ονομάζεται εκτέλεση(). Εδώ είναι ένας τρόπος να το κάνετε αυτό με έναν σωλήνα:

 grep "execute (" `βρείτε $ STRUTS_SRC_DIR -name" * .java "" | awk -F: "{print}" 

ο grep Η εντολή αναζητά αρχεία για κανονικές εκφράσεις. εδώ, το χρησιμοποιώ για να βρω εμφανίσεις της συμβολοσειράς εκτέλεση( σε αρχεία που ανακαλύφθηκαν από το εύρημα εντολή. grepΗ έξοδος έχει διοχετευτεί αχ, που εκτυπώνει το πρώτο διακριτικό - οριοθετείται από άνω και κάτω τελεία - σε κάθε γραμμή του grepΗ έξοδος (μια κατακόρυφη ράβδος σημαίνει ένα σωλήνα). Αυτό το διακριτικό είναι ένα όνομα αρχείου, οπότε καταλήγω σε μια λίστα με ονόματα αρχείων που περιέχουν τη συμβολοσειρά εκτέλεση(.

Τώρα που έχω μια λίστα ονομάτων αρχείων, μπορώ να χρησιμοποιήσω έναν άλλο σωλήνα για να ταξινομήσω τη λίστα:

 grep "execute (" `εύρεση $ STRUTS_SRC_DIR -name" * .java "" | awk -F: '{print}' | είδος

Αυτή τη φορά, έχω διοχετεύσει τη λίστα των ονομάτων αρχείων είδος. Τι γίνεται αν θέλετε να μάθετε πόσα αρχεία περιέχουν τη συμβολοσειρά εκτέλεση(; Είναι εύκολο με έναν άλλο σωλήνα:

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

ο τουαλέτα Η εντολή μετράει λέξεις, γραμμές και byte. Σε αυτήν την περίπτωση, έχω καθορίσει το -μεγάλο επιλογή για μέτρηση γραμμών, μία γραμμή για κάθε αρχείο. Πρόσθεσα επίσης ένα επιλογή για είδος για να διασφαλιστεί η μοναδικότητα για κάθε όνομα αρχείου (το η επιλογή φιλτράρει τα διπλά).

Οι σωλήνες είναι ισχυροί επειδή σας επιτρέπουν να συνθέσετε δυναμικά μια αλυσίδα λειτουργιών. Τα συστήματα λογισμικού χρησιμοποιούν συχνά το ισοδύναμο σωλήνων (π.χ. φίλτρα email ή ένα σύνολο φίλτρων για servlet). Στην καρδιά των σωλήνων και των φίλτρων βρίσκεται ένα σχέδιο σχεδίασης: Chain of Responsibility (CoR).

Σημείωση: Μπορείτε να κατεβάσετε τον πηγαίο κώδικα αυτού του άρθρου από τους πόρους.

Εισαγωγή στην ΕτΠ

Το μοτίβο Αλυσίδα Ευθύνης χρησιμοποιεί μια αλυσίδα αντικειμένων για να χειριστεί ένα αίτημα, το οποίο είναι συνήθως ένα συμβάν. Τα αντικείμενα στην αλυσίδα προωθούν το αίτημα κατά μήκος της αλυσίδας έως ότου ένα από τα αντικείμενα χειριστεί το συμβάν. Η επεξεργασία σταματά μετά το χειρισμό ενός συμβάντος.

Το σχήμα 1 απεικονίζει τον τρόπο με τον οποίο το μοτίβο CoR επεξεργάζεται αιτήματα.

Σε Σχεδιαστικά πρότυπα, οι συγγραφείς περιγράφουν το μοτίβο Αλυσίδα Ευθύνης ως εξής:

Αποφύγετε να συνδέσετε τον αποστολέα ενός αιτήματος με τον παραλήπτη του δίνοντας σε περισσότερα από ένα αντικείμενα την ευκαιρία να χειριστεί το αίτημα. Αλυσοδέστε τα αντικείμενα λήψης και περάστε το αίτημα κατά μήκος της αλυσίδας μέχρι να το χειριστεί ένα αντικείμενο.

Το μοτίβο αλυσίδας ευθύνης ισχύει εάν:

  • Θέλετε να αποσυνδέσετε τον αποστολέα και τον παραλήπτη ενός αιτήματος
  • Πολλά αντικείμενα, που καθορίζονται κατά το χρόνο εκτέλεσης, είναι υποψήφιοι για να χειριστούν ένα αίτημα
  • Δεν θέλετε να καθορίσετε ρητά τους χειριστές στον κώδικά σας

Εάν χρησιμοποιείτε το μοτίβο CoR, θυμηθείτε:

  • Μόνο ένα αντικείμενο στην αλυσίδα χειρίζεται ένα αίτημα
  • Ορισμένα αιτήματα ενδέχεται να μην αντιμετωπιστούν

Αυτοί οι περιορισμοί, φυσικά, είναι για μια κλασική εφαρμογή της ΕτΠ. Στην πράξη, αυτοί οι κανόνες είναι λυγισμένοι. Για παράδειγμα, τα φίλτρα servlet είναι μια εφαρμογή CoR που επιτρέπει σε πολλά φίλτρα να επεξεργάζονται ένα αίτημα HTTP.

Το σχήμα 2 δείχνει ένα διάγραμμα κλάσης μοτίβου CoR.

Συνήθως, οι χειριστές αιτήσεων είναι επεκτάσεις μιας βασικής κλάσης που διατηρεί μια αναφορά στον επόμενο χειριστή στην αλυσίδα, γνωστή ως διάδοχος. Η βασική τάξη μπορεί να εφαρμοστεί handleRequest () σαν αυτό:

 δημόσια αφηρημένη κλάση HandlerBase {... public void handleRequest (SomeRequestObject sro) {if (διαδόχος! = null) διαδόχος.handleRequest (sro); }} 

Έτσι, από προεπιλογή, οι χειριστές μεταβιβάζουν το αίτημα στον επόμενο χειριστή της αλυσίδας. Μια συγκεκριμένη επέκταση του Βάση χειριστή μπορεί να μοιάζει με αυτό:

 δημόσια τάξη SpamFilter επεκτείνει το HandlerBase {public void handleRequest (SomeRequestObject mailMessage) {if (isSpam (mailMessage)) {// Εάν το μήνυμα είναι ανεπιθύμητο // πραγματοποιήστε ενέργειες που σχετίζονται με ανεπιθύμητα μηνύματα. Μην προωθείτε το μήνυμα. } αλλιώς {// Το μήνυμα δεν είναι ανεπιθύμητο. super.handleRequest (mailMessage); // Διαβιβάστε το μήνυμα στο επόμενο φίλτρο της αλυσίδας. }}} 

ο Φίλτρο ανεπιθύμητων χειρίζεται το αίτημα (κατά πάσα πιθανότητα παραλαβή νέου μηνύματος ηλεκτρονικού ταχυδρομείου) εάν το μήνυμα είναι ανεπιθύμητο και, ως εκ τούτου, το αίτημα δεν προχωρά περαιτέρω. Διαφορετικά, τα αξιόπιστα μηνύματα μεταβιβάζονται στον επόμενο χειριστή, πιθανώς ένα άλλο φίλτρο email που θέλει να τα αποβάλει. Τελικά, το τελευταίο φίλτρο στην αλυσίδα μπορεί να αποθηκεύσει το μήνυμα αφού περάσει από τη μετακίνηση μέσω πολλών φίλτρων.

Λάβετε υπόψη ότι τα υποθετικά φίλτρα email που συζητήθηκαν παραπάνω είναι αμοιβαία αποκλειστικά: Τελικά, μόνο ένα φίλτρο χειρίζεται ένα αίτημα. Μπορείτε να επιλέξετε να το αλλάξετε έξω αφήνοντας πολλαπλά φίλτρα να χειριστούν ένα μόνο αίτημα, το οποίο είναι μια καλύτερη αναλογία με τους σωλήνες Unix. Σε κάθε περίπτωση, ο υποκείμενος κινητήρας είναι το μοτίβο CoR.

Σε αυτό το άρθρο, συζητώ δύο υλοποιήσεις μοτίβου Chain of Responsibility: servlet filter, μια δημοφιλής εφαρμογή CoR που επιτρέπει σε πολλά φίλτρα να χειριστούν ένα αίτημα και το αρχικό μοντέλο συμβάντος Abstract Window Toolkit (AWT), μια μη δημοφιλή κλασική εφαρμογή CoR που τελικά καταργήθηκε .

Φίλτρα Servlet

Στις πρώτες μέρες της Java 2 Platform, Enterprise Edition (J2EE), ορισμένα δοχεία servlet παρείχαν ένα εύχρηστο χαρακτηριστικό γνωστό ως servlet chaining, όπου κάποιος θα μπορούσε ουσιαστικά να εφαρμόσει μια λίστα φίλτρων σε servlet. Τα φίλτρα Servlet είναι δημοφιλή επειδή είναι χρήσιμα για ασφάλεια, συμπίεση, καταγραφή και άλλα. Και, φυσικά, μπορείτε να συνθέσετε μια αλυσίδα φίλτρων για να κάνετε μερικά ή όλα αυτά τα πράγματα ανάλογα με τις συνθήκες εκτέλεσης.

Με την έλευση του Java Servlet Specification έκδοση 2.3, τα φίλτρα έγιναν στάνταρ στοιχεία. Σε αντίθεση με το κλασικό CoR, τα servlet φίλτρα επιτρέπουν σε πολλά αντικείμενα (φίλτρα) σε μια αλυσίδα να χειρίζονται ένα αίτημα.

Τα φίλτρα Servlet είναι μια ισχυρή προσθήκη στο J2EE. Επίσης, από τη σκοπιά των σχεδιαστικών μοτίβων, παρέχουν μια ενδιαφέρουσα συστροφή: Εάν θέλετε να τροποποιήσετε το αίτημα ή την απόκριση, χρησιμοποιείτε το μοτίβο Διακοσμητή εκτός από την ΕτΠ. Το σχήμα 3 δείχνει τον τρόπο λειτουργίας των φίλτρων servlet.

Ένα απλό φίλτρο servlet

Πρέπει να κάνετε τρία πράγματα για να φιλτράρετε ένα servlet:

  • Εφαρμόστε ένα servlet
  • Εφαρμόστε ένα φίλτρο
  • Συνδέστε το φίλτρο και το servlet

Τα παραδείγματα 1-3 εκτελούν και τα τρία βήματα διαδοχικά:

Παράδειγμα 1. Ένα servlet

εισαγωγή java.io.PrintWriter; εισαγωγή javax.servlet. *; εισαγωγή javax.servlet.http. *; Η δημόσια κλάση FilteredServlet επεκτείνει το HttpServlet {public void doGet (HttpServletRequest request, HttpServletResponse response) ρίχνει το ServletException, java.io.IOException {PrintWriter out = response.getWriter (); out.println ("Φιλτραρισμένο Servlet κλήθηκε"); }} 

Παράδειγμα 2. Ένα φίλτρο

εισαγωγή java.io.PrintWriter; εισαγωγή javax.servlet. *; εισαγωγή javax.servlet.http.HttpServletRequest; δημόσια κλάση AuditFilter εφαρμόζει το φίλτρο {private ServletContext app = null; public void init (FilterConfig config) {app = config.getServletContext (); } δημόσιο κενό doFilter(ServletRequest request, ServletResponse απόκριση, FilterChain chain) ρίχνει το java.io.IOException, javax.servlet.ServletException {app.log ((((HttpServletRequest) αίτημα) .getServletPath ()); chain.doFilter(απαιτώ απάντηση); } καταστροφή δημόσιου κενού () {}} 

Παράδειγμα 3. Ο περιγραφέας ανάπτυξης

    auditFilter AuditFilter <χαρτογράφηση φίλτρων>auditFilter/ filteredServlet</ χαρτογράφηση φίλτρων> filteredServlet FilteredServlet filteredServlet / filteredServlet ... 

Εάν έχετε πρόσβαση στο servlet με τη διεύθυνση URL / filteredServlet, ο auditFilter παίρνει μια ρωγμή στο αίτημα πριν από το servlet. AuditFilter.doFilter γράφει στο αρχείο καταγραφής servlet container και στις κλήσεις chain.doFilter () για να προωθήσετε το αίτημα. Δεν απαιτούνται φίλτρα Servlet chain.doFilter (); Αν δεν το κάνουν, το αίτημα δεν προωθείται. Μπορώ να προσθέσω περισσότερα φίλτρα, τα οποία θα επικαλούνται με τη σειρά που δηλώνονται στο προηγούμενο αρχείο XML.

Τώρα που έχετε δει ένα απλό φίλτρο, ας δούμε ένα άλλο φίλτρο που τροποποιεί την απόκριση HTTP.

Φιλτράρετε την απόκριση με το μοτίβο Διακοσμητή

Σε αντίθεση με το προηγούμενο φίλτρο, ορισμένα φίλτρα servlet πρέπει να τροποποιήσουν το αίτημα ή την απόκριση HTTP. Είναι αρκετά ενδιαφέρον ότι αυτή η εργασία περιλαμβάνει το σχέδιο του Διακοσμητή. Συζήτησα το μοτίβο του Διακοσμητή σε δύο προηγούμενα Μοτίβα σχεδίασης Java άρθρα: "Καταπλήξτε τους φίλους σας προγραμματιστή με μοτίβα σχεδίασης" και "Διακοσμήστε τον κώδικα Java".

Το Παράδειγμα 4 παραθέτει ένα φίλτρο που εκτελεί μια απλή αναζήτηση και αντικαθιστά στο σώμα της απόκρισης. Αυτό το φίλτρο διακοσμεί την απόκριση servlet και περνά τον διακοσμητή στον servlet. Όταν το servlet ολοκληρώσει την εγγραφή στη διακοσμημένη απόκριση, το φίλτρο εκτελεί αναζήτηση και αντικατάσταση εντός του περιεχομένου της απόκρισης.

Παράδειγμα 4. Ένα φίλτρο αναζήτησης και αντικατάστασης

εισαγωγή java.io. *; εισαγωγή javax.servlet. *; εισαγωγή javax.servlet.http. *; δημόσια τάξη SearchAndReplaceFilter υλοποιεί το φίλτρο {private FilterConfig config; public void init (FilterConfig config) {this.config = config; } δημόσιο FilterConfig getFilterConfig () {return config; } public void doFilter (ServletRequest request, ServletResponse response, FilterChain chain) ρίχνει το java.io.IOException, javax.servlet.ServletException {StringWrapper wrapper = νέο StringWrapper((HttpServletResponse) απάντηση); chain.doFilter(αίτηση, περικάλυμμα); Απόκριση συμβολοσειράς String = wrapper.toString(); String search = config.getInitParameter ("αναζήτηση"); String Repl = config.getInitParameter ("Αντικατάσταση"); αν (αναζήτηση == null || αντικατάσταση == null) επιστροφή; // Οι παράμετροι δεν έχουν ρυθμιστεί σωστά int index = responseString.indexOf (search); if (index! = -1) {String beforeReplace = ResponsString.substring (0, ευρετήριο); String afterReplace = ResponsString.substring (index + search.length ()); response.getWriter (). εκτύπωση(BeforeReplace + αντικατάσταση + afterReplace); }} δημόσια άκυρη καταστροφή () {config = null; }} 

Το προηγούμενο φίλτρο αναζητά τις παραμέτρους init του φίλτρου που ονομάζονται Αναζήτηση και αντικαθιστώ; Εάν έχουν οριστεί, το φίλτρο αντικαθιστά την πρώτη εμφάνιση του Αναζήτηση τιμή παραμέτρου με το αντικαθιστώ τιμή παραμέτρου.

SearchAndReplaceFilter.doFilter () τυλίγει (ή διακοσμεί) το αντικείμενο απόκρισης με ένα περιτύλιγμα (διακοσμητής) που αντιπροσωπεύει την απόκριση. Πότε SearchAndReplaceFilter.doFilter () κλήσεις chain.doFilter () Για να προωθήσετε το αίτημα, περνά το περιτύλιγμα αντί της αρχικής απόκρισης. Το αίτημα προωθείται στο servlet, το οποίο δημιουργεί την απόκριση.

Πότε chain.doFilter () επιστρέφει, ο servlet γίνεται με το αίτημα, οπότε πηγαίνω στη δουλειά. Πρώτα, έχω ελέγξει για το Αναζήτηση και αντικαθιστώ παράμετροι φίλτρου εάν υπάρχει, λαμβάνω τη συμβολοσειρά που σχετίζεται με το περιτύλιγμα απόκρισης, το οποίο είναι το περιεχόμενο απόκρισης. Στη συνέχεια, κάνω την αντικατάσταση και την εκτυπώνω πίσω στην απάντηση

Το Παράδειγμα 5 παραθέτει το StringWrapper τάξη.

Παράδειγμα 5. Ένας διακοσμητής

εισαγωγή java.io. *; εισαγωγή javax.servlet. *; εισαγωγή javax.servlet.http. *; δημόσια τάξη StringWrapper επεκτείνει το HttpServletResponseWrapper {StringWriter writer = νέο StringWriter (); δημόσιο StringWrapper (HttpServletResponse απόκριση) {super (απόκριση); } δημόσιο PrintWriter getWriter () {επιστροφή νέου PrintWriter (συγγραφέας); } δημόσια συμβολοσειρά toString () {return writer.toString (); }} 

StringWrapper, που διακοσμεί την απόκριση HTTP στο Παράδειγμα 4, είναι μια επέκταση του HttpServletResponseWrapper, που μας απαλλάσσει από τη δουλειά της δημιουργίας μιας βασικής τάξης διακοσμητή για τη διακόσμηση απαντήσεων HTTP. HttpServletResponseWrapper τελικά εφαρμόζει το ServletResponse διεπαφή, έτσι εμφανίσεις του HttpServletResponseWrapper μπορεί να περάσει σε οποιαδήποτε μέθοδο αναμένοντας ένα ServletResponse αντικείμενο. Να γιατί SearchAndReplaceFilter.doFilter () μπορεί να καλέσει chain.doFilter (αίτημα, περικάλυμμα) αντί chain.doFilter (αίτημα, απάντηση).

Τώρα που έχουμε ένα φίλτρο και ένα περιτύλιγμα απόκρισης, ας συνδέσουμε το φίλτρο με ένα μοτίβο διεύθυνσης URL και να καθορίσουμε τα μοτίβα αναζήτησης και αντικατάστασης: