Horizontal concerns & SRP
We start with an object oriented tenet that states that one class must implement one and only one responsibility. This is called the Single Responsibility Principle (SRP). SRP states for instance that a UserService class can only do "user management" and nothing else. This at the outset may look very obvious to everyone. But on closer inspection of any typical UserService class the following can be observed:
- The UserService class may have to implement some logging functionality.
- User operations may have to be audited and hence some auditing functionality becomes required.
- Security needs to be enforced to ensure that only authorized users perform user operations.
- typical operations on the user object may have to be surrounded by transactions. These transactions ensure that all operations are atomic.
Thus there is more to the UserService than what meets the eye! The functionality that was specified above falls under the banner of horizontal requirements. These are also sometimes called non functional requirements since they are not part of the specification of what the UserService does directly. Horizontal concerns, if incorporated into the core code violate SRP. Since this kind of functionality is interspersed throughout the application, it is very hard to address changes in horizontal requirements without individually changing a lot of classes throughout the application. This is a cause for extreme application brittleness.
Introduction to AOP
Aspect Oriented Programming (or AOP) attempts to address this problem. Functional requirements (such as User Management) are directly handled by core classes in the application. Horizontal or non functional requirements such as security are implemented by specialized classes which are interwoven into the core classes. These specialized classes are called "aspects" and this approach to programming involving the interweaving of aspects around core target classes is called AOP. For the purposes of this article I am going to call the normal application class such as UserService that caters to a functional requirement of the application (such as User Management) as a target class. Now that we got the definitions out of the way, let us see how AOP is implemented in typical OO programming languages.
How aspects work
A typical aspect is integrated into an application as illustrated: A method in a target class is invoked. But before the control passes to the actual method, an aspect is invoked. The aspect first makes a decision whether the underlying method in the target class should be invoked. It may be possible that the aspect may decide to not invoke the underlying method. (Eg: Security aspects and caching aspects work this way) If it does decide that the underlying method should be invoked, then the control is passed to the target class after some pre-processing. The aspect also gets an opportunity to do some post processing after the underlying method in the target class returns after the invocation. Hence the AOP implementation must somehow get hold of the target class invocation and be able to "decorate" it with aspects. Let us look at some AOP implementations in Java.
In Java, AOP can be implemented in a few ways:
- Source Code Generation: In this, Java source code is generated around the class to decorate the methods with aspects.
- Byte Code Enhancement:In this strategy, the compiled java class code is ornamented with byte code that implements the aspects.
- Design Patterns: Certain design patterns facilitate interception. Examples of this include the Interceptor pattern, the Decorator pattern etc. A detailed discussion of these is not in the scope of this particular article.
- Dynamic Interception: This is the most easily implementable because this does not require code generation or enhancement in any form. It does not require controllers or facades that implement the design patterns either. The target class ( or its interface) is dynamically mocked by a proxy. This proxy first invokes certain interceptors. These interceptors perform the functionality of aspects. They then delegate control to the target class. After the target class is invoked, these interceptors also have the opportunity of modifying the output generated by the target before returning control back to the original caller.
Spring AOP takes the dynamic interception approach to implement AOP. Currently in Java, the only way to facilitate dynamic interception is by intercepting calls to all the methods of the target class. Dynamic interception does not work for fields i.e. it is not possible to intercept the call when the client is directly setting/retrieving the value of a field in the target class. This is not a big limitation since it is anathema to use a field in a class directly from another class anyways.
Spring AOP - An introduction to key terms
The AOP Alliance is a joint effort by various stakeholders to define a consistent way of accomplishing AOP in Java. The alliance defines the key interfaces but leaves the tedious details to the implementations. Spring AOP is an implementation of the AOP Alliance.
The AOP alliance introduces the notion of an Advice. An Advice is a class that is supposed to intercept the invocation of another class and render some extra functionality. Hence advices are aspects. Advices can be inserted before a method is invoked There are various types of Advices that have been defined as part of the AOP alliance. Spring AOP supoprts only AOP through method interception and hence is capable of supporting one sub-type of advice called Method Interceptor. An advice is applied on a "target" application class using a dynamically generated "proxy" class. The proxy class sets up a chain of interceptors around the target. Interceptors are typically advices or advisors. Advisor is a spring (i.e. it is not defined as part of the AOP alliance) interface that abstracts access to an advice. Every advisor must be hooked with an advice. The advisor delegates control to the advice only if certain conditions are met. For instance, a security advice is only applicable for methods that need to be secured. Hence the advisor that abstracts access to the security advice would check if the method being called currently is secured. If it is not secured, the security advice is not even invoked. In summary, an advisor can be conceived as a filter for the advice delegating control to the underlying advice only if certain conditions are met in the method invocation. A Proxy is the class that surrounds the target. The proxy is hooked to a list of advisors and advices. For each advisor, there should be an underlying advice. If an advice is hooked directly into the proxy, then it is unconditionally invoked.
A Detailed Segue into The Method Calling Sequence.
A calling class makes a call to a target class’s method (e.g: to the getUserProfile() method of a UserService class) . The call is intercepted by a proxy that surrounds the actual UserService target class. This proxy then delegates to a set of advices or advisors. if an advice is invoked, it implements its functionality eg: security. If an advisor is invoked, the advisor first checks to see if the underlying advice needs to be invoked. If it needs to be invoked, then the advice is invoked and it does the same thing as before i.e. implement some functionality. This chain of invocations ends with the target being ultimately invoked. The chain can break anywhere in between if one of the interceptors decides to return without invoking the others further down in the chain. Again, a security advice is a perfect example of an advice that can break this chain if the caller does not have the necessary credentials.
Advisor Advice Sequence
The diagram above goes into more details of this process.If you are weak of heart, you might want to skip this section!
Let us consider this sequence diagram in more detail. A client makes an initial request to a target. But before the control passes to the target, a proxy intercepts this request. The proxy creates a MethodInvocation object and loads all its associated advisors and advices within it. The MethodInvocation object is a thread unsafe object that would be passed around the AOP chain for this particular invocation. It knows what advisors/advices have been invoked so far and what else needs to be done to complete this invocation.
The MethodInvocation first starts with Advisor1. It asks the advisor if the underlying advice needs to be invoked. Since Advisor1 returns “YES” in this example, it proceeds to invoke Advice1. Advice1 does not return back after it does pre-processing. It instead invokes the proceed() method of MethodInvocation. (so it is calling back the MethodInvocation object) The proceed() method knows that Advice1 has already been invoked. So it invokes Advisor2 which in this example returns “NO”. Hence the underlying advice Advice2 is not invoked. Next the MethodInvocation object invokes Advice3. Advice3 does its pre-processing and calls back the proceed() method of MethodInvocation. (Notice again that Advice3 is not returning)
Now the MethodInvocation object knows that it has to invoke the target. The target returns back. Now the calling stack is unwound. MethodInvocation returns back from the proceed() method to Advice3 which does post processing now and returns back to MethodInvocation. MethodInvocation then returns back from the proceed() method to Advice1 which again does some post processing if required and returns back to the MethodInvocation. In all these interactions,the object that was returned from the call to the target is being passed around.
Finally, the MethodInvocation returns back to the calling Proxy which in turn returns to the Client. Phew! that was a lot of explanation. Go over it again and try to relate this to the sequence diagram again.
Spring AOP defines a special type of advisor called a PointcutAdvisor. A PointcutAdvisor makes use of a pointcut to restrict access to the advice. A Pointcut can be conceived as a rule that filters out access to the advice unless some conditions are met. Point cuts have two components namely a ClassFilter (which restricts access at the target class level) and a MethodMatcher that restricts access to a particular method. A MethodMatcher can be an “initialization time” or a “runtime” method matcher. A runtime method matcher has to return true for isRuntime(). A runtime method matcher is invoked for every invocation of the target to determine if the call (with the given set of arguments) needs to be advised. Thus there is a marginal overhead for the runtime method matcher.
Advice Advisor Class Diagram
ProxyFactoryBean in spring is typically used to instantiate individual targets in Spring.
The code looks like:
<bean id=‘userService’ class="org.springframework.aop.framework.ProxyFactoryBean">
<!-- define idrefs for other interceptor (advice or advisor) for this bean. -->
Now userService is the one that is used by all the classes for dependency injection. No one uses actualUserService . The above approach gives more control over the actual target beans that need to be proxied. On the flip-side, this can spiral into a configuration overdose since each and every target needs to be explicitly proxied. Besides, horizontal concerns should be applied in one unified way which is segregated from the actual instantiation of the beans. Hence a more sophisticated approach to proxying is needed. Spring provides this with the notion of ProxyFactories. A proxy factory is used to make a proxy for multiple targets. Proxy factories are provided with the interceptor names which are either advisors or advices so that they can make the proxy to delegate to the list of advices/advisors before the target is invoked.
There are multiple proxy factories available in spring. Each one of them is applicable for different situations. BeanNameAutoProxyCreator , for instance, proxies all the beans with the names that have been passed to it. Bean factories serve to externalize all the configuration for a horizontal requirement in one place. This modularizes the horizontal concern configuration in a very effective manner.
Let us, for instance, consider the following spring configuration:
<bean id="fooService" class="some.service"/>
<bean id="myAdvice1" class="some.advice"></bean>
<bean id="myAdvice2" class="some.other.advice"></bean>
In this case, proxyCreator1 first proxies fooService and surrounds it with advice1. Then proxyCreator2 would re-proxy the proxy and insert advice2 into it. Thus advice2 would first get invoked followed by advice1 followed by the target. This proxying the proxy thing was buggy in earlier versions of Spring. But the latest versions seem to handle it consistently.
I recommend one unified way of accomplishing AOP by using a spring bean called DefaultAdvisorAutoProxyCreator. This class automatically discovers targets to proxy. It also dynamically discovers advisors to use to achieve the proxying. This is so important that we re-iterate it again. Advisors are discovered automatically from the spring files by the DefaultAdvisorAutoProxyCreator.
What this means is that we need to specify the DefaultAdvisorAutoProxyCreator only once. We need to create as many advisors as needed so as to automatically hook advices with the target.
Let us look at a typical configuration below
<property name="advice" ref="myAdvice"></property>
As seen from the configuration above, the application would provide the pointcut and the advice to use. The proxyCreator would automatically discover “myAdvisor” and would use it as an interceptor in proxying all the beans available in the spring configuration. The pointcut attached to the advisor can then be used to choose which targets should be proxied and which should not be proxied. The pointcut can state whether the proxying should or should not happen for particular targets based on the target’s class name, method name and the arguments passed to the method at runtime. Classes and methods are not a flexible way of determining if a target needs to be proxied or not.
Aspects are a great way to incorporate horizontal functions. This is also in compliance with the Single Responsibility Principle. It is best to use proxy factories in Spring AOP for a unified way of implementing aspects. The recommended way of doing it is using the DefaultAdvisorAutoProxyCreator which has a unified way of implementing aspects.
One instance of DefaultAdvisorAutoProxyCreator would suffice to implement aspects to all. However, for individual concerns, we would need to instantiate as many advisors as there are concerns.
These strategies would ensure that horizontal services incorporation is seamless and controllable to a fine degree.
raja shankar kolluru: Thanks Ronald!
Arthur Ronald F D Garcia: Well documented article about Spring AOP. Congratulations!
santosh: Great Article, well documented , descriptive with good presentation.
raja shankar kolluru: Thanks Santosh.