AOP in the Sample Application

So far, you have seen lots of small examples of how to use the Spring AOP features, but as of yet, we have not looked at some practical uses of AOP in an application. Typical examples in this field are logging and security, and we have looked at these, albeit briefly, over the course of this chapter and the last. However, AOP is not just limited to use in logging and security, and it can be put to great use when you are implementing any application-specific logic that is crosscutting— that is, any logic in your application that needs to be called from a large number of separate components. In this section, we show you how we use Spring AOP in the sample SpringBlog application to solve a problem involving crosscutting logic.

Filtering Obscenities in SpringBlog

One of the problems we faced when building the SpringBlog application was how to filter obscenities uniformly out of postings on the blog. This includes top-level blog entries as well as any comments made about a particular entry. We needed to ensure that neither an entry nor a comment could be created containing obscenities, and that existing entries and comments could not be modified to contain obscenities. Specifically, we wanted the ability to obfuscate obscenities contained in postings automatically with non-offensive alternatives. Taking this further, we decided that in some cases, the blog owner might actually want to be able to add obscenities to their entries, acting as their own moderator, but want to restrict blog readers from posting comments containing obscenities.

The traditional approach to this problem would be to define an interface, ObscenityFilter, and then build an implementation of this interface and make it accessible through some factory class. Then in each method where an entry or comment is created or modified, you invoke the ObscenityFilter to remove obscenities from the posting. In the SpringBlog application, all business logic is exposed through the BlogManager interface. BlogManager exposes two methods, saveEntry() and saveComment(), both of which need to check for obscenities in their respective Entry and Comment objects.

There are two main problems with this. First, each implementation of BlogManager is going to have to remember to implement this check. You can reduce this burden slightly by creating a common base class for all BlogManager implementations, but nothing prevents a programmer from creating a BlogManager implementation directly and forgetting to check the ObscenityFilter. The second problem with this approach is that it relies on the same logic being present in two methods; this increases the maintenance burden and the chance of spurious errors creeping into the code.

Using Spring AOP, we can create a much more elegant solution to this problem by factoring the obscenity check into a before advice that we can apply to any method that accepts the Entry or Comment object as an argument. We could have opted to make this advice much more flexible, allowing it to explore any JavaBean for String-typed properties, but we had no use for this feature in the application; following the principles of YAGNI (You Aren't Gonna Need It), we decided to keep the implementation simple and to the point.

An interesting point about this implementation is that, for the most part, we just followed good OOP practice as suggested in the traditional approach. We defined an interface, ObscenityFilter, and then built an implementation. Thanks to Spring DI, we were able to avoid the need to create a factory class, but by following good practices, we were able to build an obscenity filter that can be used equally well in both AOP and non-AOP settings.

The BlogPosting Interface

Within the blog, there are two distinct types of postings: a main blog entry, represented by an Entry object, and a comment about an entry, represented by a Comment object. Although these two objects represent different kinds of posting, they do share similar characteristics, such as body, subject, attachments, and date of posting. For this reason, we created an interface, BlogPosting, that allows Comments and Entries to be manipulated at the same time. Because all the String-typed properties of Comment and Entry are exposed on the BlogPosting interface, we use the BlogPosting interface in our obscenity filter advice. Listing 7-29 shows the BlogPosting interface.

Listing 7-29: The BlogPosting Interface

image from book
package com.apress.prospring.domain;      import java.util.Date; import java.util.List;      public interface BlogPosting {          public List getAttachments();     public void setAttachments(List attachments);          public String getBody();     public void setBody(String body);          public Date getPostDate();     public void setPostDate(Date postDate);          public String getSubject();     public void setSubject(String subject); }
image from book

Implementing ObscenityFilter

For the SpringBlog application, we decided to create an implementation of ObscenityFilter that allows the list of obscenities to filter to be specified as a List and that replaces the obscenities using the ROT13 algorithm. Listing 7-30 shows the ObscenityFilter interface.

Listing 7-30: The ObscenityFilter Interface

image from book
package com.apress.prospring.business;      public interface ObscenityFilter {          public boolean containsObscenities(String data);          public String obfuscateObscenities(String data); } 
image from book

The basic usage of the ObscenityFilter interface involves a call to containsObscenities() to see if a particular String contains obscenities. If it does, then a call to obfuscateObscenities() takes the String containing obscenities and returns a String with them obfuscated. Our basic implementation of this interface is shown in Listing 7-31.

Listing 7-31: The ListBasedObscenityFilter Class

image from book
package com.apress.prospring.business;      import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern;      public class ListBasedObscenityFilter implements ObscenityFilter {          private List obscenities = null;          private Pattern obscenityPattern = null;          public void setObscenities(List obscenities) {         this.obscenities = obscenities;              buildRegex();     }          private void buildRegex() {         StringBuffer sb = new StringBuffer();              int size = obscenities.size();              for (int x = 0; x < size; x++) {             if (x != 0) {                 sb.append("|");             }             sb.append("(");             sb.append(obscenities.get(x));             sb.append(")");         }              obscenityPattern =                     Pattern.compile(sb.toString(), Pattern.CASE_INSENSITIVE);     }          public boolean containsObscenities(String data) {         Matcher m = obscenityPattern.matcher(data);         return m.find();     }          public String obfuscateObscenities(String data) {         Matcher m = obscenityPattern.matcher(data);         StringBuffer out = new StringBuffer(data.length());              while (m.find()) {             if (m.group(0) != null) {                 m.appendReplacement(out, rot13(m.group(0)));             }         }              return out.toString();     }          private String rot13(String in) {         char[] chars = in.toCharArray();              for (int x = 0; x < chars.length; x++) {             char c = chars[x];             if (c >= 'a' && c <= 'm')                 c += 13;             else if (c >= 'n' && c <= 'z')                 c -= 13;             else if (c >= 'A' && c <= 'M')                 c += 13;             else if (c >= 'A' && c <= 'Z') c -= 13;                  chars[x] = c;         }              return new String(chars);     } }
image from book

Here you can see that the ListBasedObscenityFilter class exposes the List-typed obscenities property, allowing the list of obscenities to filter to be specified externally. In particular, this allows the list of obscenities to be specified in the Spring configuration file. Once the list of obscenities is specified, we build a regular expression from the list, which allows the obscenity filter to match and replace obscenities easily.

The obfuscateObscenities() method uses the regular expression that we created previously to replace any obscenities in the supplied data with the ROT13 equivalent. The ROT13 algorithm is very basic, and essentially, it just rotates each letter in a String by 13 places. Listing 7-32 shows a basic JUnit test case for the ListBasedObscenityFilter that demonstrates typical usage.

Listing 7-32: Testing the ListBasedObscenityFilter

image from book
package com.apress.prospring.business;      import java.util.ArrayList; import java.util.List;      import junit.framework.TestCase;      public class ObscenityFilterTest extends TestCase {          public void testReplaceObscenity() {              ListBasedObscenityFilter filter = new ListBasedObscenityFilter();         List list = new ArrayList();         list.add("crap");         list.add("damn");         list.add("arse");         list.add("bugger");              filter.setObscenities(list);              String testData = "Crap! Kiss my arse, you damn bugger!";              assertTrue("Test data should contain obscenities", filter                 .containsObscenities(testData));              String val = filter.obfuscateObscenities(testData);              System.out.println(val);              assertTrue(val.indexOf("arse") == -1);         assertTrue(val.indexOf("Crap") == -1);         assertTrue(val.indexOf("damn") == -1);         assertTrue(val.indexOf("bugger") == -1);         assertTrue(val.indexOf("Kiss") > -1);     } }
image from book

As you can see, we specify a list of obscenities and then, using some test data containing these obscenities, we check to make sure the ObscenityFilter correctly identifies the obscenities and replaces them, without touching other words in the String. Running this test results in a pass and the following obfuscated message being written to stdout:

Penc! Kiss my nefr, you qnza ohttre

As you can see, the basic sentence structure is preserved, but the obscene words are replaced with nonsense.

Creating the ObscenityFilterAdvice

With the ListBasedObscenityFilter implementation finished, all that remains is creating an advice that allows obscenity filter capabilities to be applied declaratively to any method that accepts Entry or Comment arguments. The basis of the ObscenityFilterAdvice is to modify the arguments passed to the method so that any String properties of the Entry or Comment objects are replaced with their obfuscated alternatives. Because we only need to look at the arguments passed to a method and perhaps modify them, a before advice is ideal for this. Listing 7-33 shows the implementation of ObscenityFilterAdvice.

Listing 7-33: The ObscenityFilterAdvice Class

image from book
package com.apress.prospring.business.aop;      import java.lang.reflect.Method;      import org.springframework.aop.MethodBeforeAdvice;      import com.apress.prospring.business.ObscenityFilter; import com.apress.prospring.domain.BlogPosting;      public class ObscenityFilterAdvice implements MethodBeforeAdvice {          private ObscenityFilter filter;          public void setObscenityFilter(ObscenityFilter filter) {         this.filter = filter;     }          public void before(Method method, Object[] args, Object target)             throws Throwable {         for (int x = 0; x < args.length; x++) {             if (args[x] instanceof BlogPosting) {                 BlogPosting arg = (BlogPosting) args[x];                 if (filter.containsObscenities(arg.getBody())) {                     arg.setBody(filter.obfuscateObscenities(arg.getBody()));                 }                 if (filter.containsObscenities(arg.getSubject())) {                     arg.setSubject(filter                             .obfuscateObscenities(arg.getSubject()));                 }             }         }     } } 
image from book

Here you can see a basic before advice. We defined a single property, obscenityFilter, allowing the ObscenityFilter implementation to be defined externally. In the before() method, we check each argument to see if it is an instance of BlogPosting. If so, we test the body and subject properties, replacing them with obfuscated values as appropriate. Configuring this advice in the ApplicationContext is simple and is shown in Listing 7-34.

Listing 7-34: Configuring the ObscenityFilterAdvice

image from book

<bean                   >         <property name="obscenityFilter">             <bean >                 <property name="obscenities">                     <list>                         <value>crap</value>                     </list>                 </property>             </bean>         </property>     </bean>

image from book

As you can see, we define the obscenityFilterAdvisor bean with an anonymous inner bean for the ObscenityFilter. Notice that we are able to set the list of obscenities in the configuration. Using ProxyFactoryBean, we can advise any object in the ApplicationContext with this advice, but we do not show this here because we have special requirements for the BlogManager bean related to transaction processing. The configuration of the BlogManager proxy is discussed in more detail in Chapter 12.

Obscenity Filter Summary

As you can see from the example in this section, AOP has plenty of practical uses in a real application. We found that by using AOP for the obscenity filter, we were able to keep the code for the BlogManager implementations much cleaner and were also able to reduce the amount of code duplication within the application. When you build your own applications with Spring, it is worth it to take the time to identify crosscutting logic. Once you have done this, define the interfaces to interact with it, build the implementations, and then instead of using a factory and embedding calls against your interfaces throughout your code, use Spring AOP to weave the logic into your application transparently.



Pro Spring
Pro Spring
ISBN: 1590594614
EAN: 2147483647
Year: 2006
Pages: 189

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net