Advisors and Pointcuts in Spring

Thus far, all the examples you have seen have used the ProxyFactory.addAdvice() method to configure advice for a proxy. As we mentioned earlier, this method delegates to addAdvisor() behind the scenes, creating an instance of DefaultPointcutAdvisor and configuring it with a pointcut that points to all methods. In this way, the advice is deemed to apply to all methods on the target. In some cases, such as when you are using AOP for logging purposes, this may be desirable, but in other cases you may want to limit the methods to which an advice applies.

Of course, you could simply check in advice itself that the method that is being advised is the correct one, but this approach has several drawbacks. First, hard coding the list of acceptable methods into the advice reduces the advice's reusability. By using pointcuts, you can configure the methods to which an advice applies, without needing to put this code inside the advice; this clearly increases the reuse value of the advice. The second and third drawbacks with hard coding the list of methods into the advice are performance related. To check the method being advised in the advice, you need to perform the check each time any method on the target is invoked. This clearly reduces the performance of your application. When you use pointcuts, the check is performed once for each method and the results are cached for later use. The other performance-related drawback of not using pointcuts to restrict the list-advised methods is that Spring can make optimizations for non-advised methods when creating a proxy, which results in faster invocations on non-advised methods. These optimizations are covered in greater detail when we discuss proxies later in the chapter.

We would strongly recommend that you avoid the temptation to hard code method checks into your advice and instead use pointcuts wherever possible to govern the applicability of advice to methods on the target. That said, in some cases it is necessary to hard code the checks into your advice. Consider the earlier example of the after returning advice designed to catch weak keys generated by the KeyGenerator class. This kind of advice is closely coupled to the class it is advising and it is wise to check inside the advice to ensure that it is applied to the incorrect type. We refer to this coupling between advice and target as target affinity. In general, you should use pointcuts when your advice has little or no target affinity—that is, it can apply to any type or a wide range of types. When your advice has strong target affinity, try to check that the advice is being used correctly in the advice itself; this helps reduce head-scratching errors when an advice is misused. We also recommend that you avoid advising methods needlessly. As you will see, this results in a noticeable drop in invocation speed that can have a large impact on the overall performance of your application.

The Pointcut Interface

Pointcuts in Spring are created by implementing the Pointcut interface, as shown in Listing 6-19.

Listing 6-19: The Pointcut Interface

image from book
public interface Pointcut {          ClassFilter getClassFilter ();          MethodMatcher getMethodMatcher(); }
image from book

As you can see from this code, the Pointcut interface defines two methods, getClassFilter() and getMethodMatcher(), which return instances of ClassFilter and MethodMatcher, respectively. When creating your own pointcuts from scratch, you must implement both the ClassFilter and MethodMatcher interfaces as well. Thankfully, as you will see in the next section, this is usually unnecessary because Spring provides a selection of Pointcut implementations that cover almost if not all of your use cases.

When determining whether a Pointcut applies to a particular method, Spring first checks to see if the Pointcut applies to the method's class using the ClassFilter instance returned by Pointcut.getClassFilter(). Listing 6-20 shows the ClassFilter interface.

Listing 6-20: The ClassFilter Interface

image from book
public interface ClassFilter {          boolean matches(Class clazz); }
image from book

As you can see, the ClassFilter interface defines a single method, matches(), that is passed an instance of Class that represents the class to be checked. As you have no doubt determined, the matches() method returns true if the pointcut applies to the class and false otherwise.

The MethodMatcher interface is more complex than the ClassFilter interface, as is shown in Listing 6-21.

Listing 6-21: The MethodMatcher Interface

image from book
public interface MethodMatcher {          boolean matches(Method m, Class targetClass);          boolean isRuntime();          boolean matches(Method m, Class targetClass, Object[] args);      } 
image from book

Spring supports two different types of MethodMatcher, static and dynamic, determined by the return value of isRuntime(). Before using a MethodMatcher, Spring calls isRuntime() to determine whether the MethodMatcher is static, indicated by a return value of false, or dynamic, indicated by a return value of true.

For a static pointcut, Spring calls the matches(Method, Class) method of the MethodMatcher once for every method on the target, caching the return value for subsequent invocations of those methods. In this way, the check for method applicability is performed only once for each method and subsequent invocations of a method do not result in an invocation of matches().

With dynamic pointcuts, Spring still performs a static check using matches(Method, Class) the first time a method is invoked to determine the overall applicability of a method. However, in addition to this and provided that the static check returned true, Spring performs a further check for each invocation of a method using the matches(Method, Class, Object[]) method. In this way, a dynamic MethodMatcher can determine whether a pointcut should apply based on a particular invocation of a method, not just on the method itself.

Clearly, static pointcuts—that is, pointcuts whose MethodMatcher is static—perform much better than dynamic pointcuts because they avoid the need for an additional check per invocation. That said, dynamic pointcuts provide a greater level of flexibility for deciding whether to apply an advice. In general, we recommend that you use static pointcuts wherever you can. However, in cases where your advice adds substantial overhead, it may be wise to avoid any unnecessary invocations of your advice by using a dynamic pointcut.

In general, you rarely create your own Pointcut implementations from scratch because Spring provides abstract base classes for both static and dynamic pointcuts. We look at these base classes, along with other Pointcut implementations, over the next few sections.

Available Pointcut Implementations

Spring provides seven implementations of the Pointcut interface: two abstract classes intended as convenience classes for creating static and dynamic pointcuts; and five concrete classes, one for composing multiple pointcuts together, one for handling control flow pointcuts, one for performing simple name-based matching, and two for defining pointcuts using regular expressions. These implementations are summarized in Table 6-2.

Table 6-2: Summary of Spring Pointcut Implementations

Implementation Class

Description

org.springframework.aop
.support.ComposablePointcut

The ComposablePointcut class is used to compose two or more pointcuts together with operations such as union() and intersection(). This class is covered in more detail in the next

org.springframework.aop
.support.ControlFlowPointcut

The ControlFlowPointcut is a special case pointcut that matches all methods within the control flow of another method—that is, any method that is invoked either directly or indirectly as the result of another method being invoked. We cover ControlFlowPointcut in more detail in the next chapter.

org.springframework.aop
.support .DynamicMethodMatcherPointcut

The DynamicMethodMatcherPointcut is intended as a base class for building dynamic pointcuts.

org.springframework.aop
.support .JdkRegexpMethodPointcut

The JdkRexepMethodPointcut allows you to define pointcuts using JDK 1.4 regular expression support. This class requires JDK 1.4 or higher.

org.springframework.aop
.support .NameMatchMethodPointcut

Using the NameMatchMethodPointcut, you can create a pointcut that performs simple matching against a list of method names.

org.springframework.aop
.support .Perl5RegexpMethodPointcut

The Perl5RegexpMethodPointcut allows you to define pointcuts using Perl 5 regular expression syntax. This class depends on the Jakarta ORO project and can be used on JDKs older than 1.4.

org.springframework.aop
.StaticMethodMatcherPointcut

The StaticMethodMatcherPointcut class is intended as a base for building static pointcuts.

We cover the five basic implementations in detail in the following sections. We leave discussions of the more advanced ComposablePointcut and ControlFlowPointcut classes until the next chapter.

Using DefaultPointcutAdvisor

Before you can use any Pointcut implementation, you must first create an Advisor, or more specifically a PointcutAdvisor. Remember from our earlier discussions that an Advisor is Spring's representation of an aspect, a coupling of advice and pointcuts that governs which methods should be advised and how they should be advised. Spring provides four implementations of PointcutAdvisor, but for now we concern ourselves we just one— DefaultPointcutAdvisor. DefaultPointcutAdvisor is a simple PointcutAdvisor for associating a single Pointcut with a single Advice.

Creating a Static Pointcut Using StaticMethodMatcherPointcut

In this section, we we will create a simple static pointcut using the StaticMethodMatcherPointcut class as a base. StaticMethodMatcherPointcut requires you to implement only a single method, matches(Method, Class); the rest of the Pointcut implementation is handled automatically. Although this is the only method you are required to implement, you may also want to override the getClassFilter() method as we do in this example to ensure that only methods of the correct type get advised.

For this example, we have two classes, BeanOne and BeanTwo, with identical methods defined in both. Listing 6-22 shows the BeanOne class.

Listing 6-22: The BeanOne Class

image from book
package com.apress.prospring.ch6.staticpc;      public class BeanOne {          public void foo() {         System.out.println("foo");     }          public void bar() {         System.out.println("bar");     } }
image from book

The BeanTwo class has identical methods to BeanOne. With this example, we want to be able to create a proxy of both classes using the same DefaultPointcutAdvisor, but have the advice apply to only the foo() method of the BeanOne class. To do this, we created the SimpleStaticPointcut class, as shown in Listing 6-23.

Listing 6-23: The SimpleStaticPointcut Class

image from book
package com.apress.prospring.ch6.staticpc;      import java.lang.reflect.Method;      import org.springframework.aop.ClassFilter; import org.springframework.aop.support.StaticMethodMatcherPointcut;      public class SimpleStaticPointcut extends StaticMethodMatcherPointcut {          public boolean matches(Method method, Class cls) {         return ("foo".equals(method.getName()));     }          public ClassFilter getClassFilter() {         return new ClassFilter() {             public boolean matches(Class cls) {                 return (cls == BeanOne.class);             }         };          } } 
image from book

Here you can see that we implemented the matches(Method, Class) method as required by the StaticMethodMatcher base class. The implementation simply returns true if the name of the method is foo, otherwise it returns false. Notice that we have also overridden the getClassFilter() method to return a ClassFilter instance whose matches() method only returns true for the BeanOne class. With this static pointcut, we are saying that only methods of the BeanOne class will be matched, and furthermore, only the foo() method of that class will be matched.

Listing 6-24 shows the SimpleAdvice class that simply writes out a message on either side of the method invocation.

Listing 6-24: The SimpleAdvice Class

image from book
package com.apress.prospring.ch6.staticpc;      import org.aopalliance.intercept.MethodInterceptor; import org.aopalliance.intercept.MethodInvocation;      public class SimpleAdvice implements MethodInterceptor {          public Object invoke(MethodInvocation invocation) throws Throwable {         System.out.println(">> Invoking " + invocation.getMethod().getName());         Object retVal = invocation.proceed();         System.out.println(">> Done");         return retVal;     } }
image from book

In Listing 6-25, you can see a simple driver application for this example that creates an instance of DefaultPointcutAdvisor using the SimpleAdvice and SimpleStaticPointcut classes.

Listing 6-25: The StaticPointcutExample Class

image from book
package com.apress.prospring.ch6.staticpc;      import org.aopalliance.aop.Advice; import org.springframework.aop.Advisor; import org.springframework.aop.Pointcut; import org.springframework.aop.framework.ProxyFactory; import org.springframework.aop.support.DefaultPointcutAdvisor;      public class StaticPointcutExample {          public static void main(String[] args) {         BeanOne one = new BeanOne();         BeanTwo two = new BeanTwo();                  BeanOne proxyOne;         BeanTwo proxyTwo;              // create pointcut, advice and advisor         Pointcut pc = new SimpleStaticPointcut();         Advice advice = new SimpleAdvice();         Advisor advisor = new DefaultPointcutAdvisor(pc, advice);                  // create BeanOne proxy         ProxyFactory pf = new ProxyFactory();         pf.addAdvisor(advisor);         pf.setTarget(one);         proxyOne = (BeanOne)pf.getProxy();                  // create BeanTwo proxy         pf = new ProxyFactory();         pf.addAdvisor(advisor);         pf.setTarget(two);         proxyTwo = (BeanTwo)pf.getProxy();                  proxyOne.foo();         proxyTwo.foo();                  proxyOne.bar();         proxyTwo.bar();              } }
image from book

Notice that the DefaultPointcutAdvisor instance is then used to create two proxies: one for an instance of BeanOne and one for an instance of BeanTwo. Finally, both the foo() and bar() methods are invoked on the two proxies.

Running this example results in the following output:

>> Invoking foo foo >> Done foo bar

As you can see, the only method for which the SimpleAdvice was actually invoked was the foo() method for the BeanOne class, exactly as expected. Restricting the methods that an advice applies is quite simple and, as you will see when we discuss the different proxy options, is key to getting the best performance out of your application.

Creating a Dynamic Pointcut Using DyanmicMethodMatcherPointcut

As we will demonstrate in this section, creating a dynamic pointcut is not much different from creating a static one. For this example, we create a dynamic pointcut for the class shown in Listing 6-26.

Listing 6-26: The SampleBean Class

image from book
package com.apress.prospring.ch6.dynamicpc;1      public class SampleBean {          public void foo(int x) {         System.out.println("Invoked foo() with: "  +x);     }          public void bar() {          System.out.println("Invoked bar()");     } }
image from book

For this example, we want to advise only the foo() method, but unlike the previous example, we want to advise this method only if the int argument passed to it is greater or less than 100.

As with static pointcuts, Spring provides a convenience base class for creating dynamic pointcuts—DynamicMethodMatcherPointcut. The DynamicMethodMatcherPointcut class has a single abstract method, matches(Method, Class, Object[]), that you must implement, but as you will see, it is also prudent to implement the matches(Method, Class) method to control the behavior of the static checks. Listing 6-27 shows the SimpleDynamicPointcut class.

Listing 6-27: The SimpleDynamicPointcut Class

image from book
package com.apress.prospring.ch6.dynamicpc;      import java.lang.reflect.Method;      import org.springframework.aop.ClassFilter; import org.springframework.aop.support.DynamicMethodMatcherPointcut;      public class SimpleDynamicPointcut extends DynamicMethodMatcherPointcut {          public boolean matches(Method method, Class cls) {         System.out.println("Static check for " + method.getName());         return ("foo".equals(method.getName()));     }          public boolean matches(Method method, Class cls, Object[] args) {         System.out.println("Dynamic check for " + method.getName());              int x = ((Integer) args[0]).intValue();              return (x != 100);     }          public ClassFilter getClassFilter() {         return new ClassFilter() {                  public boolean matches(Class cls) {                 return (cls == SampleBean.class);             }         };     } }
image from book

As you can see from the code in Listing 6-27, we override the getClassFilter() method in a similar manner to the previous example shown in Listing 6-23. This removes the need to check the class in the method matching methods—something that is especially important for the dynamic check. Although we are only required to implement the dynamic check, we implement the static check as well. The reason for this is that we know the bar() method will never be advised. By indicating this using the static check, Spring makes it so it never has to perform a dynamic check for this method. If we neglect the static check, Spring performs a dynamic check each time the bar() method is invoked even though it always returns false. In the matches(Method, Class, Object[]) method, you can see that we return false if the value of the int argument passed to the foo() method is false, otherwise we return true. Note that in the dynamic check, we know that we are dealing with the foo() method, because no other method makes it past the static check.

In Listing 6-28, you can see an example of this pointcut in action.

Listing 6-28: The DynamicPointcutExample Class

image from book
package com.apress.prospring.ch6.dynamicpc;      import org.springframework.aop.Advisor; import org.springframework.aop.framework.ProxyFactory; import org.springframework.aop.support.DefaultPointcutAdvisor;      import com.apress.prospring.ch6.staticpc.SimpleAdvice;      public class DynamicPointcutExample {          public static void main(String[] args) {         SampleBean target = new SampleBean();              // create advisor         Advisor advisor = new DefaultPointcutAdvisor(                 new SimpleDynamicPointcut(), new SimpleAdvice());                  // create proxy         ProxyFactory pf = new ProxyFactory();         pf.setTarget(target);         pf.addAdvisor(advisor);         SampleBean proxy = (SampleBean)pf.getProxy();                  proxy.foo(1);         proxy.foo(10);         proxy.foo(100);                  proxy.bar();         proxy.bar();         proxy.bar();     } }
image from book

Notice that we have used the same advice class as in the static pointcut example. However, in this example, only the first two calls to foo() should be advised. The dynamic check prevents the third call to foo() from being advised and the static check prevents the bar() method from being advised. Running this example yields the following output:

Static check for foo Static check for bar Static check for hashCode Static check for clone Static check for toString Static check for foo Dynamic check for foo >> Invoking foo Invoked foo() with: 1 >> Done Dynamic check for foo >> Invoking foo Invoked foo() with: 10 >> Done Dynamic check for foo Invoked foo() with: 100 Invoked bar() Invoked bar() Invoked bar()

As we expected, only the first two invocations of the foo() method were advised. Notice that none of the bar() invocations is subject to a dynamic check, thanks to the static check on bar(). An interesting point to note here is that the foo() method is actually subject to two static checks: one during the initial phase when all methods are checked and another when it is first invoked.

As you can see, dynamic pointcuts offer a greater degree of flexibility than static pointcuts, but due to the additional runtime overhead they require, you should only use a dynamic pointcut when absolutely necessary.

Using Simple Name Matching

Often when creating a pointcut, you want to match based on just the name of the method, ignoring method signature and return type. In this case, you can avoid needing to create a subclass of StaticMethodMatcherPointcut and use the NameMatchMethodPointcut to match against a list of method names instead. When you are using NameMatchMethodPointcut, no consideration is given to the signature of the method, so if you have methods foo() and foo(int), they are both matched for the name foo.

Now for a demonstration. Listing 6-29 shows a simple class with four methods.

Listing 6-29: The NameBean Class

image from book
package com.apress.prospring.ch6.namepc;      public class NameBean {          public void foo() {         System.out.println("foo");     }          public void foo(int x) {         System.out.println("foo " + x);     }          public void bar() {         System.out.println("bar");     }          public void yup() {         System.out.println("yup");     } }
image from book

For this example, we want to match the foo(), foo(int), and bar() methods using the NameMatchMethodPointcut; this translates to matching the names foo and bar. This is shown in Listing 6-30.

Listing 6-30: Using the NameMatchMethodPointcut

image from book
package com.apress.prospring.ch6.namepc;      import org.springframework.aop.Advisor; import org.springframework.aop.framework.ProxyFactory; import org.springframework.aop.support.DefaultPointcutAdvisor; import org.springframework.aop.support.NameMatchMethodPointcut;      import com.apress.prospring.ch6.staticpc.SimpleAdvice;      public class NamePointcutExample {          public static void main(String[] args) {         NameBean target = new NameBean();              // create advisor         NameMatchMethodPointcut pc = new NameMatchMethodPointcut();         pc.addMethodName("foo");         pc.addMethodName("bar");         Advisor advisor = new DefaultPointcutAdvisor(pc, new SimpleAdvice());                  // create the proxy         ProxyFactory pf = new ProxyFactory();         pf.setTarget(target);         pf.addAdvisor(advisor);         NameBean proxy = (NameBean)pf.getProxy();                  proxy.foo();         proxy.foo(999);         proxy.bar();         proxy.yup();     } }
image from book

There is no need to create a class for the pointcut; you can simply create an instance of NameMatchMethodPointcut and you are on your way. Notice that we have added two names to the pointcut, foo and bar, using the addMethodName() method. Running this example results in the following output:

>> Invoking foo foo >> Done >> Invoking foo foo 999 >> Done >> Invoking bar bar >> Done yup

As expected, the foo(), foo(int), and bar() methods are advised, thanks to the pointcut, but the yup() method is left unadvised.

Creating Pointcuts with Regular Expressions

In the previous section, we discussed how to perform simple matching against a predefined list of methods. But what if you don't know all of the methods' names in advance, and instead you know the pattern that the names follow? For instance, what if you want to match all methods whose names starts with get? In this case, you can use one of the regular expression pointcuts, either JdkRegexpMethodPointcut or Perl5RegexpMethodPointcut, to match a method name based on a regular expression.

The code in Listing 6-31 shows a simple class with three methods.

Listing 6-31: The RegexpBean Class

image from book
package com.apress.prospring.ch6.regexppc;      public class RegexpBean {          public void foo1() {         System.out.println("foo1");     }          public void foo2() {         System.out.println("foo2");     }          public void bar() {         System.out.println("bar");     } }
image from book

Using a regular expression-based pointcut, we can match all methods in this class whose name starts with foo. This is shown in Listing 6-32.

Listing 6-32: Using Regular Expressions for Pointcuts

image from book
package com.apress.prospring.ch6.regexppc;      import org.springframework.aop.Advisor; import org.springframework.aop.framework.ProxyFactory; import org.springframework.aop.support.DefaultPointcutAdvisor; import org.springframework.aop.support.JdkRegexpMethodPointcut;      import com.apress.prospring.ch6.staticpc.SimpleAdvice;      public class RegexpPointcutExample {          public static void main(String[] args) {         RegexpBean target = new RegexpBean();                  // create the advisor         JdkRegexpMethodPointcut pc = new JdkRegexpMethodPointcut();         pc.setPattern(".*foo.*");         Advisor advisor = new DefaultPointcutAdvisor(pc, new SimpleAdvice());                  // create the proxy         ProxyFactory pf = new ProxyFactory();         pf.setTarget(target);         pf.addAdvisor(advisor);         RegexpBean proxy = (RegexpBean)pf.getProxy();                  proxy.foo1();         proxy.foo2();         proxy.bar();     } }
image from book

Notice we do not need to create a class for the pointcut; instead, we just create an instance of JdkRegexpMethodPointcut (which could just as easily be Perl5RegexpMethodPointcut), specify the pattern to match, and we are done. The interesting thing to note is the pattern. When matching method names, Spring matches the fully qualified name of the method, so for foo1(), Spring is matching against com.apress.prospring.ch6.regexppc.RegexpBean.foo1, hence the leading .* in the pattern. This is a powerful concept because it allows you to match all methods within a given package, without needing to know exactly which classes are in that package and what the names of the methods are. Running this example yields the following output:

>> Invoking foo1 foo1 >> Done >> Invoking foo2 foo2 >> Done bar

As you would expect, only the foo1() and foo2() methods have been advised, because the bar() method does not match the regular expression pattern.

Convenience Advisor Implementations

For many of the Pointcut implementations, Spring also provides a convenience Advisor implementation that acts as the Pointcut as well. For instance, instead of using the NameMatchMethodPointcut coupled with a DefaultPointcutAdvisor in the previous example, we could simply have used a NameMatchMethodPointcutAdvisor, as shown in Listing 6-33.

Listing 6-33: Using NameMatchMethodPointcutAdvisor

image from book
package com.apress.prospring.ch6.namepc;      import org.springframework.aop.framework.ProxyFactory; import org.springframework.aop.support.NameMatchMethodPointcutAdvisor;      import com.apress.prospring.ch6.staticpc.SimpleAdvice;      public class NamePointcutUsingAdvisor {          public static void main(String[] args) {         NameBean target = new NameBean();              // create advisor         NameMatchMethodPointcutAdvisor advisor = new              NameMatchMethodPointcutAdvisor(new SimpleAdvice());         advisor.addMethodName("foo");         advisor.addMethodName("bar");              // create the proxy         ProxyFactory pf = new ProxyFactory();         pf.setTarget(target);         pf.addAdvisor(advisor);         NameBean proxy = (NameBean) pf.getProxy();              proxy.foo();         proxy.foo(999);         proxy.bar();         proxy.yup();     } }
image from book

Notice in Listing 6-33 that rather than create an instance of NameMatchMethodPointcut, we configure the pointcut details on the instance of NameMatchMethodPointcutAdvisor itself. In this way, the NameMatchMethodPointcutAdvisor is acting as both the Advisor and the Pointcut.

You can find full details of the different convenience Advisor implementations by exploring the JavaDoc for the org.springframework.aop.support package. There is no noticeable performance difference between the two approaches, and aside from there being slightly less code in the second approach, there is very little difference in the actual coding approach. We prefer to stick with the first approach because we feel that the intent is slightly clearer in the code. At the end of the day, the style you choose comes down to personal preference.



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