What is Aspect Oriented Programming
Aspect Oriented Programming Concepts
Aspect Oriented Programming(AOP) is not just a feature that Spring provides, it is actually a model of programming itself.
In functional programming, we break down the problem into smaller functions. Each function accomplishes a particular unit of work. A function can call other function and that is how they communicate. In simple words, a simple program consists of many functions which are called based on the logic of the solution. When the last function is called and it completes execution, that is when the program ends as well. So, this is the way we think of functions when we write code in functional programming.
The problem here is that, in real-time applications, the design involves complexity and we would have large number of functions and lots of inter-dependency between the functions. The above graph could become really messy.
In some situations, we would get better designs if we used object-oriented programming instead of functional programming.
Unlike functional programming, we don’t think of functions in object-oriented programming when solving a problem. We think of individual entities as objects. We write objects that reflect the different entities in the problem space. Each object would contain the member variables as well as the methods. Thus, we encapsulate the entities and can write more complex solution because we would have a cleaner design and separation of concerns. This may look fine, but there is a problem here as well. Not all the tasks that we want our program to do can be classified as objects!!
We have a common method logMessage() across all the objects here. We want this method to be run and included in 3 different objects. What happens in a complex solution? No matter how many objects need the logMessage(), we would include it in each of those objects. This is not really a good design because we repeat the code by including same method in all the objects.
How to refine this design? One approach is to take that common method out and create a separate object. We cannot leave that method floating on its own. We have to include it in an object.
Now the logMessage() is included inside Logger object. ObjectX, ObjectY and ObjectZ do not have that logMessage() inside them anymore. Instead, what happens is that, whenever a logMessage() needs to be called, it would reference Logger object and then do a logMessage() on the referenced object. It would either have a dependency and create a new Logger object instance and call the logMessage() OR it would be a static method which would be called directly. Logger class could also be a parent class that would inherit logMessage() into each of our objects that need this method.
This also may look good. But still there are problems:
First problem is that, suppose you are doing a design and want to draw a diagram that depicts the relationship between different objects and you want to know the objects that are really important, based on how they are connected to other objects. That is, the objects that are connected to other objects, the maximum. Now if we write a diagram for our above design, it is most likely that, the most important objects would not be our business objects but probably the Logger object. Because every object that needs a Logger object, has a dependency on this Logger object. It is not really a good idea to have this kind of dependency to something that is not really a part of your business problem. We have too many dependencies to this Logger object which is not adding any business value. It is doing something else like a support role. That would be a problem.
1. Too many relationships to the cross-cutting objects – Cross-cutting objects here mean that the objects that concern other different objects in our problem domain. Ex : Logger object here.
2. Code is still required in all methods that need – we still need the code in other objects to make a call to the logMessage(). We may have removed the method in dependent object but we still have the code that makes a call to logger functionality.
3. All cannot be changed at once – Suppose we want a change in bulk. We want a common different method. In that case, we have to make changes in all the business objects to call the new Logger method instead. Of course this can be solved using some more intelligent design, have an interface and plug-in the different logger method implementations. But, if that cannot be solved using an interface, we would need to go to each of those classes and make code changes.
Why are these problems Significant?
Because the whole concept of having a cross-cutting object is very common in the software design. This problem may occur in all the places, wherever there are cross-cutting concerns.
Cross-cutting concerns: You have some functionalities that need to be used by objects and they may not be part of your problem domain. It could be infrastructure related, security related etc. Common examples of cross cutting concerns in a real world application are :
We will have to implement these features based on the business problem and the number of users. Since this is a common problem, there are ways to deal with it. AOP is one of the elegant ways to solve this problem.
How things change with AOP?
We have removed the common functionality in objects ObjectX, ObjectY and ObjectZ for logging. Now we create a new Aspect called Logging Aspect. For now, let us think this Logging Aspect to be a class with special privileges(We will see the details later). Similarly we can have many other aspects as needed. May be a Transaction Aspect if we are going to implement Transaction-related cross-cutting concerns as well.
How is this different from having a separate object? We could have an object for Logging, an object for Transaction etc. The difference here is – After creating these aspects, we do not reference these aspects in our code (ie., in the classes ObjectX, ObjectY and ObjectZ). We do not instantiate a logging/Transaction object and make a method call. Instead, we would define a Configuration(Aspect Configuration) that tells, which methods and which objects these aspects should apply to. This is what is different from the traditional object-oriented programming, where we take these common functionalities, create a separate class, have objects and then reference those objects inside the objects that need those functionalities. Instead, we have aspects, we write the configuration that tells which aspects apply to which methods of which classes. This is something that Spring helps us with.
What happens here is, we have particular methods in each of the classes X, Y and Z. We have a Logging Aspect that has logMessage() method. We want this method to be called just before each of these methods that we are concerned about are called. Example : We want logMessage() to be called before Methods() in each of these classes is called. Say, we have 5 methods in ObjectX, 3 methods in ObjectY and just one method in ObjectZ. We want to execute logMessage() before any of these methods are executed. What we do in this case is, we write the Aspect Configuration and say, “For all the methods inside ObjectX, ObjectY and ObjectZ, make sure logMessage() also runs whenever those methods are called”. This is something we can tell Spring to do and Spring will make sure that the logging method is called just before those methods that are configured in Aspect Configuration are executed.
So, ‘before’ is just one of the ways we can apply aspects. This model is similar to Interceptors in Struts, servlet-filters and triggers in databases. Now we can think of AOP as : we have separate pieces of features to be run before some other functionalities are executed and they are not configured in the code, they are actually separate configuration files. Since, they are all residing inside the Spring container, Spring makes it easy for that to happen. Spring reads these configurations and whenever it senses that some particular methods are called and executed, just before execution happens, the configured aspects are executed first by the Spring so that the given configuration is achieved.
Advantages of AOP:
1. Can have different configurations for different methods : If we want ObjectX to have logging and ObjectY to not have logging, we can achieve it simply by doing the right Aspect Configurations.
2. Down the line, if we want to change any configuration, we don’t have to go the object and change the code. Instead, change the configuration in Aspect Configurations. We don’t have to make any changes in the method call. This makes it easy for us to add/remove and make any change as such.
3. We can make bulk changes as well, with just changing the Aspect Configurations.
Wrapping Aspects around methods: Suppose we have a method to execute after a particular method executes, we can do the ‘after’ configuration in the Aspect Configurations to run the method after the particular method runs.
To be kept in mind is that, we can control the flow using different aspect configurations.
Steps in AOP:
1. Write Aspects : Identify the cross-cutting concerns and write them.
2. Configure where Aspects should be applied : Select the methods ‘before’/’after’ which the Aspects have to run.
In our upcoming posts we will see programming examples to implement AOP.