Stacktrace

This article shows an alternative use for AOP: replacing IoC frameworks like XWork, PicoContainer and Spring.

First let us set the stage. We have made a simple app to manage an address book. It uses external services to fetch some information, like phone numbers. In order to decrease the coupling between our address book and the external services we define the latter as simple interfaces. This also makes it easy for us to use mock implementations of the external services when we want to test the app off-line.

// Defines the external service that our app uses
interface PhoneServiceProvider {
    String fetchPhoneNumber(String name);
}
// Mock class that implements the interface and makes it possible for
// us to test the app without communicating with the external services
class TestServiceProvider implements PhoneServiceProvider {
    public String fetchPhoneNumber(String name) {
        return "charlie".equals(name) ? "555-1234" : "555-4321";
    }
}
// This class uses PhoneServiceProvider but doesn't know where it
// comes from, nor which implementation it uses.
class AddressBook {
    private PhoneServiceProvider service;

    public String lookupPhoneNumber(String name) {
        return service.fetchPhoneNumber(name);
    }
}
// Test class. (Let's ignore the testing frameworks for this example.)
public class TestAddressBook {
    public static void testPhoneLookup() {
        AddressBook book = new AddressBook();
        String charliesNumber = book.lookupPhoneNumber("charlie");
        assert charliesNumber.equals("555-1234");
    }

    // Runs all test cases
    public static void main(String[] args) {
        testPhoneLookup();
    }
}

Note that the code above is fully complete! Put each class in its own Java file, compile them and run the TestAddressBook class and see what happens! (Or – why not – put all of it in the same file, called TestAddressBook.java, and compile that.)

Ok, it probably wasn’t too hard to predict that it was going to throw up… We never told ”AddressBook” that it was suppose to use the TestServiceProvider (or any other PhoneServicePRovider for that matter), so the ”service” variable is still null.

At this point it is now common to bring an IoC framework to the table, in order to connect the concrete implementations of the interfaces to the classes that wants to use them. This is typically done in different configuration files in XML format. Here, however, we will try a different approach!

This is the aspect that does the magic:

// This aspect makes sure that the TestServiceProvider is being user by
// classes looking for a PhoneServiceProvider, but only during test runs!
aspect UseTestServiceProvider {
    PhoneServiceProvider realService = new TestServiceProvider();

    pointcut usingProvider():
        get(PhoneServiceProvider *.*) &&
        !within(UseTestServiceProvider);

    pointcut inTestCase():
        cflow(call (void TestAddressBook.test*()));

    PhoneServiceProvider around():
        usingProvider() && inTestCase()
    {
        return realService;
    }
}

The UseTestServiceProvider aspect picks out (in the ”usingProvider” pointcut) all the places in the app where a member variable of type PhoneServiceProvider is being used, e.g. at a method call (like in our case). When this happens, an around advice makes sure that the code will use the provided TestServiceProvider instance (refered to by the aspect member ”realService”) instead.

A few things to note regarding this solution:

  • Aspects are by default singletons. In our case, this means that the TestServiceProvider we use is also a singleton, since it is managed by the aspect.
  • The ”inTestCase()” pointcut makes sure that the TestServiceProvider is only used in code that is running from a method with a name starting with ”test” and is a member of the TestAddressBook class. A more natural use of this pointcut would probably be to apply it to all classes in a ”test” package.
  • This aspect does nothing for all other uses of PhoneServiceProviders, such as production use… We put the definition of these in other aspects.
  • The ”service” member variable in the AddressBook class is always null! We never give it any value, nor do we use it for anything. Its only purpose is to function as a ”handle” for us to flag where in the code a PhoneServiceProvider is needed, so we can inject it.

Kommentarer

Skriv kommentar