This chapter presents an overview of Aspect-Oriented Programming concepts, and explains their capabilities and features. Here is a brief outline of the topics covered in this chapter:
Limits of Object-Oriented Programming
The AOP solutions
Spring AOP components
Spring AOP 2.5
In this chapter we will see what the designing and realization process of an application or software system consists of.
We have to stop and think about the problems that we will see, beginning from the designing phase: how to structure the application, what are the problems in the implementation phase if we use only object-oriented programming, and in which forms they show themselves. We will also see how aspect-oriented programming can support object-oriented programming to solve problems in the implementation phase. We will finally see what Spring provides to allow us to use aspect-oriented programming with Inversion of Control (IoC).
If we use a method such as the Extreme Programming, we iteratively focus hard on the functionalities and improve them following the clients' feedback.
Therefore, who does what is described so that the functionalities that the system provides to the user are clear.
After having found these entities, we model them as classes that contain data and have behavior.
To do this, we use some features of the object-oriented languages, such as inheritance, polymorphism, encapsulation, and interfaces, to create a model that helps us solve the domain problem in the simplest way possible.
Drawing, structuring, and building software systems in this way is now considered a common practice. Nevertheless, there are some inefficiencies that emerge at the moment of realizing the project. In fact, however accurately the design may have been made with highly cohesive classes and low coupling, there are still some situations where we have to make compromises.
The object-oriented paradigm provided the concepts and the right instruments for the creation of complex programs and had a great impact on the development of new disciplines in the domain of software design. In this sense, both engineering and software design disciplines developed greatly. Particularly important has been the development of the so-called Design Patterns that allow a certain degree of systemization of the activity of software design and development.
The concept of a class that includes data and functions that change its values allows for the realization of cohesive and independent entities (high cohesion, low coupling). This in turn realizes the required business functionalities through the exchange of messages.
Using design patterns and object-oriented programming, the development of an application can be realized by dividing the activities into independent groups of functionalities. In fact, as soon as the interfaces of every entity of the application have been defined, their implementation can be realized independently by different groups of developers.
Another advantage is the reliability offered by the object. If we consider that the access to an object's data and its modification can happen only by means of the methods that it exposes through its interface, no user can unpredictably corrupt this data and make that object's state inconsistent.
Finally, the concept of inheritance allows the definition of new classes that extend the functionality of the classes from which they derive. In this sense, we obtain the extendibility and the reuse of software.
After the advantages of the new instruments given by the object-oriented programming paradigm, we have to consider the limits that occurred in practical application.
The main problem is to manage to control the complexity. To face it, we will have to choose modularization: "divide et impera", according to the Latin maxim.
If architects look at the previous projects on which they have worked, they will notice that a common feature is the constant increase in the systems' complexity.
Separating the functionalities that have to be implemented into simpler and more manageable modules helps to control the complexity. Software systems are conceptually complex by their very nature, and increasing their complexity in the implementation means increasing the expense and the probability of its failure.
The code needed to integrate a complex implementation is expensive. The cost would be even higher if new features are required. In fact, those features imply deep changes in several parts of the implementation.
If we didn't take the way of modularization and simplification, we would have a monolithic system that would be unmanageable to modify.
First of all, we have to single out the modules that will implement the core business that justifies the design and the implementation of the software. Once we have completely understood how to implement the core business, we can think about designing the rest of the application so that the core business supports the system's users.
We are used to take the best practice of dividing the application into logical layers (presentation layer, business layer, and data layer). But, there are some functionalities that cross these layers transversally. They are named crosscutting concerns.
A crosscutting concern is, therefore, an independent entity that transversally crosses other functionalities of software. Take a look at the following figure:
The most common crosscutting concerns are: security, logging, transactions management, caching, performance checking, concurrency control, and exception management.
These crosscutting concerns, if implemented only with object-oriented programming, realize a bad matching between the core business and the modules that implement its functionalities. We are forced to deal with the implementation of these transversal functionalities into various modules, moreover, adding other transversal modules or modifying the existing ones. We are also forced to modify the code in which these modules are used. This is owing to the undesired, but necessary, matching that the object-oriented implementation unavoidably brings with it.
The followings graphs (extracts from http://www.parc.com/research/projects/aspectj/downloads/SDWest2002-BetterJavaWithAJ.ppt), show the code of Servlet Engine Tomcat 4 divided in modules:
In the figure above, XML parsing fits in one module.
In the figure above, the URL pattern matching fits in two modules.
In the figure above, logging is scattered in too many modules.
This figure shows the points where Tomcat classes' logging functionalities are called (underlined in red). As we can see, they are scattered in the points of the modules where the functionality is required.
The problem of scattering code derived from the crosscutting concerns in object-oriented programming arises due to its transversality to the crosscutting concerns, which is implemented in the classes. More correctly, the crosscutting concerns should be analysed as a third dimension of the design. Whereas in the implementation there are two dimensions, as shown in the following figure:
In these situations, aspect-oriented programming provides support to object-oriented programming for uncoupling modules that implement crosscutting concerns.
Its purpose is the separation of concerns.
In object-oriented programming the basic unit is the Class, whereas in aspect-oriented programming it's the Aspect.
The aspect contains the implementation of a crosscutting concern, which in the class should coexist with the objects that collaborate with it, for each class that needs it.
In this way, we can write the object-oriented classes without involving the crosscutting concerns in the implementation.
So, classes can freely evolve without taking into account this dependency.
The functionalities provided by the crosscutting concerns in the aspects will be applied to the objects through an aspect weaver or through some proxy classes. We will deal with this in the later chapters.
Now we will see how the problems we exposed, arise in the code.
Code scattering appears when the functionality is scattered because it's implemented in several modules.
There are two sorts of code scattering:
Let's see the following code to illustrate the cases in which the code is duplicated in different modules:
The Info
interface is implemented in the same way by two different classes, ScatteringA
and ScatteringB
. Therefore, this is a useless duplication of code.
public interface Info { public String getName(); public Date getCreationDate(); } public class ScatteringA implements Info{ public ScatteringA(String name, String author){ creation = new Date(); this.name = name; this.autor = author; } public ScatteringA(Date creation, String name, String author){ this.creation = creation; this.name = name; this.autor = author; } public Date getCreationDate() { return (Date)creation.clone(); } public String getName() { return name; } public String getAutor() { return autor; } private Date creation; private String name; private String autor; } public class ScatteringB implements Info{ public ScatteringB(String name, String address){ creation = new Date(); this.name = name; this.address = address; } public ScatteringB(Date creation, String name, String address){ this.creation = creation; this.name = name; this.address = address; code scattering, object-oriented programmingcode duplication} public Date getCreationDate() { return (Date)creation.clone(); } public String getName() { return name; } public String getAddress() { return address; } private Date creation; private String name; private String address; }
Code tangling occurs when a module has to manage several concerns at the same time such as logging, exception handling, security, caching, and more or when a module has elements of the implementation of other concerns inside.
In order to show what we mean by code tangling, let's look at the following code:
public class TanglingListUserController extends MultiActionController{ public ModelAndView list(HttpServletRequest req, HttpServletResponse res) throws Exception { //logging log(req); // authorization if(req.isUserInRole("admin")){ String username = req.getRemoteUser(); List users ; //exception handling try { //cache with authorization users = cache.get(Integer.valueOf( conf.getValue("numberOfUsers")), username); } catch (Exception e) { users = usersManager.getUsers(); } return new ModelAndView("usersTemplate", "users", users); }else{ return new ModelAndView("notAllowed"); } } private void log(HttpServletRequest req) { StringBuilder sb = new StringBuilder("remoteAddress:"); sb.append(req.getRemoteAddr()); sb.append("username:"); sb.append(req.getRemoteUser()); log.fine(sb.toString()); } … }
In this Spring MultiActionController
, we can see how many features are managed: logging, authorisation, exception management, and caching.
In spite of dealing with just the presentation of a list of users, this controller has to do many things, and the consequence is that other concerns are heavier in its implementation. That is code tangling.
The object-oriented paradigm provided the concepts and the right instruments for the creation of complex programs and had a great impact on the development of new disciplines in the domain of software design. In this sense, both engineering and software design disciplines developed greatly. Particularly important has been the development of the so-called Design Patterns that allow a certain degree of systemization of the activity of software design and development.
The concept of a class that includes data and functions that change its values allows for the realization of cohesive and independent entities (high cohesion, low coupling). This in turn realizes the required business functionalities through the exchange of messages.
Using design patterns and object-oriented programming, the development of an application can be realized by dividing the activities into independent groups of functionalities. In fact, as soon as the interfaces of every entity of the application have been defined, their implementation can be realized independently by different groups of developers.
Another advantage is the reliability offered by the object. If we consider that the access to an object's data and its modification can happen only by means of the methods that it exposes through its interface, no user can unpredictably corrupt this data and make that object's state inconsistent.
Finally, the concept of inheritance allows the definition of new classes that extend the functionality of the classes from which they derive. In this sense, we obtain the extendibility and the reuse of software.
After the advantages of the new instruments given by the object-oriented programming paradigm, we have to consider the limits that occurred in practical application.
The main problem is to manage to control the complexity. To face it, we will have to choose modularization: "divide et impera", according to the Latin maxim.
If architects look at the previous projects on which they have worked, they will notice that a common feature is the constant increase in the systems' complexity.
Separating the functionalities that have to be implemented into simpler and more manageable modules helps to control the complexity. Software systems are conceptually complex by their very nature, and increasing their complexity in the implementation means increasing the expense and the probability of its failure.
The code needed to integrate a complex implementation is expensive. The cost would be even higher if new features are required. In fact, those features imply deep changes in several parts of the implementation.
If we didn't take the way of modularization and simplification, we would have a monolithic system that would be unmanageable to modify.
First of all, we have to single out the modules that will implement the core business that justifies the design and the implementation of the software. Once we have completely understood how to implement the core business, we can think about designing the rest of the application so that the core business supports the system's users.
We are used to take the best practice of dividing the application into logical layers (presentation layer, business layer, and data layer). But, there are some functionalities that cross these layers transversally. They are named crosscutting concerns.
A crosscutting concern is, therefore, an independent entity that transversally crosses other functionalities of software. Take a look at the following figure:
The most common crosscutting concerns are: security, logging, transactions management, caching, performance checking, concurrency control, and exception management.
These crosscutting concerns, if implemented only with object-oriented programming, realize a bad matching between the core business and the modules that implement its functionalities. We are forced to deal with the implementation of these transversal functionalities into various modules, moreover, adding other transversal modules or modifying the existing ones. We are also forced to modify the code in which these modules are used. This is owing to the undesired, but necessary, matching that the object-oriented implementation unavoidably brings with it.
The followings graphs (extracts from http://www.parc.com/research/projects/aspectj/downloads/SDWest2002-BetterJavaWithAJ.ppt), show the code of Servlet Engine Tomcat 4 divided in modules:
In the figure above, XML parsing fits in one module.
In the figure above, the URL pattern matching fits in two modules.
In the figure above, logging is scattered in too many modules.
This figure shows the points where Tomcat classes' logging functionalities are called (underlined in red). As we can see, they are scattered in the points of the modules where the functionality is required.
The problem of scattering code derived from the crosscutting concerns in object-oriented programming arises due to its transversality to the crosscutting concerns, which is implemented in the classes. More correctly, the crosscutting concerns should be analysed as a third dimension of the design. Whereas in the implementation there are two dimensions, as shown in the following figure:
In these situations, aspect-oriented programming provides support to object-oriented programming for uncoupling modules that implement crosscutting concerns.
Its purpose is the separation of concerns.
In object-oriented programming the basic unit is the Class, whereas in aspect-oriented programming it's the Aspect.
The aspect contains the implementation of a crosscutting concern, which in the class should coexist with the objects that collaborate with it, for each class that needs it.
In this way, we can write the object-oriented classes without involving the crosscutting concerns in the implementation.
So, classes can freely evolve without taking into account this dependency.
The functionalities provided by the crosscutting concerns in the aspects will be applied to the objects through an aspect weaver or through some proxy classes. We will deal with this in the later chapters.
Now we will see how the problems we exposed, arise in the code.
Code scattering appears when the functionality is scattered because it's implemented in several modules.
There are two sorts of code scattering:
Let's see the following code to illustrate the cases in which the code is duplicated in different modules:
The Info
interface is implemented in the same way by two different classes, ScatteringA
and ScatteringB
. Therefore, this is a useless duplication of code.
public interface Info { public String getName(); public Date getCreationDate(); } public class ScatteringA implements Info{ public ScatteringA(String name, String author){ creation = new Date(); this.name = name; this.autor = author; } public ScatteringA(Date creation, String name, String author){ this.creation = creation; this.name = name; this.autor = author; } public Date getCreationDate() { return (Date)creation.clone(); } public String getName() { return name; } public String getAutor() { return autor; } private Date creation; private String name; private String autor; } public class ScatteringB implements Info{ public ScatteringB(String name, String address){ creation = new Date(); this.name = name; this.address = address; } public ScatteringB(Date creation, String name, String address){ this.creation = creation; this.name = name; this.address = address; code scattering, object-oriented programmingcode duplication} public Date getCreationDate() { return (Date)creation.clone(); } public String getName() { return name; } public String getAddress() { return address; } private Date creation; private String name; private String address; }
Code tangling occurs when a module has to manage several concerns at the same time such as logging, exception handling, security, caching, and more or when a module has elements of the implementation of other concerns inside.
In order to show what we mean by code tangling, let's look at the following code:
public class TanglingListUserController extends MultiActionController{ public ModelAndView list(HttpServletRequest req, HttpServletResponse res) throws Exception { //logging log(req); // authorization if(req.isUserInRole("admin")){ String username = req.getRemoteUser(); List users ; //exception handling try { //cache with authorization users = cache.get(Integer.valueOf( conf.getValue("numberOfUsers")), username); } catch (Exception e) { users = usersManager.getUsers(); } return new ModelAndView("usersTemplate", "users", users); }else{ return new ModelAndView("notAllowed"); } } private void log(HttpServletRequest req) { StringBuilder sb = new StringBuilder("remoteAddress:"); sb.append(req.getRemoteAddr()); sb.append("username:"); sb.append(req.getRemoteUser()); log.fine(sb.toString()); } … }
In this Spring MultiActionController
, we can see how many features are managed: logging, authorisation, exception management, and caching.
In spite of dealing with just the presentation of a list of users, this controller has to do many things, and the consequence is that other concerns are heavier in its implementation. That is code tangling.
We have seen that with an object-oriented system, code tangling and code scattering can occur. This can cause the system to have duplicate code and functionalities not being clear and plain. Evident problems with the implementation of further requirements arise, with modules strongly coupled in the implementation.
In the previous situations, the object-oriented system can't be of any help because the following effects occur:
Difficult evolution: A module's implementation is coupled to other functionalities.
Poor quality: In the
TanglingListUserController
example, if a problem arises, it's not even clear what the module's main functionality is.Code not reusable: If the implementation involves several concerns, it won't be suitable for other scenarios.
Productivity: Scattered implementations move the problem's main focus to the periphery where the implementations are.
Traceability: Code scattering functionality is implemented at several points. To have a hold on it, you need to check all the modules in which the implementation is spread.
Aspect-oriented programming allows:
The modularization of crosscutting concerns by its constructs
The uncoupling of the modules
Using aspects, the removal of dependence of a crosscutting concern from the modules that use it
Now let's see practically what AOP provides to overcome the gaps in object-oriented programming highlighted so far in the book, and what are its main concepts.
Let's see who are the main actors that enable the aspect-oriented programming, implement the crosscutting concerns in the aspects, and define with other actors the points and the classes on which these crosscutting concerns are applied.
In the following figure, we see the normal interactions between objects:
In object-oriented programming, classes cooperate by calling mutually public methods and exchanging messages.
Crosscutting concerns are placed in the implementations of the classes A, B, and C, and this leads to the problems previously explained such as code tangling, code scattering, and so on.
The following figure conceptually compares the execution flow of the invocation of a method in the case of OOP and AOP:
In the case of object-oriented programming, where the crosscutting concerns are included into the classes' implementations, Object A in its method A invokes method B on Object B. This is, apart from exceptions, the normal flow of messages' exchange between two objects that interact. The cross interactions are called back and used just in these two methods because there isn't any other way to act.
In the flow with aspect-oriented programming, the crosscutting functionalities are extracted from the object-oriented implementations and applied as advices where they are actually useful. This is because they are applied on the flow where they really have to be carried out, that is by the pointcuts and on the target object.
The whole of the advice, the pointcut, the target object, and the joinpoint, make an aspect.
Now let's introduce the AOP terms denoting the components that take part in the implementation, which are partially pictured in the previous figure.
Aspect: Corresponds to the class in object-oriented programming. It's the crosscutting functionality.
Joinpoint: This is the application point of the aspect. It is a point of the execution of a program such as the invocation of a constructor or the execution of a method or the management of an exception (WHEN).
Advice: This is the action an aspect performs at a certain joinpoint.
Advices can be "around", "before", and "after".
Pointcut: This is the expression for the joinpoint's selection, for instance a method's execution with a certain signature (WHERE).
Introduction: This is the declaration of methods or additional fields on the object to which the aspect will be applied. It allows the introduction of new interfaces and implementations on the objects.
Target object: This is the module (Object) to which the aspect will be applied.
Weaving: This is the linking action between the aspect and the objects to which advices must be applied.
This action may be performed at the editing phase using an AspectJ compiler, or at runtime.
If a runtime action is carried out, an AOP Proxy is used to implement the contracts that the aspect has to respect.
Types of advice:
Before advice: This is an advice that executes before a joinpoint, but which does not have the ability to prevent execution flow proceeding to the joinpoint.
After returning advice: An advice to be executed after a joinpoint completes normally.
Throws advice: This is an advice to be executed if a method exits by throwing an exception.
After (finally) advice: This advice is to be executed regardless of the means by which a joinpoint exits.
Around advice: This advice can perform custom behavior before and after the method invocation. It is also responsible for choosing whether to proceed to the joinpoint, or to cut short the advised method execution by returning its own return value or throwing an exception.
In the case of Aspect-Oriented Programming in the earlier image, taking into account that the joinpoint is the invocation of methods and that the joinpoint is the method called methodB, the aspect executes the crosscutting concern included into the advice when methodB is invoked on the target, Object B. This kind of interception before methodB is that of a Before Advice.
The main aim of Spring AOP is to allow the realization of JEE functionalities in the simplest manner and without being intrusive. With this aim, it allows the use of a subset of AOP functionalities in a simple and intuitive way (introduced since version 1.x, and in version 2.x with new integrations with AspectJ).
In order to achieve this aim, since version 1.x, Spring has implemented the specifications of the AOP alliance. This is a joint effort between representatives of many open-source AOP projects, including Rod Johnson of Spring, to define a standard set of interfaces for AOP implementations.
In Spring AOP, an aspect is represented by an instance of a class that implements the Advisor interface. There are two subinterfaces of Advisor: IntroductionAdvisor
and PointcutAdvisor
. The PointcutAdvisor
interface is implemented by all Advisors that use pointcuts to control the applicability of advice to joinpoints.
In Spring, introductions are treated as special kinds of advice. Using the IntroductionAdvisor
interface, you can control those classes to which an introduction applies.
The core of Spring AOP is based around proxies. There are two ways of using proxies: programmatic modality and declarative modality.
The former consists of using a ProxyFactory
to create a proxy of the class on which you want to apply an aspect. After creating the proxy, you use the ProxyFactory
to weave all the aspects you want to use on the object.
The ProxyFactory
class controls the weaving and proxy creation process in Spring.
Using the ProxyFactory
class, you control which aspects you want to weave into the proxy. You can weave only an aspect, that is, advice combined with a pointcut.
However, in some cases you want an advice to apply to the invocation of all methods in a class, not just a selection. For this reason, the ProxyFactory
class provides the addAdvice()
method. Internally, addAdvice()
wraps the advice you pass it in an instance of DefaultPointcutAdvisor
, and configures it with a pointcut that includes all methods by default.
This is an example of class that implements the MethodBeforeAdvice
to perform a crosscutting functionality before the method of the target class.
Before advice is performed before the invocation of the method.
Let us see an example that shows the usage of before advice, with a class that implements the MethodBeforeAdvice
, and has a main
method for testing.
package org.springaop.chapter.one; import java.lang.reflect.Method; import org.springaop.target.Hello; import org.springframework.aop.MethodBeforeAdvice; import org.springframework.aop.framework.ProxyFactory; public class BeforeAdvice implements MethodBeforeAdvice{ public static void main(String[] args) { //target class Hello target = new Hello(); // create the proxy ProxyFactory pf = new ProxyFactory(); // add advice pf.addAdvice(new BeforeAdvice()); // setTarget Spring AOPbefore advice methodpf.setTarget(target); Hello proxy = (Hello) pf.getProxy(); proxy.greeting(); } public void before(Method method, Object[] args, Object target) throws Throwable { System.out.println("Good morning"); } } public class Hello { public void greeting(){ System.out.println("reader"); } }
The result will be:
After returning advice is performed after the invocation of the method.
package org.springaop.chapter.one; import java.lang.reflect.Method; import org.springaop.target.Hello; import org.springframework.aop.AfterReturningAdvice; import org.springframework.aop.framework.ProxyFactory; public class AfterRetuningAdvice implements AfterReturningAdvice { public static void main(String[] args) { // target class Hello target = new Hello(); // create the proxy ProxyFactory pf = new ProxyFactory(); // add advice pf.addAdvice(new AfterRetuningAdvice()); // setTarget pf.setTarget(target); Hello proxy = (Hello) pf.getProxy(); proxy.greeting(); } public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable { System.out.println(",this is a afterReturningAdvice message"); } } public class Hello { public void greeting(){ System.out.println("reader"); } }
The result will be:
This is the Hello
target class on which we want to apply an around advice. It is called before the method and controls its invocation.
public class Hello { public void greeting(){ System.out.println("reader"); } }
This is the advice that must be applied around the performed method; as we can see that the invocation of the method occurs with invocation.proceed
.
package org.springaop.chapter.one; import org.aopalliance.intercept.MethodInterceptor; import org.aopalliance.intercept.MethodInvocation; public class MethodDecorator implements MethodInterceptor{ public Object invoke(MethodInvocation invocation) throws Throwable { System.out.print("Hello "); Object retVal = invocation.proceed(); System.out.println("this is aop !"); return retVal; } }
This is the class where, through the ProxyFactory
, we give the advice to apply. But, in the case of the MethodDecorator
, it is an around advice.
package org.springaop.chapter.one; import org.springaop.target.Hello; import org.springframework.aop.framework.ProxyFactory; public class AroundAdvice { public static void main(String[] args) { //target class Hello target = new Hello(); // create the proxy ProxyFactory pf = new ProxyFactory(); // add advice pf.addAdvice(new MethodDecorator()); // setTarget pf.setTarget(target); Hello proxy = (Hello) pf.getProxy(); proxy.greeting(); } }
The result will be:
This advice is performed only if the method on which the advice is applied throws an exception.
This is a class that intentionally throws an exception in every method; the exceptions are of different types.
package org.springaop.target; public class ExceptionTarget { public void errorMethod() throws Exception { throw new Exception("Fake exception"); } public void otherErrorMethod() throws IllegalArgumentException { throw new NullPointerException("Other Fake exception"); } }
This is the code to try it:
package org.springaop.chapter.one; import java.lang.reflect.Method; import org.springaop.target.ExceptionTarget; import org.springframework.aop.ThrowsAdvice; import org.springframework.aop.framework.ProxyFactory; public class ThrowsAdviceClass implements ThrowsAdvice { public static void main(String[] args) { //target class ExceptionTarget errorBean = new ExceptionTarget(); // create the proxy ProxyFactory pf = new ProxyFactory(); // add advice pf.addAdvice(new ThrowsAdviceClass()); // setTarget pf.setTarget(errorBean); ExceptionTarget proxy = (ExceptionTarget) pf.getProxy(); try { proxy.errorMethod(); } catch (Exception ignored) { } try { proxy.otherErrorMethod(); } catch (Exception ignored) { } } public void afterThrowing(Exception ex) throws Throwable { System.out.println("+++"); System.out.println("Exception Capture:"+ex.getClass().getName()); System.out.println("+++\n"); } public void afterThrowing(Method method, Object[] args, Object target, NullPointerException ex) throws Throwable { System.out.println("+++"); System.out.println("NullPointerException Capture: "+ex.getClass().getName()); System.out.println("Method: " + method.getName()); System.out.println("+++\n"); } }
The result will be:
Here we will see how to use the examples described previously, configuring the classes as Spring beans declared in XML file and using a ProxyFactoryBean
.
<bean id="helloMatch" class="org.springframework.aop.framework.ProxyFactoryBean"> <property name="target"> <bean class="org.springaop.Hello"/> </property> <property name="interceptorNames"> <list> <idref bean="helloBeforeAdvice"/> <idref bean="helloAfterRetuningAdvicee"/> </list> </property> </bean> <bean id="helloBeforeAdvice" class="org.springaop.advice.BeforeAdvice"/> <bean id="helloAfterRetuningAdvice" class="org.springaop.advice.AfterRetuningAdvice"/>
In the configuration of the example, we can see how a helloMatch
is defined, which is in fact a ProxyFactoryBean
that puts together the target object on which the crosscutting concern has to be applied, and the list of advice that contains the crosscutting functionalities. In this case, two of the advices are used in the programmatic modality and are applied at the target object as a reference in the list of interceptors that can be applied on the object.
It is an implementation of Spring FactoryBean
that allows you to specify a bean to target and provides a set of advice and advisors for that bean that are eventually merged into an AOP proxy. Because you can use both advisor and advice with the ProxyFactoryBean
, you can configure not only the advice declaratively, but the pointcuts as well.
In both modalities Spring uses internally two sorts of proxy: JDK proxy or CGLIB proxy.
The concept of an Advisor principally concerns Spring 1.x. We will see later how it can benefit by the syntax of the pointcuts of AspectJ.
From the 2.x version onwards, there is a closer integration or configuration based on AspectJ and its syntax, either through annotations or through schema-based configuration. In any case, it's always possible to use AOP in the classic way as in the 1.x version.
Spring is first of all an IoC
Container, and so it allows using the components that implement the AOP as a simple bean, assembling them and obtaining the result of the AOP weaver through Proxy classes, as we previously described.
Instead, AspectJ provides a static implementation of AOP that is produced at compile time. Spring provides a dynamic implementation of AOP, as it is implemented through the creation and the use of proxy classes that permit the implementation of a chain of interceptors.
Obviously, a static implementation provides better performance, but requires greater knowledge and a compiler, whereas the dynamic implementation is easier to use and more accessible. It can be disabled from configuration and never requires anything different from the usual Java compiler.
Spring permits only method execution to be used as a joinpoint. So we can't use with Spring AOP all the features of AOP, but we can do so with AspectJ called by Spring. For example, we must use the support of AspectJ if we want to use as a joinpoint:
The invocations of constructors
Access to the domains of objects with the setter and getter
The initialization of an object
The initialization of an object with a calling
super()
The execution inside a class with
this()
The calling of a method
Therefore, the aspect can be normal Java classes with the annotation @Aspect
, or configured using configuration XML.
The advices are seen as interceptors that maintain a chain of interceptors around the joinpoint.
Pointcuts are performed according to the matching of the AspectJ pointcut expression language, or according to regular expressions following the rules present since Spring 1.x.
This introduction clearly shows that Spring has a simplified pattern of the whole set of AOP features so that AOP can be used with no special editing and alterations to the bytecode. This would be necessary together with the use of AspectJ, if we wanted all the features of AOP as we will see in the rest of this book.
Now we will see what Spring 2.5 offers compared to the 1.x version.
Now let's see some introductory examples of using the syntax of AspectJ with annotations; the purpose is to have the same sorts of advice that we saw in the programmatic examples.
<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" xmlns:p="http://www.springframework.org/schema/p" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd"> <aop:aspectj-autoproxy/> ... </beans>
In the configuration, if we use a tag <aop:aspectj-autoproxy/>
, Spring prepares autoproxy for the classic mode that uses AspectJ annotations and we define two beans to check the annotations' behavior.
This is the body of the Target Class that we will use in the examples.
public class Hello { public void greeting(){ System.out.println(label); } private String label = "reader"; public void setLabel(String label) { this.label = label; } }
Now we see how to use before advice with the annotations.
package org.springaop.chapter.one.annotation.before; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; @Aspect public class BeforeAspect { @Before("execution(* greeting(..))") public void beforeGreeting() { System.out.println("Good morning "); } } <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" xmlns:p="http://www.springframework.org/schema/p" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd"> <aop:aspectj-autoproxy/> <bean id="hello" class="org.springaop.target.Hello" p:label="writer"/> <bean id="before" class="org.springaop.chapter.one.annotation.before.BeforeAspect"/> </beans>
Result:
The following code explains how to use after returning advice with the annotations:
package org.springaop.chapter.one.annotation.after.returning; import org.aspectj.lang.annotation.AfterReturning; import org.aspectj.lang.annotation.Aspect; @Aspect public class AfterReturningAspect { @AfterReturning("execution(* greeting(..))") public void afterGreeting() { System.out.println("this is a aop !"); } } <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" xmlns:p="http://www.springframework.org/schema/p" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd"> <aop:aspectj-autoproxy/> <bean id="hello" class="org.springaop.target.Hello" p:label="writer"/> <bean id="afterReturning" class="org.springaop.chapter.one.annotation.after.returning.AfterReturningAspect"/> </beans>
Result:
Now we see how to use around advice with the annotations.
package org.springaop.chapter.one.annotation.around; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; @Aspect public class AroundAspect { @Around("execution(* greeting(..))") public Object aroundGreeting(ProceedingJoinPoint pjp) throws Throwable { System.out.print("Hello "); try { return pjp.proceed(); } finally { System.out.println("this is around aop !"); } } } <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" xmlns:p="http://www.springframework.org/schema/p" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd"> <aop:aspectj-autoproxy/> <bean id="hello" class="org.springaop.target.Hello" p:label="writer"/> <bean id="around" class="org.springaop.chapter.one.annotation.around.AroundAspect"/> </beans>
Result:
The following code explains how to use after (finally) advice
with the annotations:
package org.springaop.chapter.one.annotation.afterfinally; import org.aspectj.lang.annotation.After; import org.aspectj.lang.annotation.Aspect; @Aspect public class AfterFinallyAspect { @After("execution(* greeting(..))") public void afterGreeting() { System.out.println("this is afterAspect !"); } } <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" xmlns:p="http://www.springframework.org/schema/p" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd"> <aop:aspectj-autoproxy/> <bean id="hello" class="org.springaop.target.Hello" p:label="writer"/> <bean id="afterFinally" class="org.springaop.chapter.one.annotation.afterfinally.AfterFinallyAspect"/> </beans>
Result:
For the after throwing advice example, we're going to use a target class different from Hello, in order to be able to trigger exceptions deliberately.
This is the body of the exception target:
package org.springaop.target; public class ExceptionTarget { public void errorMethod() throws Exception { throw new Exception("Fake exception"); } public void otherErrorMethod() throws IllegalArgumentException { throw new NullPointerException("Other Fake exception"); } }
Example after throwing:
package org.springaop.chapter.one.annotation.throwsadvice; import org.aspectj.lang.annotation.AfterThrowing; import org.aspectj.lang.annotation.Aspect; @Aspect public class AfterThrowingAspect { @AfterThrowing("execution(* errorMethod(..))") public void afterGreeting() { System.out.println("+++"); System.out.println("Exception !"); System.out.println("+++"); } }
This is the body of the ExceptionTest
class:
package org.springaop.chapter.one.annotation.throwsadvice; import org.springaop.target.ExceptionTarget; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class ExceptionTest { public static void main(String[] args) { String[] paths = {"org/springaop/conf/applicationContext.xml"}; ApplicationContext ctx = new ClassPathXmlApplicationContext(paths); ExceptionTarget exceptiontarget = (ExceptionTarget)ctx.getBean("exceptionTarget"); try { exceptiontarget.errorMethod(); } catch (Exception ignored) { } } } <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" xmlns:p="http://www.springframework.org/schema/p" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd"> <aop:aspectj-autoproxy/> <bean id="exceptionTarget" class="org.springaop.target.ExceptionTarget"/> <bean id="throws" class="org.springaop.chapter.one.annotation.throwsadvice.AfterThrowingAspect"/> </beans>
The result will be:
Now let's see some introductory examples for schema-based configuration, using the code of the classes of the examples used with the annotations, and with the same output results.
The following before advice example example explains how to use the before advice with XML Schema configuration:
package org.springaop.aspects.schema; public class SpringAopAspectBeforeExample { public void beforeGreeting() { System.out.println("Good morning "); } } <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p" xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd"> <aop:config> <aop:aspect ref="before"> <aop:before method="beforeGreeting" pointcut="execution(* greeting(..))" /> </aop:aspect> </aop:config> <bean id="hello" class="org.springaop.target.Hello" p:label="writer" /> <bean id="before" class="org.springaop.aspects.schema.SpringAopAspectBeforeExample" /> </beans>
The following after advice example explains how to use the after advice with XML Schema configuration:
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p" xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd"> <bean id="hello" class="org.springaop.target.Hello" p:label="writer" /> <aop:config> <aop:aspect ref="after"> <aop:after method="afterGreeting" pointcut="execution(* greeting(..))" /> </aop:aspect> </aop:config> <bean id="after" class="org.springaop.aspects.schema.SpringAopAspectAfterExample" /> </beans> package org.springaop.aspects.schema; public class SpringAopAspectAfterExample { public void afterGreeting() { System.out.println("this is afterAspect !"); } schema based configurations, Spring AOP 2.5after advice}
The following after returning advice example explains how to use the after returning advice with XML Schema configuration:
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p" xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd"> <bean id="hello" class="org.springaop.target.Hello" p:label="writer"/> <aop:config> <aop:aspect ref="afterReturning"> <aop:after-returning method="afterGreeting" pointcut="execution(* greeting(..))" /> </aop:aspect> </aop:config> <bean id="afterReturning" class="org.springaop.aspects.schema.SpringAopAspectAfterReturningExample"/> </beans> package org.springaop.aspects.schema; public class SpringAopAspectAfterReturningExample { public void afterGreeting() { System.out.println("this is a aop !"); } }
The following after throwing advice example explains how to use the after throwing advice with XML Schema configuration:
<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-2.5.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd"> <bean id="exceptionTarget" class="org.springaop.chapter.one.schema.throwsadvice.ExceptionTarget" /> <aop:config> <aop:aspect ref="afterThrowing"> <aop:after-throwing method="afterErrorMethod" pointcut="execution(* errorMethod(..)) throws Exception" /> </aop:aspect> </aop:config> <bean id="afterThrowing" class="org.springaop.aspects.schema.SpringAopAspectAfterThrowingExample" /> </beans> <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-2.5.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd"> <bean id="exceptionTarget" class="org.springaop.chapter.one.schema.throwsadvice.ExceptionTarget" /> <aop:config> <aop:aspect ref="afterThrowing"> <aop:after-throwing method="afterErrorMethod" pointcut="execution(* errorMethod(..)) throws Exception" /> </aop:aspect> </aop:config> <bean id="afterThrowing" class="org.springaop.aspects.schema.SpringAopAspectAfterThrowingExample" /> </beans> Target Class: package org.springaop.target; public class ExceptionTarget { public void errorMethod() throws Exception { throw new Exception("Fake exception"); } public void otherErrorMethod() throws NullPointerException { throw new NullPointerException("Other Fake exception"); } } Aspect: package org.springaop.aspects.schema; public class SpringAopAspectAfterThrowingExample { public void afterErrorMethod() { System.out.println("+++"); System.out.println("Exception !"); System.out.println("+++\n"); } }
The following around advice example explains how to use the around advice with XML Schema configuration:
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p" xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd"> <bean id="hello" class="org.springaop.target.Hello" p:label="writer" /> <aop:config> <aop:aspect ref="around"> <aop:around method="aroundGreeting" pointcut="execution(* greeting(..))" /> </aop:aspect> </aop:config> <bean id="around" class="org.springaop.aspects.schema.SpringAopExampleAroundExample" /> </beans> package org.springaop.aspects.schema; public class SpringAopExampleAroundExample { public Object aroundGreeting(ProceedingJoinPoint pjp) throws Throwable { System.out.print("Hello "); try { return pjp.proceed(); } finally { System.out.println("this is around aop !"); } } after returning advice, Spring AOP 2.5around advice}
This chapter has explained the gaps in object-oriented programming and the support offered by aspect-oriented programming to fill these gaps, especially in the implementation phase.
The AOP concepts and terms have been introduced, showing conceptually how and where they act, which of them Spring supports, and how it does so.
These concepts have then been used in short and simple introductory practical examples in order to show Spring AOP functionalities both in a programmatic and declarative manner in version 1.x, and in version 2.5, with annotations and in a declarative manner with schema-based confi guration.
In the next chapters we will look into these topics in detail.