Chapter 6. Advanced AOP

In this chapter, we go into more detail about the AOP features available in Spring. In particular, we look at the topic in a much more real-world light: we explore the framework services in Spring that allow for transparent application of AOP; we cover real-world usage of AOP in the context of the sample application; and we discuss overcoming the limitations of Spring AOP using Spring/AspectJ integration.

First, we'll cover @AspectJ. Spring 2.5 brings a new way of writing aspects; it automatically turns classes with specific annotations into Spring AOP aspects. The @AspectJ support allows you to very easily and cleanly define aspects. Because the @AspectJ aspects are Spring beans, you have full access to Spring's DI features.

Introductions, mentioned briefly in the previous chapter, allow you to add interface implementations dynamically to any object on the fly using the familiar interceptor concept. In this chapter, we'll cover these in more detail.

After introductions, we'll look at how the ProxyFactoryBean, the class at the heart of Spring AOP, can affect the behavior of your application. We explain the difference between direct and proxied calls.

Next, we'll cover integrating AspectJ, which is a full featured, statically compiled AOP implementation. The feature set of AspectJ is much greater than that of Spring AOP, but AspectJ is much more complicated to use. As mentioned in the previous chapter, AspectJ is a good solution when you find that Spring AOP lacks a feature you need (particularly when it comes to the various pointcut types).

Finally, we will take a look at how you can use aspect-oriented programming in your applications. We will ignore the usual choices (e.g., logging and security) and offer a more realistic example.

In order to run some of the examples in this chapter, you need to obtain AspectJ. You can download it from http://eclipse.org/aspectj. We used version 1.5.4 of AspectJ for the examples in this chapter.

@AspectJ

@AspectJ has nothing to do with AspectJ; it is a set of Java 5 annotations that Spring uses to parse the pointcuts and advice. This also means that @AspectJ aspects have no dependency on AspectJ; they use pure Spring AOP. The @AspectJ support offers a very easy and convenient way to create aspects; some IDEs come with @AspectJ support, simplifying the process of creating aspects.

Let's begin by writing the infamous logging aspect in Listing 6-1. We will write a single piece of around advice for every method call on the TestBean class.

Example 6.1. Simple @AspectJ Aspect

@Aspect
public class LoggingAspect {

    @Around("execution(* com.apress.prospring2.ch06.simple.TestBean.*(..))")
    public Object log(ProceedingJoinPoint pjp) throws Throwable {
        System.out.println("Before");
        Object ret = pjp.proceed();
        System.out.println("After");
        return ret;
    }

}

The first annotation, @Aspect simply tells Spring to treat this bean as an aspect, that is, to extract the pointcuts and advice from it. Next, we have the @Around annotation with an AspectJ pointcut expression. The advice is very simple: it "logs" the start of the call to the method, proceeds to invoke the advised method, and finally "logs" the end of the method call. Next, let's create a sample application and the TestBean class (see Listing 6-2).

Example 6.2. The TestBean and Sample Application for the Aspect

// TestBean.java
public class TestBean {

    public void work() {
        System.out.println("work");
    }

    public void stop() {
        System.out.println("stop");
    }

}

// LoggingAspectDemo.java
public class LoggingAspectDemo {

    public static void main(String[] args) {
        ApplicationContext ac = new ClassPathXmlApplicationContext(
                "/META-INF/spring/ataspectjdemo1-context.xml"
        );
        TestBean testBean = (TestBean) ac.getBean("test");
        testBean.work();
        testBean.stop();
    }

}

Finally, we put in the last piece of the puzzle: the ataspectjdemo1-context.xml file in Listing 6-3.

Example 6.3. ApplicationContext Configuration File

<?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">

    <bean id="test" class="com.apress.prospring2.ch06.simple.TestBean"/>

</beans>

When we run the application, it gets the test bean and calls its work() and stop() methods, but the aspect doesn't seem to work. We have forgotten to enable the @AspectJ support, and we haven't declared the LoggingAspect as a Spring bean in Listing 6-3. Luckily, both these things are easy to do; in fact, all we have to do is update the ataspectjdemo1-context.xml file (see Listing 6-4).

Example 6.4. Modified ataspectjdemo1-context.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">

    <bean id="test" class="com.apress.prospring2.ch06.simple.TestBean"/>
    <bean class="com.apress.prospring2.ch06.simple.LoggingAspect"/>
    <aop:aspectj-autoproxy />

</beans>

The bold lines show the critical points of the configuration. First, we define the aop namespace. Next, we declare the LoggingAspect as a regular Spring bean, and finally, we use the <aop:aspectj-autoproxy /> tag. The code behind the <aop:aspectj-autoproxy /> tag is responsible for post-processing all beans that have at least one piece of advice. Spring will create a proxy for all advised beans. When we run the application now, we will see that our logging advice gets called, and the application prints out the following:

Before
work
After
Before
stop
After

Notice that the aspect is a regular Spring bean, which means we can use Spring to set its dependencies. As an example, let's improve the LoggingAspect to include a custom message for the before and after log entries (see Listing 6-5).

Example 6.5. ImprovedLoggingAspect

@Aspect
public class ImprovedLoggingAspect {
    private String beforeMessage;
    private String afterMessage;
@Around("execution(* com.apress.prospring2.ch06.simple.TestBean.*(..))")
    public Object log(ProceedingJoinPoint pjp) throws Throwable {
        System.out.println(String.format(this.beforeMessage,
                pjp.getSignature().getName(), Arrays.toString(pjp.getArgs())));
        Object ret = pjp.proceed();
        System.out.println(String.format(this.afterMessage,
                pjp.getSignature().getName(), Arrays.toString(pjp.getArgs())));
        return ret;
    }

    @PostConstruct
    public void initialize() {
        Assert.notNull(this.beforeMessage,
           "The [beforeMessage] property of [" + getClass().getName() +
           "] must be set.");
        Assert.notNull(this.afterMessage,
           "The [afterMessage] property of [" + getClass().getName() +
           "] must be set.");
    }

    public void setBeforeMessage(String beforeMessage) {
        this.beforeMessage = beforeMessage;
    }

    public void setAfterMessage(String afterMessage) {
        this.afterMessage = afterMessage;
    }
}

Here, you can see that we are treating the aspect as a plain Spring bean: we have defined two properties and a @PostConstruct-annotated method. Listing 6-6 shows the modifications to the ataspectjdemo1-context.xml to use the ImprovedLoggingAspect.

Example 6.6. Modified ApplicationContext Configuration File

<?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">

    <bean id="test" class="com.apress.prospring2.ch06.simple.TestBean"/>
    <bean class="com.apress.prospring2.ch06.simple.ImprovedLoggingAspect">
        <property name="beforeMessage" value="Before %s %s"/>
        <property name="afterMessage" value="After %s %s"/>
    </bean>
    <aop:aspectj-autoproxy />

</beans>

When we run the application, we see that the new aspect is working; its properties are set to the values in the configuration file. The application now prints this:

Before work []
work
After work []
Before stop []
stop
After stop []

You can see that creating aspects is no more difficult than writing standard Java code. Most IDEs will also offer some assistance with the @AspectJ work (Figure 6-1 shows IDE support in IntelliJ IDEA 7).

IntelliJ IDEA 7 @AspectJ support

Figure 6.1. IntelliJ IDEA 7 @AspectJ support

In the next section, we will take a more detailed look at the @AspectJ support in Spring.

@AspectJ Aspects in Detail

Now that we have written our first @AspectJ aspect, we need to take a more detailed look at its features. We need to take a look at how to create pointcuts (including best and recommended practices) and how we can write advice. Let's begin by looking at pointcuts; we will use @pointcut whenever we are going to reference an @Pointcut-annotated method. We will say @pointcut expression when we refer to the code used in the @Pointcut annotation. To illustrate this, take a look at the following code snippet:

@Pointcut("execution(* com.apress.prospring2.ch06.simple.TestBean.*(..))")
private void testBeanExecution() { }

In this snippet, the testBeanExecution method is an @pointcut, and "execution(* com.apress.prospring2.ch06.simple.TestBean.*(..))" is a pointcut expression.

Pointcuts

In our first aspect, we have used a pointcut in the around advice. We have specified the pointcut expression as constant. If we wanted to create another advice using the same pointcut, we'd have to duplicate the pointcut expression constant. To prevent this duplication, we can use the @Pointcut annotation to create a pointcut. Let's modify our logging aspect to use the @pointcut (see Listing 6-7).

Example 6.7. Using the @Pointcut Annotation

@Aspect
public class LoggingAspectPC {
    private String beforeMessage;
    private String afterMessage;

    @Pointcut("execution(* com.apress.prospring2.ch06.simple.TestBean.*(..))")
    private void testBeanExecution() { }

    @Around("testBeanExecution()")
    public Object log(ProceedingJoinPoint pjp) throws Throwable {
        System.out.println(String.format(this.beforeMessage,
                pjp.getSignature().getName(), Arrays.toString(pjp.getArgs())));
        Object ret = pjp.proceed();
        System.out.println(String.format(this.afterMessage,
                pjp.getSignature().getName(), Arrays.toString(pjp.getArgs())));
        return ret;
    }

    @PostConstruct
    public void initialize() {
        Assert.notNull(this.beforeMessage,
           "The [beforeMessage] property of [" + getClass().getName() +
           "] must be set.");
        Assert.notNull(this.afterMessage,
            "The [afterMessage] property of [" + getClass().getName() +
           "] must be set.");
    }

    public void setBeforeMessage(String beforeMessage) {
        this.beforeMessage = beforeMessage;
    }

    public void setAfterMessage(String afterMessage) {
        this.afterMessage = afterMessage;
    }
}

The code in bold shows that we have created the testBeanExecution @pointcut with the expression execution(* com.apress.prospring2.ch06.simple.TestBean.*(..)). The log advice uses the @pointcut, but we can now add another advice using the same @pointcut. Listing 6-8 shows a fragment of the LoggingAspectPC that uses the same testBeanExecution @pointcut.

Example 6.8. Using the Same Pointcut

@Aspect
public class LoggingAspectPC {
    @Pointcut("execution(* com.apress.prospring2.ch06.simple.TestBean.*(..))")
    private void testBeanExecution() { }

    @Around("testBeanExecution()")
    public Object log(ProceedingJoinPoint pjp) throws Throwable {
        ...
    }

    @After("testBeanExecution()")
    public void afterCall(JoinPoint jp) {
System.out.println("After");
    }

    ....
}

The code in bold shows that we have used the same @pointcut in two pieces of advice. Also note that the @pointcut is private, which means that we can only use it within this class. Let's expand this example a bit further: we will create a common set of @pointcuts and then use those in our aspects. Because an @pointcut is simply a method with the @Pointcut annotation, we can create the SystemPointcuts class in Listing 6-9.

Example 6.9. The SystemPointcuts Class

public final class SystemPointcuts {

    private SystemPointcuts() {

    }

    @Pointcut("execution(* com.apress.prospring2.ch06.simple.TestBean2.*(..))")
    public void testBeanExecution() { }

    @Pointcut("within(com.apress.prospring2.ch06.simple.TestBean2)")
____public_void_fromTestBeanExecution()_{_}
}

Notice that we have made the class final and created a private constructor: because we intend this class only as a holder for the @pointcut methods, we want to prevent anyone from creating instances of this class. Next, Listing 6-10 shows that we can now use the SystemPointcuts in any number of advices.

Example 6.10. Usage of the Pointcuts from the SystemPointcuts Class

@Aspect
public class PointcutDemoAspect {

    @Around("SystemPointcuts.testBeanExecution()")
    public Object log(ProceedingJoinPoint pjp) throws Throwable {
        System.out.println("Before");
        Object ret = pjp.proceed();
        System.out.println("After");
        return ret;
    }

    @Around("SystemPointcuts.fromTestBeanExecution()")
    public Object inTestBean(ProceedingJoinPoint pjp) throws Throwable {
        System.out.println("In Test Bean>");
        Object ret = pjp.proceed();
        System.out.println("<");
        return ret;
    }

}

The code in bold shows that we use the @pointcut methods declared in the SystemPointcuts class. We will complete the example with the TestBean2 and SimpleBean classes, diagramed in Figure 6-2.

UML class diagram of TestBean2 and SimpleBean

Figure 6.2. UML class diagram of TestBean2 and SimpleBean

We create the ApplicationContext configuration file that defines the test and simple beans, and we inject the simple bean into the test bean (see Listing 6-11).

Example 6.11. ApplicationContext Confiuration File

<?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">

    <bean id="test" class="com.apress.prospring2.ch06.simple.TestBean2">
        <property name="simpleBean" ref="simple"/>
    </bean>
    <bean id="simple" class="com.apress.prospring2.ch06.simple.SimpleBean"/>
    <bean class="com.apress.prospring2.ch06.simple.PointcutDemoAspect"/>
    <aop:aspectj-autoproxy/>

</beans>

The example application uses the configuration file from Listing 6-11 and demonstrates that Spring applies the advices correctly according to the @pointcut methods. Because we wanted to demonstrate the within @pointcut, we have called the SimpleBean.sayHello() from outside TestBean2 (see Listing 6-12).

Example 6.12. Example Application for the SystemPointcuts Class

public class PointcutDemo {

    public static void main(String[] args) {
        ApplicationContext ac = new ClassPathXmlApplicationContext(
                "/META-INF/spring/ataspectjdemo2-context.xml"
        );
        TestBean2 testBean = (TestBean2) ac.getBean("test");
        SimpleBean simpleBean = (SimpleBean) ac.getBean("simple");
        testBean.work();
testBean.stop();
        simpleBean.sayHello();
    }

}

When we run the example application, its output shows that Spring created the advised beans correctly. We have also used the execution and within @pointcut expressions. Let's now complete our discussion of @pointcut expressions by looking at available @AspectJ pointcut expressions.

Pointcut Expressions

Even though @AspectJ supports use of AspectJ syntax for pointcut expressions, Spring AOP does not support the entire range of AspectJ pointcuts. Table 6-1 summarizes the AspectJ pointcut expressions you can use in Spring AOP.

Table 6.1. @AspectJ Pointcut Expressions Supported in Spring AOP

Expression

Description

execution

Matches method execution join points; you can specify the package, class and method name, its visibility, return type, and type of arguments. This is the most widely used pointcut expression. For example, execution(* com.apress..TestBean.*(..) means to execute any method in TestBean in the com.apress subpackage with any return type and any arguments.

within

Matches join points when executed from the declared type. For example, within(com.apress..TestBean) matches calls made from methods of TestBean.

this

Matches join points by comparing the type of bean reference (the AOP proxy) with the specified type. For example, this(SimpleBean) will match only calls from a bean of type SimpleBean.

target

Matches join points by comparing the type of bean being invoked with the specified type. target(SimpleBean) will match only calls made to a bean of type SimpleBean, for example.

args

Matches join points by comparing the method argument types with the specified argument types. As an example, args(String, String) will match only methods with two arguments of type String.

@target

Matches join points by checking that the target class of the invocation has the specified annotation. @target(Magic), for example, will match calls to methods from classes with the @Magic annotation.

@args

Similar to args, @args checks the annotations on the method arguments instead of their types. An example of @args is @args(NotNull), which would match all methods with a single argument with the @NotNull annotation.

@within

Similar to within, this expression matches join points when executed from a type with the specified annotation. An example of the @within expression might be @within(Magic), which will match calls from within bean of type with the @Magic annotation.

@annotation

Matches join points by checking the annotation on the method to be called with the specified annotation. For example, @annotation(Magic) will match calls to all methods with the @Magic annotation.

bean

Matches join points by comparing the bean's ID (or names); you can also use wildcards in the bean name pattern. An example might be bean("simple"), which will match join points in the bean with ID or name simple.

You can combine pointcut expressions using the || (disjunction) and && (conjunction) operators and negate the expression using the ! (not) operator. You can apply these operators on both the @pointcut methods as well as the pointcut expressions. You can write pointcut expression directly (using code similar to Listing 6-13).

Example 6.13. Combining AspectJ Pointcut Expressions

execution(* com.apress.prospring2.ch06.simple.TestBean2.*(..))
&& within(com.apress.prospring2..*)

Alternatively, you can combine the @Pointcut-annotated methods with other @Pointcut-annotated method or a pointcut expression. The code in Listing 6-14 shows public @pointcuts same1, same2, and same3: the only difference among them is the code in the @Pointcut annotation.

Example 6.14. Combining the @Pointcut-Annotated Methods

public final class SystemPointcuts {

    private SystemPointcuts() {

    }

    @Pointcut("execution(* com.apress.prospring2.ch06.simple.TestBean2.*(..))")
    private void testBeanExec() { }

    @Pointcut("within(com.apress.prospring2..*)")
    private void withinProSpringPackage() { }

    @Pointcut("execution(* com.apress.prospring2.ch06.simple.TestBean2.*(..)) &&" +
            "within(com.apress.prospring2..*)")
    public void same1() { }

    @Pointcut("execution(* com.apress.prospring2.ch06.simple.TestBean2.*(..)) &&" +
            "withinProSpringPackage()")
    public void same2() { }

    @Pointcut("testBeanExec() && withinProSpringPackage()")
    public void same3() { }

}

Here, you can see that we have two private @pointcuts—testBeanExec and withinProSpringPackage—and that we use the private @pointcuts in the public @pointcuts same2 and same3. Before we take a look at how we can use the @pointcuts outside the @AspectJ infrastructure, we need to explore the pointcut expressions in more detail.

Exploring the Pointcut Expressions

There are ten types of pointcut expressions; each type has its specific syntax. Even the execution expression, which seems fairly straightforward, can become quite complex when you make full use of its full syntax range.

The execution Expression

The syntax of the execution pointcut expression Spring uses is the same as the AspectJ execution syntax. Listing 6-15 defines the syntax formally.

Example 6.15. Formal Definition of the Execution Pointcut Expression Syntax

execution(modifiers-pattern?
    ret-type-pattern
    declaring-type-pattern? name-pattern(param-pattern)
    throws-pattern?)

The question mark (?) suffix marks the expression element as optional; in other words, we can leave it out. Let's analyze the * com.apress.prospring2.ch06.simple.TestBean2.*(..) expression we have already used: the * indicates any return type (ret-type-pattern) followed by a full class name (declaring-type-pattern). We follow the class name with another *(..), which means a method with any name and any number (including zero) and type of arguments. Because we did not specify the modifiers-pattern or the throws-pattern, Spring AOP will match methods with any modifiers and throwing any exceptions.

To declare a pointcut that matches any method (with any return type and any arguments, throwing any exception) in any class in a subpackage of com.apress.prospring2.ch06, we would need to write * com.apress.prospring2.ch06..*.*(..) . Analyzing the expression tells us to match a method with any name and any arguments (the last *()), returning any type (the first *) in any class (the middle *) in the package whose name starts with com.apres.prospring2.ch06 (notice the .. between the package name and class name). As a more realistic example, consider the pointcut expression in Listing 6-16.

Example 6.16. A More Realistic Pointcut Expression

public final class SystemPointcuts {

    private SystemPointcuts() {

    }

    @Pointcut("execution(* com.apress.prospring2.ch06..*.*(..)) &&" +
            "!execution(* com.apress.prospring2.ch06..*.set*(..)) &&" +
            "!execution(* com.apress.prospring2.ch06..*.get*(..))")
    public void serviceExecution() { }
    ...
}

You can probably guess what we will use the serviceExecution @pointcut for: we can use it in the tx:advice and make every method in all classes in the com.apress.prospring2.ch06.services package transactional, as long it is not a simple getter or setter. For simplicity, we say that a getter is a method whose name starts with get and takes no arguments, and a setter is simply any method whose name begins with set.

Tip

We prefer to use the @Transactional annotation to mark a method to be transactional; it is even simpler than using @pointcuts.

The within Expression

The within expression's formal syntax is far simpler than the syntax of the execution expression (see Listing 6-17).

Example 6.17. Syntax of the within Pointcut Expression

within(declaring-type-pattern)

You can use the usual .. and * wildcards; for example, to declare a pointcut that would match execution of any method invoked from within any class in the com package and its subpackages, you'd need to write within(com..*).

The this Expression

The syntax of the this pointcut expression is similar to the syntax of the within expression. The only difference is that we cannot use the .. and * wildcards: the semantics of the this pointcut are such that it would match any method execution on an object whose class matches the specified expression, but how could we match any class in the com package and its subpackages? Therefore, the only allowed syntax is this(class-name), for example, this(com.apress.prospring2.ch06.simple.TestBean2).

The target Expression

The syntax of the target expression is exactly the same as the syntax of the this expression. Because the target expression defines a pointcut that would match execution of any method on an object whose class matches the specified expression, we cannot use the wildcards. Therefore, the only valid syntax is target(class-name), for example, target(com.apress.prospring2.ch06.simple.SimpleBean).

The args Expression

The syntax of the args expression is args(type-pattern? (, type-pattern)*); in other words, we can specify zero, one, or more type-pattern expressions. It is important to note that while you can use the argument-pattern in the execution expression, the execution matching evaluates the formal argument types. The args expression matching evaluates the actual types of arguments passed to the method. As an example, take the class in Listing 6-18.

Example 6.18. The SimpleBean Class

public class SimpleBean {

    public void sayHello() {
        System.out.println("Hello");
    }

    public void x(CharSequence a, String b) {

    }

}

The pointcut execution(* SimpleBean.*(CharSequence, String)) would match calls to the method x("A", "B"), because the name and formal argument types match. However, the pointcut execution(* SimpleBean.*(String, String)) would not match, even if the call to the method uses two String arguments (and not a StringBuilder and String, for example). If we wanted to create a pointcut that matches calls to the x(CharSequence, String) method if and only if the actual argument types are String, String, we'll need to write args(String, String).

You can also use the .. wildcard in the type-pattern; to match calls to the method with the first argument of type Integer, followed by any number of arguments of any type and ending with a String argument, we'd write args(Integer, .., String).

The most common use of the args expression is in argument binding, which we will explore in the next section.

The @target Expression

The @target expression is another example of a simple expression that requires a full type name. Moreover, the type name should represent an @interface. Example uses of this expression are, therefore, @target(Magic) or @target(org.springframework.transaction.annotation.Transactional), where both Magic and Transactional are @interfaces with the @Retention(RetentionPolicy.RUNTIME) annotation and whose @Target annotations include ElementType.TYPE. The pointcut will match the call to all methods in the type with the annotation; to match methods with a particular annotation, use the @annotation expression.

The @within Expression

The @within expression requires a full @interface type name, and it will match calls to any methods originating from objects (or methods) with the specified annotation. As an example, we can write @within(StartsTransaction), where StartsTransaction is an @interface.

The @annotation Expression

Like the @target expression, the @annotation expression will match execution of any method with the specified annotation. A good example is @annotation(Transactional), which will match execution of any method with the Transactional annotation.

The @args Expression

The @args expression is similar to the args expression; the only difference is that it compares argument annotations rather than argument types. We can use this expression to match calls to methods with the specified annotated arguments. You can use the same wildcards as in the args expression.

The bean Expression

This last expression is truly Spring specific; it will match calls to all methods in a bean with an id or a name that matches the specified name. You can use the * wildcard in the bean name. To match calls to all methods in the simple bean, we'd write bean(simple); to match calls to all methods in beans whose id (or one of its names) ends with Service, we'd write bean(*Service).

Using the @Pointcuts in XML

Because the pointcuts defined using the @Pointcut annotation are Spring AOP-specific, we can use them even in the XML configuration. If we wanted to make all method executions in the TestBean2 participate in a transaction, we could use the SystemPointcuts.testBeanExecution() in the <aop:advisor . . ./> element. The XML configuration file in Listing 6-19 shows an example of this approach.

Example 6.19. Using the @Pointcuts in the XML Configuration File

<?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:tx="http://www.springframework.org/schema/tx"
       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
            http://www.springframework.org/schema/tx
            http://www.springframework.org/schema/tx/spring-tx.xsd">

    <bean id="test" class="com.apress.prospring2.ch06.simple.TestBean2">
        <property name="simpleBean" ref="simple"/>
    </bean>
    <bean id="simple" class="com.apress.prospring2.ch06.simple.SimpleBean"/>
    <aop:config>
        <aop:advisor advice-ref="tx-advice"
                     pointcut="com.apress.prospring2.ch06.simple.
Using the @Pointcuts in the XML Configuration File
SystemPointcuts.testBeanExecution()"/> </aop:config> <bean id="transactionManager" class="com.apress.prospring2.ch06.simple.NoopTransactionManager"/> <tx:advice id="tx-advice" transaction-manager="transactionManager"> <tx:attributes> <tx:method name="*" propagation="REQUIRED"/> </tx:attributes> </tx:advice> </beans>

Do not worry about the transactionManager bean and the tx:advice; the important portions of code are in bold. We define an advice (using the <aop:advisor . . ./> element); the advice is referencing the @Pointcut-annotated method from the SystemPointcuts class; the body of the advice is tx:advice. We give more detail about declarative transaction support in Spring in Chapter 16; for now, it is enough to say that the method execution will take part in a transaction, and the advice will commit the transaction if the method execution does not throw any exceptions; it will roll back the transaction if the target method throws any exception.

Types of Advice

Now that we know how to write pointcut expressions (whether we use the @Pointcut-annotated methods or write the pointcut expressions directly in XML), we need to take a look at the types of advices we can use. We have already used an around advice (in the @Aspect example in Listing 6-1 and in the tx:advice in the XML configuration in Listing 6-19). We will cover the basic advices (before, after, after throwing, and around); we will then take a look at how we can access the arguments in the join point.

Before Advice

Let's begin with the before advice. As the name suggests, it runs before execution proceeds to the method's body, but unless we throw an exception, we cannot avoid the execution of the target method. This makes the before advices suitable for access control: we check whether the caller is allowed to call the target; if not, we throw an exception. Take a look at the UML class diagram in Figure 6-3, which shows the classes we are going to be using in the discussion of the before advice.

UML class diagram for the before advice classes

Figure 6.3. UML class diagram for the before advice classes

We now want to secure the StockService so that we can only call its methods after we have logged in. In fact, we'll say that we want to secure all classes in the com.apress.prospring2.ch06.services package so that we don't have to worry about security in the future. A naïve attempt at implementation of the before advice is in Listing 6-20.

Example 6.20. Naïve Implementation of the Before Advice

@Aspect
public class BeforeAspect {

    @Before("execution(* com.apress.prospring2.ch06.services.*.*(..))")
    public void beforeLogin() throws Throwable {
        if (SecurityContext.getCurrentUser() == null)
            throw new RuntimeException("Must login to call this method.");
    }

}

At first glance, this looks absolutely fine. To test the advice, we will combine the BeforeAspect with the XML configuration in Listing 6-21.

Example 6.21. The XML Configuration File for the Before Advice

<?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">

    <bean id="userService" class="com.apress.prospring2.ch06.services.UserService"/>
    <bean id="stockService"
        class="com.apress.prospring2.ch06.services.StockService"/>
    <bean class="com.apress.prospring2.ch06.before.BeforeAspect"/>

    <aop:aspectj-autoproxy_/>

</beans>

We have the BeforeAspect as a Spring bean, and we use the aspectj-autoproxy: we can therefore use the code from Listing 6-22 to access the advised services.

Example 6.22. Sample Application for the Before Advice

public class BeforeDemo {

    public static void main(String[] args) {
        ApplicationContext ac = new ClassPathXmlApplicationContext(
                "/META-INF/spring/beforedemo1-context.xml"
        );
        StockService stockService = (StockService) ac.getBean("stockService");
        System.out.println(stockService.getStockLevel("ABC"));
    }
}

When we run the application, it fails with a RuntimeException with the message "Must login to call this method." Great, it works! To test that we can call the service methods after logging in, we will add a call to get the UserService bean and call its login method. Listing 6-23 shows a code fragment that does just that.

Example 6.23. Code Fragment to Get the UserService and login

UserService userService = (UserService) ac.getBean("userService");
userService.login("janm");

The trouble is that the login method is also in a class in the services package; therefore, it is advised by the beforeLogin advice. Thus, we cannot log in, because we're not logged in. We'll need to modify our pointcut expression to exclude the login method. We can write the pointcut expression directly, but we're going to create two private @pointcut expressions and use them in the before advice's pointcut expression. Listing 6-24 shows how we've implemented the change.

Example 6.24. Change to the BeforeAspect

@Aspect
public class BeforeAspect {
@Pointcut("execution(* com.apress.prospring2.ch06.services.*.*(..))")
    private void serviceExecution() { }

    @Pointcut(
        "execution(* com.apress.prospring2.ch06.services.UserService.login(..))")
    private void loginExecution() { }

    @Before("serviceExecution() && !loginExecution()")
    public void beforeLogin() throws Throwable {
        if (SecurityContext.getCurrentUser() == null)
            throw new RuntimeException("Must login to call this method.");
    }

}

When we run the sample application now, it succeeds: we are allowed to call the UserService.login method even without being logged in, but we must be logged in to call all other methods.

After Returning Advice

As its name suggests, after returning advice runs after the target method finishes normally. To "finish normally" means that the method has not thrown any exceptions. The usual application for after returning advice is to perform auditing. Let's take the code in Listing 6-25, which shows after returning advice.

Example 6.25. After Returning Advice

@Aspect
public class AfterAspect {

    @AfterReturning("execution(* com.apress.prospring2.ch06.services.*.*(..))")
    public void auditCall() {
        System.out.println("After method call");
    }

}

This aspect defines one piece after returning advice for execution of all methods in all classes in the com.apress.prospring2.ch06.services package. To implement the sample application, let's begin the configuration file from Listing 6-26.

Example 6.26. XML Configuration File for the Sample Application

<?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">

    <bean id="userService" class="com.apress.prospring2.ch06.services.UserService"/>
    <bean id="stockService"
        class="com.apress.prospring2.ch06.services.StockService"/>
<bean class="com.apress.prospring2.ch06.afterreturning.AfterAspect"/>

    <aop:aspectj-autoproxy />

</beans>

Here, we define two service beans (the userService and stockService) and the AfterAspect bean. The anonymous AfterAspect bean contains our auditCall() after returning advice. We use this configuration file in the sample application that simply obtains the userService and stockService beans and uses their methods. The sample application's source code is in Listing 6-27.

Example 6.27. Sample Application for After Returning Advice

public class AfterDemo {

    public static void main(String[] args) {
        ApplicationContext ac = new ClassPathXmlApplicationContext(
                "/META-INF/spring/afterreturningdemo1-context.xml"
        );
        UserService userService = (UserService) ac.getBean("userService");
        userService.login("janm");

        StockService stockService = (StockService) ac.getBean("stockService");
        System.out.println(stockService.getStockLevel("ABC"));
    }

}

When we run this application, the output verifies that the auditCall after returning advice works; the application prints After method call, After method call, 193734. The problem is that the audit messages cannot access any details of the call: the advice, as we have written it, only knows that a method that matched the pointcut finished normally. We don't know which method it is, nor do we know the returned value. We can improve the advice by using the JoinPoint argument or by using the returning field of the @AfterReturning annotation. Listing 6-28 shows the first option—adding the JoinPoint argument to the advice method.

Example 6.28. Using the JoinPoint Argument

@Aspect
public class AfterAspect {

    @AfterReturning("execution(* com.apress.prospring2.ch06.services.*.*(..))")
    public void auditCall(JoinPoint jp) {
        System.out.println("After method call of " + jp);
    }

}

We can use the JoinPoint argument interface to find out details about the method that has just returned: perhaps the most interesting information we can obtain from the JoinPoint is the Method being executed and its arguments. The only trouble is that we can't use the JoinPoint to find the return value. To do this, we must stop using the JoinPoint argument and explicitly set the returning() and argNames() properties of the @AfterReturning annotation. Listing 6-29 shows the advice that can examine (and modify) the return value.

Example 6.29. Using the returning() and argNames() Properties

@Aspect
public class AfterAspect {

    @AfterReturning(
        pointcut = "execution(* com.apress.prospring2.ch06.services.*.*(..))",
        returning = "ret", argNames = "ret")
    public void auditCall(Object ret) {
        System.out.println("After method call of " + ret);
        if (ret instanceof User) {
            ((User)ret).setPassword("****");
        }
    }

}

Notice that we have defined the pointcut(), returning(), and argNames() properties of the @AfterReturning annotation to create much more powerful after returning advice. We can see the return value from the method call. We cannot change the returned value directly, but if the return type allows it, we can change the returned value's properties. This is similar to attempting to modify the value of a method argument and passing the modified value to the calling code.

After Throwing Advice

The next type of advice is after throwing, which executes when its target method throws an exception. As with the after returning advice, we can create advice that can have access to the thrown exception. Furthermore, the type of the exception in the advice's argument filters the exception type. Listing 6-30 shows an aspect that filters all IOExceptions thrown in the service calls.

Example 6.30. IOException After Throwing Advice

@Aspect
public class AfterThowingAspect {

    @AfterThrowing(
        pointcut = "execution(* com.apress.prospring2.ch06.services.*.*(..))",
        throwing = "ex", argNames = "ex")
    public void logException(IOException ex) {
        System.out.println("After method call of " + ex);
    }

}

The code in bold shows the most important areas of the advice: the throwing() property of the @AfterThrowing advice specifies the argument that will receive the exception, and the argNames() specifies the names of the arguments in the order in which they are declared. Now, when we run the sample application from Listing 6-31, it will fail, because the StockService.getStockLevel(String) method will throw a NullPointerException if the sku argument is null.

Example 6.31. Sample Application for the After Throwing Advice

public class AfterThrowingDemo {

    public static void main(String[] args) {
        ApplicationContext ac = new ClassPathXmlApplicationContext(
                "/META-INF/spring/afterthrowingdemo1-context.xml"
        );
        UserService userService = (UserService) ac.getBean("userService");
        userService.login("janm");

        StockService stockService = (StockService) ac.getBean("stockService");
        System.out.println(stockService.getStockLevel(null));
    }

}

Even though we have written after throwing advice (and configured the beans correctly in afterthrowingdemo1-context.xml), the advice does not get executed, because the type of the exception in its argument is IOException, not NullPointerException. If we change the advice's argument to Exception, it will work, printing the following.

...
Exception in thread "main" java.lang.NullPointerException
After method call of java.lang.NullPointerException
   at com.apress.prospring2.ch06.services.StockService.
Sample Application for the After Throwing Advice
getStockLevel(StockService.java:9) ...

After Advice

The final type of after advice is the after or, more formally, finally advice. This executes regardless of whether the target method completes normally or throws an exception. However, we cannot access the method's return value or any exceptions it may have thrown. In most cases, you will use the finally advice to release resources, very much as you would in a try / catch / finally construct in Java.

Around Advice

The most powerful and most complicated advice is the around advice. It executes around the target method invocation. Therefore, the advice needs at least one argument, and it must return a value. The argument specifies the target being invoked, and the return value specifies the return value, regardless of where the return value comes from. You would typically find around advice in transaction management: the advice will begin a transaction before proceeding to call the target. If the target returns normally, the advice will commit the transaction; in case of any exceptions, it will perform a rollback. Another example is caching: the code in Listing 6-32 shows simple caching around advice.

Example 6.32. Around Advice for Caching

@Aspect
public class CachingAspect {
    private Map<MethodAndArguments, Object> cache =
            Collections.synchronizedMap(
                    new HashMap<MethodAndArguments, Object>());
    private Object nullValue = new Object();
private static class MethodAndArguments {
        private Object target;
        private Object[] arguments;

        private MethodAndArguments(Object target, Object[] arguments) {
            this.target = target;
            this.arguments = arguments;
        }

        public boolean equals(Object o) {
            if (this == o) return true;
            if (o == null || getClass() != o.getClass()) return false;

            MethodAndArguments that = (MethodAndArguments) o;

            return Arrays.equals(arguments, that.arguments) &&
                target.equals(that.target);
        }

        public int hashCode() {
            int result;
            result = target.hashCode();
            result = 31 * result + Arrays.hashCode(arguments);
            return result;
        }
    }

    @Around("execution(* com.apress.prospring2.ch06.services.*.*(..))")
    public Object cacheCalls(ProceedingJoinPoint pjp) throws Throwable {
        Object cacheRet;
        final MethodAndArguments methodAndArguments =
            new MethodAndArguments(pjp.getTarget(), pjp.getArgs());
        cacheRet = this.cache.get(methodAndArguments);
        if (cacheRet == this.nullValue) return null;
        if (cacheRet == null) {
            Object ret = pjp.proceed();
            cacheRet = ret;
            if (cacheRet == null) cacheRet = this.nullValue;
            this.cache.put(methodAndArguments, cacheRet);
            return ret;
        }
        return cacheRet;
    }

}

The cache implemented in this aspect is definitely not an enterprise-grade cache, but it will demonstrate the around advice quite nicely. It takes the ProceedingJoinPoint argument and returns an Object; the argument gives everything we need to know about the execution of the target method, and the calling code will receive the value returned from the advice.

You may be wondering whether we must invoke the target method at all or whether we can invoke it more than once; the answer is yes. We can do anything we wish in the advice: we can implement advice that can handle retries, attempting to call the target three times before giving up. Or, as you have seen in Listing 6-32, we can skip the call to the target completely!

Argument Binding

You may have noticed that we sometimes need to access the values of the arguments passed to the target from the advice. So far, you have only seen explicit access to the return value; you can also use the JoinPoint.getArgs to access the arguments. While this is possible, it feels like too much work, because the arguments are passed in as an Object[], and we would have to perform bounds checking and casting ourselves. Luckily, @AspectJ supports binding, where we can bind a value from the target to an argument in the advice. As an example, let's take a look at the code in Listing 6-33.

Example 6.33. An Aspect with Bound Arguments

@Aspect
public class BindingAspect {

    @Around(value =
            "execution(* com.apress.prospring2.ch06.services.StockService.*(..)) " +
            "&& args(cutoffDate, minimumDiscount)",
            argNames = "pjp, cutoffDate, minimumDiscount")
    public Object discountEnforcement(ProceedingJoinPoint pjp, Date cutoffDate,
                                      BigDecimal minimumDiscount)
            throws Throwable {
        return pjp.proceed();
    }
}

Here, we can see that the discountEnforcement advice will execute on execution of any method in the StockService class (the execution clause), as long as it has two arguments (the args clause) and as long as the type of the cutoffDate argument's type is Date and the minimumDiscount argument's type is BigDecimal (inferred from the advice method arguments). In addition to the two arguments passed in from the target, the advice method will receive the ProceedingJoinPoint instance. Also note the argNames() property of the @Around annotation: it explicitly specifies that the advice method's arguments are named pjp, cutoffDate, and minimumDiscount. The Spring AOP infrastructure will use the argNames() to decide which argument gets the bound value from the pointcut expression.

Note

The reason for the argNames() method's existence is that, once Java source code is compiled into bytecode, determining the argument names is impossible; we can obtain only their indexes and types. However, we refer to the argument names in pointcut and bind expressions; the argNames() method, therefore, represents a mechanism that can resolve an argument name to its index.

Even though you can leave out the JoinPoint (and its subinterfaces) from the argNames() property if it appears as the first argument of the advice method, we do not recommend doing so. Forgetting that the JoinPoint needs to be the first argument is far too easy; in addition, some IDEs will report errors if the names in argNames do not match the advice method's arguments (see Figure 6-4).

IntelliJ IDEA reporting a problem with argNames()

Figure 6.4. IntelliJ IDEA reporting a problem with argNames()

If you find that argNames is a bit clumsy, you do not have to specify it, and Spring AOP will use the debug information in the class files to determine the argument names. This is usually not enabled by default, but you can turn on argument names debug information using the -g:vars javac directive. Apart from allowing Spring AOP to determine the argument names, it will slightly increase the size of the compiled *.class files, which is usually not a problem at all.

More significant problems may be that the *.class files compiled with the -g:vars directive will be more readable when decompiled and that the compiler will not be able to apply an optimization to remove unused arguments. If you are working on an open source application, the first problem should not trouble you at all; the second problem is also moot if you write your code carefully and do not leave in unused arguments. If your class files do not have the necessary debugging information, Spring will attempt to match the arguments using their types. In our example in Listing 6-33, Spring can easily do this matching: ProceedingJoinPoint does not share a class hierarchy with either Date or BigDecimal. Similarly, Date and BigDecimal are different classes. If Spring cannot safely match the arguments using their types (for example, the advised method takes the ProceedingJoinPoint as its argument), it will throw an AmbiguousBindingException.

Introductions

Introductions are an important part of the AOP feature set available in Spring. By using introductions, you can add new functionality to an existing object dynamically. In Spring, you can introduce an implementation of any interface to an existing object. You may well be wondering exactly why this is useful—why would you want to add functionality dynamically at runtime when you can simply add that functionality at development time? The answer to this question is easy. You add functionality dynamically when the functionality is crosscutting and thus not easily implementable using traditional object-oriented programming.

The Spring documentation gives two typical examples of introduction use: object locking and modification detection. In the case of object locking, which is implemented in the Spring documentation, we have an interface, Lockable, that defines the method for locking and unlocking an object. The intended application for this interface involves enabling the application to lock an object so that its internal state cannot be modified. Now, you could simply implement this interface manually for every class you wish to make lockable. However, this would result in a lot of duplicated code across many classes. Sure, you can refactor the implementation into an abstract base class, but you lose your one shot at concrete inheritance, and you still have to check the lock status in every method that modifies the state of the object. Clearly, this is not an ideal situation and has the potential to lead to many bugs and no doubt some maintenance nightmares.

By using introductions, you can overcome all of these issues. Using an introduction, you can centralize the implementation of the Lockable interface into a single class and, at runtime, have any object you wish adopt this implementation of Lockable. Not only does the object adopt the implementation of Lockable but it becomes an instance of Lockable in that it passes the instanceof test for the Lockable interface, even though its class does not implement this interface.

Using introductions obviously overcomes the problem of centralizing the implementation logic without affecting the concrete inheritance hierarchy of your classes, but what about all the code you need to write to check the lock status? Well, an introduction is simply an extension of a method interceptor, and as such, it can intercept any method on the object on which the introduction was made. Using this feature, you could check the lock status before any calls are made to setter methods and throw an Exception if the object is locked. All of this code is encapsulated in a single place, and none of the Lockable objects need to be aware of this.

Introductions are key to providing declarative services in applications. For instance, if you build an application that is fully aware of the Lockable interface, by using introductions, you declaratively define exactly which objects should be made Lockable.

We won't be spending any more time looking at the Lockable interface and how to implement it using introductions, because this is fully discussed in the Spring documentation. Instead, we focus on the other, unimplemented, example from the documentation—object modification detection. However, before we start, we will take a look at the basics behind building an introduction.

Call Tracking with Introductions

Object modification detection is a useful technique for many reasons. Typically, you apply modification detection to prevent unnecessary database access when you are persisting object data. If an object is passed to a method for modification but comes back unmodified, there is little point in issuing an update statement to the database. Using a modification check in this way can increase application throughput, especially when the database is already under a substantial load or is located on some remote network making communication an expensive operation.

Unfortunately, this kind of functionality is difficult to implement by hand, because it requires you to add to every method that can modify object state to check if the object state is actually being modified. When you consider all the null checks that have to be made and the checks to see if the value is actually changing, you are looking at around eight lines of code per method. You could refactor this into a single method, but you still have to call this method every time you need to perform the check. Spread this across a typical application with many different classes that require modification checks, and the maintenance of such an application will become much more difficult.

This is clearly a place where introductions will help. We do not want to have to make it so each class that requires modification checks inherits from some base implementation, losing its only chance for inheritance as a result, nor do we really want to be adding checking code to each and every state-changing method. Using introductions, we can provide a flexible solution to the modification detection problem without having to write a bunch of repetitive, error-prone code.

In this example, we are going to build a statistics collection framework using introductions. The modification check logic is encapsulated by the CallTracker interface, an implementation of which will be introduced into the appropriate objects, along with interception logic to perform modification checks automatically.

The CallTracker Interface

Central to the statistics solution is the CallTracker interface, which our fictional application uses to track its health. We do not look at how the application would use CallTracker; instead, we focus on the implementation of the introduction. Listing 6-34 shows the CallTracker interface.

Example 6.34. The CallTracker Interface

public interface CallTracker {

    void markNormal();

    void markFailing();

    int countNormalCalls();

    int countFailingCalls();

    String describe();

}

Nothing special here—the mark* methods simply increase a counter of normal or failing calls, and the count* methods return the appropriate counter value.

Creating a Mix In

The next step is to create the code that implements CallTracker and is introduced to the objects; this is referred to as a mix in. Listing 6-35 shows the implementation of the CallTracker interface; again, the implementation does nothing difficult.

Example 6.35. The DefaultCallTracker Class

public class DefaultCallTracker implements CallTracker {
    private int normalCalls;
    private int failingCalls;

    public void markNormal() {
        this.normalCalls++;
    }

    public void markFailing() {
        this.failingCalls++;
    }

    public int countNormalCalls() {
        return this.normalCalls;
    }

    public int countFailingCalls() {
        return this.failingCalls;
    }

    public String describe() {
        return toString();
    }

    @Override
    public String toString() {
        final StringBuilder sb = new StringBuilder();
        sb.append("DefaultCallTracker");
        sb.append("{normalCalls=").append(normalCalls);
        sb.append(", failingCalls=").append(failingCalls);
sb.append('}'),
        return sb.toString();
    }
}

The first thing to notice here is the implementation of CallTracker, which is made up of the private normalCalls and failingCalls fields. This example highlights why you must have one mix in instance per advised object: the mix in introduces not only methods to the object but also a state. If you share a single instance of this mix in across many different objects, you are also sharing the state, which means all objects show as modified the first time a single object becomes modified.

Creating the Mix-In Aspect

The next step is to create an aspect that will include the after returning and after throwing advice as well as the mix-in declaration. Listing 6-36 shows the code we have written.

Example 6.36. Creating Advice for the Mix In

@Aspect
public class CallTrackerAspect {

    @Pointcut("execution(* com.apress.prospring2.ch06.services.*.*(..))")
    private void serviceCall() { }

    @DeclareParents(
        value = "com.apress.prospring2.ch06.services.*",
        defaultImpl = DefaultCallTracker.class)
    public static CallTracker mixin;

    @AfterReturning(
        value = "serviceCall() && this(tracker)",
        argNames = "tracker")
    public void normalCall(CallTracker tracker) {
        tracker.markNormal();
    }

    @AfterThrowing(
        value = "serviceCall() && this(tracker)",
        throwing = "t",
        argNames = "tracker, t")
    public void failingCall(CallTracker tracker, Throwable t) {
        tracker.markFailing();
    }

}

The new code in this aspect is the @DeclareParents annotation for the mixin field; it declares that for all classes in com.apress.prospring2.ch06.services we will introduce the CallTracker interface (the type of the field) using the DefaultCallTracker implementation.

Next, we define an @pointcut serviceCall() (because we will use it in the two separate pieces of advice).

Then, we create the after returning advice using the pointcut serviceCall() && this(tracker). The serviceCall() references to the @pointcut, and this(tracker) will match execution of all methods on an object that implements the CallTracker interface (because the type of the tracker expression is CallTracker in the advice method argument). Spring AOP will bind the tracker argument to the advised object; therefore, we can use the tracker argument in the advice body.

Finally, we write the after throwing advice using the same pointcut expression (serviceCall() && this(tracker)); Spring AOP will bind the tracker argument to the argument of the failingCall advice method. In addition to the CallTracker argument, we define in the @AfterThrowing annotation that we want to receive the exception thrown as the second argument of the failingCall method. Spring AOP infers the type of the exception from the type of the argument (Throwable, in this case).

Putting It All Together

Now that we have the aspect that introduces the mix in and the appropriate advice, we can write a sample application that uses the UserService and StockService beans we have already implemented. Listing 6-37 shows the source code of the sample application.

Example 6.37. The Introductions Sample Application

public class IntroductionDemo {

    public static void main(String[] args) {
        ApplicationContext ac = new ClassPathXmlApplicationContext(
                "/META-INF/spring/introductionsdemo1-context.xml"
        );
        UserService userService = (UserService) ac.getBean("userService");
        describeTracker(userService);
        userService.login("janm");
        userService.setAdministratorUsername("x");
        describeTracker(userService);

        StockService stockService = (StockService) ac.getBean("stockService");
        describeTracker(stockService);
        try {
            stockService.getStockLevel(null);
        } catch (Exception ignored) {

        }
        System.out.println(stockService.getStockLevel("ABC"));
        stockService.applyDiscounts(new Date(), new BigDecimal("10.0"));
        describeTracker(stockService);
    }

    private static void describeTracker(Object o) {
        CallTracker t = (CallTracker)o;
        System.out.println(t.describe());
    }

}

Notice that we are using the userService and stockService beans as usual: we call userService.login("janm"), followed by userService.setAdministratorUsername("x"); calls to the stockService bean are similar. However, we can now cast the userService and stockService beans to CallTracker; they now implement the newly introduced interface. We are sure you won't be too surprised to learn that we did not need to write any additional configuration apart from the standard XML configuration file in Listing 6-38.

Example 6.38. XML Configuration File for the Introductions Demonstration

<?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">

    <bean id="userService"
        class="com.apress.prospring2.ch06.services.DefaultUserService"/>
    <bean id="stockService"
        class="com.apress.prospring2.ch06.services.DefaultStockService"/>
    <bean class="com.apress.prospring2.ch06.introductions.CallTrackerAspect"/>

    <aop:aspectj-autoproxy />

</beans>

That is really all we needed to do. Spring AOP will proxy the DefaultUserService and DefaultStockService. Let's examine the DefaultUserService in more detail: it implements only the UserService, but the type of the userService bean after applying the CallTrackerAspect is JdkDynamicAopProxy—it is not an instance of the DefaultUserService. The proxy implements both the UserService interface and CallTracker mix-in interface. It intercepts calls to all methods and delegates the calls on the UserService interface to the DefaultUserService. The proxy also creates an instance of the DefaultCallTracker (defined in the @DeclareParents annotation) and delegates all calls on the CallTracker interface to the DefaultCallTracker instance.

The output from running the sample application should therefore come as no surprise:

before userService.login("janm"):
    DefaultCallTracker{normalCalls=0, failingCalls=0}

after userService.setAdministratorUsername("x"):
    DefaultCallTracker{normalCalls=2, failingCalls=0}

before stockService.getStockLevel(null):
    DefaultCallTracker{normalCalls=0, failingCalls=0}
193734

after stockService.applyDiscounts(...):
    DefaultCallTracker{normalCalls=2, failingCalls=1}

This output verifies that the CallTracker interface introduced to all classes in the com.apress.prospring2.ch06.services package worked and that we got one instance of the DefaultCallTracker for every advised class.

Introductions Summary

Introductions are one of the most powerful features of Spring AOP; they allow you not only to extend the functionality of existing methods but also to extend the set of interfaces an object implements dynamically. Using introductions is the perfect way to implement crosscutting logic that your application interacts with through well-defined interfaces. In general, this is the kind of logic that you want to apply declaratively rather than programmatically.

Obviously, because introductions work via proxies, they add a certain amount of overhead, and all methods on the proxy are considered to be advised, because the proxy needs to route every call to the appropriate target at runtime. However, in the case of many of the services you can implement using introductions, this performance overhead is a small price to pay for the reduction in code required to implement the service, as well the increase in stability and maintainability that comes from fully centralizing the service logic and adding management interface to your application.

The Aspect Life Cycle

So far, the aspects we have written are singletons—more accurately, Spring takes the singleton @Aspect-annotated beans and advices the targets using a singleton instance of the aspect. Let's take the aspect from Listing 6-39 and use it in our sample application.

Example 6.39. Simple Singleton Aspect

@Aspect
public class CountingAspect {
    private int count;

    @Before("execution(* com.apress.prospring2.ch06.services.*.*(..))")
    public void count() {
        this.count++;
        System.out.println(this.count);
    }

}

Let's complete the example with code from Listing 6-40, which shows the demonstration application. It obtains the familiar userService and stockService beans and makes two calls to the userService.login method, followed by one call to the stockService.getStockLevel() method.

Example 6.40. Sample Application for the Singleton Aspect

public class LifecycleDemo {

    public static void main(String[] args) {
        ApplicationContext ac = new ClassPathXmlApplicationContext(
                "/META-INF/spring/lifecycledemo1-context.xml"
        );
        UserService userService = (UserService) ac.getBean("userService");
        StockService stockService = (StockService) ac.getBean("stockService");

        for (int i = 0; i < 2; i++) {
            userService.login("janm");
        }
        stockService.getStockLevel("A");
    }

}

The last piece of the puzzle is the XML configuration file, which simply declares the userService, stockService, and CountingAspect beans.

Example 6.41. XML Configuration for the LifecycleDemo Application

<?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">

    <bean id="userService"
        class="com.apress.prospring2.ch06.services.DefaultUserService"/>
    <bean id="stockService"
        class="com.apress.prospring2.ch06.services.DefaultStockService"/>
    <bean class="com.apress.prospring2.ch06.lifecycle.CountingAspect"/>

    <aop:aspectj-autoproxy />

</beans>

Now, when we run the application, it will print 1, 2, 3, thus proving that the aspect exists in a single instance. If we want to keep state for different targets, we'll need to change the scope of the aspect bean to prototype. If we modify the CountingAspect bean definition in the XML configuration file from Listing 6-41 to the following

<bean class="com.apress.prospring2.ch06.lifecycle.CountingAspect"
    scope="prototype"/>

and run the sample application again with the new aspect bean scope, it will print 1, 2, 1: the application now maintains one instance of the aspect for every target. In effect, we have implemented a per this aspect; we can make this explicit by using a perthis expression in the @Aspect annotation:

@Aspect("perthis(execution(" +
        "* com.apress.prospring2.ch06.services.UserService.*(..)))")
public class PertargetCountingAspect {
...
}

The result of the modification is that Spring AOP will create a new instance of the aspect for each unique object executing the advised object.

Another life cycle strategy Spring AOP supports is pertarget. This works just like perthis, except it will create a new instance of the aspect for every unique target object at the matched join points.

Framework Services for AOP

Up to now, we have used the @AspectJ support in Spring to write aspects. We have relied on the underlying Spring AOP framework services to process the annotated beans and instrument the beans. If you cannot use annotations (perhaps you are running on a pre-1.5 JDK or you just don't like them), you can use XML configuration using the aop namespace. In this section, we will discuss the XML configuration and look under the hood of Spring AOP to find out how the @AspectJ support turns the annotated beans into a format usable for Spring AOP support.

Creating Our First Aspect Using the aop Namespace

To get us started, let's write an aspect that works just like the aspect in Listing 6-1, but this time we will use only XML configuration. We will start with Listing 6-42, which shows the XML configuration for the aspect.

Example 6.42. XML Configuration File

<?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">

    <bean id="userService"
        class="com.apress.prospring2.ch06.services.DefaultUserService"/>
    <bean id="stockService"
        class="com.apress.prospring2.ch06.services.DefaultStockService"/>

    <bean id="aspectBean" class="com.apress.prospring2.ch06.xml.AspectBean"/>

    <aop:config>
        <aop:pointcut id="serviceCall"
            expression="execution(* com.apress.prospring2.ch06.services.*.*(..))"/>

        <aop:aspect id="firstAspect" ref="aspectBean">
            <aop:before method="logCall" pointcut-ref="serviceCall"/>
        </aop:aspect>
    </aop:config>

</beans>

The code in bold declares a pointcut that matches execution of any method with any arguments in any class in the com.apress.prospring2.ch06.services package. The code we have written is similar to the @AspectJ code; the only code we need to show is the AspectBean in Listing 6-43.

Example 6.43. AspectBean Code

public class AspectBean {

    public void logCall(JoinPoint jp) {
        System.out.println(jp);
    }

}

The code is similar to the @AspectJ code; the difference is that there are no annotations at all. The sample application in Listing 6-44 uses the XML configuration and calls the now well-known methods of the userService and stockService beans.

Example 6.44. Sample Application for the XML aop Support

public class XmlDemo1 {

    public static void main(String[] args) {
        ApplicationContext ac = new ClassPathXmlApplicationContext(
                "/META-INF/spring/xmldemo1-context.xml"
        );
        UserService userService = (UserService) ac.getBean("userService");
        StockService stockService = (StockService) ac.getBean("stockService");

        userService.login("janm");
        stockService.getStockLevel("A");
    }

}

The code in the sample application remains the same as the code with @AspectJ aspects; in other words, the XML configuration is completely transparent, and the calling code is not aware that it is calling methods on advised beans. When we run the sample application, it prints the following:

> userService.login("janm"):
    org.springframework.aop.aspectj.MethodInvocationProceedingJoinPoint: 
Sample Application for the XML aop Support
execution(login) > stockService.getStockLevel("A)": org.springframework.aop.aspectj.MethodInvocationProceedingJoinPoint:
Sample Application for the XML aop Support
execution(getStockLevel)

We can see that both userService and stockService are correctly advised and that the AspectBean.logCall() method executes.

Pointcuts in the aop Namespace

Just like in the @AspectJ support section, we will take a look at configuration elements for pointcut definitions. To define a pointcut, you need to write an <aop:pointcut . . ./> element as a child of the <aop:config> element. Let's take a look at the code from Listing 6-43 in more detail: Listing 6-45 shows a section of the XML configuration file.

Example 6.45. AOP Section of the XML Configuration File

...
    <aop:config>
        <aop:pointcut id="serviceCall"
            expression="execution(* com.apress.prospring2.ch06.services.*.*(..))"/>

        <aop:aspect id="firstAspect" ref="aspectBean">
            <aop:before method="logCall" pointcut-ref="serviceCall"/>
        </aop:aspect>
    </aop:config>
...

The code in bold shows that we have a pointcut with id="serviceCall", which we then use in the definition of the before advice in the firstAspect. Just as with @AspectJ support, we can refer to an @pointcut in the expression of the pointcut. Listing 6-46 shows a familiar definition of an @pointcut.

Example 6.46. @pointcut Definition

public final class Pointcuts {
    private Pointcuts() {

    }

    @Pointcut("execution(* com.apress.prospring2.ch06.services.*.*(..))")
    public void serviceExecution() { }

}

We can now use the Pointcuts.serviceExecution() @pointcut in the XML configuration by making a simple change to the <aop:pointcut . . ./> element.

<aop:pointcut id="serviceCall"
                  expression="com.apress.prospring2.ch06.xml.Pointcuts.
@pointcut Definition
serviceExecution()"/>

The only drawback is that we are using annotations again. We believe that it is better to choose one configuration style in your application and stick with it; a mixture of @AspectJ and XML configuration can quickly get too complex to manage.

Going back to pure XML configuration, you can use all Spring AOP-supported pointcut expressions, and some IDEs come with code completion even for the XML-based Spring AOP (see Figure 6-5).

Code completion for XML AOP confiruation in IntelliJ IDEA

Figure 6.5. Code completion for XML AOP confiruation in IntelliJ IDEA

Creating Advice Using the aop Namespace

Advice in the XML configuration is similar to advice in @AspectJ; the only differences are that the beans representing the aspects are simple Spring beans and the configuration is split between the XML files and Java code in the aspect beans. The aop namespace is as powerful as the @AspectJ support. We have already seen an example of a simple before advice in Listing 6-42; let's explore the XML advice in more detail. Just like @AspectJ advice, XML advice needs a pointcut. You can refer to an existing pointcut (using the pointcut-ref attribute in the advice definition), or you can specify a pointcut expression directly (using the pointcut attribute in the advice definition). Listing 6-47 shows equivalent definitions of the before advice.

Example 6.47. Using pointcut-ref and pointcut Attributes

...
<aop:aspect id="firstAspect" ref="aspectBean">
    <aop:before method="logCall" pointcut-ref="serviceCall"/>
</aop:aspect>

<aop:aspect id="firstAspect" ref="aspectBean">
    <aop:before method="logCall"
        pointcut="execution(* com.apress.prospring2.ch06.services.*.*(..))"/>
</aop:aspect>
...

The difference is obvious: in the first snippet, we reference an existing pointcut declared using the <pointcut . . ./> element; in the second snippet, we specify the pointcut expression.

Next, we will look into the advices in the aop namespace in more detail.

Before Advice

To declare before advice, we need to create the <before> element as a child of the <aspect> element. The attributes of the <before> element are method, pointcut or pointcut-ref, and arg-names. You must always set the method and pointcut or pointcut-ref attributes. The method attribute is the name of a public method in a bean you reference in the <aspect> element; its arguments can vary, ranging from no arguments at all, through JoinPoint, to all arguments you are binding in the pointcut expression. If you are using binding, you should consider using the arg-names attribute to help the Spring AOP Framework correctly identify the bound arguments.

After Returning Advice

To create after returning advice, create an <after-returning> element as a child of the <advice> element. Just like in the before advice, you need to set the method and pointcut or pointcut-ref attributes. In most cases, you would also want to know the return value; to obtain this, set the returning attribute to the name of an Object argument of the method. Finally, you can also use arg-names to specify the argument names of the method in the method attribute. Let's take a look at the code in Listing 6-48, which shows how we can create an aspect that will audit calls to every method and censor some return values.

Example 6.48. Using the After Returning Advice

<?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">

    <bean id="userService"
         class="com.apress.prospring2.ch06.services.DefaultUserService"/>
    <bean id="stockService"
         class="com.apress.prospring2.ch06.services.DefaultStockService"/>

    <bean id="aspectBean" class="com.apress.prospring2.ch06.xml.AspectBean"/>
<aop:config>
        <aop:aspect id="firstAspect" ref="aspectBean">
            <aop:after-returning method="auditCall"
                                 pointcut="execution(* com.apress.prospring2.
Using the After Returning Advice
ch06.services.*.*(..))
Using the After Returning Advice
&& target(target)" arg-names="target, ret" returning="ret"/> </aop:aspect> </aop:config> </beans>

Notice that our pointcut expression specifies execution of any method in any class in the services package and that we are binding the invocation target to an argument called target. We are also going to receive the return value the target returned in the AspectBean.auditCall() method. Looking at the arg-names attribute, you can clearly see that the auditCall method needs two arguments, and because we want to receive a target of any type and process any type of return value, the type of the arguments needs to be Object. We can now write the code for the AspectBean.auditCall method (see Listing 6-49).

Example 6.49. The AspectBean auditCall Method

public class AspectBean {

    public void auditCall(Object target, Object ret) {
        System.out.println("After method call of " + ret);
        if (ret instanceof User) {
            ((User)ret).setPassword("****");
        }
 }

}

We now have the auditCall method with two arguments: target and ret. Notice that the XML configuration in Listing 6-48 file specified the argument names in the correct order. This is quite easy to get wrong. Luckily, some IDEs will detect this and suggest a correction; Figure 6-6 shows this error correction in IntelliJ IDEA when we swapped the order of arguments from the correct target, ret to ret, target.

Arg-names error detection in IntelliJ IDEA

Figure 6.6. Arg-names error detection in IntelliJ IDEA

You can see that the <after-returning> element gives you the same configuration options as the @AfterReturning annotation in @AspectJ.

After Throwing Advice

As you already know, after throwing advice executes only when the matching method throws an exception. To create after throwing advice declaratively, we'll use the %(after-throwing%) element in the <aspect> element. We need to set at least the method, pointcut or pointcut-ref, and throwing attributes; in most cases, we will also set the arg-names attribute. Without any delay, let's take a look at the XML configuration in Listing 6-50.

Example 6.50. After Throwing Advice 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">

    <bean id="userService"
        class="com.apress.prospring2.ch06.services.DefaultUserService"/>
    <bean id="stockService"
        class="com.apress.prospring2.ch06.services.DefaultStockService"/>

    <bean id="aspectBean" class="com.apress.prospring2.ch06.xml.AspectBean"/>

    <aop:config>
        <aop:aspect id="afterThrowingAspect" ref="aspectBean">
            <aop:after-throwing method="healthMonitoring"
                                pointcut="execution(* com.apress.prospring2.
After Throwing Advice in XML
ch06.services.*.*(..)) &&
After Throwing Advice in XML
target(target)" arg-names="target,ex" throwing="ex"/> </aop:aspect> </aop:config> </beans>

This after throwing advice binds the target of the invocation and the exception thrown to the arguments of method healthMonitoring. This means that the healthMonitoring method needs to have two arguments; to catch any exception on any target, the arguments' types would have to be Object and Throwable. However, to show filtering based on argument types, we will take the code from Listing 6-51.

Example 6.51. After Throwing Method and Argument Filtering

public class AspectBean {

    public void healthMonitoring(Object target, NullPointerException ex) {
        System.out.println("Target " + target + " has thrown " + ex);
    }

}

Notice that the second argument's type is NullPointerException, so the advice will only run if the exception thrown by the target method is NullPointerException. To demonstrate after throwing advice, we will take the code from Listing 6-44, but call this:

stockService.getStockLevel(null);

After Advice

The after, or finally, advice executes after the target method completes, regardless of whether the target method finished normally or threw any exceptions. We declare the after advice using the <after> element in the <aspect> element; because it is after advice, we only need to set the method and pointcut or pointcut-ref attributes. We can use the arg-names attribute if we are using argument binding. Because of the nature of the advice, we cannot use the returning and throwing attributes. As an example, Listing 6-52 shows a simple after advice.

Example 6.52. After Advice Configuration

<?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">

    <bean id="userService"
         class="com.apress.prospring2.ch06.services.DefaultUserService"/>
    <bean id="stockService"
         class="com.apress.prospring2.ch06.services.DefaultStockService"/>

    <bean id="aspectBean" class="com.apress.prospring2.ch06.xml.AspectBean"/>

    <aop:config>
        <aop:aspect id="afterAspect" ref="aspectBean">
            <aop:after-throwing method="after"
                                pointcut="execution(* com.apress.prospring2.
After Advice Configuration
ch06.services.*.*(..))
After Advice Configuration
&& target(target)" arg-names="target"/> </aop:aspect> </aop:config> </beans>

The advice method (AspectBean.after) needs only one argument, and because we only want to advise the UserService bean, we declare it as public void after(UserService target).

Around Advice

We've left the most general advice til last. As the name suggests, it runs around the matched method's execution, so you can write code that will execute before the matched method (you can manipulate the arguments or even skip the matched method execution completely). You can also execute any code after the matched method completes; you are free to catch any exceptions using a standard try / catch block. You can also manipulate the return value, as long as you return a type that is assignable to the return value of the matched method (e.g., you can return Long from advice that advises a method that returns Number, but you can't return String). To declare around advice, use the <around> element in the <aspect> element; you must set the method and pointcut or pointcut-ref attributes, and you may want to set the arg-names attribute. The code in Listing 6-53 shows our around advice.

Example 6.53. XML Configuration of Around Advice

<?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">

    <bean id="userService"
        class="com.apress.prospring2.ch06.services.DefaultUserService"/>
    <bean id="stockService"
        class="com.apress.prospring2.ch06.services.DefaultStockService"/>

    <bean id="aspectBean" class="com.apress.prospring2.ch06.xml.AspectBean"/>

    <aop:config>
        <aop:aspect id="aroundAspect" ref="aspectBean">
            <aop:around method="censorStringArguments"
                        pointcut="execution(* com.apress.prospring2.ch06.
XML Configuration of Around Advice
services.*.*(..)) and
XML Configuration of Around Advice
args(argument)" arg-names="pjp, argument"/> </aop:aspect> </aop:config> </beans>

Notice that we have defined two arguments, but we don't use the pjp argument in the pointcut expression at all. Your around advice method must have at least the ProceedingJoinPoint argument (otherwise, you would not be able to invoke the matched method!) and must return Object. In addition to the ProceedingJoinPoint argument, you are free to use as many bound arguments as you need. In our example, we have implemented a censoring advice method (its source code is in Listing 6-54).

Example 6.54. Around Advice Method

public class AspectBean {

    public Object censorStringArguments(ProceedingJoinPoint pjp, String argument)
        throws Throwable {
        Object[] arguments;
        if (argument != null) {
            System.out.println("censored " + argument + "!");
            arguments = new Object[] { "****" };
        } else {
            arguments = new Object[] { null };
        }
        return pjp.proceed(arguments);
    }
...
}

Here, you can see that the method's arguments match the value in the arg-names attribute and that the type of the second argument is String, which means that we'll only match methods that have one argument of type String; in our small application, those are UserService.setAdministratorUsername, UserService.login, and StockService.getStockLevel.

Now that we have covered all types of advice using XML configuration, we will take a look at the last area, introductions.

Introductions in the aop Namespace

The last area we will cover in this section is introductions; if you recall, introductions offer a way to add additional methods to existing objects by proxying the advised objects, making the proxy implement the interfaces that the original implements plus any interfaces we declare. To use XML introductions, you need to use the <declare-parents> element under the <aspect> element; to actually make use of the newly declared parents (e.g., a newly implemented interface), you will need to create at least one piece of advice that uses the introduced interface. We are going to rework the example from Listing 6-36: we will introduce the CallTracker interface to the DefaultUserService and DefaultStockService. Listing 6-55 shows the configuration of the introduction of the CallTracker interface in XML.

Example 6.55. Introduction 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">

    <bean id="userService"
        class="com.apress.prospring2.ch06.services.DefaultUserService"/>
    <bean id="stockService"
        class="com.apress.prospring2.ch06.services.DefaultStockService"/>

    <bean id="aspectBean" class="com.apress.prospring2.ch06.xml.AspectBean"/>

    <aop:config>
        <aop:aspect id="aroundAspect" ref="aspectBean">
            <aop:declare-parents
                                 types-matching="com.apress.prospring2.
Introduction Declared in XML
ch06.services.*" implement-interface="com.apress.prospring2.
Introduction Declared in XML
ch06.introductions.CallTracker" default-impl="com.apress.prospring2.ch06.
Introduction Declared in XML
introductions.DefaultCallTracker"/> <aop:after-returning method="normalCall" arg-names="tracker" pointcut="execution(* com.apress.prospring2.
Introduction Declared in XML
ch06.services.*.*(..)) and this(tracker)"/> <aop:after-throwing method="failingCall" arg-names="tracker" pointcut="execution(* com.apress.prospring2.
Introduction Declared in XML
ch06.services.*.*(..)) and this(tracker)"/> </aop:aspect> </aop:config> </beans>

In the <declare-parents> element, the types-matching attribute specifies the classes to which we want to introduce the interface defined in the implement-interface. To complete the <declare-parents> element, we need to specify the default-impl—the name of the class that implements the interface in implement-interface. If we stopped there, we could write

CallTracker usct = (CallTracker)ac.getBean("userService");

Assuming that ac is the ApplicationContext instance configured using the XML configuration file from Listing 6-55, this would verify that the userService bean now implements the CallTracker interface, even though the DefaultUserService, the actual class of the bean, only implements the UserService. The only problem is that the application would not track calls; the values in the normalCalls and failingCalls fields in the DefaultCallTracker would never change. To complete this example, we need to create two pieces of advice: after returning and after throwing. Listing 6-56 shows the configuration of these two.

Example 6.56. After Returning and After Throwing Advices

...
    <aop:config>
        <aop:aspect id="aroundAspect" ref="aspectBean">
            <aop:declare-parents ... />

            <aop:after-returning method="normalCall"
                                 arg-names="tracker"
                                 pointcut="execution(* com.apress.prospring2.
After Returning and After Throwing Advices
ch06.services.*.*(..)) and this(tracker)"/> <aop:after-throwing method="failingCall" arg-names="tracker" pointcut="execution(* com.apress.prospring2.
After Returning and After Throwing Advices
ch06.services.*.*(..)) and this(tracker)"/> </aop:aspect> </aop:config> ...

To complete the advice, we need to implement the normalCall and failingCall methods in the AspectBean. Both pieces of after advice have the arg-names attribute set to tracker, so the methods will need one argument with the name tracker. Its type should be CallTracker; we will use its methods to count the calls. Listing 6-57 shows the implementation of the two advice methods.

Example 6.57. Implementation of the After Advice Methods

public class AspectBean {
...
    public void normalCall(CallTracker tracker) {
        tracker.markNormal();
    }

    public void failingCall(CallTracker tracker) {
        tracker.markFailing();
    }
...
}

The methods are very simple: they use the tracker argument to increment the appropriate call counter. We will complete the example with Listing 6-58, which shows the demonstration application. The application makes calls to the userService and stockService beans and displays the call statistics using the introduced CallTracker interface.

Example 6.58. Example Application for the Introductions

public class XmlDemo6 {

    public static void main(String[] args) {
        ApplicationContext ac = new ClassPathXmlApplicationContext(
                "/META-INF/spring/xmldemo6-context.xml"
        );
        UserService userService = (UserService) ac.getBean("userService");
        StockService stockService = (StockService) ac.getBean("stockService");

        userService.login("janm");
        stockService.getStockLevel("A");
        stockService.applyDiscounts(new Date(), BigDecimal.ONE);
        describeTracker(userService);
        describeTracker(stockService);
    }

    private static void describeTracker(Object o) {
        CallTracker t = (CallTracker)o;
        System.out.println(t.describe());
    }

}

The application works and proves that we have successfully introduced the CallTracker interface on both beans.

Which Style Should You Use?

You may be wondering which Spring AOP style to choose? @AspectJ is easy to use but requires that you use 1.5 JDK; XML-based configuration works with a pre-1.5 JDK. Our recommendation is to use @AspectJ wherever you can. The XML-based configuration may feel more familiar to seasoned Spring developers, but the issue is that, in XML-based configuration, the developers may not be aware that they are working on an aspect, and the XML configuration splits the functionality of a single unit into two files. Moreover, you cannot combine pointcuts with as much flexibility as you can with @AspectJ. If you recall, you can use a pointcut to specify the pointcut expression or pointcut-ref attribute to specify a reference to an existing pointcut expression in the XML configuration. However, you cannot combine a reference to an existing pointcut and a pointcut expression. For example, you cannot write code similar to the code in Listing 6-59.

Example 6.59. Illegal Combination of pointcut and pointcut-ref

<aop:config>
    <aop:pointcut id="x" expression="..."/>
    <aop:aspect ...>
        <aop:before pointcut="x() and target(y)" />
    </aop:aspect>
</aop:config>

The line in bold is not valid, because the pointcut expression x() and target(y) is not valid. The same value in the pointcut-ref attribute is also invalid, because no (pointcut) bean with ID x() and target(y) exists.

Possibly the worst approach is to combine the two approaches and use both @AspectJ and XML-based configuration: this can only lead to confusion and possibly duplicate advice.

Working with Spring AOP Proxies

Spring AOP support uses proxies; Figure 6-7 shows a UML class diagram of a proxy pattern.

UML class diagram of a proxy pattern

Figure 6.7. UML class diagram of a proxy pattern

The figure shows a JDK dynamic proxy; the proxy implements Interface1, Interface2, and Interface3. The implementation of the InvocationHandler.invoke() method handles all calls to all methods of the interfaces implemented by the proxy. In the case of Spring AOP, the InvocationHandler holds a reference to the advised object; it handles all before, after returning, after throwing, or around advice pertaining to the target method. If the target objects do not implement any interfaces or if you do not wish to use JDK dynamic proxies, you can use CGLIB proxies. CGLIB is a bytecode manipulation library, which you can use to proxy a class that does not implement any interface. Figure 6-8 shows the class diagram of a CGLIB proxy.

CGLIB proxy class diagram

Figure 6.8. CGLIB proxy class diagram

Essentially, CGLIB proxies are subclasses of the target class; because you cannot override final methods, advice on final methods will not work. Also, the constructor of the target (the advised class) will be called twice: once to create the advised class and the second time to create the proxy. Usually, two constructor calls do not cause any problems; if all you do in a constructor is check the arguments and set the instance fields, then you can run the constructor as many times as you want. However, if there is some kind of business logic in the constructor, it may cause problems. You can control which type of proxy Spring creates by setting the proxy-target-class="true" in the <aop:config . . ./> or the <aop:aspectj-autoproxy . . ./> elements. If you have more than one <aop:config . . ./> or <aop:aspectj-autoproxy . . ./> element in your configuration files and if at least one of them specifies proxy-target-class="true", then all proxies will use CGLIB as if we set proxy-target-class="true" in every configuration element.

Impact of Proxies

Let's now take a look at the impact the proxying may have on your code. As an example, we will take the StockService interface and its implementation, the DefaultStockService. To demonstrate some of the concepts we will discuss in this section, we will make a subtle modification to the DefaultStockService. Listing 6-60 shows the modifications.

Example 6.60. Modified DefaultStockService

public class DefaultStockService implements StockService {

    public long getStockLevel(String sku) {
        try {
            Thread.sleep(2000L);
        } catch (InterruptedException ignored) {
        }
        return getPredictedStockLevel (sku) / 2L;
    }

    public long getPredictedStockLevel(String sku) {
        return 6L * sku.hashCode();
    }

    public void applyDiscounts(Date cutoffDate, BigDecimal maximumDiscount) {
        // do some work
    }

}

If we create an unproxied instance of the DefaultStockService and then call the getStockLevel("X") method, the call to getPredictedStockLevel operates on the same instance. Listing 6-61 demonstrates this concept more clearly.

Example 6.61. Calling Unproxied Methods

public class ProxyDemo {

    public static void main(String[] args) {
        StockService dss = new DefaultStockService();
        dss.getStockLevel("X");
    }

}

When we call dss.getStockLevel("X"), the dss references the DefaultStockService directly and therefore when the DefaultStockService.getStockLevel() calls the DefaultStockService.getPredictedStockLevel() method, it is the same as if we called ((DefaultStockService)dss).getPredictedStockLevel() from the main() method.

Next, consider a similar scenario, but this time, the StockService interface will be a proxy to the DefaultStockService. We will begin with JDK dynamic proxies. Listing 6-62 shows a JDK dynamic proxy for the StockService.

Example 6.62. JDK Dynamic Proxy for the StockService

public class ProxyDemo2 {

    private static class DelegatingInvocationHandler
        implements InvocationHandler {
        private Object target;

        private DelegatingInvocationHandler(Object target) {
            this.target = target;
        }
public Object invoke(Object target,
                             Method method,
                             Object[] args) throws Throwable {
            return method.invoke(this.target, args);
        }
    }

    public static void main(String[] args) {
        DefaultStockService targetReference = new DefaultStockService();
        StockService proxyReference =
                (StockService) Proxy.newProxyInstance(
                        ProxyDemo2.class.getClassLoader(),
                        new Class<?>[] {StockService.class},
                        new DelegatingInvocationHandler(
                                targetReference
                        ));
        proxyReference.getStockLevel("X");
    }

}

When we call proxyReference.getStockLevel("X"), we are in fact calling the proxy. The proxy's InvocationHandler uses the DefaultStockService instance to delegate the method calls. Therefore, the proxyReference.getStockLevel() method operates on a different instance than the call to getPredictedStockLevel() in the DefaultStockService.

Before we can discuss the practical impact of proxying, we need to take a look at how Spring creates proxies. As you saw in Listing 6-62, creating even the simplest proxy is a lot of work. Spring simplifies this by providing the ProxyFactory class, which can not only create a proxy to the target object but also add any advice. Listing 6-63 shows a simple use of the ProxyFactory.

Example 6.63. Using the ProxyFactory

public class ProxyDemo3 {

    public static void main(String[] args) {
        DefaultStockService target = new DefaultStockService();
        ProxyFactory pf = new ProxyFactory(target);
        pf.addInterface(StockService.class);

        StockService stockService = (StockService) pf.getProxy();
        stockService.getStockLevel("A");
    }

}

We have created the target object (the DefaultStockService) and then used the ProxyFactory to create a proxy that implements the StockService. Next, we need to take a look at how we can use a subclass of the ProxyFactory to create advised proxies. To do this, we will look at the simple before advice in Listing 6-64.

Example 6.64. Simple Aspect with Before Advice

public class BeforeAspect {

    @Before("execution(* com.apress.prospring2.ch06.services.*.*(..))")
    public void simpleLog(JoinPoint jp) {
System.out.println("Before " + jp);
    }

}

There is nothing extraordinary about the aspect; it simply prints a message before the call to the advised method. Now that we have the aspect, we will use the AspectJProxyFactory to create an advised proxy. Listing 6-65 shows the code needed to create the advised proxy.

Example 6.65. Using AspectJProxyFactory

public class ProxyDemo4 {

    public static void main(String[] args) {
        DefaultStockService target = new DefaultStockService();
        AspectJProxyFactory pf = new AspectJProxyFactory(target);
        pf.addInterface(StockService.class);
        pf.addAspect(BeforeAspect.class);

        StockService stockService = (StockService) pf.getProxy();
        stockService.getStockLevel("A");
    }

}

When we now run this example, it will show the same output as if we used Spring's ApplicationContext configured with <aop:aspectj-autoproxy . . ./> and the aspect bean.

Now that we know how to use Spring to create advised proxies, we need to look at the implication of the proxy calls, which can become significant: imagine an advised object with an around advice that begins and commits every transaction for every method call. If you get the advised object (the proxy) and call its methods, the advice will work. However, when you call an advised method from within an advised object, the call will not go through the proxy, and the advice will not run. Therefore, when we use the StockService proxy in the sample application, we'll see that only the getStockLevel call is advised; the internal call to the getPredictedStockLevel method is not.

The best way to avoid this situation is not to chain method calls in your advised classes. In some situations, this chaining can in fact lead to problems (we explore some of the problems you can face in Chapter 11). However, there is another way that will allow you to make calls in the advised objects but direct them through the appropriate proxy. We strongly discourage you from doing so, but if you must, you can use the AopContext.currentProxy() method to obtain the proxy representing this. Listing 6-66 shows a modification to the DefaultStockService that demonstrates how to use this call.

Example 6.66. Modified DefaultStockService

public class DefaultStockService implements StockService {

    public long getStockLevel(String sku) {
        try {
            Thread.sleep(2000L);
        } catch (InterruptedException ignored) {
        }
        return ((StockService)AopContext.currentProxy()).
            getPredictedStockLevel(sku) / 2L;
    }
public long getPredictedStockLevel(String sku) {
        return 6L * sku.hashCode();
    }

    public void applyDiscounts(Date cutoffDate, BigDecimal maximumDiscount) {
        // do some work
    }

}

Instead of calling this.getPredictedStockLevel(sku), we obtain the proxy from AopContext.currentProxy() and call the getPredictedStockLevel on the proxy. If we attempt to run the sample application now, it will fail. We have to expose the current proxy in the configuration of the ProxyFactory (see Listing 6-67).

Example 6.67. Modified ProxyFactory Configuration

public class ProxyDemo4 {

    public static void main(String[] args) {
        DefaultStockService target = new DefaultStockService();
        AspectJProxyFactory pf = new AspectJProxyFactory(target);
        pf.addInterface(StockService.class);
        pf.setExposeProxy(true);
        pf.addAspect(BeforeAspect.class);

        StockService stockService = (StockService) pf.getProxy();
        stockService.getStockLevel("A");
    }

}

This is the final change needed to make the application work—when we run it, it shows that the before advice from Listing 6-64 ran for getStockLevel and getPredictedStockLevel:

Before org.springframework.aop.aspectj.
Modified ProxyFactory Configuration
MethodInvocationProceedingJoinPoint:
Modified ProxyFactory Configuration
execution(getStockLevel) Before org.springframework.aop.aspectj.
Modified ProxyFactory Configuration
MethodInvocationProceedingJoinPoint:
Modified ProxyFactory Configuration
execution(getPredictedStockLevel)

Even though this is the solution to the problem caused by proxying when chaining advised methods, we strongly urge you to rethink your design to prevent such chained calls. Only if there is no other option should you consider using the AopContext and changing the configuration of the ProxyFactory. Doing so will tie your beans very closely to Spring AOP; making your code dependent on the framework you use is the very thing Spring tries to avoid!

AspectJ Integration

AOP provides a powerful solution to many of the common problems that arise with OOP applications. When using Spring AOP, you can take advantage of a select subset of AOP functionality that, in most cases, allows you to solve problems you encounter in your application. However, in some cases, you may wish to use some AOP features that are outside the scope of Spring AOP. In this case, you need to look at an AOP implementation with a fuller feature set. Our preference, in this case, is to use AspectJ, and because you can now configure AspectJ aspects using Spring, AspectJ forms the perfect complement to Spring AOP.

AspectJ is a fully featured AOP implementation that uses compile-time weaving to introduce aspects into your code. In AspectJ, aspects and pointcuts are built using a Java-like syntax, which reduces the learning curve for Java developers. We are not going to spend too much time looking at AspectJ and how it works, because that is beyond the scope of this book. Instead, we present some simple AspectJ examples and show you how to configure them using Spring. For more information on AspectJ, you should definitely read AspectJ in Action by Raminvas Laddad (Manning, 2003).

Creating Your First AspectJ Aspect

Let's get started with a simple example: we'll create an aspect and use the AspectJ compiler to weave it in. Next, we will configure the aspect as a standard Spring bean. We can do this because each AspectJ aspect exposes a method, aspectOf(), which can be used to access the aspect instance. Using the aspectOf method and a special feature of Spring configuration, you can have Spring configure the aspect for you. The benefits of this cannot be overstated. You can take full advantage of AspectJ's powerful AOP feature set without losing out on Spring's excellent DI and configuration abilities. This also means that you do not need two separate configuration methods for your application; you can use the same Spring ApplicationContext approach for all your Spring-managed beans and for your AspectJ aspects.

As an example, we are going to use AspectJ to advise all methods of all classes in the com.apress.prospring2.ch06.services package and write out a message before and after the method invocation. These messages will be configurable using Spring. Listing 6-68 shows the StockServiceAspect aspect (the file name is StockServiceAspect.aj in com/apress/prospring2/ch06/aspectj).

Example 6.68. StockServiceAspect Aspect

package com.apress.prospring2.ch06.aspectj;

public aspect StockServiceAspect {
    private String suffix;
    private String prefix;

    public void setPrefix(String prefix) {
        this.prefix = prefix;
    }

    public void setSuffix(String suffix) {
        this.suffix = suffix;
    }

    pointcut doServiceCall() :
        execution(* com.apress.prospring2.ch06.services.*.*(..));

    before() : doServiceCall() {
        System.out.println(this.prefix);
    }

    after() : doServiceCall() {
        System.out.println(this.suffix);
    }
}

Much of this code should look familiar. Essentially, we create an aspect called StockServiceAspect, and just like for a normal Java class, we give the aspect two properties, suffix and prefix, which we will use when advising all methods in all classes in the com.apress.prospring2.ch06.services package. Next, we define a named pointcut, doServiceCall(), for a single joinpoint, in this case, the execution of the service methods (AspectJ has a huge number of joinpoints, but coverage of those is outside the scope of this example). Finally, we define two pieces of advices: one that executes before the doServiceCall pointcut and one that executes after it. The before advice writes a line containing the prefix, and the after advice writes a line containing the suffix. Listing 6-69 shows how this aspect is configured in Spring.

Example 6.69. Configuring an AspectJ 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"
       xsi:schemaLocation="
            http://www.springframework.org/schema/beans
            http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="userService"
        class="com.apress.prospring2.ch06.services.DefaultUserService"/>
    <bean id="stockService"
        class="com.apress.prospring2.ch06.services.DefaultStockService"/>
    <bean class="com.apress.prospring2.ch06.aspectj.StockServiceAspect"
        factory-method="aspectOf">
        <property name="prefix" value="Before call"/>
        <property name="suffix" value="After call"/>
    </bean>

</beans>

As you can see, much of the configuration of the aspect bean is very similar to standard bean configuration. The only difference is the use of the factory-method attribute of the <bean> tag. The factory-method attribute is intended to allow classes that follow a traditional Factory pattern to be integrated seamlessly into Spring. For instance, if you have a class Foo with a private constructor and a static factory method, getInstance(), using factory-method allows a bean of this class to be managed by Spring. The aspectOf() method exposed by every singleton AspectJ aspect allows you to access the instance of the aspect and thus allows Spring to set the properties of the aspect. However, notice that we have not used the aop namespace at all; on top of that, the StockServiceAspect is not a valid Java source. To complete the example, Listing 6-70 shows a familiar code that uses the stockService and userService beans.

Example 6.70. AspectJ Sample Application

public class AspectJDemo1 {
    public static void main(String[] args) {
        ApplicationContext ac = new ClassPathXmlApplicationContext(
                "/META-INF/spring/aspectjdemo1-context.xml"
        );
        UserService userService = (UserService) ac.getBean("userService");
        userService.login("janm");

        StockService stockService = (StockService) ac.getBean("stockService");
        System.out.println(stockService.getStockLevel("ABC"));
    }

}

If we try to run the application directly from our IDE, it will fail. The output will indicate that there is no StockServiceAspect class; we expected this problem, because the StockServiceAspect is an aspect, not a Java class.

...
Exception in thread "main" 
AspectJ Sample Application
org.springframework.beans.factory.CannotLoadBeanClassException:
AspectJ Sample Application
Cannot find class [com.apress.prospring2.ch06.aspectj.StockServiceAspect]
AspectJ Sample Application
for bean with name 'com.apress.prospring2.ch06.aspectj.StockServiceAspect#0'
AspectJ Sample Application
defined in class path resource [META-INF/spring/aspectjdemo1-context.xml];
AspectJ Sample Application
nested exception is java.lang.ClassNotFoundException:
AspectJ Sample Application
com.apress.prospring2.ch06.aspectj.StockServiceAspect ...

To make the sample application run, we must use the AspectJ compiler. The AspectJ compiler is going to turn the StockServiceAspect into a Java class file, and it will weave in the aspect's code into the advised classes. In other words, the AspectJ compiler is going to produce different bytecode for the DefaultStockService than a standard Java compiler would, because it will weave in the StockServiceAspect. In addition to compile-time weaving, the AspectJ compiler will produce a valid Java bytecode file from the StockServiceAspect.aj source file. Perhaps the most important method in the compiled StockServiceAspect class is the public static Aspect aspectOf() method that we use in the Spring bean definition.

Compiling the Sample Application

If you are familiar with AspectJ, you can skip this section; otherwise, read on to find out how to set up your computer to work with the AspectJ compiler. The first step is to get the AspectJ distribution from http://www.eclipse.org/aspectj. Download the installation package (.jar file) and install AspectJ to $ASPECTJ_HOME—typically /usr/share/aspectj-1.5 or C:Program Filesaspectj1-5. Once you have the AspectJ compiler installed, modifying the PATH environment variable to include $ASPECTJ_HOME/bin is convenient. When you now type ajc -version at the command prompt, you should see this:

AspectJ Compiler 1.5.4 built on Thursday Dec 20, 2007 at 13:44:10 GMT

Because using the AspectJ compiler directly is rather complex, we are going to use Apache Ant to simplify the work (for more information about Ant, go to http://ant.apache.org). The AspectJ distribution includes custom Ant tasks; to get these custom tasks installed, copy aspectjtools.jar from $ASPECTJ_HOME/lib to $ANTHOME/lib.

Now that we have installed the AspectJ compiler and configured Ant to include the AspectJ custom tasks, we can take a look at the build file (see Listing 6-71).

Example 6.71. The Ant Build File

<?xml version="1.0"?>
<project name="ch06" default="all" basedir="."
        xmlns:aspectj="antlib:org.aspectj">

    <property name="dir.src.main.java" value="./src/main/java"/>
    <property name="dir.src.main.resources" value="./src/main/resources"/>
    <property name="dir.module.main.build" value="./target/build-main"/>
    <property name="dir.lib" value="../../lib"/>
<property name="module.jar" value="ch06.jar"/>
    <path id="module.classpath">
        <fileset dir="${dir.lib}" includes="**/*.jar"/>
        <fileset dir="${dir.lib}" includes="**/*/*.jar"/>
    </path>

    <target name="all">
        <aspectj:iajc
            outjar="${module.jar}"
            sourceRootCopyFilter="**/*.java"
            source="1.5"
            target="1.5">
            <classpath refid="module.classpath"/>
            <sourceroots>
                <path location="${dir.src.main.java}
The Ant Build File
/com/apress/prospring2/ch06/services"/> <path location="${dir.src.main.java}
The Ant Build File
/com/apress/prospring2/ch06/aspectj"/> <path location="${dir.src.main.java}
The Ant Build File
/com/apress/prospring2/ch06/common"/> <path location="${dir.src.main.resources}"/> </sourceroots> </aspectj:iajc> <java classname="com.apress.prospring2.ch06.aspectj.AspectJDemo1" fork="yes"> <classpath> <path refid="module.classpath"/> <pathelement location="${module.jar}"/> </classpath> </java> </target> </project>

When we build the sample application using this Ant script, the AspectJ compiler will weave in the aspect and create the StockServiceAspect. When we run the sample application (using ant) from Listing 6-70, it will print the following:

...
[java] DEBUG [main] CachedIntrospectionResults.<init>(265) | 
The Ant Build File
Found bean property 'suffix' of type [java.lang.String] ... [java] DEBUG [main] AbstractBeanFactory.getBean(197) |
The Ant Build File
Returning cached instance of singleton bean 'userService' userService.login("janm") [java] Before call [java] After call [java] DEBUG [main] AbstractBeanFactory.getBean(197) |
The Ant Build File
Returning cached instance of singleton bean 'stockService' stockService.getStockLevel("ABC") [java] Before call stockService.getPredictedStockLevel("ABC") [java] Before call
[java] After call
[java] After call
[java] 193734

This output clearly shows that the aspect worked! We have successfully advised the DefaultStockService and DefaultUserService methods and set the aspect's prefix and suffix properties. Because the AspectJ compiler performs compile-time weaving, we do not need to worry about any proxying issues: the getPredictedStockLevel call from DefaultStockService.getStockLevel was advised without applying any AopContext wizardry.

AspectJ's Aspect Scope

By default, AspectJ aspects are singletons—you get a single instance per classloader. If you need to use different instances of the aspect depending on a pointcut, you'll have to write and configure the aspects differently. Listing 6-72 shows a per this aspect; "per this" means that AspectJ will create a different instance of the aspect for every match of the pointcut.

Example 6.72. Stateful (Per This) Aspect

package com.apress.prospring2.ch06.aspectj;

public aspect ThisCountingAspect perthis(doServiceCall()) {
    private int count;

    pointcut doServiceCall() :
        execution(* com.apress.prospring2.ch06.services.*.*(..));

    before() : doServiceCall() {
        this.count++;
        System.out.println("Before call");
    }

    after(Object target) : doServiceCall() && this(target) {
        System.out.println(target + " executed " + this.count + " times");
    }
}

The only problem is that we cannot configure this aspect in Spring: if we configure it with the usual prototype code, it will set the property only in one instance of the aspect, but that instance won't be the one that gets used in the join points. If we need a scoped AspectJ aspect, we cannot use Spring to configure its properties.

Load-Time Weaving

Load-time weaving is a process during which the weaver applies the aspects when the ClassLoader loads the advised class. AspectJ supports load-time weaving using a Java agent; you need to specify the agent's JAR file (see JVM's -javaagent command-line argument for details). However, agents bring some limitations: the obvious one is that agents only work with 1.5 and later JVMs, and a second limitation is that the JVM loads and uses the agent for the entire JVM. This may not be a significant problem in smaller applications, but when you get to a point where you need to deploy more than one application in a virtual machine, you may need more fine-grained control over the load-time weaving process.

Spring's load-time weaving support does just that: it allows you to control the load-time weaving for each ClassLoader. This is a great help when you are deploying a web application in a servlet container or an application server. You can configure a different load-time weaver for each web application and leave the container's class loading intact. Further, if you use Spring load-time weaving support, you may not need to change the configuration of some application servers and servlet containers.

Your First Load-Time Weaving Example

We'll get started with a simple aspect that just tracks calls to all service methods. The aspect is trivial; its source code is in Listing 6-73.

Example 6.73. A Load-Time Weaving Demonstration Aspect

@Aspect
public class AuditAspect {

    @After(value =
              "execution(* com.apress.prospring2.ch06.services.*.*(..)) && " +
              "this(t)",
           argNames = "jp,t")
    public void audit(JoinPoint jp, Object t) {
        System.out.println("After call to " + t + " (" + jp + ")");
    }

}

Now that we have the aspect, we'll use it to advise our userService and stockService beans. Instead of using the <aop:aspectj-autoproxy />, we will use spring-agent.jar as the JVM agent and use the context namespace to initiate the load-time weaving. In addition to the ApplicationContext XML configuration file, we need to create the META-INF/aop.xml file. The aop.xml file is a standard component of AspectJ; it tells the AspectJ weaver which classes to weave at load time. Listing 6-74 shows the contents of the aop.xml file.

Example 6.74. The META-INF/aop.xml File

<!DOCTYPE aspectj PUBLIC
        "-//AspectJ//DTD//EN"
        "http://www.eclipse.org/aspectj/dtd/aspectj.dtd">
<aspectj>
    <weaver>
        <include within="com.apress.prospring2.ch06.services.*"/>
    </weaver>
    <aspects>
        <aspect name="com.apress.prospring2.ch06.ltw.AuditAspect"/>
    </aspects>
</aspectj>

The code in bold tells the AspectJ weaver to weave in the AuditAspect into all classes in the com.apress.prospring2.ch06.services package. To complete the example, Listing 6-75 shows the ApplicationContext XML configuration file with the <context:load-time-weaver> tag. The <context:load-time-weaver> tag has a single attribute, aspectj-weaving. You can set it to "on" to turn on load-time weaving, "off" to turn the load-time weaving, well, off, and "autodetect" to turn on load-time weaving if there is at least one META-INF/aop.xml file. If you omit the aspectj-weaving attribute, Spring assumes the value is "autodetect".

Example 6.75. The ApplicationContext XML Configuration File

<?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:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="
            http://www.springframework.org/schema/beans
            http://www.springframework.org/schema/beans/spring-beans.xsd
            http://www.springframework.org/schema/context
            http://www.springframework.org/schema/context/spring-context.xsd">

    <bean id="userService"
        class="com.apress.prospring2.ch06.services.DefaultUserService"/>
    <bean id="stockService"
        class="com.apress.prospring2.ch06.services.DefaultStockService"/>

    <context:load-time-weaver />

</beans>

Before we can run this application to find out whether the load-time weaving worked, we need to add the -javaagent JVM argument. In our particular setup, the argument's value is -javaagent:../lib/org/springframework/spring/spring-agent.jar. Without the agent, the application would fail with an IllegalStateException:

...
Caused by: java.lang.IllegalStateException: 
The ApplicationContext XML Configuration File
ClassLoader [sun.misc.Launcher$AppClassLoader]
The ApplicationContext XML Configuration File
does NOT provide an 'addTransformer(ClassFileTransformer)' method.
The ApplicationContext XML Configuration File
Specify a custom LoadTimeWeaver or start your Java virtual machine with
The ApplicationContext XML Configuration File
Spring's agent: -javaagent:spring-agent.jar

The error message tells us what we already know: we need to specify the spring-agent.jar library as a JVM agent. When we do this, the application runs and prints this:

userService.login("janm")
After call to com.apress.prospring2.ch06.services.
The ApplicationContext XML Configuration File
DefaultUserService@4cb44131
The ApplicationContext XML Configuration File
(execution(void com.apress.prospring2.ch06.services.
The ApplicationContext XML Configuration File
DefaultUserService.login(String))) stockService.getStockLevel("ABC") After call to com.apress.prospring2.ch06.services.
The ApplicationContext XML Configuration File
DefaultStockService@197a64f2
The ApplicationContext XML Configuration File
(execution(long com.apress.prospring2.ch06.services.
The ApplicationContext XML Configuration File
DefaultStockService.getPredictedStockLevel(String))) DefaultStockService.getPredictedStockLevel("ABC") After call to com.apress.prospring2.ch06.services.
The ApplicationContext XML Configuration File
DefaultStockService@197a64f2
The ApplicationContext XML Configuration File
(execution(long com.apress.prospring2.ch06.services.
The ApplicationContext XML Configuration File
DefaultStockService.getStockLevel(String))) 193734

Not only does the application work with load-time weaving but the call from DefaultStockService.getStockLevel to DefaultStockService.getPredictedStockLevel is advised, demonstrating that there are no problems with proxying—the bytecode of the DefaultStockService class is not the same as the bytecode on disk, and the AspectJ weaver completed its work before the application's ClassLoader loaded the class. Not only does load-time weaving solve the proxying problems, it also removes any performance decrease introduced by the proxies. Because there is no proxy, the code runs as if it were compiled with the advice's code coded into every matching join point.

LoadTimeWeaver Lookup Strategies

Spring uses the InstrumentationSavingAgent in the JVM agent library spring-agent.jar to save the current instance of the Instrumentation interface the JVM exposes. The DefaultContextLoadTimeWeaver will attempt to automatically detect the LoadTimeWeaver instance that best matches the application's environment. Table 6-2 shows the LoadTimeWeaver implementations for different environments.

Table 6.2. LoadTimeWeaver Implementations

LoadTimeWeaver

Environment

InstrumentationLoadTimeWeaver

JVM started with Spring InstrumentationSavingAgent (using -javaagent:$LIB/spring-agent.jar)

WebLogicLoadTimeWeaver

LoadTimeWeaver implementation in a running BEA WebLogic 10 or later application server

GlassFishLoadTimeWeaver

Works in the GlassFish application server V2.

OC4JLoadTimeWeaver

The implementation of the LoadTimeWeaver that works with Oracle Application Server version 10.1.3.1 or later

ReflectiveLoadTimeWeaver

Intended to be used with the TomcatInstrumentableClassLoader to provide load-time weaving in the Tomcat servlet container and the default fallback LoadTimeWeaver implementation

SimpleLoadTimeWeaver

A test-only implementation of the LoadTimeWeaver (By "test-only," we mean that it performs the necessary weaving transformations on a newly created ClassLoader.)

Whichever strategy you use, it is important to realize that load-time weaving uses AspectJ, not @AspectJ, as you might think after looking at the code in Listing 6-74. This means that we cannot use the bean() pointcut @AspectJ supports.

Practical Uses of AOP

The first thing that comes to mind when someone mentions AOP is logging, followed by transaction management. However, these are just special applications of AOP. In our application development experience, we have used AOP for health and performance monitoring, call auditing, caching, and error recovery. More advanced uses of AOP include compile-time checks of architecture standards. For example, you can write an aspect that will enforce that you only call certain methods from certain classes. However, the more advanced cases have little to do with Spring AOP, so we will demonstrate performance and health monitoring. You can also refer to Chapter 22 for an example of the Cache SpringModule, which uses AOP to implement declarative caching support.

Performance and Health Monitoring

Being able to monitor applications is critical when they are deployed in the production environment. While you can use logging, finding a particular problem in the log is often difficult. It would be much better if we could track the performance of the application and quickly find any significant drops in performance. Also, it would be useful to record the exceptions that occurred during the application's runtime. Apart from recording the exception type (and message), recording the values of all arguments that could have caused the exception is important. To help us focus on the problems causing the most trouble in the application most of the time, we will also group the exception reports by exception type and argument values.

Before we can proceed with the implementation of the aspects, we will take a look at the other components of the application. Figure 6-9 shows the UML class diagram of the application's components.

UML class diagram of the main components

Figure 6.9. UML class diagram of the main components

Now, we can take a look at the implementation of the PeformanceAndHealthCollectingAspect. The aspect only has one piece of around advice; this advice collects statistics about method execution times and possible exceptions. Listing 6-76 shows the PerformanceAndHealthCollectingAspect.

Example 6.76. PerformanceAndHealthCollectingAspect

@Aspect
public class PerformanceAndHealthCollectingAspect {

    private PerformanceLogDao performanceLogDao;
    private ExceptionLogDao exceptionLogDao;
    private PerformanceLogExtractor performanceLogExtractor =
            new PerformanceLogExtractor();
    private ExceptionLogExtractor exceptionLogExtractor =
            new ExceptionLogExtractor();

    @Pointcut("execution(* com.apress.prospring2.ch06.services.*.*(..))")
    private void serviceCall() { }

    @Around(value = "serviceCall() && target(target)",
            argNames = "pjp, target")
    public Object collectPerformance(ProceedingJoinPoint pjp, Object target)
            throws Throwable {
        Throwable exception = null;
        Object ret = null;

        StopWatch stopWatch = new StopWatch();
        stopWatch.start();
        try {
            ret = pjp.proceed();
        } catch (Throwable t) {
            exception = t;
        }
        stopWatch.stop();

        if (exception == null) {
            this.performanceLogDao.insert(
                    this.performanceLogExtractor.extract(pjp, target),
                    stopWatch.getLastTaskTimeMillis()
        } else {
            this.exceptionLogDao.insert(
                    this.exceptionLogExtractor.extract(pjp, target),
                    exception
            );
        }

        if (exception != null) throw exception;
        return ret;
    }

    public void setPerformanceLogDao(PerformanceLogDao performanceLogDao) {
        this.performanceLogDao = performanceLogDao;
    }
public void setExceptionLogDao(ExceptionLogDao exceptionLogDao) {
        this.exceptionLogDao = exceptionLogDao;
    }
}

You can see that we record method performance and possible exceptions. We will use this information in the PerformanceMonitoringAspect. As its name suggests, this aspect is going to monitor the performance of the application. The aspect needs before and after advice; the before advice will record a start of the method call; the after advice will match and remove the start record. You may ask why we cannot use an around advice, as doing so would simplify tracking. The reason is that we need to be able to find out that a particular method is taking much longer than usual while it is still running. If we had implemented the health monitoring using around advice, our code would only find out that the method took much longer after it completed. Listing 6-77 outlines the implementation of the aspect.

Example 6.77. Outline Implementation of the PerformanceMonitoringAspect

@Aspect
public class PerformanceMonitoringAspect {

    @Pointcut("execution(* com.apress.prospring2.ch06.services.*.*(..))")
    private void serviceCall() { }

    @Before(value = "serviceCall() && target(target)",
            argNames = "jp, target")
    public void logStartCall(JoinPoint jp, Object target) {
        // log start call
    }

    @After(value = "serviceCall() && target(target)",
            argNames = "jp, target")
    public void logEndCall(JoinPoint jp, Object target) {
        // log end call
    }

}

The logging infrastructure must insert a record for each start call and remove it after the method finishes. The last component is the PerformanceMonitor class, which periodically scans the call log and for all entries found in the call log, checks the statistics collected in the PerformanceAndHealthMonitoringAspect. If the PerformanceMonitor finds a call that is taking much longer than usual, it can alert the application's administrators.

Summary

In this chapter, we concluded our discussion on AOP. We looked at the advanced options for pointcutting, as well as how to extend the set of interfaces implemented by an object using introductions. A large part of this chapter focused on using Spring Framework services to configure AOP declaratively, thus avoiding the need to hard-code AOP proxy construction logic into your code. We spent some time looking at how Spring and AspectJ are integrated to allow you to use the added power of AspectJ without losing any of the flexibility of Spring. Next, we looked at how you can use AspectJ with load-time weaving to minimize the overhead Spring AOP's proxies add to the application. Finally, we looked at how we can use AOP to solve an application-specific problems in real-life applications.

Even though we have discussed how to use AOP in Spring applications, we have barely scratched the surface of all the features AspectJ provides. If you are interested in more details about AspectJ, we recommend two excellent books. The first one is Eclipse AspectJ by Adrian Colyer, Andy Clement, George Harley, and Matthew Webster (Addison-Wesley, 2005), which provides a comprehensive introduction and reference for the AspectJ language; the second book, AspectJ in Action by Ramnivas Laddad (Manning, 2003), focuses on the syntax of AspectJ as well as covering a lot of general AOP topics.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.21.39.142