AOP in Spring

You can think of Spring's AOP implementation as coming in two logical parts. The first part is the AOP core, which provides fully decoupled, purely programmatic AOP functionality. The second part of the AOP implementation is the set of framework services that make AOP easier to use in your applications. On top of this, other components of Spring, such as the transaction manager and EJB helper classes, provide AOP-based services to simplify the development of your application. In this chapter, we focus solely on the basics of the AOP core. The framework services and the advanced functionality of the core are covered in

Spring AOP is really a subset of the full AOP feature set, implementing only a handful of the constructs available in implementations like AspectJ. Don't be fooled into thinking Spring AOP is not useful, however. Indeed, one of the most powerful aspects of Spring AOP is that it is so simple to use because it is unencumbered with extraneous features that you often do not need. The implementation of only a subset of the AOP feature set is a specific design goal of Spring, allowing Spring to focus on simple access to the most common features of AOP. To make sure that you are not left without the AOP features that you need, in the 1.1 release, Spring's designers designed Spring to fully integrate with AspectJ.

The AOP Alliance

The AOP Alliance (http://aopalliance.sourceforge.net/) is a joint effort between representatives of many open source AOP projects, including Rod Johnson of Spring, to define a standard set of interfaces for AOP implementations. The AOP Alliance is being very conservative, resisting the temptation to over-constrain AOP while it is still growing, and as a result they have only defined interfaces for a subset of AOP features. Wherever applicable, Spring uses the AOP Alliance interfaces rather than defining its own. This allows you to reuse certain advice across multiple AOP implementations that support the AOP Alliance interfaces.

Hello World in AOP

Before we dive into discussing the Spring AOP implementation in detail, we want to present a simple example to provide some context for these discussions. In this example, we take a simple class that outputs the message "World", and then using AOP, we transform an instance of this class at runtime to output "Hello World!" instead. Listing 6-1 shows the basic MessageWriter class.

Listing 6-1: The MessageWriter Class

image from book
package com.apress.prospring.ch6;      public class MessageWriter {          public void writeMessage() {         System.out.print("World");     } }
image from book

The MessageWriter class is nothing special; it has just one method that writes the message "World" to stdout. We want to advise—that is, add some advice to—this class so that the writeMessage() method actually writes "Hello World!" instead.

To do this, we need to execute some code before the method body executes to write "Hello", and some code after the method body executes to write "!". In AOP terms, what we need is an around advice—that is, advice that executes around a joinpoint. In this case, the joinpoint is the invocation of the writeMessage() method. Listing 6-2 shows the implementation of the around advice, the MessageDecorator class.

Listing 6-2: Implementing Around Advice

image from book
package com.apress.prospring.ch6;      import org.aopalliance.intercept.MethodInterceptor; import org.aopalliance.intercept.MethodInvocation;      public class MessageDecorator implements MethodInterceptor {          public Object invoke(MethodInvocation invocation) throws Throwable {         System.out.print("Hello ");         Object retVal = invocation.proceed();         System.out.println("!");         return retVal;     } }
image from book

The MethodInterceptor interface is the AOP Alliance standard interface for implementing around advice for method invocation joinpoints. The MethodInvocation object represents the method invocation that is being advised, and using this object, we control when the method invocation is actually allowed to proceed. Because this is around advice, we are essentially capable of performing some actions before the method is invoked and some actions after it is invoked but before it returns. In Listing 6-2, we simply write Hello to stdout, invoke the method with a call to MethodInvocation.proceed(), and then write ! to stdout.

The final step in this sample is to weave the MessageDecorator advice into the code. To do this, we create an instance of MessageWriter, the target, and then create a proxy of this instance, instructing the proxy factory to weave in the MessageDecorator advice. This is shown in Listing 6-3.

Listing 6-3: .Weavin: the MessageDecorator Advice

image from book
package com.apress.prospring.ch6;      import org.springframework.aop.framework.ProxyFactory;      public class HelloWorldAOPExample {          public static void main(String[] args) {         MessageWriter target = new MessageWriter();                  // create the proxy         ProxyFactory pf = new ProxyFactory();              pf.addAdvice(new MessageDecorator());         pf.setTarget(target);              MessageWriter proxy = (MessageWriter) pf.getProxy();                  // write the messages         target.writeMessage();         System.out.println("");         proxy.writeMessage();     } }
image from book

The important part here is that we use the ProxyFactory class to create the proxy of the target object, weaving in the advice at the same time. We pass the MessageDecorator advice to the ProxyFactory with a call to addAdvice() and specify the target for weaving with a call to setTarget(). Once the target is set and some advice is added to the ProxyFactory, we generate the proxy with a call to getProxy(). Finally, we call writeMessage() on both the original target object and the proxy object. Here are the results of running this example:

World Hello World!

As you can see, calling writeMessage() on the untouched target object resulted in a standard method invocation, and no extra content is written to stdout. However, the invocation of the proxy caused the code in the MessageDecorator to execute, creating the desired output of Hello World! From this example, you can see that the advised class had no dependencies on Spring or the AOP Alliance interfaces; the beauty of Spring AOP, and indeed AOP in general, is that you can advise almost any class, even if that class was created without AOP in mind. The only restriction, in Spring AOP at least, is that you can't advise final classes, because they cannot be overridden and therefore cannot be proxied.

Spring AOP Architecture

The core architecture of Spring AOP is based around proxies. When you want to create an advised instance of a class, you must use the ProxyFactory class to create a proxy of an instance of that class, first providing the ProxyFactory with all the aspects that you want to be woven into the proxy. Using ProxyFactory is a purely programmatic approach to creating AOP proxies. For the most part, you don't need to use this in your application; instead you can rely on the ProxyFactoryBean class to provide declarative proxy creation. However, it is important to understand how proxy creation works. For the rest of this chapter, we will use the programmatic approach to proxy creation. In the next chapter, we discuss using ProxyFactoryBean when creating proxies.

Internally, Spring has two proxy implementations: JDK dynamic proxy and CGLIB proxy. In previous releases of Spring, there was not much difference between the two proxy types, and CGLIB proxies were only used when you wanted to proxy classes rather than interfaces, or when you explicitly specified them. As of the 1.1 release of Spring, the CGLIB proxy is noticeably faster than JDK dynamic proxies in most cases. This is especially true when you are running on a 1.3 VM, which suffers from poor reflection performance. Understanding proxies and how they are used internally is key to getting the best performance out of your application. We discuss proxies in great detail later in the chapter, in the section titled "All About Proxies."

Joinpoints in Spring

One of the more noticeable simplifications in Spring AOP is that it only supports one joinpoint type: method invocation. At first glance, this might seem like a severe limitation if you are familiar with other AOP implementations like AspectJ, which supports many more joinpoints, but in fact this actually makes Spring more accessible.

The method invocation joinpoint is by far the most useful joinpoint available, and using it, you can achieve many of the tasks that make AOP useful in day-to-day programming. Remember that if you need to advise some code at a joinpoint other than a method invocation, you can always use Spring and AspectJ together.

Aspects in Spring

In Spring AOP, an aspect is represented by an instance of a class that implements the Advisor interface. Spring provides a selection of convenience Advisor implementations that you can use in your applications, thus removing the need for you to create lots of different Advisor implementations for your example. There are two subinterfaces of Advisor: IntroductionAdvisor and PointcutAdvisor. The PointcutAdvisor interface is implemented by all Advisors that use pointcuts to control the applicability of advice to joinpoints.

In Spring, introductions are treated as special kinds of advice. Using the IntroductionAdvisor interface, you can control those classes to which an introduction applies. We cover this in more detail in the next chapter.

We discuss the different PointcutAdvisor implementations in detail later in this chapter in the section titled "Advisors and Pointcuts in Spring."

About the ProxyFactory Class

The ProxyFactory class controls the weaving and proxy creation process in Spring AOP. Before you can actually create a proxy, you must specify the advised or target object. You can do this, as you saw earlier, using the setTarget() method. Internally, ProxyFactory delegates the proxy creation process to an instance of DefaultAopProxyFactory, which in turn delegates to either Cglib2AopProxy or JdkDynamicAopProxy, depending on the settings of your application. We discuss proxy creation in more detail later in this chapter.

Using the ProxyFactory class, you control which aspects you want to weave into the proxy. As mentioned earlier, you can weave only an aspect—that is, advice combined with a pointcut— into advised code. However, in some cases you want an advice to apply to the invocation of all methods in a class, not just a selection. For this reason, the ProxyFactory class provides the addAdvice() method that you saw in Listing 6-3. Internally, addAdvice() wraps the advice you pass it in an instance of DefaultPointcutAdvisor, which is the standard implementation of PointcutAdvisor, and configures it with a pointcut that includes all methods by default. When you want more control over the Advisor that is created, or when you want to add an introduction to the proxy, create the Advisor yourself and use the addAdvisor() method of the ProxyFactory().

You can use the same ProxyFactory instance to create many different proxies, each with different aspects. To help with this, ProxyFactory has removeAdvice() and removeAdvisor() methods, which allow you to remove any advice or Advisors from the ProxyFactory that you previously passed to it. To check whether or not a ProxyFactory has a particular advice attached to it, call adviceIncluded(), passing in the advice object for which you want to check.

Be aware that ProxyFactory defines quite a few methods that have deprecated in favor of other methods such as addAdvice(). You can find full details of these methods in the JavaDoc. Avoid using the deprecated methods, because they will likely be removed in future versions of Spring, and there is an alternative to each of them. If you stick with the methods used in this book, you will be okay.

Creating Advice in Spring

Spring supports five different flavors of advice, described in Table 6-1.

Table 6-1: Advice Types in Spring

Advice Name

Interface

Description

Before

org.springframework.aop .MethodBeforeAdvice

Using before advice, you can perform custom processing before a joinpoint executes. Because a joinpoint in Spring is always a method invocation, this essentially allows you to perform preprocessing before the method executes. A before advice has full access to the target of the method invocation as well as the arguments passed to the method, but it has no control over the execution of the method itself.

After returning

org.springframework. aop.AfterReturningAdvice

After returning advice is executed after the method invocation at the joinpoint has finished executing and has returned a value. The after returning advice has access to the target of the method invocation, the arguments passed to the method, and the return value as well. Because the method has already executed when the after returning advice is invoked, it has no control over the method invocation at all.

Around

org.aopalliance. intercept.MethodInterceptor

In Spring, around advice is modeled using the AOP Alliance standard of a method interceptor. Your advice is allowed to execute before and after the method invocation, and you can control the point at which the method invocation is allowed to proceed. You can choose to bypass the method altogether if you want, providing your own implementation of the logic.

Throws

org.springframework .aop.ThrowsAdvice

Throws advice is executed after a method invocation returns, but only if that invocation threw an exception. It is possible for a throws advice to catch only specific exceptions, and if you choose to do so, you can access the method that threw the exception, the arguments passed into the invocation, and the target of the invocation.

Introduction

org.springframework.aop .IntroductionInterceptor

Spring models introductions as special types of interceptors. Using an introduction interceptor, you can specify the implementation for methods that are being introduced by the advice. Introductions are covered in more detail in the next chapter.

We have found that these advice types, coupled with the method invocation joinpoint, allow us to perform about 90 percent of the tasks we want to perform with AOP. For the other 10 percent, which we use only rarely, we fall back on AspectJ.

Interfaces for Advice

From our previous discussion of the ProxyFactory class, recall that advice is added to a proxy either directly, using the addAdvice() method, or using an Advisor, with the addAdvisor() method. In previous releases of Spring, we had an addXXX() method for each type of advice; indeed, these methods are still present, albeit deprecated. Originally, each advice interface was separate from the others, but more recently, a well-defined hierarchy has been created for advice interfaces. This hierarchy is based on the AOP Alliance interfaces and is shown in detail in Figure 6-1.

image from book
Figure 6-1: Interfaces for Spring advice types

This kind of hierarchy has the benefit of not only being sound OO design, but it also means that you can deal with advice types generically, as in using a single addAdvice() method on the ProxyFactory, and you can add new advice types easily without having to modify the ProxyFactory class.

Creating Before Advice

Before advice is one of the most useful advice types available in Spring. A before advice can modify the arguments passed to a method and can prevent the method from executing by raising an exception. In the next chapter, you will see before advice used frequently when we look at how AOP is used in the SpringBlog application. In this section, we show you two examples of using before advice: a simple example that writes a message to stdout containing the name of the method before the method executes, and a simple security advice that you can use to restrict access to methods on an object.

In Listing 6-4, you can see the code for the SimpleBeforeAdvice class.

Listing 6-4: The SimpleBeforeAdvice Class

image from book
package com.apress.prospring.ch6;      import java.lang.reflect.Method;      import org.springframework.aop.MethodBeforeAdvice; import org.springframework.aop.framework.ProxyFactory;      public class SimpleBeforeAdvice implements MethodBeforeAdvice {          public static void main(String[] args) {         MessageWriter target = new MessageWriter();              // create the proxy         ProxyFactory pf = new ProxyFactory();              pf.addAdvice(new SimpleBeforeAdvice());         pf.setTarget(target);              MessageWriter proxy = (MessageWriter) pf.getProxy();              // write the messages         proxy.writeMessage();     }          public void before(Method method, Object[] args, Object target)             throws Throwable {         System.out.println("Before method: " + method.getName());     }      }
image from book

In this code, you can see that we have advised an instance of the MessageWriter class that we created earlier with an instance of the SimpleBeforeAdvice class. The MethodBeforeAdvice interface, which is implemented by SimpleBeforeAdvice, defines a single method, before(), which the AOP framework calls before the method at the joinpoint is invoked. Remember that, for now, we are using the default pointcut provided by the addAdvice() method, which matches all methods in a class. The before() method is passed three arguments: the method that is to be invoked, the arguments that will be passed to that method, and the Object that is the target of the invocation. The SimpleBeforeAdvice class uses the Method argument of the before() method to write a message to stdout containing the name of the method to be invoked. Running this example gives us the following output:

Before method: writeMessage World

As you can see, the output from the call to writeMessage() is shown, but just before it, you can see the output generated by the SimpleBeforeAdvice.

Securing Method Access Using Before Advice

The last example was fairly trivial and didn't really show the power of AOP. In this section, we are going to build a before advice that checks user credentials before allowing the method invocation to proceed. If the user credentials are invalid, an exception is thrown by the advice, thus preventing the method from executing. The example in this section is simplistic. It allows users to authenticate with any password, and it also allows only a single, hard-coded user access to the secured methods. However, it does illustrate how easy it is to use AOP to implement a crosscutting concern such as security.

Listing 6-5 shows the SecureBean class. This is the class that we will be securing using AOP.

Listing 6-5: The SecureBean Class

image from book
package com.apress.prospring.ch6.security;      public class SecureBean {          public void writeSecureMessage() {         System.out.println("Every time I learn something new, "                 + "it pushes some old stuff out of my brain");     } }
image from book

The SecureBean class imparts a small pearl of wisdom from Homer Simpson, wisdom that we don't want everyone to see. Because this example requires users to authenticate, we are somehow going to need to store their details. Listing 6-6 shows the UserInfo class we use to store a user's credentials.

Listing 6-6: The UserInfo Class

image from book
package com.apress.prospring.ch6.security;      public class UserInfo {     private String userName;          private String password;          public UserInfo(String userName, String password) {         this.userName = userName;         this.password = password;     }          public String getPassword() {         return password;     }     public String getUserName() {         return userName;     } } 
image from book

There is nothing really of interest in this class; it simply holds data about the user so that we can do something useful with it. Listing 6-7 shows the SecurityManager class, which is responsible for authenticating users and storing their credentials for later retrieval.

Listing 6-7: The SecurityManager Class

image from book
package com.apress.prospring.ch6.security;      public class SecurityManager {          private static ThreadLocal threadLocal = new ThreadLocal();          public void login(String userName, String password) {         // assumes that all credentials         // are valid for a login         threadLocal.set(new UserInfo(userName, password));     }          public void logout() {         threadLocal.set(null);     }          public UserInfo getLoggedOnUser() {         return (UserInfo) threadLocal.get();     } }
image from book

The application uses the SecurityManager class to authenticate a user and, later, to retrieve the details of the currently authenticated user. The application authenticates a user using the login() method. In a real application, the login() method would probably check the supplied application against a database or LDAP directory, but here we assume all users are allowed to authenticate. The login() method creates a UserInfo object for the user and stores it on the current thread using a ThreadLocal. The logout() method sets any value that might be stored in the ThreadLocal to null. Finally, the getLoggedOnUser() method returns the UserInfo object for the currently authenticated user. This method returns null if no user is authenticated.

In order to check whether or not a user is authenticated and if so, whether or not the user is permitted to access the methods on SecureBean, we need to create an advice that executes before the method and checks the UserInfo object returned by SecurityManager.getLoggedOnUser() against the set of credentials for allowed users. The code for this advice, SecurityAdvice, is shown in Listing 6-8.

Listing 6-8: The SecurityAdvice Class

image from book
package com.apress.prospring.ch6.security;      import java.lang.reflect.Method;      import org.springframework.aop.MethodBeforeAdvice;      public class SecurityAdvice implements MethodBeforeAdvice {          private SecurityManager securityManager;          public SecurityAdvice() {         this.securityManager = new SecurityManager();     }          public void before(Method method, Object[] args, Object target)             throws Throwable {         UserInfo user = securityManager.getLoggedOnUser();              if (user == null) {             System.out.println("No user authenticated");             throw new SecurityException(                     "You must login before attempting to invoke the method: "                             + method.getName());         } else if ("robh".equals(user.getUserName())) {             System.out.println("Logged in user is robh - OKAY!");         } else {             System.out.println("Logged in user is " + user.getUserName()                     + " NOT GOOD :(");             throw new SecurityException("User " + user.getUserName()                     + " is not allowed access to method " + method.getName());         }          } }
image from book

The SecurityAdvice class creates an instance of SecurityManager in its constructor and then stores this instance in a field. You should note that the application and the SecurityAd- vice don't need to share the same SecurityManager instance, because all data is stored with the current thread using ThreadLocal. In the before() method, we perform a simple check to see if the user name of the authenticated user is robh. If so, we allow the user access; otherwise, an exception is raised. Also notice that we check for a null UserInfo object, which indicates that the current user is not authenticated.

In Listing 6-9, you can see a sample application that uses the SecurityAdvice class to secure the SecureBean class.

Listing 6-9: The SecurityExample Class

image from book
package com.apress.prospring.ch6.security;      import org.springframework.aop.framework.ProxyFactory;      public class SecurityExample {          public static void main(String[] args) {         // get the security manager         SecurityManager mgr = new SecurityManager();                  // get the bean         SecureBean bean = getSecureBean();              // try as robh         mgr.login("robh", "pwd");         bean.writeSecureMessage();         mgr.logout();                  // try as janm         try {             mgr.login("janm", "pwd");             bean.writeSecureMessage();         } catch(SecurityException ex) {             System.out.println("Exception Caught: " + ex.getMessage());         } finally {             mgr.logout();         }                  // try with no credentials         try {             bean.writeSecureMessage();         } catch(SecurityException ex) {             System.out.println("Exception Caught: " + ex.getMessage());         }          }          private static SecureBean getSecureBean() {         // create the target         SecureBean target = new SecureBean();              // create the advice         SecurityAdvice advice = new SecurityAdvice();                  // get the proxy         ProxyFactory factory = new ProxyFactory();         factory.setTarget(target);         factory.addAdvice(advice);         SecureBean proxy = (SecureBean)factory.getProxy();                  return proxy;              } }
image from book

In the getSecureBean() method, we create a proxy of the SecureBean class that is advised using an instance of SecurityAdvice. This proxy is returned to the caller. When the caller invokes any method on this proxy, the call is first routed to the instance of SecurityAdvice for a security check. In the main() method, we test three different scenarios, invoking the SecureBean.writeSecureMessage() method with two different sets of user credentials and then no user credentials at all. Because the SecurityAdvice only allows method calls to proceed if the currently authenticated user is robh, we expect that the only successful scenario in Listing 6-9 is the first of these scenarios. Running this example gives the following output:

Logged in user is robh - OKAY! Every time I learn something new, it pushes some old stuff out of my brain Logged in user is janm NOT GOOD :( Exception Caught: User janm is not allowed access to method writeSecureMessage No user authenticated Exception Caught: You must login before attempting to invoke the method:¿ writeSecureMessage

As you can see, only the first invocation of SecureBean.writeSecureMessage() was allowed to proceed. The remaining invocations were prevented by the SecurityException thrown by the SecurityAdvice.

This example is simple, but it does highlight the usefulness of the before advice. Security is a typical example of before advice, but we also find it useful when a scenario demands the modification of arguments going into the method. In Chapter 7, we show you how to use before advice to create an obscenity filter for the SpringBlog application.

Creating After Returning Advice

As its name implies, after returning advice is executed after the method invocation at the join- point returns. Given that the method has already executed, you can't change the arguments that are passed to it. Although you can read these, you can't change the execution path, nor can you prevent the method from executing. These restrictions are expected; what is not expected, however, is the fact that you cannot modify the return value in the after returning advice; you are simply restricted to performing some additional processing. Although after returning advice cannot modify the return value of a method invocation, it can throw an exception that can be sent up the stack instead of the return value.

In this section, we look at two examples of using after returning advice in an application. The first example simply writes a message to stdout after the method has been invoked. The second example shows how you can use after returning advice to add additional error checking to a method. Consider a class, KeyGenerator, that generates keys for cryptographic purposes. Many cryptographic algorithms suffer from the problem that a small number of keys in the keyspace are considered weak. A weak key is any key whose characteristics make it significantly easier to derive the original message without knowing the key. For the DES algorithm, there are a total of 256 possible keys. From this keyspace, 4 keys are considered weak and another 12 are considered semi-weak. Although the chance of one of these keys being generated randomly is ridiculously small (1 in 252), testing for the keys is so simple that it seems almost lax to ignore the test. In the second example of this section, we build an after returning advice that checks for weak keys generated by the KeyGenerator and raises an exception if one is found.

Note 

For more information on weak keys and cryptography at large, we recommend that you read Applied Cryptography by Bruce Schneier (Wiley, 1995).

In Listing 6-10, you can see the SimpleAfterReturningAdvice class, which demonstrates the use of after returning advice by writing a message to stdout after a method has returned.

Listing 6-10: The SimpleAfterReturningAdvice Class

image from book
package com.apress.prospring.ch6;      import java.lang.reflect.Method;      import org.springframework.aop.AfterReturningAdvice; import org.springframework.aop.framework.ProxyFactory;      public class SimpleAfterReturningAdvice implements AfterReturningAdvice {          public static void main(String[] args) {         MessageWriter target = new MessageWriter();              // create the proxy         ProxyFactory pf = new ProxyFactory();              pf.addAdvice(new SimpleAfterReturningAdvice());         pf.setTarget(target);              MessageWriter proxy = (MessageWriter) pf.getProxy();              // write the messages         proxy.writeMessage();     }          public void afterReturning(Object returnValue, Method method, Object[] args,             Object target) throws Throwable {         System.out.println("");         System.out.println("After method: " + method.getName());     } }
image from book

This example is really not that different from the SimpleBeforeAdvice class that you saw earlier. Notice that the AfterReturningAdvice interface declares a single method, afterReturning(), which is passed the return value of method invocation, a reference to the method that was invoked, the arguments that were passed to the method, and the target of the invocation. Running this example results in the following output:

World After method: writeMessage

The output is very similar to that of the before advice example except that, as expected, the message written by the advice appears after the message written by the writeMessage() method.

A good use of after returning advice is to perform some additional error checking when it is possible for a method to return an invalid value. In the scenario we described earlier, it is possible for a cryptographic key generator to generate a key that is considered weak for a particular algorithm. Ideally, the key generator would check for these weak keys, but since the chance of these keys arising is often very small, many generators do not check. By using an after returning advice, we can advise the method that generates the key and performs this additional check. Listing 6-11 shows an extremely primitive key generator.

Listing 6-11: The KeyGenerator Class

image from book
package com.apress.prospring.ch6.crypto;      import java.util.Random;      public class KeyGenerator {          public static final long WEAK_KEY = 0xFFFFFFF0000000L;     public static final long STRONG_KEY = 0xACDF03F590AE56L;          private Random rand = new Random();          public long getKey() {         int x = rand.nextInt(3);                  if(x == 1) {             return WEAK_KEY;         } else {             return STRONG_KEY;         }     } } 
image from book

It is plain to see that this key generator is ridiculously insecure, but we didn't want you to have to wait around for years while a real key generator produced a weak key, so we created this generator, which has a 1-in-3 chance of producing a weak key. In Listing 6-12, you can see the WeakKeyCheckAdvice that checks to see if the result of the getKey() method is a weak key.

Listing 6-12: Checking for Weak Keys

image from book
package com.apress.prospring.ch6.crypto;      import java.lang.reflect.Method;      import org.springframework.aop.AfterReturningAdvice;      public class WeakKeyCheckAdvice implements AfterReturningAdvice {          public void afterReturning(Object returnValue, Method method,             Object[] args, Object target) throws Throwable {              if ((target instanceof KeyGenerator)                 && ("getKey".equals(method.getName()))) {             long key = ((Long) returnValue).longValue();                  if (key == KeyGenerator.WEAK_KEY) {                 throw new SecurityException(                         "Key Generator generated a weak key. Try again");             }         }     } }
image from book

In the afterReturning() method, we check first to see if the method that was executed at the joinpoint was the getKey() method. If so, we then check the result value to see if it was the weak key. If we find that the result of the getKey() method was a weak key, then we throw a SecurityException to inform the calling code of this. Listing 6-13 shows a simple application that demonstrates the use of this advice.

Listing 6-13: Testing the WeakKeyCheckAdvice Class

image from book
package com.apress.prospring.ch6.crypto;      import org.springframework.aop.framework.ProxyFactory;      public class AfterAdviceExample {          public static void main(String[] args) {         KeyGenerator keyGen = getKeyGenerator();                  for(int x = 0; x < 10; x++) {             try {                 long key = keyGen.getKey();                 System.out.println("Key: " + key);             } catch(SecurityException ex) {                 System.out.println("Weak Key Generated!");             }         }              }          private static KeyGenerator getKeyGenerator() {                  KeyGenerator target = new KeyGenerator();                  ProxyFactory factory = new ProxyFactory();         factory.setTarget(target);         factory.addAdvice(new WeakKeyCheckAdvice());                  return (KeyGenerator)factory.getProxy();     } }
image from book

After creating an advised proxy of a KeyGenerator target, the AfterAdviceExample class attempts to generate ten keys. If a SecurityException is thrown during a single generation, then a message is written to stdout informing the user that a weak key was generated, otherwise the generated key is displayed. A single run of this on our machine generated the following output:

Weak Key Generated! Key: 48658904092028502 Key: 48658904092028502 Key: 48658904092028502 Key: 48658904092028502 Key: 48658904092028502 Weak Key Generated! Weak Key Generated! Key: 48658904092028502 Weak Key Generated!

As you can see, the KeyGenerator class sometimes generates weak keys, as expected, and the WeakKeyCheckAdvice ensures that a SecurityException is raised whenever a weak key is encountered.

Creating Around Advice

Around advice functions like a combination of before and after advice, with one big difference— you can modify the return value. Not only that, you can also prevent the method from actually executing. This means that using around advice, you can essentially replace the entire implementation of a method with new code. Around advice in Spring is modeled as an interceptor using the MethodInterceptor interface. There are many uses for around advice, and you will find that many features of Spring are created using method interceptors, such as the remote proxy support and the transaction management features. Method interception is also a good mechanism for profiling the execution of your application, and it forms the basis of the example in this section.

We are not going to build a simple example for method interception; instead, we refer back to the first example in Listing 6-2, which shows how to use a basic method interceptor to write out a message on either side of a method invocation. Notice from this earlier example that the invoke() method of the MethodInterceptor class does not provide the same set of arguments as the MethodBeforeAdvice and AfterReturningAdvice—that is, the method is not passed the target of the invocation, the method that was invoked, or the arguments used. However, you can get access to this data using the MethodInvocation object that is passed to invoke(). You will see a demonstration of this in the following example.

For this example, we want to achieve some way to advise a class so that we get basic information about the runtime performance of its methods. Specifically, we want to know how long the method took to execute. To achieve this, we can use the StopWatch class included in Spring, and we clearly need a MethodInterceptor, because we need to start the StopWatch before the method invocation and stop it right afterward.

Listing 6-14 shows the WorkerBean class that we are going to profile using the StopWatch class and an around advice.

Listing 6-14: The WorkerBean Class

image from book
package com.apress.prospring.ch6.profiling;      public class WorkerBean {          public void doSomeWork(int noOfTimes) {         for(int x = 0; x < noOfTimes; x++) {             work();         }     }          private void work() {         System.out.print("");     } }
image from book

This is a very simple class. The doSomeWork() method accepts a single argument, noOfTimes, and calls the work() method exactly the number of times specified by this method. The work() method simply has a dummy call to System.out.print(), which passes in an empty String. This prevents the compiler from optimizing out the work() method, and thus the call to work().

In Listing 6-15, you can see the ProfilingInterceptor class that uses the StopWatch class to profile method invocation times. We use this interceptor to profile the WorkerBean class shown in Listing 6-14.

Listing 6-15: The ProfilingInterceptor Class

image from book
package com.apress.prospring.ch6.profiling;      import java.lang.reflect.Method;      import org.aopalliance.intercept.MethodInterceptor; import org.aopalliance.intercept.MethodInvocation; import org.springframework.util.StopWatch;      public class ProfilingInterceptor implements MethodInterceptor {          public Object invoke(MethodInvocation invocation) throws Throwable {         // start the stop watch         StopWatch sw = new StopWatch();         sw.start(invocation.getMethod().getName());              Object returnValue = invocation.proceed();              sw.stop();         dumpInfo(invocation, sw.getTotalTimeMillis());         return returnValue;     }          private void dumpInfo(MethodInvocation invocation, long ms) {         Method m = invocation.getMethod();         Object target = invocation.getThis();         Object[] args = invocation.getArguments();              System.out.println("Executed method: " + m.getName());         System.out.println("On object of type: " + target.getClass().getName());              System.out.println("With arguments:");         for (int x = 0; x < args.length; x++) {             System.out.print("    > " + args[x]);         }         System.out.print("\n");              System.out.println("Took: " + ms + " ms");     } } 
image from book

In the invoke() method, which is the only method in the MethodInterceptor interface, we create an instance of StopWatch and then start it running immediately, allowing the method invocation to proceed with a call to MethodInvocation.proceed(). As soon as the method invocation has ended and the return value has been captured, we stop the StopWatch and pass the total number of milliseconds taken, along with the MethodInvocation object, to the dumpInfo() method. Finally, we return the Object returned by MethodInvocation.proceed() so that the caller obtains the correct return value. In this case, we did not want to disrupt the call stack in any way; we were simply acting as an eavesdropper on the method invocation. If we had wanted to, we could have changed the call stack completely, redirecting the method call to another object or a remote service, or we could simply have reimplemented the method logic inside the interceptor and returned a different return value.

The dumpInfo() method simply writes some information about the method call to stdout, along with the time taken for the method to execute. In the first three lines of dumpInfo(), you can see how you can use the MethodInvocation object to determine the method that was invoked, the original target of the invocation, and the arguments used.

Listing 6-16 shows the ProfilingExample class that first advises an instance of WorkerBean with a ProfilingInterceptor and then profiles the doSomeWork() method.

Listing 6-16: The ProfilingExample Class

image from book
package com.apress.prospring.ch6.profiling;      import org.springframework.aop.framework.ProxyFactory;      public class ProfilingExample {          public static void main(String[] args) {         WorkerBean bean = getWorkerBean();         bean.doSomeWork(10000000);     }          private static WorkerBean getWorkerBean() {         WorkerBean target = new WorkerBean();                  ProxyFactory factory = new ProxyFactory();         factory.setTarget(target);         factory.addAdvice(new ProfilingInterceptor());                  return (WorkerBean)factory.getProxy();     } }
image from book

You should be more than familiar with this code by now. Running this example on our machine produces the following output:

Executed method: doSomeWork On object of type: com.apress.prospring.ch6.profiling.WorkerBean With arguments:     > 10000000 Took: 4141 ms 

From this output, you can see which method was executed, what the class of the target was, what arguments were passed in, and how long the invocation took.

Creating Throws Advice

Throws advice is similar to after returning advice in that it executes after the joinpoint, which is always a method invocation, but throws advice only executes if the method threw an exception. Throws advice is also similar to after returning advice in that it has little control over program execution. If you are using a throws advice, you can't choose to ignore the exception that was raised and return a value for the method instead. The only modification you can make to the program flow is to change the type of exception that is thrown. This is actually quite a powerful idea and can make application development much simpler. Consider a situation where you have an API that throws an array of poorly defined exceptions. Using a throws advice, you can advise all classes in that API and reclassify the exception hierarchy into something more manageable and descriptive. Of course, you can also use throws advice to provide centralized error logging across your application, thus reducing the amount of error logging code that is spread across your application.

As you saw from the diagram in Figure 6-1, throws advice is implemented by the ThrowsAdvice interface. Unlike the interfaces you have seen so far, ThrowsAdvice does not define any methods; instead, it is simply a marker interface used by Spring. The reason for this is that Spring allows typed throws advice, which allows you to define exactly which Exception types your throws advice should catch. Spring achieves this by detecting methods with certain signatures using reflection. Spring looks for two distinct method signatures. This is best demonstrated with a simple example. Listing 6-17 shows a simple bean with two methods that both simply throw exceptions of different types.

Listing 6-17: The ErrorBean Class

image from book
package com.apress.prospring.ch6;      public class ErrorBean {          public void errorProneMethod() throws Exception {         throw new Exception("Foo");     }          public void otherErrorProneMethod() throws IllegalArgumentException {         throw new IllegalArgumentException("Bar");     } }
image from book

In Listing 6-18, you can see the SimpleThrowsAdvice class that demonstrates both of the method signatures that Spring looks for on a throws advice.

Listing 6-18: The SimpleThrowsAdvice Class

image from book
package com.apress.prospring.ch6;      import java.lang.reflect.Method;      import org.springframework.aop.ThrowsAdvice; import org.springframework.aop.framework.ProxyFactory;      public class SimpleThrowsAdvice implements ThrowsAdvice {          public static void main(String[] args) throws Exception {         ErrorBean errorBean = new ErrorBean();              ProxyFactory pf = new ProxyFactory();         pf.setTarget(errorBean);         pf.addAdvice(new SimpleThrowsAdvice());              ErrorBean proxy = (ErrorBean) pf.getProxy();              try {             proxy.errorProneMethod();         } catch (Exception ignored) {              }              try {             proxy.otherErrorProneMethod();         } catch (Exception ignored) {              }          }          public void afterThrowing(Exception ex) throws Throwable {         System.out.println("***");         System.out.println("Generic Exception Capture");         System.out.println("Caught: " + ex.getClass().getName());         System.out.println("***\n");     }          public void afterThrowing(Method method, Object[] args, Object target,             IllegalArgumentException ex) throws Throwable {         System.out.println("***");         System.out.println("IllegalArgumentException Capture");         System.out.println("Caught: " + ex.getClass().getName());         System.out.println("Method: " + method.getName());         System.out.println("***\n");     } } 
image from book

We are sure that you understand the code in the main() method, so now we will just focus on the two afterThrowing() methods. The first thing Spring looks for in a throws advice is one or more public methods called afterThrowing(). The return type of the methods is unimportant, although we find it best to stick with void because this method can't return any meaningful value. The first afterThrowing() method in the SimpleThrowsAdvice class has a single argument of type Exception. You can specify any type of Exception as the argument, and this method is ideal when you are not concerned about the method that threw the exception or the arguments that were passed to it. Note that this method catches Exception and any subtypes of Exception unless the type in question has its own afterThrowing() method.

In the second afterThrowing() method, we declared four arguments to catch the Method that threw the exception, the arguments that were passed to the method, and the target of the method invocation. The order of the arguments in this method is important, and you must specify all four. Notice that the second afterThrowing() method catches exceptions of type IllegalArgumentException (or its subtype). Running this example produces the following output:

*** Generic Exception Capture Caught: java.lang.Exception ***     *** IllegalArgumentException Capture Caught: java.lang.IllegalArgumentException Method: otherErrorProneMethod ***

As you can see, when a plain old Exception is thrown, the first afterThrowing() method is invoked, but when an IllegalArgumentException is thrown, the second afterThrowing() method is invoked. Spring only invokes a single afterThrowing() method for each Exception and, as you saw from the example in Listing 6-18, Spring uses the method whose signature contains the best match for the Exception type. In the situation where your after throwing advice has two afterThrowing() methods, both declared with the same Exception type but one with a single argument and the other with four arguments, Spring invokes the four-argument afterThrowing() method.

As we mentioned earlier, after throwing advice is useful in a variety of situations; it allows you to reclassify entire Exception hierarchies as well as build centralized Exception logging for your application. We have found that after throwing advice is particularly useful when we are debugging a live application, because it allows us to add extra logging code without needing to modify the application's code.

Choosing an Advice Type

In general, the choice of which advice type you want to use is driven by the requirements of your application, but you should choose the most specific advice type for your need. That is to say, don't use an around advice when a before advice will do. In most cases, an around advice can accomplish everything that the other three advice types can, but it may be overkill for what you are trying to achieve. By using the most specific type of advice, you are making the intention of your code clearer and you are also reducing the possibility of errors. Consider an advice that counts method calls. When you are using before advice, all you need to code is the counter, but with an around advice, you need to remember to invoke the method and return the value to the caller. These small things can allow spurious errors to creep into your application. By keeping the advice type as focused as possible, you reduce the scope for errors.



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