📜 ⬆️ ⬇️

Introduction to AOP

Programming paradigms


In the modern world of IT development, there are quite a lot of different approaches to writing programs. For example, someone likes to present a program as a sequence of actions, and someone thinks that a program should be a multitude of objects that communicate with each other. The combination of these ideas and concepts form a kind of writing program, which is commonly called the programming paradigm .

Each paradigm has its own characteristics, however, the main factor distinguishing them is the concept of the basic unit of the program. Here are the most popular ones:

It is worth noting that in the general case, the programming language definitely does not define the paradigm: you can write both imperative and object-oriented programs in the same PHP.

In this article I want to talk about a relatively young, but extremely, in my opinion, useful programming paradigm - aspect-oriented programming .
')


Basics of AOP


Consider some spherical service in vacuum (for example, a web service) that implements the following method:
public BookDTO getBook(Integer bookId) {
BookDTO book = bookDAO.readBook(bookId);
return book;
}

The method is quite simple and obvious: reading information about a book by its identifier. But let's think about what's missing here? First of all, we should think about logging - without it, as you understand, the web-service is nowhere:
public BookDTO getBook(Integer bookId) {
LOG.debug( "Call method getBook with id " + bookId);

BookDTO book = bookDAO.readBook(bookId);

LOG.debug( "Book info is: " + book.toString());
return book;
}

Next, you need to implement exception handling (so that the services layer returns the corresponding exceptions, hiding the exceptions of the underlying layers):
public BookDTO getBook(Integer bookId) throws ServiceException {
LOG.debug( "Call method getBook with id " + bookId);
BookDTO book = null ;

try {
book = bookDAO.readBook(bookId);
} catch(SQLException e) {
throw new ServiceException(e);
}


LOG.debug( "Book info is: " + book.toString());
return book;
}

Also, do not forget about checking access rights:
public BookDTO getBook(Integer bookId) throws ServiceException, AuthException {
if (!SecurityContext.getUser().hasRight("GetBook"))
throw new AuthException("Permission Denied");


LOG.debug( "Call method getBook with id " + bookId);
BookDTO book = null ;

try {
book = bookDAO.readBook(bookId);
} catch (SQLException e) {
throw new ServiceException(e);
}

LOG.debug( "Book info is: " + book.toString());
return book;
}

In addition, it makes sense to cache the result of the work:
public BookDTO getBook(Integer bookId) throws ServiceException, AuthException {
if (!SecurityContext.getUser().hasRight( "GetBook" ))
throw new AuthException( "Permission Denied" );

LOG.debug( "Call method getBook with id " + bookId);
BookDTO book = null ;
String cacheKey = "getBook:" + bookId;

try {
if (cache.contains(cacheKey)) {
book = (BookDTO) cache.get(cacheKey);
} else {

book = bookDAO.readBook(bookId);
cache.put(cacheKey, book);
}

} catch (SQLException e) {
throw new ServiceException(e);
}

LOG.debug( "Book info is: " + book.toString());
return book;
}

You can continue to improve this method, but enough for a start. In the course of our refinements, we obtained a method 10 times (from 2 to 20 LOC) exceeding the original size. The most interesting thing is that the volume of business logic in it has not changed - it's all the same 1 line. The rest of the code implements some common service functionality of the application: logging, error handling, access control, caching, and so on.

In principle, the intertwining of business logic with official functionality is not as bad as your application is small. However, the more complex the program becomes, the more carefully it should be approached to its architecture in general and the allocation of general functionality in particular. That is why, watching the evolution of programming languages, we first see the emergence of functions, then modules, then objects. However, practice shows that to distinguish some common functionality, the paradigms mentioned above are not enough. This functionality is called “end-to-end” or “scattered”, since its implementation is really scattered across different parts of the application. Examples of end-to-end functionality, as we have seen above, can be:

The main task of aspect-oriented programming (AOP) is the modularization of end-to-end functionality, highlighting it into aspects . To do this, languages ​​that support the concept of AOP, implement the following tools to highlight end-to-end functionality:


Usage Example (AspectJ)


AspectJ is an aspect-oriented extension / framework for the Java language. At the moment this is probably the most popular and developing AOP engine.

Consider the implementation of the aspect of logging with it:
@Aspect
public class WebServiceLogger {
private final static Logger LOG =
Logger.getLogger(WebServiceLogger. class );

@Pointcut( "execution(* example.WebService.*(..))" )
public void webServiceMethod() { }

@Pointcut( "@annotation(example.Loggable)" )
public void loggableMethod() { }

@Around( "webServiceMethod() && loggableMethod()" )
public Object logWebServiceCall(ProceedingJoinPoint thisJoinPoint) {
String methodName = thisJoinPoint.getSignature().getName();
Object[] methodArgs = thisJoinPoint.getArgs();

LOG.debug( "Call method " + methodName + " with args " + methodArgs);

Object result = thisJoinPoint.proceed();

LOG.debug( "Method " + methodName + " returns " + result);

return result;
}
}

First of all, the aspect of service method logging is created - the WebServiceLogger class, marked with the Aspect annotation. Next, two slices of the connection points are defined: webServiceMethod (call to a method belonging to the class WebService) and loggableMethod (call to a method marked with the @Loggable annotation). At the end, a tip is announced (the logWebServiceCall method), which is executed instead of ( Around annotation) junction points that satisfy the slice (“webServiceMethod () && loggableMethod ()”).

The board code retrieves information about the current method (connection point), logs the start of the method execution, directly calls the requested method, logs and returns the result of the work.

AspectJ has a fairly large amount of supported slices of connection points. The following are the main ones:

As for the tips, their number is much smaller, but they fully cover all the necessary set of situations:

More information about AspectJ constructions can be found in the corresponding section [ 1 , 2 ] of official documentation .

In order to use aspects of AspectJ, they will have to be compiled and “ stitched ” into the main classes using a special AJC compiler.

The product is free. Distributed under Eclipse License.



Usage Example (PostSharp)


PostSharp is an aspect-oriented framework for the .NET platform. There are other AOP implementations for .NET, however, judging by comparisons from the PostSharp site, it is he who takes the lead.

Consider how to use it to describe the aspect of exception handling. The first step is to create a class that extends the corresponding aspect:

public class ExceptionDialogAttribute : OnExceptionAspect
{
public override void OnException(MethodExecutionEventArgs eventArgs)
{
string message = eventArgs.Exception.Message;
Window window = Window.GetWindow((DependencyObject)eventArgs.Instance);
MessageBox.Show(window, message, "Exception" );
eventArgs.FlowBehavior = FlowBehavior.Continue;
}
}


Strictly speaking, aspects in PostSharp terminology are, as we can see, aspect and advice in AOP terminology.

In order to specify the cut of intersection points for this aspect, the following line should be added to the assembly settings file (AssemblyInfo.cs):

[assembly: ExceptionDialog ( AttributeTargetTypes= "Example.WorkflowService.*" ,
AttributeTargetMemberAttributes = AttributeTargetElements.Public )]


Or explicitly mark the methods you are interested in with the ExceptionDialog attribute:

[ExceptionDialog]
public BookDTO GetBook(Integer bookId)


That's all: now all exceptions thrown in the corresponding methods will be handled by the created aspect.

In view of the fact that PostSharp partially glues the concepts of advice and aspect, the latter it turns out quite a lot. Details can be found in the documentation . The following are the main ones:

PostSharp requires a compiler and a library to be connected to the project. Aspect insertion is based on post-processing of bytecode during application assembly.

Product paid. There is a Community Edition.



From theory to practice


And so, we have just seen how beautifully and effectively it is possible to solve the problem of “putting out” the end-to-end functionality in your application. However, this is all theory. In practice, everything, of course, is a little different :)

First of all, in both cases, for compiling and “weaving” aspects, you will have to use a special compiler and drag along with the project additional libraries. It seems that this is not a problem: the compiler is easy to download and integrates into the environment (for example, using maven, the task will be reduced only to adding the aspectj-maven-plugin plug-in), and many dependencies are common, at least for Java applications (solved by the same maven'a). However, the need to include in the project something that requires a separate compilation, but still does not have wide distribution, often scares off developers, despite all the potential advantages.

In this case, the Spring Framework [ 1 , 2 ] can be a solution. This framework has many advantages, however, within the framework of this article, we are interested in its AOP component. The Spring Framework implements limited AOP functionality on pure Java (C #) without using third-party libraries by creating proxy objects (JDK Dynamic Proxy, CGLIB). In other words, in Spring AOP, you can use only “execute method” junction points. However, as practice shows, this restriction does not play a significant role, since for solving most problems, it is necessary to connect points of this type.

In addition, the Spring Framework supports application configuration using @AspectJ annotations, as well as integrating aspects compiled directly with AspectJ.

We use Spring AOP in our company. Considering the other merits of the Spring Framework, in my opinion, it is the most accessible and convenient platform for working with AOP, making a significant contribution to its popularization and development.



Summary


Summing up, I would like to highlight three main thoughts regarding AOP:


Related Links


Source: https://habr.com/ru/post/114649/


All Articles