1. Aspect-Oriented Programming
The inherent limitations of the OO paradigm were identified quite a
few years ago, not many years after the introduction of OOP. However,
today AOP still is not widely implemented even though everybody agrees
on the benefits it produces. The main reason for such a limited adoption
is essentially the lack of proper tools. We are pretty sure the day
that AOP is (even only partially) supported by the .NET platform will
represent a watershed in the history of AOP.
The concept of AOP was developed at Xerox PARC laboratories in the
1990s. The team also developed the first (and still most popular) AOP
language: AspectJ. Let’s discover more about AOP by exploring its key
concepts.
Note
We owe to the Xerox PARC laboratories many software-related
facilities we use every day. In addition to AOP (which we don’t exactly
use every day), Xerox PARC is "responsible" for laser printers,
Ethernet, and mouse-driven graphical user interfaces. They always
churned out great ideas, but failed sometimes to push their widespread
adoption—look at AOP. The lesson that everybody should learn from this
is that technical excellence is not necessarily the key to success, not
even in software. Some good commercial and marketing skills are always
(strictly) required.
AOP is about separating the implementation of cross-cutting concerns
from the implementation of core concerns. For example, AOP is about
separating a logger class from a task class so that multiple task
classes can use the same logger and in different ways.
We
have seen that dependency injection techniques allow you to inject—and
quite easily, indeed—external dependencies in a class. A cross-cutting
concern (for example, logging) can certainly be seen as an external
dependency. So where’s the problem?
Dependency injection requires up-front design or refactoring, which
is not always entirely possible in a large project or during the update
of a legacy system.
In AOP, you wrap up a cross-cutting concern in a new component called an aspect. An aspect is a reusable component that encapsulates the behavior that multiple classes in your project require.
In a classic OOP scenario, your project is made of a number of source
files, each implementing one or more classes, including those
representing a cross-cutting concern such as logging. As shown in Figure 1, these classes are then processed by a compiler to produce executable code.
In an AOP scenario, on the other hand, aspects are not directly
processed by the compiler. Aspects are in some way merged into the
regular source code up to the point of producing code that can be
processed by the compiler. If you are inclined to employ AspectJ, you
use the Java programming language to write your classes and the AspectJ
language to write aspects. AspectJ supports a custom syntax through
which you indicate the expected behavior for the aspect. For example, a
logging aspect might specify that it will log before and after a certain
method is invoked and will validate input data, throwing an exception
in case of invalid data.
In other words, an aspect describes a piece of standard and reusable
code that you might want to inject in existing classes without touching
the source code of these classes. (See Figure 2.)
In the AspectJ jargon, the weaver
is a sort of preprocessor that takes aspects and weaves their content
with classes. It produces output that the compiler can render to an
executable.
In other AOP-like frameworks, you might not find an explicit weaver
tool. However, in any case, the content of an aspect is always processed
by the framework and results in some form of code injection. This is
radically different from dependency injection. We mean that the code
declared in an aspect will be invoked at some specific points in the
body of classes that require that aspect.
Before we discuss an example in .NET, we need to introduce a few
specific terms and clarify their intended meaning. These concepts and
terms come from the original definition of AOP. We suggest that you do
not try to map them literally to a specific AOP framework. We suggest,
instead, that you try to understand the concepts—the pillars of AOP—and
then use this knowledge to better and more quickly understand the
details of a particular framework.
As mentioned, an aspect is the implementation of a cross-cutting concern. In the definition of an aspect, you need to specify advice to apply at specific join points.
A join point represents a
point in the class that requires the aspect. It can be the invocation
of a method, the body of a method or the getter/setter of a property, or
an exception handler. In general, a join point indicates the point
where you want to inject the aspect’s code.
A pointcut represents a
collection of join points. In AspectJ, pointcuts are defined by criteria
using method names and wildcards. A sample pointcut might indicate that
you group all calls to methods whose name begins with Get.
An advice
refers to the code to inject in the target class. The code can be
injected before, after, and around the join point. An advice is
associated with a pointcut.
Here’s a quick example of an aspect defined using AspectJ:
public aspect MyAspect
{
// Define a pointcut matched by all methods in the application whose name begins with
// Get and accepting no arguments. (There are many other ways to define criteria.)
public pointcut allGetMethods ():
call (* Get*() );
// Define an advice to run before any join points that matches the specified pointcut.
before(): allGetMethods()
{
// Do your cross-cutting concern stuff here
// for example, log about the method being executed
.
.
.
}
}
The weaver processes the aspect along with the source code (regular
class-based source code) and generates raw material for the compiler.
The code actually compiled ensures that an advice is invoked
automatically by the AOP runtime whenever the execution flow reaches a
join point in the matching pointcut.
When we turn to AOP, we essentially want our existing code to do
extra things. And we want to achieve that without modifying the source
code. We need to specify such extra things (advice) and where we want to
execute them (join points). Let’s briefly go through these points from
the perspective of the .NET Framework.
How can you express the semantic of aspects?
The ideal option is to create a custom language a là
AspectJ. In this way, you can create an ad hoc aspect tailor-made to
express advice at its configured pointcuts. If you have a custom
language, though, you also need a tool to parse it—like a weaver.
A very cost-effective alternative is using an external file (for
example, an XML file) where you write all the things you want to do and
how to do it. An XML file is not ideal for defining source code; in such
a file, you likely store mapping between types so that when a given
type is assigned an aspect, another type is loaded that contains advice
and instructions about how to join it to the execution flow. This is the
approach taken by Microsoft’s Policy Injection Application Block (PIAB)
that we’ll look at in a moment.
How can you inject an aspect’s advice into executable code?
There
are two ways to weave a .NET executable. You can do that at compile
time or at run time. Compile-time weaving is preferable, but in our
opinion, it requires a strong commitment from a vendor. It can be
accomplished by writing a weaver tool that reads the content of the
aspect, parses the source code of the language (C#, Visual Basic .NET,
and all of the other languages based on the .NET common type system),
and produces modified source code, but source code that can still be
compiled. If you want to be language independent, write a weaver tool
that works on MSIL and apply that past the compilation step.
Alternatively, you can write a brand new compiler that understands an
extended syntax with ad hoc AOP keywords.
If you want to weave a .NET executable at run time, you have to
review all known techniques to inject code dynamically. One is emitting
JIT classes through Reflection.Emit;
another one is based on the CLR’s Profiling API. The simplest of all is
perhaps managing to have a proxy sitting in between the class’s aspects
and its caller. In this case, the caller transparently invokes a proxy
for the class’s aspects. The proxy, in turn, interweaves advice with
regular code. This is the same mechanism used in .NET Remoting and
Windows Communication Foundation (WCF) services.
Using a transparent proxy has the drawback of requiring that to apply AOP to the class, the class must derive from ContextBoundObject or MarshalByRefObject. This solution is employed by PIAB.