• 沒有找到結果。

Introducing Spring AOP

N/A
N/A
Protected

Academic year: 2022

Share "Introducing Spring AOP"

Copied!
79
0
0

加載中.... (立即查看全文)

全文

(1)

Introducing Spring AOP

Besides Dependency Injection (DI), another core feature that the Spring Framework offers is support for aspect-oriented programming (AOP. AOP is often referred to as a tool for implementing crosscutting concerns.

The term crosscutting concerns refers to logic in an application that cannot be decomposed from the rest of the application and may result in code duplication and tight coupling. By using AOP for modularizing individual pieces of logic, known as concerns, you can apply these to many parts of an application without duplicating the code or creating hard dependencies. Logging and security are typical examples of crosscutting concerns that are present in many applications. Consider an application that logs the start and end of every method for debugging purposes.

You will probably refactor the logging code into a special class, but you still have to call methods on that class twice per method in your application in order to perform the logging. Using AOP, you can simply specify that you want the methods on your logging class to be invoked before and after each method call in your application.

It is important to understand that AOP complements object-oriented programming (OOP), rather than competing with it. OOP is very good at solving a wide variety of problems that we, as programmers, encounter.

However, if you look at the logging example again, it is obvious to see where OOP is lacking when it comes to implementing crosscutting logic on a large scale. Using AOP on its own to develop an entire application is practically impossible, given that AOP functions on top of OOP. Likewise, although it is certainly possible to develop entire applications by using OOP, you can work smarter by employing AOP to solve certain problems that involve crosscutting logic.

This chapter covers the following topics:

• AOP basics: Before discussing Spring’s AOP implementation, we cover the basics of AOP as a technology. Most of the concepts covered in this section are not specific to Spring and can be found in any AOP implementation. If you are already familiar with another AOP implementation, feel free to skip this section.

• Types of AOP: There are two distinct types of AOP: static and dynamic. In static AOP, like that provided by AspectJ’s (http://eclipse.org/aspectj/) compile-time weaving mechanisms, the crosscutting logic is applied to your code at compile time, and you cannot change it without modifying the code and recompiling. With dynamic AOP, such as Spring AOP, crosscutting logic is applied dynamically at runtime. This allows you to make changes to the AOP configuration without having to recompile the application. These types of AOP are complementary, and, when used together, they form a powerful combination that you can use in your applications.

• Spring AOP architecture: Spring AOP is only a subset of the full AOP feature set found in other implementations such as AspectJ. In this section, we take a high-level look at which features are present in Spring, how they are implemented, and why some features are excluded from the Spring implementation.

(2)

• Proxies\ in Spring AOP: Proxies are a huge part of how Spring AOP works, and you must understand them to get the most out of Spring AOP. In this section, we look at the two kinds of proxy: the JDK dynamic proxy and the CGLIB proxy. In particular, we look at the different scenarios in which Spring uses each proxy, the performance of the two proxy types, and some simple guidelines to follow in your application to get the most from Spring AOP.

• Using Spring AOP: In this section, we present some practical examples of AOP usage. We start off with a simple “Hello World” example to ease you into Spring’s AOP code, and we continue with a detailed description of the AOP features that are available in Spring, complete with examples.

• Advanced use of pointcuts: We explore the ComposablePointcut and ControlFlowPointcut classes, introductions, and the appropriate techniques you should employ when using pointcuts in your application.

• AOP framework services: The Spring Framework fully supports configuring AOP transparently and declaratively. We look at three ways (the ProxyFactoryBean class, the aop namespace, and

@AspectJ-style annotations) to inject declaratively defined AOP proxies into your application objects as collaborators, thus making your application completely unaware that it is working with advised objects.

• Integrating AspectJ: AspectJ is a fully featured AOP implementation. The main difference between AspectJ and Spring AOP is that AspectJ applies advice to target objects via weaving (either compile-time or load-time weaving), while Spring AOP is based on proxies. The feature set of AspectJ is much greater than that of Spring AOP, but it is much more complicated to use than Spring. AspectJ is a good solution when you find that Spring AOP lacks a feature you need.

AOP Concepts

As with most technologies, AOP comes with its own specific set of concepts and terms, and it’s important to understand what they mean. The following are the core concepts of AOP:

• Joinpoints: A joinpoint is a well-defined point during the execution of your application.

Typical examples of joinpoints include a call to a method, the method invocation itself, class initialization, and object instantiation. Joinpoints are a core concept of AOP and define the points in your application at which you can insert additional logic using AOP.

• Advice: The code that is executed at a particular joinpoint is the advice, defined by a method in your class. There are many types of advice, such as before, which executes before the joinpoint, and after, which executes after it.

• Pointcuts: A pointcut is a collection of joinpoints that you use to define when advice should be executed. By creating pointcuts, you gain fine-grained control over how you apply advice to the components in your application. As mentioned previously, a typical joinpoint is a method invocation, or the collection of all method invocations in a particular class. Often you can compose pointcuts in complex relationships to further constrain when advice is executed.

• Aspects: An aspect is the combination of advice and pointcuts encapsulated in a class. This combination results in a definition of the logic that should be included in the application and where it should execute.

(3)

• Weaving: This is the process of inserting aspects into the application code at the appropriate point. For compile-time AOP solutions, this weaving is generally done at build time. Likewise, for runtime AOP solutions, the weaving process is executed dynamically at runtime. AspectJ supports another weaving mechanism called load-time weaving (LTW), in which it intercepts the underlying JVM class loader and provides weaving to the bytecode when it is being loaded by the class loader.

• Target: An object whose execution flow is modified by an AOP process is referred to as the target object. Often you see the target object referred to as the advised object.

• Introduction: This is the process by which you can modify the structure of an object by introducing additional methods or fields to it. You can use introduction AOP to make any object implement a specific interface without needing the object’s class to implement that interface explicitly.

Don’t worry if you find these concepts confusing; this will all become clear when you see some examples. Also, be aware that you are shielded from many of these concepts in Spring AOP, and some are not relevant because of Spring’s choice of implementation. We will discuss each of these features in the context of Spring as we progress through the chapter.

Types of AOP

As we mentioned earlier, there are two distinct types of AOP: static and dynamic. The difference between them is really the point at which the weaving process occurs and how this process is achieved.

Using Static AOP

In static AOP, the weaving process forms another step in the build process for an application. In Java terms, you achieve the weaving process in a static AOP implementation by modifying the actual bytecode of your application, changing and extending the application code as necessary. This is a well-performing way of achieving the weaving process because the end result is just Java bytecode, and you do not perform any special tricks at runtime to determine when advice should be executed.

The drawback of this mechanism is that any modifications you make to the aspects, even if you simply want to add another joinpoint, require you to recompile the entire application. AspectJ’s compile-time weaving is an excellent example of a static AOP implementation.

Using Dynamic AOP

Dynamic AOP implementations, such as Spring AOP, differ from static AOP implementations in that the weaving process is performed dynamically at runtime. How this is achieved is implementation-dependent, but as you will see, Spring’s approach is to create proxies for all advised objects, allowing for advice to be invoked as required. The drawback of dynamic AOP is that, typically, it does not perform as well as static AOP, but the performance is steadily increasing. The major benefit of dynamic AOP implementations is the ease with which you can modify the entire aspect set of an application without needing to recompile the main application code.

Choosing an AOP Type

Choosing whether to use static or dynamic AOP is quite a hard decision. Both have their own benefits, and you are not restricted to using only one type. In general, static AOP implementations have been around longer and tend to have more feature-rich implementations, with a greater number of available joinpoints. Typically, if performance is absolutely critical or you need an AOP feature that is not implemented in Spring, you will want to use AspectJ. In most

(4)

other cases, Spring AOP is ideal. Keep in mind that many AOP-based solutions such as transaction management are already provided for you by Spring, so check the framework capabilities before rolling your own!

As always, let the requirements of your application drive your choice of AOP implementation, and don’t restrict yourself to a single implementation if a combination of technologies would better suit your application. In general, Spring AOP is less complex than AspectJ, so it tends to be an ideal first choice.

AOP in Spring

Spring’s AOP implementation can be viewed as two logical parts. The first part is the AOP core, which provides fully decoupled, purely programmatic AOP functionality (also known as the Spring AOP API). 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.

The AOP Alliance

The AOP Alliance (http://aopalliance.sourceforge.net/) is a joint effort among representatives of many open source AOP projects to define a standard set of interfaces for AOP implementations. 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, let's take a look at an example. We will take a simple class that outputs the message “World,” and then using AOP, transform an instance of this class at runtime to output “Hello World!” instead. Listing 5-1 shows the basic MessageWriter class.

Listing 5-1. The MessageWriter Class package com.apress.prospring4.ch5;

public class MessageWriter { public void writeMessage() { System.out.print("World");

} }

With our message-printing method implemented, let’s advise—which in AOP terms means add advice to—this method so that writeMessage() prints “Hello World!” instead.

To do this, we need to execute code prior to the existing body executing (to write “Hello”), and we need to execute code after that method body executes (to write “!”). In AOP terms, what we need is around advice, which executes around a joinpoint. In this case, the joinpoint is the invocation of the writeMessage() method. Listing 5-2 shows the code of the MessageDecorator class, which acts as our around-advice implementation.

Listing 5-2. Implementing Around Advice package com.apress.prospring4.ch5;

import org.aopalliance.intercept.MethodInterceptor;

import org.aopalliance.intercept.MethodInvocation;

(5)

public class MessageDecorator implements MethodInterceptor { @Override

public Object invoke(MethodInvocation invocation) throws Throwable { System.out.print("Hello ");

Object retVal = invocation.proceed();

System.out.println("!");

return retVal;

} }

The MethodInterceptor interface is a standard AOP Alliance 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 allowed to proceed. Because this is around advice, we are capable of performing actions before the method is invoked as well as after it is invoked but before it returns.

In Listing 5-2, we simply write “Hello” to console output, invoke the method with a call to MethodInvocation.

proceed(), and then write “!” to console output.

The final step in this sample is to weave the MessageDecorator advice (more specifically, the invoke() method) 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 5-3.

Listing 5-3. Weaving the MessageDecorator Advice package com.apress.prospring4.ch5;

import org.springframework.aop.framework.ProxyFactory;

public class HelloWorldAOPExample {

public static void main(String[] args) { MessageWriter target = new MessageWriter();

ProxyFactory pf = new ProxyFactory();

pf.addAdvice(new MessageDecorator());

pf.setTarget(target);

MessageWriter proxy = (MessageWriter) pf.getProxy();

target.writeMessage();

System.out.println("");

proxy.writeMessage();

} }

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.

Running the code from Listing 5-3 results in the following output:

World Hello World!

(6)

As you can see, calling writeMessage() on the untouched target object results in a standard method invocation, and no extra content is written to console output. However, the invocation of the proxy causes the code in

MessageDecorator to execute, creating the desired “Hello World!” output. 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 ProxyFactory to create a proxy instance of that class, first providing 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 declarative AOP configuration mechanisms provided by Spring (the ProxyFactoryBean class, the aop namespace, and @AspectJ- style annotations) to take advantage of declarative proxy creation. However, it is important to understand how proxy creation works, so we will first use the programmatic approach to proxy creation and then dive into Spring’s declarative AOP configurations.

At runtime, Spring analyzes the crosscutting concerns defined for the beans in ApplicationContext and generates proxy beans (which wrap the underlying target bean) dynamically. Instead of calling the target bean directly, callers are injected with the proxied bean. The proxy bean then analyzes the running condition (that is, joinpoint, pointcut, or advice) and weaves in the appropriate advice accordingly. Figure 5-1 shows a high-level view of a Spring AOP proxy in action.

Figure 5-1. Spring AOP proxy in action

Internally, Spring has two proxy implementations: JDK dynamic proxies and CGLIB proxies. By default, when the target object to be advised implements an interface, Spring will use a JDK dynamic proxy to create proxy instances of the target. However, when the advised target object doesn’t implement an interface (for example, it’s a concrete class), CGLIB will be used for proxy instance creation. One major reason is that JDK dynamic proxy supports only the proxying of interfaces. We discuss proxies in detail in the section “Understanding Proxies.”

(7)

Joinpoints in Spring

One of the more noticeable simplifications in Spring AOP is that it supports only one joinpoint type: method

invocation. At first glance, this might seem like a severe limitation if you are familiar with other AOP implementations such as AspectJ, which supports many more joinpoints, but in fact this 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 convenience Advisor implementations that you can reuse in your applications, thus removing the need for you to create custom Advisor implementations. There are two subinterfaces of Advisor: PointcutAdvisor and IntroductionAdvisor.

The PointcutAdvisor interface is implemented by all Advisor implementations that use pointcuts to control the advice applied to joinpoints. In Spring, introductions are treated as special kinds of advice, and by using the IntroductionAdvisor interface, you can control those classes to which an introduction applies.

We discuss PointcutAdvisor implementations in detail in the upcoming section “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 create a proxy, you must specify the advised or target object. You can do this, as you saw earlier, by 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.

The ProxyFactory class provides the addAdvice() method that you saw in Listing 5-3 for cases where you want advice to apply to the invocation of all methods in a class, not just a selection. 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 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 a ProxyFactory has particular advice attached to it, call adviceIncluded(), passing in the advice object for which you want to check.

Creating Advice in Spring

Spring supports six flavors of advice, described in Table 5-1.

(8)

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. 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. In case the before advice throws an exception, further execution of the interceptor chain (as well as the target method) will be aborted, and the exception will propagate back up the interceptor chain.

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.

In case the target method throws an exception, the after- returning advice will not be run, and the exception will be propagated up to the call stack as usual.

After (finally) org.springframework.aop .AfterAdvice

After-returning advice is executed only when the advised method completes normally. However, the after (finally) advice will be executed no matter the result of the advised method. The advice is executed even when the advised method fails and an exception is thrown.

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

Table 5-1. Advice Types in Spring

(9)

Interfaces for Advice

From our previous discussion of the ProxyFactory class, recall that advice is added to a proxy either directly, by using the addAdvice() method, or indirectly, by using Advisor with the addAdvisor() method. The main difference between Advice and Advisor is that Advisor carries Advice with the associated Pointcut, which provides more fine-grained control on which joinpoints the Advice will intercept. With regard to advice, Spring has created a well- defined hierarchy for advice interfaces. This hierarchy is based on the AOP Alliance interfaces and is shown in detail in Figure 5-2.

Figure 5-2. Interfaces for Spring advice types

This kind of hierarchy has the benefit of not only being sound OO design but also enabling you to deal with advice types generically, such as by 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. This advice can modify the arguments passed to a method and can prevent the method from executing by raising an exception. In this section, we show you two simple examples of using before advice: one that writes a message to console output containing the name of the method before the method executes, and another that you can use to restrict access to methods on an object.

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

(10)

Listing 5-4. The SimpleBeforeAdvice Class package com.apress.prospring4.ch5;

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();

ProxyFactory pf = new ProxyFactory();

pf.addAdvice(new SimpleBeforeAdvice());

pf.setTarget(target);

MessageWriter proxy = (MessageWriter) pf.getProxy();

proxy.writeMessage();

}

@Override

public void before(Method method, Object[] args, Object target) throws Throwable {

System.out.println("Before method: " + method.getName());

} }

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 console output 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 SimpleBeforeAdvice.

Securing Method Access by Using Before Advice

In this section, we are going to implement 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.

(11)

Note

■ this is just an example demonstrating the use of before advice. For securing the method execution of Spring beans, the Spring Security project already provides comprehensive support; you don’t need to implement the features by yourself.

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

Listing 5-5. The SecureBean Class package com.apress.prospring4.ch5;

public class SecureBean {

public void writeSecureMessage() {

System.out.println("Every time I learn something new, "

+ "it pushes some old stuff out of my brain");

} }

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 5-6 shows the UserInfo class we use to store a user’s credentials.

Listing 5-6. The UserInfo Class package com.apress.prospring4.ch5;

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;

} }

This class simply holds data about the user so that we can do something useful with it. Listing 5-7 shows the SecurityManager class, which is responsible for authenticating users and storing their credentials for later retrieval.

(12)

Listing 5-7. The SecurityManager Class package com.apress.prospring4.ch5;

public class SecurityManager {

private static ThreadLocal<UserInfo> threadLocal = new ThreadLocal<UserInfo>();

public void login(String userName, String password) { threadLocal.set(new UserInfo(userName, password));

}

public void logout() { threadLocal.set(null);

}

public UserInfo getLoggedOnUser() { return threadLocal.get();

} }

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 by using the login() method. In a real application, the login() method would probably check the supplied credentials against a database or LDAP directory, but here we assume that all users are allowed to authenticate. The login() method creates a UserInfo object for the user and stores it on the current thread by using ThreadLocal. The logout() method sets any value that might be stored in 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.

To check whether a user is authenticated and, if so, whether the user is permitted to access the methods on SecureBean, we need to create 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 5-8.

Listing 5-8. The SecurityAdvice Class package com.apress.prospring4.ch5;

import java.lang.reflect.Method;

import org.springframework.aop.MethodBeforeAdvice;

public class SecurityAdvice implements MethodBeforeAdvice { private SecurityManager securityManager;

public SecurityAdvice() {

this.securityManager = new SecurityManager();

}

@Override

public void before(Method method, Object[] args, Object target) throws Throwable {

UserInfo user = securityManager.getLoggedOnUser();

(13)

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 ("chris".equals(user.getUserName())) {

System.out.println("Logged in user is chris - 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());

} } }

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 SecurityAdvice don’t need to share the same SecurityManager instance, because all data is stored with the current thread by using ThreadLocal. In the before() method, we perform a simple check to see whether the username of the authenticated user is chris. 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 5-9, you can see a sample application that uses the SecurityAdvice class to secure the SecureBean class.

Listing 5-9. The SecurityExample Class package com.apress.prospring4.ch5;

import org.springframework.aop.framework.ProxyFactory;

public class SecurityExample {

public static void main(String[] args) {

SecurityManager mgr = new SecurityManager();

SecureBean bean = getSecureBean();

mgr.login("chris", "pwd");

bean.writeSecureMessage();

mgr.logout();

try {

mgr.login("invaliduser", "pwd");

bean.writeSecureMessage();

} catch(SecurityException ex) {

System.out.println("Exception Caught: " + ex.getMessage());

} finally { mgr.logout();

}

(14)

try {

bean.writeSecureMessage();

} catch(SecurityException ex) {

System.out.println("Exception Caught: " + ex.getMessage());

} }

private static SecureBean getSecureBean() { SecureBean target = new SecureBean();

SecurityAdvice advice = new SecurityAdvice();

ProxyFactory factory = new ProxyFactory();

factory.setTarget(target);

factory.addAdvice(advice);

SecureBean proxy = (SecureBean)factory.getProxy();

return proxy;

} }

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 scenarios, invoking the SecureBean.writeSecureMessage() method with two sets of user credentials and then no user credentials at all.

Because SecurityAdvice allows method calls to proceed only if the currently authenticated user is chris, we expect that the only successful scenario in Listing 5-9 is the first. Running this example gives the following output:

Logged in user is chris - OKAY!

Every time I learn something new, it pushes some old stuff out of my brain Logged in user is invaliduser NOT GOOD :(

Exception Caught: User invaliduser 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 exception thrown by SecurityAdvice.

This example is simple, but it does highlight the usefulness of before advice. Security is a typical example of before advice, but we also find it useful when a scenario demands the modification of arguments passed to the method.

Creating After-Returning Advice

After-returning advice is executed after the method invocation at the joinpoint returns. Given that the method has already executed, you can’t change the arguments that are passed to it. Although you can read these arguments, you can’t change the execution path, and you can’t prevent the method from executing. These restrictions are expected;

what is not expected, however, is that you cannot modify the return value in the after-returning advice. When using after-returning advice, you are limited to adding 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.

(15)

In this section, we look at two examples of using after-returning advice in an application. The first example simply writes a message to console output after the method has been invoked. The second example shows how you can use after-returning advice to add error checking to a method. Consider a class, KeyGenerator, which generates keys for cryptographic purposes. Many cryptographic algorithms suffer from the problem that a small number of keys 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 key-space, 4 keys are considered weak, and another 12 are considered semi-weak. Although the chance of one of these keys being generated randomly is small (1 in 252), testing for the keys is so simple that it's worthwhile to do so. In the second example of this section, we build after-returning advice that checks for weak keys generated by the KeyGenerator, raising an exception if one is found.

Note

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

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

Listing 5-10. The SimpleAfterReturningAdvice Class package com.apress.prospring4.ch5;

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();

ProxyFactory pf = new ProxyFactory();

pf.addAdvice(new SimpleAfterReturningAdvice());

pf.setTarget(target);

MessageWriter proxy = (MessageWriter) pf.getProxy();

proxy.writeMessage();

}

@Override

public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable { System.out.println("");

System.out.println("After method: " + method.getName());

} }

(16)

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 after- returning advice, we can advise the method that generates the key and performs this additional check. Listing 5-11 shows an extremely primitive key generator.

Listing 5-11. The KeyGenerator Class package com.apress.prospring4.ch5;

import java.util.Random;

public class KeyGenerator {

protected static final long WEAK_KEY = 0xFFFFFFF0000000L;

protected 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;

}

return STRONG_KEY;

} }

This key generator should not be considered secure. It’s purposely simple for this example and has a one-in-three chance of producing a weak key. In Listing 5-12, you can see the WeakKeyCheckAdvice that checks to see whether the result of the getKey() method is a weak key.

Listing 5-12. Checking for Weak Keys package com.apress.prospring4.ch5;

import java.lang.reflect.Method;

import org.springframework.aop.AfterReturningAdvice;

public class WeakKeyCheckAdvice implements AfterReturningAdvice { @Override

(17)

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");

} } } }

In the afterReturning() method, we check first to see whether the method that was executed at the joinpoint was the getKey() method. If so, we then check the result value to see whether 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 5-13 shows a simple application that demonstrates the use of this advice.

Listing 5-13. Testing the WeakKeyCheckAdvice Class package com.apress.prospring4.ch5;

import org.springframework.aop.framework.ProxyFactory;

public class AfterAdviceExample {

private static KeyGenerator getKeyGenerator() { KeyGenerator target = new KeyGenerator();

ProxyFactory factory = new ProxyFactory();

factory.setTarget(target);

factory.addAdvice(new WeakKeyCheckAdvice());

return (KeyGenerator)factory.getProxy();

}

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!");

} } } }

(18)

After creating an advised proxy of the KeyGenerator target, the AfterAdviceExample class attempts to generate ten keys. If a SecurityException is thrown during a single generation, a message is written to the console, 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:

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

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

Key: 48658904092028502 Key: 48658904092028502

As you can see, the KeyGenerator class sometimes generates weak keys, as expected, and WeakKeyCheckAdvice ensures that 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, but you can prevent the method from executing. This means that by 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 by 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 to the first example in Listing 5-2, which shows how to use a basic method interceptor to write 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 MethodBeforeAdvice and AfterReturningAdvice. The method is not passed the target of the invocation, the method that was invoked, nor the arguments used. However, you can access this data by 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 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 StopWatch before the method invocation and stop it right afterward.

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

Listing 5-14. The WorkerBean Class package com.apress.prospring4.ch5;

public class WorkerBean {

public void doSomeWork(int noOfTimes) { for(int x = 0; x < noOfTimes; x++) { work();

} }

(19)

private void work() { System.out.print("");

} }

This is a 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 5-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 5-14.

Listing 5-15. The ProfilingInterceptor Class package com.apress.prospring4.ch5;

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 { @Override

public Object invoke(MethodInvocation invocation) throws Throwable { 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");

} }

(20)

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 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, 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 console output, 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 5-16 shows the ProfilingExample class that first advises an instance of WorkerBean with a ProfilingInterceptor and then profiles the doSomeWork() method.

Listing 5-16. The ProfilingExample Class package com.apress.prospring4.ch5;

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();

} }

Running this example on our machine produces the following output:

Executed method: doSomeWork

On object of type: com.apress.prospring4.ch5.WorkerBean With arguments:

> 10000000 Took: 477 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.

(21)

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 executes only if the method throws an exception. Throws advice is also similar to after- returning advice in that it has little control over program execution. If you are using 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 quite a powerful concept and can make application development much simpler. Consider a situation where you have an API that throws an array of poorly defined exceptions. Using 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 5-2, 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 5-17 shows a simple bean with two methods that both simply throw exceptions of different types.

Listing 5-17. The ErrorBean Class package com.apress.prospring4.ch5;

public class ErrorBean {

public void errorProneMethod() throws Exception { throw new Exception("Foo");

}

public void otherErrorProneMethod() throws IllegalArgumentException { throw new IllegalArgumentException("Bar");

} }

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

Listing 5-18. The SimpleThrowsAdvice Class package com.apress.prospring4.ch5;

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());

(22)

ErrorBean proxy = (ErrorBean) pf.getProxy();

try {

proxy.errorProneMethod();

} catch (Exception ignored) { }

try {

proxy.otherErrorProneMethod();

} catch (Exception ignored) { }

}

@Override

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");

}

@Override

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");

} }

The first thing Spring looks for in 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

***

(23)

***

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 invokes a single afterThrowing() method only for each Exception, and as you saw from the example in Listing 5-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.

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, choosing an advice type 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 around advice when before advice will do. In most cases, 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 advice that counts method calls. When you are using before advice, all you need to code is the counter, but with 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.

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. 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 the advice applies.

Of course, you could simply perform the checking in the advice itself that the method 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. Other drawbacks with hard-coding the list of methods into the advice are performance related. To inspect 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 nonadvised methods when creating a proxy, which results in faster invocations on nonadvised methods. These optimizations are covered in greater detail when we discuss proxies later in the chapter.

We 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

(24)

class it is advising, and it is wise to check inside the advice to ensure that it is applied to the correct 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 advice is misused. We also recommend 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 5-19.

Listing 5-19. The Pointcut Interface package org.springframework.aop;

public interface Pointcut { ClassFilter getClassFilter ();

MethodMatcher getMethodMatcher();

}

As you can see from this code, the Pointcut interface defines two methods, getClassFilter() and getMethodMatcher(), which return instances of ClassFilter and MethodMatcher, respectively. Obviously, if you choose to implement the Pointcut interface, you will need to implement these methods. Thankfully, as you will see in the next section, this is usually unnecessary because Spring provides a selection of Pointcut implementations that cover most, if not all, of your use cases.

When determining whether a Pointcut applies to a particular method, Spring first checks to see whether the Pointcut applies to the method’s class by using the ClassFilter instance returned by Pointcut.getClassFilter().

Listing 5-20 shows the ClassFilter interface.

Listing 5-20. The ClassFilter Interface org.springframework.aop;

public interface ClassFilter { boolean matches(Class<?> clazz);

}

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 shown in Listing 5-21.

Listing 5-21. The MethodMatcher Interface package org.springframework.aop;

public interface MethodMatcher {

boolean matches(Method m, Class<?> targetClass);

boolean isRuntime();

boolean matches(Method m, Class<?> targetClass, Object[] args);

}

(25)

Spring supports two types of MethodMatcher, static and dynamic, which are determined by the return value of isRuntime(). Before using MethodMatcher, Spring calls isRuntime() to determine whether 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<T>) 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 by using matches(Method, Class<T>) 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 by using the matches(Method, Class<T>, 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. For example, a pointcut needs to be applied only when the argument is an Integer with a value larger than 100. In this case, the matches(Method, Class<T>, Object[]) method can be coded to perform further checking on the argument for each invocation.

Clearly, static pointcuts perform much better than dynamic pointcuts because they avoid the need for an additional check per invocation. Dynamic pointcuts provide a greater level of flexibility for deciding whether to apply advice. In general, we recommend 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

As of version 4.0, Spring provides eight implementations of the Pointcut interface: two abstract classes intended as convenience classes for creating static and dynamic pointcuts, and six concrete classes, one for each of the following:

Composing multiple pointcuts together

Handling control flow pointcuts

Performing simple name-based matching

Defining pointcuts using regular expressions

Defining pointcuts using AspectJ expressions

Defining pointcuts that look for specific annotations at the class or method level

Table 5-2 summarizes the eight Pointcut interface implementations.

(26)

Figure 5-3 shows the Unified Modeling Language (UML) diagram for the Pointcut implementation classes.

Table 5-2. Summary of Spring Pointcut Implementations

Implementation Class Description

org.springframework.aop.support .annotation.AnnotationMatchingPointcut

Pointcut that looks for specific Java annotation on a class or method. This class requires JDK 5 or higher.

org.springframework.aop

.aspectj.AspectJExpressionPointcut

Pointcut that uses AspectJ weaver to evaluate a pointcut expression in AspectJ syntax.

org.springframework.aop .support.ComposablePointcut

The ComposablePointcut class is used to compose two or more pointcuts together with operations such as union() and intersection().

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.

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

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

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

(27)

Using DefaultPointcutAdvisor

Before you can use any Pointcut implementation, you must first create an instance of the Advisor interface, or more specifically a PointcutAdvisor interface. Remember from our earlier discussions that an Advisor is Spring’s representation of an aspect (in the previous section “Aspects in Spring”), a coupling of advice and pointcuts that governs which methods should be advised and how they should be advised. Spring provides a number of implementations of PointcutAdvisor, but for now we concern ourselves with just one, DefaultPointcutAdvisor.

DefaultPointcutAdvisor is a simple PointcutAdvisor for associating a single Pointcut with a single Advice.

Creating a Static Pointcut by Using StaticMethodMatcherPointcut

In this section, we will create a simple static pointcut by extending the abstract StaticMethodMatcherPointcut class.

Since the StaticMethodMatcherPointcut class extends the StaticMethodMatcher class (an abstract class too), which implements the MethodMatcher interface, you are required to implement the method matches(Method, Class<?>);

Figure 5-3. Pointcut implementation classes

(28)

the rest of the Pointcut implementation is handled automatically. Although this is the only method you are required to implement (when extending the StaticMethodMatcherPointcut class), 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 5-22 shows the BeanOne class.

Listing 5-22. The BeanOne Class package com.apress.prospring4.ch5;

public class BeanOne { public void foo() {

System.out.println("foo");

}

public void bar() {

System.out.println("bar");

} }

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

Listing 5-23. The SimpleStaticPointcut Class package com.apress.prospring4.ch5;

import java.lang.reflect.Method;

import org.springframework.aop.ClassFilter;

import org.springframework.aop.support.StaticMethodMatcherPointcut;

public class SimpleStaticPointcut extends StaticMethodMatcherPointcut { @Override

public boolean matches(Method method, Class<?> cls) { return ("foo".equals(method.getName()));

}

@Override

public ClassFilter getClassFilter() { return new ClassFilter() {

public boolean matches(Class<?> cls) { return (cls == BeanOne.class);

} };

} }

Here you can see that we implemented the matches(Method, Class<?>) method as required by the StaticMethodMatcher abstract 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

(29)

ClassFilter instance whose matches() method returns true only 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 5-24 shows the SimpleAdvice class that simply writes out a message on either side of the method invocation.

Listing 5-24. The SimpleAdvice Class package com.apress.prospring4.ch5;

import org.aopalliance.intercept.MethodInterceptor;

import org.aopalliance.intercept.MethodInvocation;

public class SimpleAdvice implements MethodInterceptor { @Override

public Object invoke(MethodInvocation invocation) throws Throwable { System.out.println(">>Invoking " + invocation.getMethod().getName());

Object retVal = invocation.proceed();

System.out.println(">>Done");

return retVal;

} }

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

Listing 5-25. The StaticPointcutExample Class package com.apress.prospring4.ch5;

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;

Pointcut pc = new SimpleStaticPointcut();

Advice advice = new SimpleAdvice();

Advisor advisor = new DefaultPointcutAdvisor(pc, advice);

ProxyFactory pf = new ProxyFactory();

pf.addAdvisor(advisor);

pf.setTarget(one);

proxyOne = (BeanOne)pf.getProxy();

參考文獻

相關文件

• To the right of the Draw mode buttons you find push buttons through which you can access all the functions that you need to define and solve the PDE problem: define

 If I buy a call option from you, I am paying you a certain amount of money in return for the right to force you to sell me a share of the stock, if I want it, at the strike price,

The hashCode method for a given class can be used to test for object equality and object inequality for that class. The hashCode method is used by the java.util.SortedSet

Now, nearly all of the current flows through wire S since it has a much lower resistance than the light bulb. The light bulb does not glow because the current flowing through it

find a bosom friend (Analects: Selected Reading (II): Methods of choosing friends”) and guides students to carry out a class discussion to understand the methods

For example, Liu, Zhang and Wang [5] extended a class of merit functions proposed in [6] to the SCCP, Kong, Tuncel and Xiu [7] studied the extension of the implicit Lagrangian

By exploiting the Cartesian P -properties for a nonlinear transformation, we show that the class of regularized merit functions provides a global error bound for the solution of

/** Class invariant: A Person always has a date of birth, and if the Person has a date of death, then the date of death is equal to or later than the date of birth. To be