Aspect Oriented Programming =========================== *Aspect oriented programming* (AOP) is a horizontal programming paradigm, where some type of behavior is applied to several classes that don't share the same vertical, object-oriented inheritance. In AOP, programmers implement these *cross cutting concerns* by writing an *aspect* then applying it conditionally based on a *join point*. This is referred to as applying *advice*. This section shows how to use the AOP module of Spring Python. External dependencies --------------------- Spring Python's AOP itself doesn't require any special external libraries to work however the IoC configuration format of your choice, unless you use :doc:`PythonConfig `, will likely need some. Refer to the :doc:`IoC documentation ` for more details. Interceptors ------------ Spring Python implements AOP advice using *proxies* and *method interceptors*. NOTE: Interceptors only apply to method calls. Any request for attributes are passed directly to the target without AOP intervention. Here is a sample service. Our goal is to wrap the results with "wrapped" tags, without modifying the service's code:: class SampleService: def method(self, data): return "You sent me '%s'" % data def doSomething(self): return "Okay, I'm doing something" If we instantiate and call this service directly, the results are straightforward:: service = SampleService() print service.method("something") "You sent me 'something'" .. highlight:: xml To configure the same thing using the IoC container, put the following text into a file named app-context.xml:: .. highlight:: python To instantiate the IoC container, use the following code:: from springpython.context import ApplicationContext from springpython.config import XMLConfig container = ApplicationContext(XMLConfig("app-context.xml")) service = container.get_object("service") You can use either mechanism to define an instance of your service. Now, let's write an interceptor that will catch any results, and wrap them with tags:: from springpython.aop import * class WrappingInterceptor(MethodInterceptor): def invoke(self, invocation): results = "" + invocation.proceed() + "" return results *invoke(self, invocation)* is a dispatching method defined abstractly in the *MethodInterceptor* base class. *invocation* holds the target method name, any input arguments, and also the callable target function. In this case, we aren't interested in the method name or the arguments. So we call the actual function using *invocation.proceed()*, and than catch its results. Then we can manipulate these results, and return them back to the caller. In order to apply this advice to a service, a stand-in proxy must be created and given to the client. One way to create this is by creating a *ProxyFactory*. The factory is used to identify the target service that is being intercepted. It is used to create the dynamic proxy object to give back to the client. You can use the Spring Python APIs to directly create this proxied service:: from springpython.aop import * factory = ProxyFactory() factory.target = SampleService() factory.interceptors.append(WrappingInterceptor()) service = factory.getProxy() .. highlight:: xml Or, you can insert this definition into your app-context.xml file:: .. highlight:: python If you notice, the original Spring Python "service" object has been renamed as "targetService", and there is, instead, another object called "serviceFactory" which is a Spring AOP ProxyFactory. It points to the target service and also has an interceptor plugged in. In this case, the interceptor is defined as an inner object, not having a name of its own, indicating it is not meant to be referenced outside the IoC container. When you get a hold of this, you can request a proxy:: from springpython.context import ApplicationContext from springpython.config import XMLConfig container = ApplicationContext(XMLConfig("app-context.xml")) serviceFactory = container.get_object("serviceFactory") service = serviceFactory.getProxy() Now, the client can call *service*, and all function calls will be routed to *SampleService* with one simple detour through *WrappingInterceptor*:: print service.method("something") "You sent me 'something'" Notice how I didn't have to edit the original service at all? I didn't even have to introduce Spring Python into that module. Thanks to the power of Python's dynamic nature, Spring Python AOP gives you the power to wrap your own source code as well as other 3rd party modules. Proxy Factory Objects --------------------- The earlier usage of a *ProxyFactory* is useful, but often times we only need the factory to create one proxy. There is a shortcut called *ProxyFactoryObject*:: from springpython.aop import * service = ProxyFactoryObject() service.target = SampleService() service.interceptors = [WrappingInterceptor()] print service.method(" proxy factory object") "You sent me a 'proxy factory object'" .. highlight:: xml To configure the same thing using the IoC container, put the following text into a file named *app-context.xml*:: .. highlight:: python In this case, the *ProxyFactoryObject* acts as both a proxy and a factory. As a proxy, it behaves just like the target service would, and it also provides the ability to wrap the service with aspects. It saved us a step of coding, but more importantly, the *ProxyFactoryObject* took on the persona of being our service right from the beginning. To be more pythonic, Spring Python also allows you to initialize everything at once:: from springpython.aop import * service = ProxyFactoryObject(target = SampleService(), interceptors = [WrappingInterceptor()]) Pointcuts --------- Sometimes we only want to apply advice to certain methods. This requires definition of a *join point*. Join points are composed of rules referred to as point cuts. In this case, we want to only apply our *WrappingInterceptor* to methods that start with "do":: from springpython.aop import * pointcutAdvisor = RegexpMethodPointcutAdvisor(advice = [WrappingInterceptor()], patterns = [".*do.*"]) service = ProxyFactoryObject(target = SampleService(), interceptors = pointcutAdvisor) print service.method("nothing changed here") "You sent me 'nothing changed here'" print service.doSomething() "Okay, I'm doing something`. Imagine you had set up a simple context like this:: from springpython.config import * from springpython.context import * class MovieBasedApplicationContext(PythonConfig): def __init__(self): super(MovieBasedApplicationContext, self).__init__() @Object(scope.PROTOTYPE) def MovieLister(self): lister = MovieLister() lister.finder = self.MovieFinder() lister.description = self.SingletonString() self.logger.debug("Description = %s" % lister.description) return lister @Object(scope.SINGLETON) def MovieFinder(self): return ColonMovieFinder(filename="support/movies1.txt") @Object # scope.SINGLETON is the default def SingletonString(self): return StringHolder("There should only be one copy of this string") From an AOP perspective, it is easy to intercept *MovieFinder* and wrap it with some advice. Because you have already exposed it as an injection point with this pure-Python IoC container, you just need to make this change:: from springpython.aop import * from springpython.config import * from springpython.context import * class MovieBasedApplicationContext(PythonConfig): def __init__(self): super(MovieBasedApplicationContext, self).__init__() @Object(scope.PROTOTYPE) def MovieLister(self): lister = MovieLister() lister.finder = self.MovieFinder() lister.description = self.SingletonString() self.logger.debug("Description = %s" % lister.description) return lister # By renaming the original service to this... def targetMovieFinder(self): return ColonMovieFinder(filename="support/movies1.txt") #...we can substitute a proxy that will wrap it with an interceptor @Object(scope.SINGLETON) def MovieFinder(self): return ProxyFactoryObject(target=self.targetMovieFinder(), interceptors=MyInterceptor()) @Object # scope.SINGLETON is the default def SingletonString(self): return StringHolder("There should only be one copy of this string") class MyInterceptor(MethodInterceptor): def invoke(self, invocation): results = "" + invocation.proceed() + "" return results Now, everything that was referring to the original *ColonMovieFinder* instance, is instead pointing to a wrapping interceptor. The caller and callee involved don't know anything about it, keeping your code isolated and clean. .. note:: Shouldn't you decouple the interceptor from the IoC configuration? It is usually good practice to split up configuration from actual business code. These two were put together in the same file for demonstration purposes.