|
||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
|
Announcements
Chapters
Services
Feature Zones
|
Note: This is an unedited contribution. If this article is inappropriate,
needs attention or copies someone else's work without reference then please
Report This Article
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:
Introducing AspectJ and AspectsWhile 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:
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 AspectJAspectJ 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:
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 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 CreationAspectJ 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 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 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 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 package com.apress.springbook.chapter04; public class SunnyDay { public void relax() { // go to the beach } }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.
Configuring @AspectJ-Style Aspects in SpringBy 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 usingProxyFactoryBean 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 DefinitionThe aspect for this example is shown in Listing 4-3. It has one pointcut that selects allstartMatch() 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 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 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 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
The pointcut declaration selects all instance methods named Target ClassThe target class in this example is our friendDefaultTournamentMatchManager, 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 { // implementation omitted } /* other methods omitted */ }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 ConfigurationListing 4-5 shows the required configuration in a Spring XML configuration file to have the printMessageToInformMatchStarts advice print a message to the console before thestartMatch() 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 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 The printMessageToInformMatchStarts advice will be called when the An Integration Test for the Configuration and AspectWe 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 itsstartMatch() 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 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 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 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:
Now, let's look at the advice types supported by aspects in Spring AOP. @AspectJ-Style Advice TypesAspects 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.
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 AdviceAfter 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) AdviceAfter (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 AdviceAround 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:
Another interesting thing to notice in Listing 4-10 is the call to the 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 ReuseYou 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. The 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 ContainerWe'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 SchemaThe 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 SelectionThe 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 So let's wrap up the rules for auto-proxy creation in the Spring container based on the example:
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 Advice and Aspect OrderingAdvice 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 AdviceIn 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 AspectsWhen 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:
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 <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 TagsThe 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 TagsThe 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:
XML Aspect ConfigurationThe @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 Listing 4-25. xml-aspect-context.xml: Turning the <?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. 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. 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 XMLYou 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 XMLThe 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 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=&q | |||||||||||||||||||||||||||||