|
Title | Building Spring 2 Enterprise Applications | Author(s) | Interface21, Bram Smeets, Seth Ladd | Publisher | Apress | Published | August 2007 | ISBN | 978-1-59059-918-1 | Pages | 335 |
|
Welcome to the chapter on the future of Spring AOP. In Chapter 3, we described how Spring AOP has been used up to the Spring 1.2.x releases. This chapter covers features added to Spring AOP in the 2.0 release.
That's right, new features have been added, so everything you learned about AOP so far is still applicable and available. This really is proof that the Spring 2.0 release remains fully backward compatible with Spring 1.2.x. We strongly recommend upgrading your Spring version to the latest 2.0 release. Full backward-compatibility is assured.
If you haven't done so already, now is a good time to review the concepts covered in Chapter 3, as they continue to be the foundations of Spring AOP.
The following are the new features that will be covered in detail in this chapter:
- The @AspectJ-style of writing aspects with Java 5 annotations, including the supported advice types
- The AspectJ pointcut language
- The Spring AOP XML tags to declare aspects in XML for those cases where Java 5 is not available or existing classes must be used as advice
- The Spring AOP XML advisor tag to combine classic Spring AOP advice classes and the AspectJ pointcut language
Introducing AspectJ and Aspects
While classic Spring AOP (covered in Chapter 3) works with advice, pointcuts, and advisors, the new Spring AOP works with advice, pointcuts, advisors, and
aspects. Not much of a difference you may think, but as you'll find out soon, things have changed significantly. Literally all the new Spring AOP features are built on top of the integration with the AspectJ AOP framework. (The proxy-based interception mechanism remains in place, so the skills you've gained from the previous chapter will remain useful.) So what is AspectJ? The AspectJ FAQ (http://www.eclipse.org/aspectj/doc/released/ faq.html) answers this question as follows:
AspectJ is a simple and practical extension to the Java programming language that adds to Java aspect-oriented programming (AOP) capabilities. AOP allows developers to reap the benefits of modularity for concerns that cut across the natural units of modularity. In objectoriented programs like Java, the natural unit of modularity is the class. In AspectJ, aspects modularize concerns that affect more than one class.
And what is an aspect? That is also answered by the same FAQ as follows:
Aspects are how developers encapsulate concerns that cut across classes, the natural unit of modularity in Java.
From the previous chapter, you know that cross-cutting concerns are modularized as advice. These are
encapsulated by an advisor, which combines one advice and one pointcut. This encapsulation tells at which join points in the software the advice is executed.
Aspects and advisors seem to have much in common: they both encapsulate concerns that cut across classes. Advice is executed at join points that are matched by a pointcut; however, a given pointcut may not match any join points in an application.
Now let's look at what you can do with an aspect:
- You can declare pointcuts.
- You can declare errors and warnings for each join point that is selected by the associated pointcut.
- You can declare new fields, constructors, and methods in classes. These are called inter-type declarations in AspectJ.
- You can declare one or more advices, each one executed for all joint points matched by a pointcut.
When comparing the two, it quickly becomes clear an aspect is a much more sophisticated construct than an advisor. For now, it's sufficient to understand aspects and advisors both encapsulate cross-cutting concerns yet take a different approach.
Join Points and Pointcuts in AspectJ
AspectJ supports many more join point types than Spring AOP, which supports only method executions. The following is a selection of join points supported by AspectJ:
- Calls to methods and execution of instance and static methods
- Calls to get and set values on instance fields and static fields
- Calls to constructors and execution of constructors
- Classes and packages
None of these additional join points are featured in Spring AOP. However, it's useful to have an idea about which join points are supported by AspectJ when discussing pointcuts.
To select the rich set of supported join points, AspectJ has its own pointcut language. The following pointcut selects all static and instance methods named relax
, regardless of their arguments, return type, or classes:
execution(* relax(..))
When you consider all the join point types supported by AspectJ, a proper language is the only flexible way to define pointcuts. Any other means, including XML configuration or an API, would be a nightmare to write, read, and maintain.
Spring AOP integrates with this AspectJ pointcut language, which is covered later in this chapter, in the "Working with Pointcuts" section. For now, all you need to know is that the asterisk (*) matches any method or class name or any argument type, and the double dot (..) matches zero or more arguments.
AspectJ Aspect Creation
AspectJ has its own language that extends the Java language specifications for creating aspects.
Originally, this was the only way to declare aspects with AspectJ. Because aspects and pointcuts are treated as first-class citizens, it's a very practical AOP language. Spring AOP does not integrate with this language, but to give you a better understanding of AspectJ aspects, here's a very simple example:
package com.apress.springbook.chapter04.aspects;
public aspect MySimpleAspectJAspect {
before(): execution(* relax(..)) {
System.out.println("relax() method is about to be executed!");
}
}
As you can see, the aspect is somewhat comparable to a Java class, but you wouldn't be able to compile it with a regular Java compiler.
AspectJ 1.5 has introduced Java 5 annotations to allow programmers to write AspectJ aspects as an alternative to the AspectJ language. (If you're not familiar with Java 5 annotations, you can find an introduction at http://www.developer.com/java/other/article.php/3556176.) Spring AOP integrates with this way of writing aspects, as detailed in this chapter. Listing 4-1 shows how the previous aspect looks when it's rewritten with annotations. This style is called the @AspectJ-style, although the @Aspect
annotation is used. As you can see, aspects become regular Java classes.
Listing 4-1. A Simple AspectJ Aspect Written in the @AspectJ-Style
package com.apress.springbook.chapter04.aspects;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
@Aspect
public class MySimpleAtAspectJAspect {
@Before("execution(* relax(..))")
public void beforeRelaxingMethod() {
System.out.println("relax() method is about to be executed!");
}
}
The aspect in Listing 4-1 declares a regular Java class that is annotated with the @AspectJ-style Java 5 annotations. The class declares one pointcut/advice pair. The
@Aspect
annotation on the class declaration indicates that this class is an @AspectJ-style aspect. A class needs to have this annotation to qualify as an aspect.
The @Before
annotation is used to turn the regular beforeRelaxingMethod()
method into an advice declaration and holds the pointcut declaration for the advice. In AspectJ, an advice cannot exist without a pointcut.
The annotation type also defines the advice type; in this case, it's before advice. The @AspectJstyle supports the advice types defined in Chapter 3 plus one more. Only instance methods with an @AspectJ advice type annotation are advice declarations, so an aspect class can also have regular methods.
The @AspectJ
annotations can be used on abstract classes and even interfaces, although this is not very useful, as the annotations are not inherited.
Listing 4-2 shows a class with one method that will be one of the join points matched by the pointcut in Listing 4-1.
Listing 4-2. The relax()
method in the SunnyDay Class Is Selected As a Join Point
package com.apress.springbook.chapter04;
public class SunnyDay {
public void relax() {
}
}
Before the
relax()
method is executed, a message will be printed on the console. The print statement is the actual advice that is executed. The @AspectJ-style requires Java 5. Also, existing classes that don't declare the
@AspectJ
annotations cannot be used as advice.
In the typical Spring style, you can declare aspects in Spring AOP without using Java 5 and annotations. By making clever use of the Spring 2.0 XML Schema support (introduced in Chapter 2), the Spring developers have been able to define AOP tags for declaring aspects, advice, and pointcuts. There is also a new tag to declare advisors. This chapter covers these new XML tags after introducing the @AspectJ-style of declaring aspects and the pointcut language in more detail.
Now, without further ado, here comes Spring 2.0 AOP.
Note
You can find much more information about AspectJ at http://www.eclipse.org/aspectj/ . Another excellent resource is AspectJ in Action by Ramnivas Laddad (Manning, 2003).
Configuring @AspectJ-Style Aspects in Spring
By now, you know what an aspect looks like and how you can write one yourself. In this section, we'll start with an example of an @AspectJ-style aspect that's configured in the Spring container.
This will demonstrate how the Spring AOP framework uses aspects and creates proxy objects. After the example, we'll look at the details of advice types, pointcuts, and proxy objects.
A Simple @AspectJ-Style Aspect
@AspectJ-style aspects must be configured in the Spring container to be usable by Spring AOP. From the previous chapter, you'll remember proxy objects were created by using
ProxyFactoryBean
in the Spring container. In that case, we took our first AOP steps with a configuration per target object to create proxy objects. With @AspectJ-style aspects, Spring AOP takes a different approach to creating proxy objects based on the pointcuts in aspects, as this example will demonstrate. In this example, we'll use one simple pointcut so we can focus on the aspects. As the chapter progresses, we'll use more elaborate pointcut examples.
Aspect Definition
The aspect for this example is shown in Listing 4-3. It has one pointcut that selects all
startMatch()
methods it can find and an advice that prints a message to the console when this occurs. In the next sections, we'll look in more detail at how join points are searched for and what happens if they are found.
Listing 4-3. Aspect with Pointcut That Selects All startMatch()
methods and Advice That Prints a Message Before the Join Point Is Executed
package com.apress.springbook.chapter04.aspects;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
@Aspect
public class MessagePrintingAspect {
@Before("execution(* startMatch(..))")
public void printMessageToInformMatchStarts() {
System.out.println("Attempting to start tennis match!");
}
}
Note
You need to include the aspectjweaver.jar
and aspectjrt.jar
in your classpath. Both files can be found in the Spring Framework distribution under the lib/aspectj
directory.
The MessagePrintingAspect in Listing 4-3 is a regular Java class with Java 5 annotations. It's also an aspect declaration because of the @AspectJ-style annotations.
The @Aspect
annotation on the class declaration turns this class into an aspect declaration.
It can now hold pointcut declarations and advice/pointcut combinations. The aspect is called MessagePrintingAspect, indicating its responsibility is to print messages to the console. When we want to print messages for other join points, we can add more advice/pointcut combinations to this aspect. By organizing (or modularizing) advice that logically belongs together in aspects, it will be trivial to get an overview of which messages are printed to the console for which join points.
The @Before
annotation on the printMessageToInformMatchStarts()
method declaration has two roles: it defines the advice type (before advice), and it holds the pointcut declaration. Again, we've chosen a name, printMessageToInformMatchStarts<code>, that explains the responsibilities of the advice.
Tip
Giving descriptive names to advice helps to organize your thoughts and organize advices. If you're having trouble coming up with names for your advices that exactly describe what they do, maybe they're overloaded with responsibilities and should be split into smaller parts.
The pointcut declaration selects all instance methods named startMatch()
, regardless of the number of arguments, argument types, throws declarations, return type, visibility, or classes that declare them. Now that you understand the aspect declaration, it's time to look at the target class of this example.
Target Class
The target class in this example is our friend
DefaultTournamentMatchManager
, as shown in Listing 4-4.
Listing 4-4. DefaultTournamentMatchManager Class
package com.apress.springbook.chapter04;
public class DefaultTournamentMatchManager implements TournamentMatchManager {
public Match startMatch(long matchId) throws
UnknownMatchException, MatchIsFinishedException,
MatchCannotBePlayedException, PreviousMatchesNotFinishedException {
}
}
The
startMatch()
method matches the criteria of the pointcut in Listing 4-3. This doesn't mean, however, that Spring AOP will start creating proxy objects just like that. First, we must configure a target object and the @AspectJ-style aspect in the Spring container, as discussed in the next section.
Aspect Configuration
Listing 4-5 shows the required configuration in a Spring XML configuration file to have the printMessageToInformMatchStarts advice print a message to the console before the
startMatch()
method is executed (there is another way to do this, which we'll explore in the "Using AOP XML Tags" section later in this chapter).
Listing 4-5. aspect-config.xml: Required Configuration in a Spring XML File
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN"
"http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
<bean class="org.springframework.aop.aspectj.annotation. -->
AnnotationAwareAspectJAutoProxyCreator"/>
<bean class="com.apress.springbook.chapter04.aspects.MessagePrintingAspect"/>
<bean id="tournamentMatchManager"
class="com.apress.springbook.chapter04.DefaultTournamentMatchManager">
<!-- properties omitted -->
</bean>
</beans>
Spring AOP provides a powerful integration with the Spring container called
auto-proxy creation. Spring AOP will extend the bean life cycle of the Spring container to create proxy objects for those beans in the container that have join points that are matched by one or more pointcuts.
We'll look into the details of how the proxy is created in the next sections. For now, it's sufficient to understand that an object for the AnnotationAwareAspectJAutoProxyCreator bean definition in Listing 4-5 will be created first when the Spring container (ApplicationContext) loads. Once this is done, the Spring container detects any classes that have @Aspect
annotation and uses them to configure Spring AOP.
The AnnotationAwareAspectJAutoProxyCreator bean has the potential to affect all other beans that are created by the container. During the bean life cycle of the tournamentMatchManager bean, AnnotationAwareAspectJAutoProxyCreator will create a proxy object for this bean and replace the original bean with the proxy object because one of its join points (the startMatch()
method) is matched by the advice/pointcut combination in the MessagePrintingAspect.
The printMessageToInformMatchStarts advice will be called when the startMatch()
method is executed on the tournamentMatchManager bean. Now, let's find out if the printMessageToInform MatchStarts advice actually gets called and prints a message before the startMatch()
method is executed.
An Integration Test for the Configuration and Aspect
We can now use a simple integration test to verify if the message is printed to the console when the startMatch() method is called on the tournamentMatchManager bean. We'll also add a test that creates a new DefaultTournamentMatchManager object and calls its
startMatch()
method to verify that
no message is printed when this method is called. Listing 4-6 shows the integration test case.
Listing 4-6. Integration Test Case for the Spring AOP Configuration and the Aspect
package com.apress.springbook.chapter04;
import org.springframework.test.AbstractDependencyInjectionSpringContextTests;
public class MessagePrintingAspectIntegrationTests extends
AbstractDependencyInjectionSpringContextTests {
protected String[] getConfigLocations() {
return new String[] {
"classpath:com/apress/springbook/chapter04/" +
"aspect-config.xml"
};
}
private TournamentMatchManager tournamentMatchManager;
public void setTournamentMatchManager(
TournamentMatchManager tournamentMatchManager) {
this.tournamentMatchManager = tournamentMatchManager;
}
public void testCallStartMatchMethodOnBeanFromContainer()
throws Exception {
System.out.println("=== GOING TO CALL METHOD " +
"ON BEAN FROM CONTAINER ===");
this.tournamentMatchManager.startMatch(1);
System.out.println("=== FINISHED CALLING METHOD " +
"ON BEAN FROM CONTAINER ===");
}
public void testCallStartMatchMethodOnNewlyCreatedObject()
throws Exception {
TournamentMatchManager newTournamentMatchManager =
new DefaultTournamentMatchManager();
System.out.println("=== GOING TO CALL METHOD " +
"ON NEWLY CREATED OBJECT ===");
newTournamentMatchManager.startMatch(1);
System.out.println("=== FINISHED CALLING METHOD " +
"ON NEWLY CREATED OBJECT ===");
}
}
The test case in Listing 4-6 loads the Spring XML configuration file (Listing 4-5). It declares two tests:
testCallStartMatchMethodOnBeanFromContainer(): This test uses a tournamentMatchManager object that is injected from the container. This is the tournamentMatchManager bean defined in the Spring XML configuration file. The test calls the startMatch()
method on this object. The tournamentMatchManager bean is a proxy object that has been created by the AnnotationAware AspectJAutoProxyCreator bean. A proxy object was created because the sole pointcut in the MessagePrintingAspect matches the startMatch()
join point. When the startMatch()
method is executed on the proxy object, the printMessageToInformMatchStarts advice, which prints its message to the console, will be executed, and then the actual method on the target object will be executed.
testCallStartMatchMethodOnNewlyCreatedObject(): This test creates a new DefaultTournament MatchManager object. This object is not a proxy and is in no way touched or affected by Spring AOP. When its startMatch()
method is called, no advice will be executed. Because this object is not created by the Spring container, it is not affected by the MessagePrintingAspect.
When the test case in Listing 4-6 is executed, messages will be printed on the console as follows:
=== GOING TO CALL METHOD ON BEAN FROM CONTAINER ===
Attempting to start tennis match!
=== FINISHED CALLING METHOD ON BEAN FROM CONTAINER ===
=== GOING TO CALL METHOD ON NEWLY CREATED OBJECT ===
=== FINISHED CALLING METHOD ON NEWLY CREATED OBJECT ===
The printMessageToInformMatchStarts()
advice method declared in MessagePrintingAspect is executed when the startMatch()
join point is executed on the tournamentMatchManager bean.
Our example touches many facets of how Spring AOP deals with aspects. You've been exposed to all the requirements that must be met in order to use @AspectJ-style aspects with Spring AOP:
- Join points need to be public or protected instance methods on objects.
- Objects must be created by the Spring container.
- Callers need to call methods on the proxy objects, not on the original objects.
- Aspect instances must also be created by the Spring container.
- A special bean must be created by the Spring container to take care of auto-proxy creation.
Now, let's look at the advice types supported by aspects in Spring AOP.
@AspectJ-Style Advice Types
Aspects in Spring AOP are not declared by interfaces as is the case for classic Spring AOP. Instead, an advice is declared as a regular Java method, which can have arguments, return objects, and throw exceptions. As you saw in the previous example, the advice type is defined by the
@Aspect
annotation declaration on methods. The following advice types are supported:
Before advice (@Before): Executed before a join point is executed. It has the same semantics as before advice described in the previous chapter. It can prevent method execution on the target object from happening only by throwing an exception.
After returning advice (@AfterReturning): Executed after a join point has been executed without throwing an exception. It has the same semantics as after returning advice described in the previous chapter. It can have access to the return value of the method execution if it wants to, but can't replace the return value.
After throwing advice (@AfterThrowing): Executed after executing a join point that threw an exception. It has the same semantics as throws advice described in the previous chapter. It can have access to the exception that was thrown if it wants to, but can't prevent this exception from being thrown to the caller unless it throws another exception.
After (finally) advice (@After): Always called after a join point has been executed, regardless of whether the join point execution threw an exception or not. This is a new advice type that is not available in classic Spring AOP. It can't get access to the return value or an exception that was thrown.
Around advice (@Around): Executed as an interceptor around the execution of a join point. As with around advice described in the previous chapter, it's the most powerful advice type, but also the one that requires the most work.
Note
Actually, the Spring 2.0 AOP framework supports a sixth advice type: the introduction advice. We won't discuss this advice type in this book since it's not often used. You can just remember it is available and that it can be used to add methods and properties to the advised class.
You saw an example of before advice in the previous examples; MessagePrintingAspect contained before advice. Let's take a quick look at the other advice types and how to declare them in an @AspectJ-style aspect.
After Returning Advice
After returning advice is called when a join point has been executed and has exited with a return value or without a return value if the return type is void. Listing 4-7 shows MessagePrintingAspect with after returning advice.
Listing 4-7. Printing a Message After a Join Point Has Been Executed Normally
package com.apress.springbook.chapter04.aspects;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.AfterReturning;
@Aspect
public class MessagePrintingAspect {
@AfterReturning("execution(* startMatch(..))")
public void printMessageWhenTennisMatchHasBeenStartedSuccessfully() {
System.out.println("Tennis match was started successfully!");
}
}
After Throwing Advice
If you want to do some work when a join point throws an exception, you can use after throwing advice. Listing 4-8 shows MessagePrintingAspect with after throwing advice that prints out a warning when an exception is thrown.
Listing 4-8. Printing a Warning Message After a Join Point Has Thrown an Exception
package com.apress.springbook.chapter04.aspects;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.AfterThrowing;
@Aspect
public class MessagePrintingAspect {
@AfterThrowing("execution(* startMatch(..))")
public void printMessageWhenSomethingGoesWrong() {
System.out.println("Oops, couldn't start the tennis match. " +
"Something went wrong!");
}
}
After (Finally) Advice
After (finally) advice is always executed after a join point has been executed, but it can't get hold of the return value or any exception that is thrown. In other words, this advice type can't determine the outcome of the execution of the join point. It's typically used to clean up resources, such as to clean up objects that may still be attached to the current thread.
Listing 4-9 shows MessagePrintingAspect with after (finally) advice that prints a message to bring closure to the tennis match-starting event.
Listing 4-9. Printing a Message When a Tennis Match Start Has Been Attempted
package com.apress.springbook.chapter04.aspects;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.After;
@Aspect
public class MessagePrintingAspect {
@After("execution(* startMatch(..))")
public void printMessageToConcludeTheTennisMatchStartAttempt() {
System.out.println("A tennis match start attempt has taken place. " +
"We haven't been informed about the outcome but we sincerely " +
"hope everything worked out OK and wish you very nice day!");
}
}
Around Advice
Around advice is the most complicated type to use because it hasn't been specifically designed for any particular tasks. Instead, it's based on an interception model that allows you to take full control over the join point execution.
Its semantics are the same as those of MethodInterceptor, which was discussed in the previous chapter. As is the case with MethodInterceptor, this advice needs to able to proceed with the ongoing method execution. For this purpose, every around advice method must have a ProceedingJoinPoint declared as its first argument, as shown in Listing 4-10.
Listing 4-10. Passing on Our Regards and Then Getting on with Things
package com.apress.springbook.chapter04.aspects;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.ProceedingJoinPoint;
@Aspect
public class MessagePrintingAspect {
@Around("execution(* startMatch(..))")
public Object printMessageToTellHowNiceTheLifeOfAnAdviceIs(
ProceedingJoinPoint pjp) throws Throwable {
System.out.println("Greetings, Master, how are you today? I'm "
"very glad you're passing by today and hope you'll enjoy " +
"your visit!");
try {
return pjp.proceed();
} finally {
System.out.println("Au revoir, Master, I'm sorry you can't stay " +
"longer, but I'm sure you'll pay me a visit again. Have a very " +
"nice day yourself, sir!");
}
}
}
The example of around advice in Listing 4-10 looks different from the previous examples. The first thing to notice is the advice method signature. Three things are special about this signature:
- The return type is java.lang.Object; the other advice types have return type void.
- The first argument is of type org.aspectj.lang.ProceedingJoinPoint.
- The method declares java.lang.Throwable in its throws clause.
Another interesting thing to notice in Listing 4-10 is the call to the proceed()
method on the ProceedingJoinPoint object. The entire advice method is actually very comparable to the invoke()
method on MethodInterceptor used by classic Spring AOP:
package org.aopalliance.intercept; public interface MethodInterceptor extends Interceptor { Object invoke(MethodInvocation invocation) throws Throwable; } If you're familiar with how the MethodInceptor and its MethodInvocation object work, you'll find around advice and ProceedingJoinPoint very easy to use.
Pointcut Declaration and Reuse
You can also declare named pointcuts in @AspectJ-style aspects. These pointcuts are a great way to reuse pointcut declarations throughout your aspects.
Listing 4-11 shows an example of an aspect with a named pointcut declaration.
Listing 4-11. An Aspect That Declares Systemwide Pointcuts
package com.apress.springbook.chapter04.aspects;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
@Aspect
public class SystemPointcutsAspect {
@Pointcut("within(com.apress.springbook.chapter04.service..*)")
public void inServiceLayer() { }
}
The inServiceLayer pointcut selects all join points in the com.apress.springbook.chapter04.
service package, meaning all public and protected methods of all classes in this package and its subpackages. within()
is a pointcut designator, which we'll discuss in the "Working with Pointcuts" section later in this chapter.
The inServiceLayer()
method is a pointcut declaration, but also a regular Java method. However, Spring AOP will never execute this method; instead, it will read its @Pointcut
annotation. So it's not useful to add any implementation to the method body, and it's not even useful to call this method yourself, because it's a pointcut declaration. We recommend that methods with the @Pointcut
annotation always have an empty method body. It's the name of the method that's important here.
We can now reuse the inServiceLayer pointcut in other aspects, as shown in Listing 4-12 (if you do this, remember to configure both aspects).
Listing 4-12. Reusing the inServiceLayer Pointcut in Another Aspect
package com.apress.springbook.chapter04.aspects;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
@Aspect
public class SecurityAspect {
@Before("com.apress.springbook.chapter04.aspects." +
"SystemPointcutsAspect.inServiceLayer()")
public void denyAccessToAll() {
throw new IllegalStateException("This system has been compromised. " +
"Access is denied to all!");
}
}
Pointcut reuse provides you with a powerful tool to select join points in one place and reuse these declarations anywhere. In Listing 4-11, we've defined the pointcut that selects the service layer of our application. In Listing 4-12, we decide to deny access to the system for all, since there's an unresolved security issue.
We can add more behaviors to the service layer by reusing the same pointcut in other aspects.
Reusing pointcut declarations will make your applications easier to maintain.
Auto-Proxy Creation in the Spring Container
We've already covered how to use AnnotationAwareAspectJAutoProxyCreator in the Spring container to enable auto-proxy creation, which is a requirement to use @AspectJ-style aspects with Spring AOP. In this section, we'll discuss another way of enabling auto-proxy creation. We'll also explain how Spring AOP 2.0 decides which proxy types to use, and we'll shed some more light on how Spring AOP decides to create proxy objects for beans.
Auto-Proxy Creation with the AOP XML Schema
The other way of enabling auto-proxy creation is to use the Spring AOP XML Schema and its <aop:aspectj-autoproxy> tag. Listing 4-13 shows a Spring XML configuration file that uses the AOP XML Schema and the aop namespace.
Listing 4-13. Using Spring's AOP XML Schema to Enable @AspectJ-Style Aspects in the Spring Container
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns: aop="http://www.springframework.org/schema/aop"
xsi: schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<aop:aspectj-autoproxy/>
</beans>
Using the <aop:aspectj-autoproxy> XML tag has an advantage: if you accidentally define this tag more than once in your Spring configuration, no harm is done. If you configure the AnnotationAwareAspectJAutoProxyCreator more than once in your configuration, auto-proxy creation will occur twice for each bean—something you want to avoid. Otherwise, both approaches have exactly the same effect: auto-proxy creation is enabled for the entire Spring container.
Proxy Type Selection
The proxy type selection strategy in Spring AOP 2.0 has changed slightly from the previous version of Spring AOP. Since version 2.0, JDK proxy objects will be created for target objects that implement at least one interface. For target objects that implement no interfaces, CGLIB proxy objects will be created.
You can force the creation of CGLIB proxies for all target classes by setting the proxy-targetclass option to true:
<aop:aspectj-autoproxy proxy-target-class="true"/> Forcing the use of CGLIB proxy objects is required when at least one object for which a proxy object is created in the Spring container implements one or more interfaces, but is used as the class type by its callers. In this case, a JDK proxy object that implements only the interfaces would not be type-compatible for certain callers. Proxy objects created by CGLIB remain type-compatible with the target object.
In all other cases, you can safely leave the proxy-target-class option disabled.
The Proxying Process
In the example in Listing 4-5, we configured three beans to be loaded by the Spring container.
A proxy object was created for only one of them. Let's review the role of each bean definition in Listing 4-5:
AnnotationAwareAspectJAutoProxyCreator: This class is responsible for auto-proxy creation.
There's no need to create a proxy object for this bean, since it's not called by the application itself. Instead, it will enhance the bean life cycle for all other beans in the container.
MessagePrintingAspect: This is a regular Java class and an @AspectJ-style aspect. No proxy object is created for this bean, since it's also not called by the application. Instead, it embeds advices and pointcuts that will determine for which other beans in the container proxy objects will be created.
DefaultTournamentMatchManager: This class is part of the application logic. What's more, it has a join point that is matched by the pointcut in the MessagePrintingAspect: its startMatch()
method. Because at least one join point is matched, a proxy object will be created and will replace the original bean in the container during the bean life cycle. So once the bean life cycle has been completed successfully for the tournamentMatchManager bean, the container will return a proxy object with advice and its target object.
So let's wrap up the rules for auto-proxy creation in the Spring container based on the example:
- Beans that implement the org.springframework.beans.factory.BeanPostProcessor or org.springframework.beans.factory.BeanFactoryPostProcessor interfaces will never be proxied. AnnotationAwareAspectJAutoProxyCreator implements the BeanPostProcessor interface, which allows it to enhance the bean life cycle for beans created by the Spring container.
- @AspectJ-style aspects, beans that implement the org.springframework.aop.Advisor or org.springframework.aop.Pointcut interfaces, and beans that implement any of the classic Spring AOP advice-type interfaces discussed in the previous chapter are excluded from autoproxy creation because they have infrastructural roles.
- All other beans that are created by the Spring container are eligible for auto-proxy creation.
During the life cycle of each bean created by the Spring container—both singleton and prototype beans—the container will ask all aspects and advisors found in the container if one or more of the bean join points are matched by one of their pointcuts. If there is at least one match, a proxy object will be created for the bean and will replace that bean. The proxy object will have all the advice embedded for all join points that were matched.
To fully understand the last rule, you need to know how join points will be matched by any given pointcut. If you look back at the example and its pointcut in Listing 4-3, it's obvious that only methods named startMatch()
will be matched. Later in this chapter, in the "Working with Pointcuts" section, we'll discuss other pointcuts that select join points in specific ways.
Advice and Aspect Ordering
Advice declared in aspects is automatically selected and added to a proxy object during autoproxy creation. It is entirely possible that two advices apply to the same join point. Consider the MessagePrintingAspect @AspectJ-style aspect shown in Listing 4-14.
Listing 4-14. Two Advices Will Be Executed for the Same Join Points
package com.apress.springbook.chapter04.aspects;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.annotation.Before;
@Aspect
public class MessagePrintingAspect {
@Pointcut("execution(* startMatch(..))")
public void atMatchStart() {}
@Before("atMatchStart()")
public void printHowAnnoyedWeAre() {
System.out.println("Leave it out! Another tennis match!?");
}
@Before("atMatchStart()")
public void printHowExcitedWeAre() {
System.out.println("Hurray for another tennis match!");
}
}
The aspect in Listing 4-14 declares two advices that will be executed for the same join points.
This may leave you wondering in what order they will be executed, In this example, the actual order is not very important, but in other scenarios, it may be important to understand the exact order. And what would the order be if these two advices were defined in different aspects?
Ordering Advice
In those cases where advices are declared in the same aspect and they are both executed for the same join point, Spring AOP uses the same order as AspectJ: the order of declaration. So, advices in the same aspect that are executed for the same join point will maintain their order of declaration.
For the aspect in Listing 4-14, consider the Spring configuration in Listing 4-15.
Listing 4-15. Configuring DefaultTournamentMatchManager with Two Advices Declared in the Same Aspect
<beans>
<bean class="org.springframework.aop.aspectj.annotation. -->
AnnotationAwareAspectJAutoProxyCreator"/>
<bean class="com.apress.springbook.chapter04.aspects.MessagePrintingAspect"/>
<bean id="tournamentMatchManager"
class="com.apress.springbook.chapter04.DefaultTournamentMatchManager">
<!-- properties omitted -->
</bean>
</beans>
When the
startMatch()
method on the tournamentMatchManager bean is executed, the following messages are printed on the console:
Leave it out! Another tennis match!?
Hurray for another tennis match! So, the two aspects are executed in the order in which they are declared.
Ordering Aspects
When two advices that are declared in
different aspects are executed for the same join point, the order is determined by the org.springframework.core.Ordered interface, as shown in Listing 4-16.
Listing 4-16. Spring's Ordered Interface
package org.springframework.core;
public interface Ordered {
int getOrder();
}
The Spring Framework uses the Ordered interface whenever a list of object needs to be processed in a particular order. By implementing the Ordered interface aspects, you can place your advices in a specific spot in the order of advice execution for join points. The ordering rules for aspects are as follows:
- Aspects that don't implement the Ordered interface are in an undetermined order and come after the aspects that do implement the interface.
- Aspects that implement the Ordered interface are ordered according to the return value of the
getOrder()
method. The lowest values get the first position. - Two or more aspects that have the same return value for the
getOrder()
method are in an undetermined order.
To demonstrate how the ordering of aspects works, we first create a common pointcut, as shown in Listing 4-17.
Listing 4-17. A Common Pointcut
package com.apress.springbook.chapter04.aspects;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
@Aspect
public class TennisMatchEventsAspect {
@Pointcut("execution(* startMatch(..))")
public void atMatchStart() { }
}
Next, we'll declare two advices in separate aspects, as shown in Listings 4-18 and 4-19.
Listing 4-18. Aspect That Implements Spring's Ordered Interface
package com.apress.springbook.chapter04.aspects;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.core.Ordered;
@Aspect
public class HappyMessagePrintingAspect implements Ordered {
private int order = Integer.MAX_VALUE;
public int getOrder() { return this.order; }
public void setOrder(int order) { this.order = order; }
@Before("com.apress.springbook.chapter04.aspects." +
"TennisMatchEventsAspect.atMatchStart()")
public void printHowExcitedWeAre() {
System.out.println("Hurray for another tennis match!");
}
}
Listing 4-19. Aspect That Doesn't Implement the Ordered Interface
package com.apress.springbook.chapter04.aspects;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
@Aspect
public class AnnoyedMessagePrintingAspect {
@Before("com.apress.springbook.chapter04.aspects." +
"TennisMatchEventsAspect.atMatchStart()")
public void printHowAnnoyedWeAre() {
System.out.println("Leave it out! Another tennis match!?");
}
}
Next, we load these two aspects in the Spring container, as shown in Listing 4-20.
Listing 4-20. Configuring the Two Aspects for Loading by the Spring Container
<beans>
<bean class="org.springframework.aop.aspectj.annotation. -->
AnnotationAwareAspectJAutoProxyCreator"/>
<bean class="com.apress.springbook.chapter04.aspects.HappyMessagePrintingAspect"/>
<bean class="com.apress.springbook.chapter04.aspects. -->
AnnoyedMessagePrintingAspect"/>
<bean id="tournamentMatchManager"
class="com.apress.springbook.chapter04.DefaultTournamentMatchManager">
<!-- properties omitted -->
</bean>
</beans>
When we call the
startMatch()
method on the tournamentMatchManager bean, the following messages will be printed to the console:
Hurray for another tennis match! Leave it out! Another tennis match!?
We get this order of messages because the HappyMessagePrintingAspect implements the Ordered interface and the AnnoyedMessagePrintingAspect doesn't.
Because we have implemented the setOrder()
method in HappyMessagePrintingAspect, we can change the order value via the bean definition, as follows:
<bean class="com.apress.springbook.chapter04.aspects.HappyMessagePrintingAspect"/> <property name="order" value="20"/> </bean> Although we can control the order of aspects and thus their advices, the order of declaration for advices within individual aspects remains.
So far, we've discussed only @AspectJ-style aspects in this chapter, but there is an alternative, which we'll cover next.
Using AOP XML Tags
The Spring developers have come up with a way to define aspects in XML by creating an AOP XML Schema for the Spring 2.0 custom XML Schema support. It allows you to turn any public instance methods on beans created by the Spring container into advice methods. These methods are comparable to the methods annotated with @Aspect in @AspectJ-style aspects.
@AspectJ-style aspects use Java 5 annotations, so they are not an option when Java 5 is not used in the production environment (many organizations still use Java 1.4). Also, you may want to use methods on existing classes as advice methods.
This section explains how to create aspects in XML, which solves these problems. We will also show how you can replace the pointcut classes covered in the previous chapter with the AspectJ pointcuts discussed in this chapter.
As you will notice, XML aspects and advice declarations are straightforward to understand and work with when you're familiar with @AspectJ-style aspects. You may also notice that with XML declarations, the advice type and pointcut are separated from the advice method (which some see as a disadvantage because it splits a unit of functionality). For this reason, we recommend that you to write aspects with the @AspectJ-style when possible.
AOP Configuration Tags
The first step to using the AOP XML tags for declaring aspects, pointcuts, and advisors is to create a Spring XML file, as shown in Listing 4-21.
Listing 4-21. A Spring XML Configuration File Based on Spring 2.0 XML Schemas
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns: xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns: aop="http://www.springframework.org/schema/aop"
xsi: schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
</beans>
You can load this file, together with other, classic, Spring XML configuration files, into the Spring container. To declare aspects and advisors in XML, add the <aop:config> tag to the XML file, as shown in Listing 4-22.
Listing 4-22. Creating an AOP Configuration Unit in XML with the aop:config Tag
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns: xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns: aop="http://www.springframework.org/schema/aop"
xsi: schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<aop:config>
</aop:config>
</beans>
You can declare multiple <aop:config> tags in one or multiple XML files. The <aop:config> tag can contain zero or more of the following tags:
- <aop:aspect>: Allows you to create aspects in XML that are comparable to @AspectJ-style aspects.
- <aop:advisor>: Allows you to create an advisor object with an AspectJ pointcut and a classic Spring AOP advice object.
- <aop:pointcut>: Allows you to declare and reuse pointcuts in XML aspects.
We'll cover these tags in more detail as we work through our next example. Now we'll re-create the @AspectJ-style aspect from Listing 4-3 in XML.
XML Aspect Configuration
The @AspectJ-style concepts we've discussed in the chapter also apply to aspects declared in XML.
The only difference is the use of XML instead of annotations.
The first step for creating an aspect with XML is to add the <aop:aspect> tag to <aop:config>, as shown in Listing 4-23.
Listing 4-23. xml-aspect-context.xml: Creating an Empty XML Aspect
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns: xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns: aop="http://www.springframework.org/schema/aop"
xsi: schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org /schema/aop/spring-aop.xsd">
<aop:config>
<aop:aspect ref="messagePrinter">
</aop:aspect>
</aop:config>
<bean id="messagePrinter"
class="com.apress.springbook.chapter04.MessagePrinter"/>
<bean id="tournamentMatchManager"
class="com.apress.springbook.chapter04.DefaultTournamentMatchManager">
<!-- properties omitted -->
</bean>
</beans>
The <aop:aspect> tag takes a ref attribute that holds the name of a bean definition (messagePrinter). Listing 4-24 shows the MessagePrinter class.
Listing 4-24. The MessagePrinter Class
package com.apress.springbook.chapter04;
public class MessagePrinter {
public (.*?) {
System.out.println("Attempting to start tennis match!");
}
}
The MessagePrinter class is comparable to the MessagePrintingAspect in Listing 4-3, but without the @AspectJ-style annotations. So MessagePrinter is a regular Java class and not an aspect declaration.
But we've configured MessagePrinter in a bean definition in Listing 4-23, and we've let the <aop:aspect> tag refer to the messagePrinter bean definition. This configuration declares the messagePrinter bean as an aspect, not the MessagePrinter class. We've also added the tournamentMatchManager bean to the configuration in Listing 4-23.
So far, this aspect configuration won't do anything out of the ordinary. However, we can add more configuration to the <aop:aspect> tag to turn the printMessageToInformMatchStarts()
method on the messagePrinter bean into an advice method, as shown in Listing 4-25.
Listing 4-25. xml-aspect-context.xml: Turning the printMessageToInformMatchStarts()
method on the messagePrinter Bean into an Advice Method
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns: xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns: aop="http://www.springframework.org/schema/aop"
xsi: schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<aop:config>
<aop:aspect ref="messagePrinter">
<aop:before method="printMessageToInformMatchStarts"
pointcut="execution(* startMatch(..))"/>
</aop:aspect>
</aop:config>
<bean id="messagePrinter"
class="com.apress.springbook.chapter04.MessagePrinter"/>
<bean id="tournamentMatchManager"
class="com.apress.springbook.chapter04.DefaultTournamentMatchManager">
<!-- properties omitted -->
</bean>
</beans>
That's it—we've created the XML aspect. The <aop:before> tag declares that the printMessageToInformMatchStarts() method on the messagePrinter bean becomes before advice and also declares the pointcut.
Let's run through the configuration in Listing 4-25 to look at all elements and their roles.
The <aop:config> tag activates auto-proxy creation in the Spring container. It also holds the configuration for one or more XML aspect, pointcut, or advisor declarations. The <aop:aspect> tag refers to the messagePrinter bean and declares that bean as an aspect.
This bean itself is not touched or affected in any way. In fact, the messagePrinter bean is not aware that it's being declared as an aspect. This tag by itself won't trigger the creation of proxy objects during auto-proxy creation. The tag only declares an aspect that can hold zero or more pointcut and advice/pointcut declarations in XML.
The <aop:before> tag is an advice/pointcut declaration that will execute the printMessageToInformMatchStarts() method on the messagePrinter bean for the execution of all join points that are matched by the pointcut. The messagePrinter bean is an ordinary bean created and configured based on an ordinary bean definition by the Spring container. It is not aware of Spring AOP, advice, auto-proxy creation, or pointcuts. In fact, you can get this bean and execute its methods, and you'll find they will respond as expected. The tournamentMatchManager bean is affected by auto-proxy creation during its bean life cycle since it has one join point—the startMatch()
method—that is matched by the pointcut in the XML aspect declaration. When its startMatch()
method is executed, the printMessageToInformMatchStarts() method on the messagePrinter bean will be executed first.
Next, we'll load xml-aspect-context.xml into a test case to verify the tournamentMatchManager bean is proxied correctly. The test case in Listing 4-26 shows MessagePrintingXmlAspectIntegration Tests, which extends MessagePrintingAspectIntegrationTests from Listing 4-6.
Listing 4-26. Test Case to Verify the XML Aspect Works As Expected
package com.apress.springbook.chapter04;
public class MessagePrintingXmlAspectIntegrationTests extends
MessagePrintingAspectIntegrationTests {
protected String[] getConfigLocations() {
return new String[] {
"classpath:com/apress/springbook/chapter04/" +
"xml-aspect-context.xml"
};
}
}
The test case in Listing 4-26 runs the test methods declared in Listing 4-6 and overwrites the getConfigLocations() method to load the Spring XML file in Listing 4-25. The following messages shown are printed to the console when the test runs:
=== GOING TO CALL METHOD ON BEAN FROM CONTAINER ===
Attempting to start tennis match!
=== FINISHED CALLING METHOD ON BEAN FROM CONTAINER ===
=== GOING TO CALL METHOD ON NEWLY CREATED OBJECT ===
=== FINISHED CALLING METHOD ON NEWLY CREATED OBJECT ===
Pointcut Declaration and Reuse with XML
You can declare and reuse pointcuts in the AOP XML configuration, and you can reuse pointcuts declared in @AspectJ-style aspects. Listing 4-27 reuses the pointcut declared in the SystemPointcutsAspect (Listing 4-11). The SecurityEnforcer class is the same as the SecurityAspect class, but has been stripped of its aspect status.
Listing 4-27. Reusing a Pointcut Declared in an @AspectJ-Style Aspect
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns: xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns: aop="http://www.springframework.org/schema/aop"
xsi: schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<aop:config>
<aop:aspect ref="securityEnforcer">
<aop:before method="denyAccessToAll"
pointcut="com.apress.springbook.chapter04.aspects. -->
SystemPointcutsAspect.inServiceLayer()"/>
</aop:aspect>
</aop:config>
<bean id="securityEnforcer"
class="com.apress.springbook.chapter04.SecurityEnforcer"/>
</beans>
You can also declare pointcuts in XML, and you can declare them in two places. The first option is shown in Listing 4-28, which declares a pointcut inside the <aop:aspect> tag. This pointcut can be reused only inside this aspect.
Listing 4-28. Declaring and Reusing a Pointcut in an XML Aspect
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns: xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns: aop="http://www.springframework.org/schema/aop"
xsi: schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<aop:config>
<aop:aspect ref="securityEnforcer">
<aop:pointcut id="inServiceLayer"
expression="within(com.apress.springbook.chapter04..*)"/>
<aop:before method="denyAccessToAll"
pointcut-ref="inServiceLayer"/>
</aop:aspect>
</aop:config>
<bean id="securityEnforcer"
class="com.apress.springbook.chapter04.SecurityEnforcer"/>
</beans>
The <aop:pointcut> tag declares a pointcut and takes a name (id) and pointcut expression (expression). This pointcut is then reused by the <aop:before> tag (pointcut-ref).
Listing 4-29 shows a pointcut that is declared inside the <aop:config> tag. This pointcut can be reused inside <aop:aspect> tags in this and other Spring XML files.
Listing 4-29. Declaring a Pointcut Outside an Aspect and Reusing It Inside an XML Aspect
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns: xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns: aop="http://www.springframework.org/schema/aop"
xsi: schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org /schema/aop/spring-aop.xsd">
<aop:config>
<aop:pointcut id="inServiceLayer"
expression="within(com.apress.springbook.chapter04..*)"/>
<aop:aspect ref="securityEnforcer">
<aop:before method="denyAccessToAll"
pointcut-ref="inServiceLayer"/>
</aop:aspect>
</aop:config>
<bean id="securityEnforcer"
class="com.apress.springbook.chapter04.SecurityEnforcer"/>
</beans>
Pointcuts declared in XML have certain limitations, in that they cannot be reused in @AspectJ-style aspects. Also, they cannot take dynamic pointcut designators such as args()
and @annotation() (pointcut designators are discussed in the "Working with Pointcuts" section later in this chapter). The reason has to do with the fact that pointcut declarations are not coupled to a method, as they are in @AspectJ-style aspects.
Advice Declaration in XML
The aspects that are declared in XML support the same advice types as @AspectJ-style aspects with exactly the same semantics. As explained in the previous section, advice declarations in XML use regular Java methods on an object as advice methods.
Now we'll look at how to declare each of the different advice types in XML. Later, in the "Binding Advice Arguments" section, we'll rewrite the aspects we've used to explain how to bind advice arguments on @AspectJ-style advice methods and show the equivalent XML, so that you can easily compare the two.
Listing 4-30 shows an example for a before advice XML declaration. Listing 4-30. Before Advice Declaration in XML
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns: xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns: aop="http://www.springframework.org/schema/aop"
xsi: schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org /schema/aop/spring-aop.xsd">
<aop:config>
<aop:aspect ref="messagePrinter">
<aop:before method="printMessageToInformMatchStarts"
pointcut="execution(* startMatch(..))"/>
</aop:aspect>
</aop:config>
<bean id="messagePrinter"
class="com.apress.springbook.chapter04.MessagePrinter"/>
<bean id="tournamentMatchManager"
class="com.apress.springbook.chapter04.DefaultTournamentMatchManager">
<!-- properties omitted -->
</bean>
</beans>
Listing 4-31 shows an example of using after returning advice.
Listing 4-31. After Returning Advice Declared in XML
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns: xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns: aop="http://www.springframework.org/schema/aop"
xsi: schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<aop:config>
<aop:aspect ref="messagePrinter">
<aop:after-returning method="printMessageToInformMatchHasStarted"
pointcut="execution(* startMatch(..))"/>
</aop:aspect>
</aop:config>
<bean id="messagePrinter"
class="com.apress.springbook.chapter04.MessagePrinter"/>
<bean id="tournamentMatchManager"
class="com.apress.springbook.chapter04.DefaultTournamentMatchManager">
<!-- properties omsitted -->
</bean>
</beans>
Declaring after throwing advice in XML is equally straightforward, as shown in Listing 4-32.
Listing 4-32. After Throwing Advice Declared in XML
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns: xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns: aop="http://www.springframework.org/schema/aop"
xsi: schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org /schema/aop/spring-aop.xsd">
<aop:config>
<aop:aspect ref="messagePrinter">
<aop:after-throwing method="printMessageWhenMatchIdentifierIsNotFound"
pointcut="execution(* startMatch(..) throws -->
com.apress.springbook.chapter04.UnknownMatchException)"/>
</aop:aspect>
</aop:config>
<bean id="messagePrinter"
class="com.apress.springbook.chapter04.MessagePrinter"/>
<bean id="tournamentMatchManager"
class="com.apress.springbook.chapter04.DefaultTournamentMatchManager">
<!-- properties omitted -->
</bean>
</beans>
In the after (finally) advice example shown in Listing 4-33, we again use a pointcut to match the startMatch() method. Listing 4-33. After (Finally) Advice Declared in XML
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns: xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns: aop="http://www.springframework.org/schema/aop"
xsi: schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org /schema/aop/spring-aop.xsd">
<aop:config>
<aop:aspect ref="messagePrinter">
<aop:after method="printMessageWhenStartMatchAttemptIsOver"
pointcut="execution(* startMatch(..))"/>
</aop:aspect>
</aop:config>
<bean id="messagePrinter"
class="com.apress.springbook.chapter04.MessagePrinter"/>
<bean id="tournamentMatchManager"
class="com.apress.springbook.chapter04.DefaultTournamentMatchManager">
<!-- properties omitted -->
</bean>
</beans>
The code in Listing 4-34 shows the printMessageWhenStartMatchAttemptIsOver()
method on MessagePrinter.
Listing 4-34. The printMessageWhenStartMatchAttemptIsOver()
method on MessagePrinter
package com.apress.springbook.chapter04;
public class MessagePrinter {
public (.*?) {
System.out.println("Tried to start a match and this attempt is now over!"); }
}
Again, there are no surprises since the after (finally) advice XML declaration is very similar to the @AspectJ-style declaration we've discussed previously.
And last, but not least, is the around advice as an XML declaration. As you may suspect, this advice type requires the declaration of a ProceedingJoinPoint argument in the advice method. This binds the MessagePrinter class to the AspectJ API. Listing 4-35 shows an around advice declaration in XML.
Listing 4-35. Around Advice Declared in XML
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns: xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns: aop="http://www.springframework.org/schema/aop"
xsi: schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org /schema/aop/spring-aop.xsd">
<aop:config>
<aop:aspect ref="messagePrinter">
<aop:around method="controlStartMatchMethodExecution"
pointcut="execution(* startMatch(..))"/>
</aop:aspect>
</aop:config>
<bean id="messagePrinter"
class="com.apress.springbook.chapter04.MessagePrinter"/>
<bean id="tournamentMatchManager"
class="com.apress.springbook.chapter04.DefaultTournamentMatchManager">
<!-- properties omitted -->
</bean>
</beans>
Listing 4-36 shows the controlStartMatchMethodExecution()
method on MessagePrinter.
Listing 4-36. The controlStartMatchMethodExecution()
method on MessagePrinter
package com.apress.springbook.chapter04;
import org.aspectj.lang.ProceedingJoinPoint;
public class MessagePrinter {
public Object controlStartMatchMethodExecution(ProceedingJoinPoint pjp)
throws Throwable {
System.out.println("A match is about to be started!");
try {
Object result = pjp.proceed();
System.out.println("The match has been started successfully!");
return result;
} catch (Throwable t) {
System.out.println("Oops, something went wrong while starting the match.");
throw t;
}
}
}
The controlStartMatchMethodExecution()
method on MessagePrinter is a classic example of interception advice.
Advice Ordering in XML
Proxy objects for aspects declared in XML are also created via auto-proxy creation. Declaring the <aop:config> tag once will automatically configure the Spring container. As we've discussed previously in this chapter, auto-proxy creation means the order of advice is undetermined, and you may need control over the advice order. Ordering aspects declared in XML is very similar to ordering aspects declared in @AspectJ-style aspects:
- Advices declared in the same XML aspect that is executed on one join point are ordered according to their order of declaration in the XML file.
- If you want to control the order of advice declared in different XML aspects, you must implement the org.springframework.core.Ordered interface on the classes where advice methods are declared (like the MessagePrinter class in Listing 4-24).
By implementing the Ordered interface, you can also control the order between XML advice and @AspectJ-style advice on the same join point.
Advisors with AspectJ Pointcuts
In the previous chapter, you learned that an advisor holds an advice object and a pointcut object.
When you add an advisor to the Spring container and configure auto-proxy creation, its pointcut can match join points on beans in the container; when it does, proxy objects will be created.
Consider the example in Listing 4-37. Note that you must define advisors before aspects.
Listing 4-37. Configuring a PointcutAdvisor with the <aop:advisor> Tag
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns: xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns: aop="http://www.springframework.org/schema/aop"
xsi: schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org /schema/aop/spring-aop.xsd">
<aop:config>
<aop:advisor advice-ref="loggingAdvice" pointcut="execution(* startMatch(..))"/>
</aop:config>
<bean id="loggingAdvice"
class="org.springframework.aop.interceptor.CustomizableTraceInterceptor">
<property name="enterMessage"
value="Entering $[methodName] on $[targetClassShortName]"/>
</bean>
<bean id="tournamentMatchManager"
class="com.apress.springbook.chapter04.DefaultTournamentMatchManager">
<!-- properties omitted -->
</bean>
</beans>
Auto-proxy creation will create a proxy for the tournamentMatchManager bean, since its startMatch() method is matched by the advisor's pointcut.
The advisors take part in auto-proxy creation, which means their advice objects can be executed together with other advices on the same join point. To set the order of an advisor, you can configure the order attribute, as shown in Listing 4-38.
Listing 4-38. Setting the Order of the Advice for This Advisor
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns: xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns: aop="http://www.springframework.org/schema/aop"
xsi: schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org /schema/aop/spring-aop.xsd">
<aop:config>
<aop:advisor advice-ref="loggingAdvice"
order="20"
pointcut="execution(* startMatch(..))"/>
</aop:config>
<bean id="loggingAdvice"
class="org.springframework.aop.interceptor.CustomizableTraceInterceptor">
<property name="enterMessage"
value="Entering $[methodName] on $[targetClassNameShort]"/>
</bean>
<bean id="tournamentMatchManager"
class="com.apress.springbook.chapter04.DefaultTournamentMatchManager">
<!-- properties omitted -->
</bean>
</beans>
Proxy Type Selection in XML
When working with aspect declarations in XML, you can choose between automatic proxy type detection—JDK or CGLIB proxy objects—or forced use of CGLIB proxy objects. You can toggle between these two modes by setting the proxy-target-class property to true on the <aop:config> XML tag, as shown in Listing 4-39.
Listing 4-39. Forcing the Use of CGLIB Proxy Objects
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns: xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns: aop="http://www.springframework.org/schema/aop"
xsi: schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org /schema/aop/spring-aop.xsd">
<aop:config proxy-target-class="true">
</aop:config>
</beans>
Since you can add multiple <aop:config> tags to your configuration and toggle the proxy-target-class attribute value in each one, it may not be clear which proxy object creation mode is active. The rule for setting this mode is simple: when at least one <aop:config> tag sets the proxy-target-class value to true or <aop:aspectj-autoproxy> sets the value to true, Spring AOP will always create CGLIB proxy objects.
So, now that you've seen the two different ways to configure Spring AOP, let's look at pointcuts in more detail.
Working with Pointcuts
In this chapter, we've used the same simple pointcut in aspects over and over. The AspectJ pointcut language is extremely powerful and versatile in how it can select join points. Remember that Spring AOP supports only method executions as join points. While this is far less than what is supported by AspectJ, you can still do some pretty neat things with method executions alone. Specifically, pointcuts allow you to select methods based on a wide variety of criteria, and you can combine criteria with logical operators to narrow down the selection. In Spring AOP, the goal of pointcuts is to select public or protected methods. Methods are declared in Java classes, but when selecting them, you often want to escape the narrow boundary of classes and generalize. Here are just some examples of what you may want to select:
- All methods of a specific class
- All methods of all classes in a specific package
- All methods of all classes in a specific package and its subpackages
- All methods with a specific annotation
- All methods of a class with a specific annotation
- All methods with a specific name
- All methods where the name contains a specific string
- All methods with specific argument types
To select methods, you can use these constructs in pointcut expressions:
- Pointcut designators: We've already used
execution()
and within()
. Spring AOP supports more designators, as you'll see in the following examples. They narrow down the selection of the join points based on some property. Other constructs in this list are used in combination with these designators. - Boolean operators: You can combine pointcut designators to narrow down the selection. The AND (&&) and OR (||) operators, as well as the NOT operator (!), are supported.
- Class names: Often, you need to specify class names, such as to narrow down the selection to a target type or a method argument type. These class names must always be fully qualified (including the full package name), even for classes in the java.lang package.
- Visibility operators: You can narrow the search to only public or protected methods.
- Wildcards: Frequently, you will want to use a wildcard operator. Supported wildcard operators are the asterisk (*) for any method or class name or any argument type and double dot (..) for zero or more arguments.
- Method names: These can be the full name, a partial match of the name, or a wildcard (any name).
- Annotations: You can select methods based on annotations, as discussed in the next sections.
Let's start with how to select methods directly.
Selecting Methods Directly
Most of the time, you want to select methods based on some of their parts, such as the name, return type, throws declaration, or arguments. When selecting based on method parts, you will use the execution() or args()
pointcut designators or a combination of these designators.
Selecting on Method Names
The simplest way to select methods is by narrowing down the selection based on their names.
Listing 4-40 shows a pointcut that selects all relax()
, enjoy()
, and chillOut()
methods.
Listing 4-40. Selecting Some Enjoyable Methods
package com.apress.springbook.chapter04.aspects;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
@Aspect
public class SystemPointcutsAspect {
@Pointcut("execution(* relax(..)) || execution(* enjoy(..)) || " +
"execution(* chillOut(..))")
public void goodTimes() { }
}
Each execution()
pointcut designator in Listing 4-40 selects methods based on their names, regardless of all other properties.
You can also use wildcards in method names. Listing 4-41 shows a pointcut that selects all methods whose names start with do.
Listing 4-41. Selecting All Methods Whose Names Start with do
package com.apress.springbook.chapter04.aspects;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
@Aspect
public class SystemPointcutsAspect {
@Pointcut("execution(* do*(..))")
public void performAction() { }
}
Selecting methods on their names is one of the most basic pointcut criteria. They can be combined with other criteria to narrow the selection to make sure only the intended methods are selected.
One powerful way to narrow down the method selection is to also select on method arguments.
Selecting on Argument Types
You can also select on the argument declarations of methods. Listing 4-42 shows pointcuts that narrow down the selection to the following:
- All methods without arguments
- All methods with one argument, regardless of its type
- All methods with one java.lang.String argument
- All methods with java.lang.String as the first argument and zero or more other arguments
- All methods with java.lang.String as the second argument and zero or more other arguments
Listing 4-42. Selecting Methods Based on Their Arguments
package com.apress.springbook.chapter04.aspects;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
@Aspect
public class SystemPointcutsAspect {
@Pointcut("execution(* *())")
public void allMethodsWithoutArguments() {}
@Pointcut("execution(* *(*))")
public void allMethodsWithOneArgumentRegarlessOfType() {}
@Pointcut("execution(* *(java.lang.String))")
public void allMethodsWithOneArgumentOfTypeString() {}
@Pointcut("execution(* *(java.lang.String,..))")
public void
allMethodsWithFirstArgumentOfTypeStringAndZeroOrMoreOtherArguments() {}
@Pointcut("execution(* *(*,java.lang.String,..))")
public void
allMethodsWithSecondArgumentOfTypeStringAndZeroOrMoreOtherArguments () { }
}
When narrowing down the selection based on arguments with the execution()
pointcut designator, Spring AOP will look at the static method declarations. This is a safe option in combination with auto-proxy creation, since Spring AOP can look at method metadata to determine if there is a match. Remember that this matching is important to decide if a proxy object must be created for any given bean in the Spring container. Another way to select arguments is based on the actual types that are passed to method executions of the proxy object. Listing 4-43 shows an aspect with a pointcut that selects methods with one argument.
Listing 4-43. Selecting Methods Based on Actual Argument Values Instead of Argument Declaration
package com.apress.springbook.chapter04.aspects;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.annotation.Before;
@Aspect
public class MessagePrintingAspect {
@Before("args(java.util.Hashtable)")
public void printWarningForUsageOfHashtable() {
System.out.println("Warning: java.util.Hashtable is passed as argument!");
}
}
java.util.Hashtable is a synchronized class meant to be used in scenarios where one map object is shared in a multithreaded environment. Hashtable performs poorly compared to nonsynchronized maps, and we want to print a warning when it's being used in a single-threaded setting.
Three method signatures will all be selected by this pointcut:
public java.util.List getUniqueValuesFromMap(java.util.Map map) { … }
public Object getLowestKeyValueFromMap(java.util.Map map) { … }
public String transformObjectToString(Object o) { … }
These three method signatures will be matched by the pointcut in Listing 4-43 during autoproxy creation since Spring AOP has no way of determining which argument value will be passed to these methods. Spring AOP knows any of these single-argument methods can be potentially matched by this pointcut. But even though Spring AOP will decide to create a proxy object for beans that implement any of these methods, that does not mean that the advice will always be executed for these methods. Spring AOP will need to do a match each time these methods are executed to decide whether or not to execute the advice, based on the argument value that is passed. This process may have the undesired side effect of creating many more proxy objects than intended.
In Spring AOP, proxy creation is relatively inexpensive and of no concern for singleton beans.
It's a different story for prototype beans, since a proxy object needs to be created each time it's requested via dependency lookup or dependency injection. You should be aware of the performance impact dynamic pointcuts may have on prototype bean creation in the Spring container if you're going to create many prototype objects.
Selecting on a Return Type Declaration
Another way to select methods is based on their return type declaration, which can be any type or void. Let's consider the pointcut we used at the start of this chapter to select all methods named startMatch():
execution(* startMatch(..))
The asterisk (*) in this pointcut matches any return type; the return type and the method signature are both required fields for the execution()
pointcut designator. We can change this pointcut to match only startMatch()
methods that return the type we expect, like this:
execution(com.apress.springbook.chapter04.Match startMatch(..))
Why do we want to specify the return type when we have only one startMatch()
method in the entire application and * is much less typing?
When writing pointcuts, you need to be careful about which join points you will select. Any join point that is selected as an unintended side effect is overhead. When applications grow, many small overheads often become significant.
You can also use the void keyword as the return type to select only methods that don't return anything.
Note
Selecting methods on their return type declaration does not require a dynamic pointcut since Spring AOP can look at static method signatures at auto-proxy creation time.
Selecting on a throws Declaration
Sometimes you want to select methods based on their throws declaration. These pointcuts are not strictly related to after throwing advice; they can be used with any advice type.
To match an exception type, it must be declared in method signatures. As you are probably aware, Java has two exception types:
Unchecked exceptions: These are unrecoverable exceptions, meaning there is usually little or nothing an application can do in response to these exceptions when they occur. Because of their unrecoverable nature, Java does not require these exceptions to be declared in method signatures or caught by application code. Exceptions that are type-compatible with java.lang.Error and java.lang.RuntimeException are unchecked.
Checked exceptions: These are usually recoverable exceptions, meaning the application logic can do something significant in response to their occurrence. These exceptions must be either caught in a try/catch block or declared in the method signature to pass them on to the caller.
Exceptions that are not type-compatible with java.lang.Error and java.lang.Runtime Exception are checked.
Checked exceptions must be declared in method signatures if not caught in the method body; unchecked exceptions may be declared, but this is not required. Unchecked exceptions are sometimes declared in method signatures to inform callers they may be thrown. However, even when unchecked exceptions are declared in method signatures, they don't need to be caught by the caller.
This leads us to conclude the following:
- Checked exceptions are typically declared in method signatures.
- Unchecked exceptions are often not declared in method signatures.
Since pointcuts can match only methods that declare exceptions in their signatures, you will typically be able to match only against checked exceptions, as shown in Listing 4-44.
Listing 4-44. Matching Against java.io.IOException
package com.apress.springbook.chapter04.aspects;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.annotation.Before;
@Aspect
public class MessagePrintingAspect {
@Before("execution(* *(..) throws java.lang.IOException)")
public void printWarningIOExceptionMayBeThrown() {
System.out.println("Warning: IOException may be thrown. Brace yourselves!");
}
}
You need to specify only the exception types you want to match against. Method signatures may declare additional types yet will still be matched. The following method signatures will be matched by the pointcut in Listing 4-44:
public String readFileToString(java.io.File file) throws java.io.IOException, java.lang.IllegalArgumentException { … } public void copy(java.io.InputStream in, java.io.OutputStream out) java.io.IOException, java.lang.IllegalStateException { … } When we talk about binding advice method arguments later in this chapter, we'll look at how to respond to exceptions that are actually thrown by methods.
Note
Selecting methods on their throws
declaration does not require a dynamic pointcut since Spring AOP can look at static method signatures at auto-proxy creation time.
Selecting Methods via Classes, Packages, and Inheritance
Selecting methods directly is very useful when writing advice for specific methods or methods with specific properties. The most interesting use of aspects, however, is enforcement of systemwide policies, as in the following examples:
- Logging: You want to learn exactly how your application components interact, or you want to measure the performance of method executions.
- Auditing: You have a requirement to log who executes operations on a series of components.
- Security: You want to enforce user roles when invoking methods, or you want to filter return values based on security policies.
- Optimization: You want to learn how your users use your domain logic to learn where you can add more features or how you can improve performance, reduce memory usage, or reduce database access for common operations.
Each of these requirements typically has an impact on large sets of methods based on generic properties, such as containing classes, containing packages, or inheritance hierarchies. Some of these requirements can be implemented only by binding advice methods arguments, which we'll discuss later in this chapter. Here, we'll focus on how to select methods based on classes, packages, and inheritance.
Selecting on a Class
There are two important distinctions when selecting all methods within one class:
- Pointcuts that can determine matches based on static (as in nondynamic) class and method information. The
execution()
and within()
pointcut designators will look only at static information and are great to narrow down the selection on a pointcut. - Pointcuts that require a method execution context to determine a match. Pointcuts that require method execution can't always be avoided, but it's important that you know how to limit the number of objects that will be proxied to only the intended ones.
You're already familiar with execution()
, but we haven't discussed yet how you can select methods in one class. The within()
pointcut designator can also narrow the selection to all methods on a specific type. Here are two pointcuts that select exactly the same join points:
execution(* com.apress.springbook.chapter04.DefaultTournamentMatchManager.*(..))
within(com.apress.springbook.chapter04.DefaultTournamentMatchManager)
These pointcuts select all methods on the DefaultTournamentMatchManager class. They use static information so don't require the context of the method execution. However, sometimes you need this, specifically when using advice method argument binding, as we'll discuss shortly. We've already discussed the args()
pointcut designator, which enforces dynamic pointcuts.
Two other dynamic pointcut designators are target()
and this()
. The target()
designator narrows down the selection on the target object of a proxy object. The this()
designator narrows down the selection on the proxy object itself. (See Chapter 6 of the Spring 2.0 reference manual for more details on target()
and this()
.)
Selecting Methods via Inheritance
You can very easily apply what you've learned in the previous section to class hierarchies that are created through inheritance. The DefaultTournamentMatchManager class implements the TournamentMatchManager interface so is part of this interface's hierarchy. We can rewrite the pointcuts shown in the previous section to match all members of a class hierarchy instead of one specific class, like this:
execution(* com.apress.springbook.chapter04.TournamentMatchManager+.*(..))
within(com.apress.springbook.chapter04.TournamentMatchManager+)
These pointcuts will select all objects that are type-compatible with the TournamentMatch Manager interface. Notice the use of the + operator to tell Spring AOP to take the class hierarchy into account.
Selecting Methods on all Classes in a Package
It's very easy to narrow the selection of join points to all classes in a package, and you can optionally include all subpackages. To select all methods on all classes in the com.apress.springbook. chapter04 package, simply use the asterisk (*) operator:
execution(* com.apress.springbook.chapter04.*.*(..))
within(com.apress.springbook.chapter04.*)
If you want to include all subpackages, you need to provide on extra dot (..). Compare the preceding pointcuts to these:
execution(*com.apress.springbook.chapter04..*.*(..))
within(com.apress.springbook.chapter04..*)
Narrowing down on the package level helps you to select specific methods.
Selecting Methods via Annotations
Selecting all methods of all classes in a package may be too coarse-grained for certain aspects. For example, with auditing, you know in advance which methods on which classes require the retention of audit information.
Here, we'll use the example of auditing to demonstrate how to select methods with annotations and methods in classes with annotations. Throughout this example, we'll use the @Audit
annotation shown in Listing 4-45.
Listing 4-45. The @Audit
annotation
package com.apress.springbook.chapter04;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@Retention(RetentionPolicy.RUNTIME)
public @interface Audit {
}
We can now use this annotation on the BusinessOperations class, in shown Listing 4-46.
Listing 4-46. Using the @Audit
annotation to Mark a Method That Requires Retention of Audit Information
package com.apress.springbook.chapter04;
public class BusinessOperations {
@Audit
public void sensitiveOperation(long recordId) { … }
}
By declaring the @Audit
annotation on the sensitiveOperation()
method, we've indicated we need to retain audit information for this method. We haven't determined, however, which information to retain, where to store this information, and for how long.
The information that is to be stored and how it is to be stored is very likely defined in a policy document and is equal for all methods. We've only marked a method and can now implement the retention policy with an aspect.
Selecting on Method Annotation Declarations
We need to write an aspect that implements the audit retention policy. Selecting which methods are subject to audit information retention is a separate effort and of no concern to the aspect or the developers who implement it. Because we can base the pointcut on a Java 5 annotation, we get finegrained semantics.
We will select only the methods that declare the @Audit
annotation and leave other methods unaffected. This is a great way of working, since we can be sure there will be no side effects. We'll delegate the actual saving of audit information to the AuditInformationRetentionPolicy interface, as shown in Listing 4-47.
Listing 4-47. The AuditInformationRetentionPolicy Interface
package com.apress.springbook.chapter04;
public interface AuditInformationRetentionPolicy {
public void retainMethodInvocationInformation(
String currentUser, String methodDescription, Object[] arguments);
}
As you'll notice, the retainMethodInvocationInformation()
method on the AuditInformation RetentionPolicy interface requires the name of the current user. We need to get this name, but we
don't want to tie the implementation of our aspect to a specific authentication mechanism. We create the CurrentUserInformation interface, as shown in Listing 4-48.
Listing 4-48. The CurrentUserInformation Interface
package com.apress.springbook.chapter04;
public interface CurrentUserInformation {
public String getUsername();
}
We now know how to retain audit information and how to get the current username. Since we delegate these two responsibilities to collaboration objects, we will need to configure our aspect in the Spring container.
The first step is to add a pointcut to the SystemPointcutsAspect, since we want to centralize systemwide pointcuts, as shown in Listing 4-49.
Listing 4-49. Adding a Systemwide Pointcut
package com.apress.springbook.chapter04.aspects;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
@Aspect
public class SystemPointcutsAspect {
@Pointcut("@annotation(com.apress.springbook.chapter04.Audit)")
public void auditableMethods() { }
}
The pointcut in Listing 4-49 uses the @annotation() pointcut designator to select join points that have declared the @Audit
annotation (we'll discuss the @annotation() pointcut designator more in the "Binding Annotations" section later in this chapter). Spring AOP can now select only those beans in the Spring container during auto-proxy creation.
Notice that we're not selecting specific methods, classes, or packages anymore. We can obviously further narrow down the selection of the pointcut if desired.
Listing 4-50 shows the AuditInformationRetentionAspect that is responsible for trapping all executions of methods that are marked with the @Audit
annotation and call the retainMethod InvocationInformation() on the AuditInformationRetentionPolicy interface.
Listing 4-50. The AuditInformationRetentionAspect Is Responsible for Saving Audit Information for Sensitive Operations
package com.apress.springbook.chapter04.aspects;
import com.apress.springbook.chapter04.CurrentUserInformation;
import com.apress.springbook.chapter04.AuditInformationRetentionPolicy;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.JoinPoint;
@Aspect
public class AuditInformationRetentionAspect {
private AuditInformationRetentionPolicy auditInformationRetentionPolicy;
private CurrentUserInformation currentUserInformation;
public void setAuditInformationRetentionPolicy(
AuditInformationRetentionPolicy auditInformationRetentionPolicy
) {
this.auditInformationRetentionPolicy = auditInformationRetentionPolicy;
}
public void setCurrentUserInformation
(
CurrentUserInformation currentUserInformation
) {
this.currentUserInformation = currentUserInformation;
}
public void init() {
if (this.auditInformationRetentionPolicy == null) {
throw new IllegalStateException("AuditInformationRetentionPolicy " +
"object is not set!");
}
if (this.currentUserInformation == null) {
throw new IllegalStateException("CurrentUserInformation " +
"object is not set!");
}
}
@Before("com.apress.springbook.chapter04.aspects." +
"SystemPointcutsAspect.auditableMethods()")
public void retainMethodInvocationInformation(JoinPoint joinPoint) {
String currentUser = this.currentUserInformation.getUsername();
String methodDescription = joinPoint.getSignature().toLongString();
Object[] arguments = joinPoint.getArgs();
this.auditInformationRetentionPolicy.retainMethodInvocationInformation(
currentUser, methodDescription, arguments);
}
}
In Listing 4-50, the retainMethodInvocationInformation()
advice method has a JoinPoint argument. Any advice method, except around advice, can have a first argument of this type. If this argument is declared, Spring AOP will pass a JoinPoint object that contains information about the current join point.
The aspect in Listing 4-50 uses the org.aspectj.lang.JoinPoint object to obtain a description of the method that is executed and the arguments that have been passed.
Listing 4-51 shows the Spring XML configuration file for the BusinessOperations class and the AuditInformationRetentionAspect aspect.
Listing 4-51. Configuring the Auditable Class and Audit Aspect
<beans>
<bean class="org.springframework.aop.aspectj.annotation. -->
AnnotationAwareAspectJAutoProxyCreator"/>
<bean class="com.apress.springbook.chapter04.aspects. -->
AuditInformationRetentionAspect"
init-method="init">
<property name="auditInformationRetentionPolicy"
ref="auditInformationRetentionPolicy"/>
<property name="currentUserInformation" ref="currentUserInformation"/>
</bean>
<bean id="businessOperations"
class="com.apress.springbook.chapter04.BusinessOperations">
<!-- properties omitted -->
</bean>
</beans>
The Spring configuration in Listing 4-51 configures the AuditInformationRetentionAspect with AuditInformationRetentionPolicy and CurrentUserInformation objects. It's not important for this example and the aspect how these interfaces are implemented. The aspect delegates these two responsibilities, so that its advice implementation is small, easy to maintain, and easy to implement.
Selecting on Class Annotation Declarations
The example in the previous section elaborates on how to match methods with @Audit declaration.
Sometimes you also want to mark an entire class with an annotation to enforce a policy for all methods in this class. This is a viable approach in those cases where all the responsibilities of a class require the enforcement of a policy such as auditing. Methods that don't require these policies consequently don't belong in this class.
While this is an interesting approach, you need to take into account one consequence. If you recall, one of the requirements of working with aspects in Spring AOP is that callers use the proxy object, not the target object.
Consider the MoreBusinessOperations class in Listing 4-52.
Listing 4-52. The MoreBusinessOperations Class
package com.apress.springbook.chapter04;
@Audit
public class MoreBusinessOperations {
public (.*?) {
someOtherSensitiveOperation(recordId);
}
public void someOtherSensitiveOperation(long recordId) {
}
}
In Listing 4-52, the someSensitiveOperation()
method calls the someOtherSensitiveOperation()
method on the same object. This object, however, is the target object—the original object for which a proxy object may have been created.
Whether or not the someSensitiveOperation()
method has been called via a proxy object is not relevant. This method calls another method on the same object, so that the method call will not pass through a proxy object. This means the auditing policy will not be enforced for the someOtherSensitiveOperation(). Although this sounds dramatic, we need to consider if saving audit information when the someSensitiveOperation()
is executed is sufficient to cover both method executions. If this is not sufficient, then do two methods that both require audit information retention, one called by the other, belong in the same class? It's entirely possible that placing these two methods in the same class overloads the class with responsibilities.
If the someOtherSensitiveOperation()
method always requires audit information to be saved, it should probably be moved to a separate class. An object of that class can then be injected in a MoreBusinessOperations object, which can then call the someOtherSensitiveOperation()
method on the injected object.
There are no hard-and-fast rules for solving this kind of conflict between application design and limitations imposed by technical frameworks such as Spring AOP. We recommend that you use some common sense and consider the class, the methods, and the policy to be enforced to come to a solution.
To match methods in classes that declare the @Audit
annotation, we need to change the auditableMethods() pointcut in SystemPointcutsAspect, as shown in Listing 4-53.
Listing 4-53. Also Match Methods in Classes That Declare the @Audit
annotation
package com.apress.springbook.chapter04.aspects;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
@Aspect
public class SystemPointcutsAspect {
@Pointcut("@within(com.apress.springbook.chapter04.Audit)")
public void auditableMethods() { }
}
The @within() pointcut designator used in the auditableMethods()
pointcut in Listing 4-53 also matches methods declared within classes that declare the @Audit
annotation. In other words, the @within() pointcut designator matches only @Audit
annotations at the class level.
Now that we've covered how to match Java 5 annotations with pointcuts, let's look at how to bind advice.
Binding Advice Arguments
We're going to take what you've learned about pointcuts so far a couple of steps further. Now, we'll add arguments to advice methods. This is called argument binding, and it allows you to create much more powerful advice methods. You can add arguments to advice methods and bind any method argument value, exception, return value, or annotation object.
Let's start with the aspect from the first examples in this chapter, shown in Listing 4-54.
Listing 4-54. Printing a Message When a Tennis Match Starts
package com.apress.springbook.chapter04.aspects;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
@Aspect
public class MessagePrintingAspect {
@Before("execution(* startMatch(..))")
public void printMessageToInformMatchStarts() {
System.out.println("Attempting to start tennis match!");
}
}
The printMessageToInformMatchStarts()
advice method in Listing 4-54 has no arguments.
Let's look again at the TournamentMatchManager interface and its startMatch()
method, shown in Listing 4-55.
Listing 4-55. The TournamentMatchManager Interface
package com.apress.springbook.chapter04;
public interface TournamentMatchManager {
public Match startMatch(long matchId) throws
UnknownMatchException, MatchIsFinishedException,
MatchCannotBePlayedException, PreviousMatchesNotFinishedException;
}
Say we want to print the match identifier that is passed to startMatch()
in the printMessage ToInformMatchStarts() advice method. We need to get the argument somehow. We've already used the JoinPoint argument before, and we can use it again here to obtain the match identifier argument value of the startMatch()
method, as shown in Listing 4-56.
Listing 4-56. Getting an Argument Value via the JoinPoint Object
package com.apress.springbook.chapter04.aspects;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.JoinPoint;
@Aspect
public class MessagePrintingAspect {
@Before("execution(* startMatch(..))")
public void printMessageToInformMatchStarts(JoinPoint jp) {
Long matchId = (Long)jp.getArgs()[0];
System.out.println("Attempting to start tennis match with identifier " +
matchId + "!");
}
}
Spring AOP will detect that the printMessageToInformMatchStarts()
advice method has JoinPoint as its first argument type and will pass a JoinPoint object, which has an Object array with the argument values of the method invocation.
So we now have the match identifier, but there is a problem: the pointcut expression must be changed to avoid errors. The current pointcut expression will select any startMatch() method, regardless of its argument types. We need to change the pointcut to select only methods that have a first argument of type long, as shown in Listing 4-57, because the advice method assumes it's present.
Listing 4-57. Changing the Pointcut Expression to Further Narrow the Method Selection
package com.apress.springbook.chapter04.aspects;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.JoinPoint;
@Aspect public class MessagePrintingAspect { @Before("execution(* startMatch(long,..))") public void printMessageToInformMatchStarts(JoinPoint jp) { Long matchId = (Long)jp.getArgs()[0]; System.out.println("Attempting to start tennis match with identifier " + matchId + "!"); } } This is better, but Listing 4-57 can still be improved. Spring AOP allows us to declare a long argument in the printMessageToInformMatchStarts()
advice method, instead of having to use the JoinPoint object, as the next sections demonstrate.
Spring AOP supports advice argument binding as follows:
- Binding values, possible for all advice types
- Binding return values, possible only for after returning advice
- Binding exception objects, possible only for after throwing advice
- Binding annotation objects, possible for all advice types
You need to configure your Java compiler to include debug information in Java classes to make argument binding work correctly. Typically, the compilers in IDEs are configured this way by default. See Chapter 6 of the Spring 2.0 reference manual for more details.
Binding Method Argument Values
You can bind argument values that were passed to the method execution on the proxy object to arguments of advice methods. Binding method argument values is possible for all advice types.
For the example, we want to bind the match identifier value to an argument of the print MessageToInformMatchStarts() advice method, so first we need to add an argument to this method.
However, we also need to use the args()
pointcut designator to specify that we want to bind method arguments. We've changed the MessagePrintingAspect as shown in Listing 4-58.
Listing 4-58. Binding the Match Identifier Value to the Advice Method
package com.apress.springbook.chapter04.aspects;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.JoinPoint;
@Aspect
public class MessagePrintingAspect {
@Before("execution(* startMatch(..)) && args(matchId, ..)")
public void printMessageToInformMatchStarts(long matchId) {
System.out.println("Attempting to start tennis match with identifier " +
matchId + "!");
}
}
The pointcut in Listing 4-58 tells Spring AOP to bind the first argument value of the join point to the sole argument of the printMessageToInformMatchStarts()
advice method. When this advice method is executed, its argument will contain the value that was passed to the startMatch()
method execution on the proxy object.
Note the following about the pointcut and advice method in Listing 4-58:
- We've kept the static argument selection in the
execution()
point designator. Remember that execution()
uses static method signature information, while args()
needs a dynamic pointcut. To avoid selecting too many startMatch()
methods as join points that can match the pointcut at auto-proxy creation time, we add as much static criteria as we can. - The
printMessageToInformMatchStarts()
advice method cannot change the value of the match identifier. To change argument values, the JoinPoint object must be used. - When adding the argument to the
printMessageToInformMatchStarts()
advice methods, this argument must be bound by the pointcut, so we must add the args()
pointcut designator.
When we add more arguments, we will need to change the pointcut so that these extra arguments will also be bound. The names used in the args()
pointcut designator must match the argument names in the advice method arguments.
To do this in XML, add the following to your configuration file:
<aop:aspect ref="messagePrinter">
<aop:before method="printMessageToInformMatchStarts"
arg-names="matchId"
pointcut="execution(* startMatch(..)) && args(matchId, ..)"/>
</aop:aspect>
Binding Return Values
You can also bind the return value to arguments of advice methods, but this is only possible for after returning advice. Listing 4-59 shows the MessagePrintingAspect that gets access to the return value of the startMatch()
method.
Listing 4-59. Getting the Return Value
package com.apress.springbook.chapter04.aspects;
import com.apress.springbook.chapter04.Match;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.JoinPoint;
@Aspect
public class MessagePrintingAspect {
@AfterReturning(
value = "execution(com.apress.springbook.chapter04.Match" +
" startMatch(..))",
returning = "match"
)
public void printMessageToInformMatchHasStarted(Match match) {
System.out.println("This match has been started: " + match);
}
}
Binding the return value is a special kind of static pointcut. Spring AOP doesn't need to do any matching at runtime, but it does need to pass the return value as an argument to the printMessage ToInformMatchHasStarted() advice method. Notice that we provide the return type in the execution()
pointcut designator as a safety measure, so that the advice method will be executed for only the startMatch()
methods with the correct return type.
We've specified the returning property on the @AfterReturning
annotation in Listing 4-59 to indicate we want to bind the return value to the advice method. The value we pass to the returning property is the argument name to which we want to bind. To do this in XML, add the returning attribute:
<aop:aspect ref="messagePrinter">
<aop:after-returning method="printMessageToInformMatchHasStarted"
returning="match"
pointcut="execution(* startMatch(..))"/>
</aop:aspect>
Binding Exceptions
When an exception occurs, you can also bind this object as an argument to your advice methods, but only if you're using after throwing advice. Listing 4-60 shows the MessagePrintingAspect that gets access to one exception type thrown by the startMatch()
method on DefaultTournamentMatch Manager.
Listing 4-60. Getting Exceptions That Are Thrown by the startMatch()
method
package com.apress.springbook.chapter04.aspects;
import com.apress.springbook.chapter04.UnknownMatchException;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.JoinPoint;
@Aspect
public class MessagePrintingAspect {
@AfterThrowing(
value = "execution(* startMatch(..) throws " +
"com.apress.springbook.chapter04." +
"UnknownMatchException)",
throwing = "exception"
)
public void printMessageWhenMatchIdentifierIsNotFound(
UnknownMatchException exception) {
System.out.println("No match found for match identifier " +
exception.getInvalidMatchIdentifier() + "!");
}
}
The pointcut in Listing 4-60 uses only the execution()
pointcut designator, meaning it will use only static method signature information to match join points at auto-proxy creation time. When the UnknownMatchException type is thrown by the startMatch()
method, the printMessageWhen MatchIdentifierIsNotFound() advice method will be executed.
However, what will happen if another exception type is thrown by the startMatch()
method?
The startMatch()
declares three other exception types in addition to UnknownMatchException and can also throw any unchecked exception.
The printMessageWhenMatchIdentifierIsNotFound()
advice method will be executed only if the exception type declared in its sole argument is thrown; otherwise, the advice will not be executed at all. This allows us to add more @AfterThrowing advice to handle specific exception types. We don't necessarily need to use the exception object, but by binding it to advice methods, Spring AOP can choose the correct advice.
Note, however, the pointcut in Listing 4-60 is not a dynamic pointcut. Spring AOP will match join points based on the static execution()
pointcut designator. When an exception is thrown, Spring AOP will decide per advice if it needs to be executed, based on the binding information. This means the static pointcut must be sufficiently strict to select only the methods that can actually throw the exception. A pointcut that is not strict enough will trigger the creation of proxy objects for too many beans during auto-proxy creation.
The throwing property on the @AfterThrowing
annotation must be present when binding exceptions and should have the name of the exception argument that is declared in the advice method as its value.
To bind on the exception using XML notation, you would do the following:
<aop:aspect ref="messagePrinter">
<aop:after-throwing method="printMessageWhenMatchIdentifierIsNotFound"
throwing="exception"
pointcut="execution(* startMatch(..) throws -->
com.apress.springbook.chapter04.UnknownMatchException)"/>
</aop:aspect>
Binding Annotations
Since annotations are added to the bytecode of classes and are part of class and method declarations, they are static and immutable. This means Spring AOP can get annotations with static (nondynamic) pointcuts.
We will continue to look at binding annotation objects to advice method arguments that come from two locations:
- Annotations declared on methods
- Annotations declared on classes In this section, we will extend the @Audit example we used when we introduced annotations earlier in this chapter. First, we will add a property to the
@Audit
annotation, as shown in Listing 4-61.
Listing 4-61. Adding a Property to the @Audit
annotation
package com.apress.springbook.chapter04;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@Retention(RetentionPolicy.RUNTIME)
public @interface Audit {
String value() default "";
}
By adding a value to the @Audit
annotation, we can pass specific information that we can use while retaining audit information. Next, we'll change the auditableMethods()
pointcut declaration on the SystemPointcutsAdvice to work with argument binding, as shown in Listing 4-62.
Listing 4-62. Changing the auditableMethods()
pointcut Declaration to Work with Argument Binding
package com.apress.springbook.chapter04.aspects;
import com.apress.springbook.chapter04.Audit;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
@Aspect
public class SystemPointcutsAspect {
@Pointcut("@annotation(audit)")
public void auditableMethods(Audit audit) { }
}
We've changed the pointcut declaration to support binding @Audit
annotation objects that are declared on method execution join points. The @annotation() pointcut designator takes the name of the audit argument in the auditableMethods()
method. @annotation() takes a variable name, instead of a type, to specify that we want to bind an annotation object. auditableMethods()
declares an argument of type Audit to tell Spring AOP that only @Audit
annotations are to be selected by this pointcut.
Listing 4-63 shows the BusinessOperations class with the @Audit
annotation.
Listing 4-63. Declaring the @Audit
annotation with Additional Information
package com.apress.springbook.chapter04;
public class BusinessOperations {
@Audit("top secret")
public void sensitiveOperation(long recordId) { … }
}
We need to change the AuditInformationRetentionAspect aspect slightly from Listing 4-50 to enable the argument binding of the annotation object that is declared on a join point, as shown in Listing 4-64.
Listing 4-64. Binding Annotation Objects Declared on Objects
package com.apress.springbook.chapter04.aspects;
import com.apress.springbook.chapter04.Audit;
import com.apress.springbook.chapter04.CurrentUserInformation;
import com.apress.springbook.chapter04.AuditInformationRetentionPolicy;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.JoinPoint;
@Aspect
public class AuditInformationRetentionAspect {
private AuditInformationRetentionPolicy auditInformationRetentionPolicy;
private CurrentUserInformation currentUserInformation;
public void setAuditInformationRetentionPolicy(
AuditInformationRetentionPolicy auditInformationRetentionPolicy) {
this.auditInformationRetentionPolicy = auditInformationRetentionPolicy;
}
public void setCurrentUserInformation(
CurrentUserInformation currentUserInformation) {
this.currentUserInformation = currentUserInformation;
}
public void init() {
if (this.auditInformationRetentionPolicy == null) {
throw new IllegalStateException("AuditInformationRetentionPolicy " +
"object is not set!");
}
if (this.currentUserInformation == null) {
throw new IllegalStateException("CurrentUserInformation " +
"object is not set!");
}
}
@Before("com.apress.springbook.chapter04.aspects." +
"SystemPointcutsAspect.auditableMethods(audit)")
public void retainMethodInvocationInformation(JoinPoint joinPoint, Audit audit) {
String currentUser = this.currentUserInformation.getUsername();
String methodDescription = audit.value() + ":" +
joinPoint.getSignature().toLongString();
Object[] arguments = joinPoint.getArgs();
this.auditInformationRetentionPolicy.retainMethodInvocationInformation(
currentUser, methodDescription, arguments);
}
}
The auditableMethods()
pointcut on SystemPointcutsAspect takes the name of the argument in the retainMethodInvocationInformation()
advice method. Notice this advice method still has the JoinPoint argument as its first argument and the audit annotation type as the second argument.
The JoinPoint argument is bound automatically by Spring AOP, and auditableMethods()
calls @annotation() to bind the Audit annotation argument.
Because the pointcut doesn't specify the annotation type, Spring AOP decides that the argument in @annotation() is referred to by name. In this way, the retainMethodInvocationInformation()
advice method is executed only for join points that declare the @Audit
annotation.
Notice the pointcut in Listing 4-64 doesn't reuse another pointcut. The reuse of pointcuts doesn't support binding arguments to advice methods.
We can also bind annotations that are declared on classes to advice methods using the @within() pointcut designator. Consider again the MoreBusinessOperations class, as shown in Listing 4-65.
Listing 4-65. The MoreBusinessOperations Class Is Now Classified Top Secret
package com.apress.springbook.chapter04;
@Audit("top secret")
public class MoreBusinessOperations {
public (.*?) {
someOtherSensitiveOperation(recordId);
}
public void someOtherSensitiveOperation(long recordId) {
}
}
Getting the @Audit
annotation object on the MoreBusinessOperations class and binding it to the retainMethodInvocationInformation() advice method requires a change to the SystemPointcuts Aspect, as shown in Listing 4-66.
Listing 4-66. SystemPointcutsAspect Selects @Audit
annotations on Classes
package com.apress.springbook.chapter04.aspects;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
@Aspect
public class SystemPointcutsAspect {
@Pointcut("@within(audit)")
public void auditableMethods(Audit audit) { }
}
Again, the @within()
point designators matches @Audit
annotation declarations on the class level. Also notice that no dynamic pointcut is used, since @within() can match join points based on static method signature information during auto-proxy creation.
Being able to create your own annotations, annotate your methods and classes, and bind the annotation objects to your advice method arguments is an extremely interesting feature of Spring AOP. See Chapter 6 of the Spring reference manual for more details about binding advice arguments, as well as other Spring AOP topics.
Summary
The popularity of the Spring Framework is in part thanks to Spring AOP and how other parts of the framework use it. In Spring 2.0, the already excellent integration between Spring AOP and the Spring container has been improved by introducing aspects and the AspectJ pointcut language.
AspectJ aspects are rich, fine-grained, and powerful constructs that allow you to change and augment the behavior of Java classes. This chapter covered all you need to know to get started with the new Spring AOP features and aspects in your application. Aspects are supported for Java 5 by leveraging the @AspectJ-style annotations and for older Java versions by leveraging the AOP XML Schema. The next chapter introduces data access with the Spring Framework.
This sample chapter is an excerpt from the book, Building Spring 2 Enterprise Applications, by Interface 21, Bram Smeets, Seth Ladd, Copyright 2007, Apress, Inc.
The source code for this book is available to readers at http://www.apress.com/book/view/1590599187.
Interface21 is a privately held international company made up of consultants who build, use, and train for the most popular application framework in the world. Springframework.com is operated by Interface21 Limited, a privately owned company. The developers who brought you the Spring framework also provide training, consulting, and support. Interface21 also has partnerships in Europe and North America, partnering with companies that value a high quality of service and have experience with Spring, as well as partnerships with developers who deliver .NET services. Interface21 wants to be your first partner for enterprise development.
Seth Ladd is a software engineer and professional Spring Framework trainer and mentor specializing in object-oriented and testable web applications. He started his own company building websites at age 17, but now enjoys having a real job. Currently working for Camber Corporation, Seth has built and deployed systems for NEC, Rochester Institute of Technology, Brivo Systems, and National Information Consortium. He has architected and developed enterprise applications in Java and C for both the server and remotely connected embedded devices. He enjoys speaking and teaching, and is a frequent presenter at local Java user groups and at corporate developer conferences. Seth is very thankful for living and working in Kailua, Hawaii, with his wife.
Bram Smeets is a Java architect with over 8 years’ experience in developing enterprise Java applications. Currently, Bram is technical director at JTeam (www.jteam.nl), a Java software development company based in the Netherlands and senior consultant at SpringSource (www.springsource.com). He is a regular speaker at technology–focused conferences like The Ajax Experience and SpringOne. Using GWT, Bram has delivered several successful RIA projects at JTeam. He also delivered Ajax and GWT trainings at several companies.