Nice book written by Ramnivas.I know 2nd edition of this book exists but i thought let me know understand basics before understanding Spring with AOP.
I skipped chapter6 - Chapter 12 for few reasons and i thought to revisit at later point of time.
Please note that i am using eclipse 3.4 with aspectj plugin AJDT installed.
I wanted to share few important points i found from this book.
1) AspectJ was an implementation of aspect-oriented programming (AOP), a new methodology that specifically targeted the management of crosscutting concerns.
2) AspectJ is now an open source project under eclipse.org.
3) AOP is a new methodology that provides separation of crosscutting concerns by introducing a new unit of modularization—an aspect—that crosscuts other modules. With AOP you implement crosscutting concerns in aspects instead of fusing them in the core modules. An aspect weaver, which is a compiler-like entity,composes the final system by combining the core and crosscutting modules through a process called weaving. The result is that AOP modularizes the crosscutting concerns in a clear-cut fashion, yielding a system architecture that is easier to design, implement, and maintain.
4) The most fundamental way that AOP differs from OOP in managing crosscutting concerns is that in AOP, the implementation of each concern is oblivious to the crosscutting behavior being introduced into it. For example, a business logic module is unaware that its operations are being logged or authorized. As a result, the implementation of each individual concern evolves independently.
5) Much of the early work that led to AOP today was done in universities all over the world. Cristina Lopes and Gregor Kiczales of the Palo Alto Research Center (PARC), a subsidiary of Xerox Corporation, were among the early contributors to AOP. Gregor coined the term “AOP” in 1996. He led the team at Xerox that created
AspectJ, one of the first practical implementations of AOP, in the late 1990s.Xerox recently transferred the AspectJ project to the open source community at eclipse.org, which will continue to improve and support the project.
6) The AOP methodology
In many ways, developing a system using AOP is similar to developing a system using other methodologies: identify the concerns, implement them, and form the final system by combining them. The AOP research community typically defines these three steps in the following way:
a) Aspectual decomposition—In this step, you decompose the requirements to identify crosscutting and core concerns. This step separates core-level concerns from crosscutting, system-level concerns. For example, in the SomeBusinessClass example, we would identify the following concerns: core business logic, logging, cache management, thread safety, contract enforcement, persistence, and authorization. Of these,only the core business logic is the core concern of SomeBusinessClass. All other concerns are system wide concerns that will be needed by many other modules and therefore are classified as crosscutting concerns.
b) Concern implementation—In this step, you implement each concern independently.Using the previous example, developers would implement the business logic unit, logging unit, authorization unit, and so forth. For the core concern of a module, you can utilize procedural or OOP techniques as usual. For example, let’s look at authorization. If you are using OOP techniques, you may write an interface for the authorization, a few concrete implementations for it, and perhaps a class to abstract the creation of the authorization implementation used in the system.Understand that the term “core” is a relative term. For the authorization module itself, the core concern would be mapping users to credentials and determining if those credentials are sufficient to access an authorized service. However, for the business logic module, the authorization concern would be a peripheral concern and so would not be implemented in the module at this time.
c) Aspectual recomposition—In this step, you specify the recomposition rules by creating modularization units, or aspects. The actual process of recomposition, also known as weaving or integrating, uses this information to compose the final system. For our example, you would specify, in the language provided by the AOP implementation, that each operation must first ensure that the client has been authorized before it proceeds with the business logic.
7) Myths and realities of AOP
Although AOP has grown in popularity in recent years, it is still often perceived as difficult to implement and hard to learn. Let’s examine some common assumptions about AOP, and whether or not they are true:
a) The program flow in an AOP-based system is hard to follow: True. This is actually a step in the right direction! Given the limited number of concerns our brain can deal with simultaneously, we can either worry about the order in which instructions are executed or how the functionality is implemented at a higher level. AOP is not the first time we are giving up the control of a detailed and understandable program flow. In OOP too, polymorphic methods make analyzing program flow a complex task. Even in procedural languages such as C, if you use function pointers, the program flow is not static and requires some effort to be understood.
b) AOP doesn’t solve any new problems: True. AOP is not about providing solutions to yet unsolved problems; it is about solving the problems in a better way, with much less effort and with improved maintainability. You can solve problems with any methodology and language, and the only difference is the complexity of the solution. In fact, there is nothing that cannot be implemented with machine code.
c) AOP promotes sloppy design: False. An aspect-oriented solution is not the cure for sloppy programs. AOP merely provides new ways to solve problems in areas where procedural and OOP naturally fall short. In fact, AOP requires a good design of core concerns and makes it easy to achieve an overall clean design goal.
d) AOP is nice, but a nice abstract OOP interface is all you need: False. This issue stems from the problem with the way crosscutting concerns are implemented using OOP. The technique in OOP is to use an abstract API and swap implementations underneath without the need to modify the clients of the API. While a clean OO interface simplifies the subsystems, it still requires you to call that API from all the places that use it. A wellabstracted API absolutely helps—in OOP and AOP—but the interface is no substitute for AOP.
e) The AOP compiler simply patches the core implementation: False. Patching is a process of fixing the implementation without fixing the underlying problem and results in making the overall system hard to understand. The patching process tends to be unrestricted in terms of the kind of modifications that are permitted. AOP, on the other hand, provides a methodological approach permitting only modifications that improve comprehensibility and traceability.
f) AOP breaks the encapsulation: True, but only in a systematic and controlled way. In object-oriented programming, a class encapsulates all the behavior. The weaving added by AOP, however, removes this level of control from the class. In this sense, AOP offers considerable power, and it can work wonders if you utilize the power correctly.
g) AOP will replace OOP: False. Core concerns will continue to be implemented in OOP (or even procedural programming). AOP simply adds a few additional concepts to OOP, just as OOP adds to procedural programming.However, AOP will change the way we use OOP and procedural languages for implementing crosscutting concerns. A few currently used design patterns and idioms that specifically address the problems of crosscutting concerns will lose their importance. Further, crosscutting concerns will receive a lot less attention during the initial design phase.
8) An AspectJ compiler produces class files that conform to the Java byte-code specification, allowing any compliant Java virtual machine (VM) to execute those class files.
9) In AspectJ, the implementation of the weaving rules by the compiler is called crosscutting; the weaving rules cut across multiple modules in a systematic way in order to modularize the crosscutting concerns. AspectJ defines two types of crosscutting: static crosscutting and dynamic crosscutting.
10) Dynamic crosscutting
Dynamic crosscutting is the weaving of new behavior into the execution of a program. Most of the crosscutting that happens in AspectJ is dynamic. Dynamic crosscutting augments or even replaces the core program execution flow in a way that cuts across modules, thus modifying the system behavior. For example, if you want to specify that a certain action be executed before the execution of certain methods or exception handlers in a set of classes, you can just specify the weaving points and the action to take upon reaching those points in a separate module.
11) Join pointA join point is an identifiable point in the execution of a program. It could be a call to a method or an assignment to a member of an object. In AspectJ, everything revolves around join points, since they are the places where the crosscutting actions are woven in. Let’s look at some join points in this code snippet:
public class Account {
void credit(float amount) {
_balance += amount;
}
}
The join points in the Account class include the execution of the credit() method and the access to the _balance instance member.
12) Pointcut
A pointcut is a program construct that selects join points and collects context at those points. For example, a pointcut can select a join point that is a call to a method, and it could also capture the method’s context, such as the target object on which the method was called and the method’s arguments.We can write a pointcut that will capture the execution of the credit() method in the Account class shown earlier:
execution(void Account.credit(float))
To understand the difference between a join point and pointcut, think of pointcuts as specifying the weaving rules and join points as situations satisfying those rules.
13) Advice
Advice is the code to be executed at a join point that has been selected by a pointcut.Advice can execute before, after, or around the join point. Around advice can modify the execution of the code that is at the join point, it can replace it, or it can even bypass it. Using an advice, we can log a message before executing the code at certain join points that are spread across several modules. The body of
advice is much like a method body—it encapsulates the logic to be executed upon reaching a join point.Using the earlier pointcut, we can write advice that will print a message before the execution of the credit() method in the Account class:
before() : execution(void Account.credit(float)) {
System.out.println("About to perform credit operation");
}
Pointcuts and advice together form the dynamic crosscutting rules. While the pointcuts identify the required join points, the advice completes the picture by providing the actions that will occur at the join points.
14) Compile-time declaration
The compile-time declaration is a static crosscutting instruction that allows you to add compile-time warnings and errors upon detecting certain usage patterns.For example, you can declare that it is an error to call any Abstract Window Toolkit (AWT) code from an EJB.
The following declaration causes the compiler to issue a warning if any part of the system calls the save() method in the Persistence class. Note the use of the call() pointcut to capture a method call:
declare warning : call(void Persistence.save(Object))
: "Consider using Persistence.saveOptimized()";
15) Introduction
The introduction is a static crosscutting instruction that introduces changes to the classes, interfaces, and aspects of the system. It makes static changes to the modules
that do not directly affect their behavior. For example, you can add a method or field to a class.The following introduction declares the Account class to implement the BankingEntity interface:
declare parents: Account implements BankingEntity;
16) Below example gives one simple implementation of AspectJ
package chapter2_Example1;
public class MessageCommunicator {
public static void deliver(String message) {
System.out.println(message);
}
public static void deliver(String person, String message) {
System.out.print(person + ", " + message);
}
}
package chapter2_Example1;
public aspect MannersAspect {
pointcut deliverMessage()
: call(* MessageCommunicator.deliver(..));
before() : deliverMessage() {
System.out.print("Hello! ");
}
}
package chapter2_Example1;
public class Test {
public static void main(String[] args) {
MessageCommunicator.deliver("Wanna learn AspectJ?");
MessageCommunicator.deliver("Prashant", "having fun?");
}
}
Let’s understand the magic this new aspect and ajc perform.
The Manners-Aspect.aj file declares the MannersAspect aspect:
a) The declaration of an aspect is similar to a class declaration.
b) The aspect defines a pointcut deliverMessage() that captures calls to all the methods named deliver() in the MessageCommunicator class. The * indicates that we don’t care about the return type, and the .. inside parentheses after deliver specifies that we don’t care about the number of arguments or their types either. In our example, the pointcut would capture calls to both of the overloaded versions of deliver() in the MessageCommunicator class.
c) Then we define a piece of advice to execute before reaching the deliverMessage() pointcut. The before() part indicates that the advice should run prior to the execution of the advised join point—in our case, prior to calling any MessageCommunicator.deliver() method. In the advice, we simply print a message “Hello!” without a linefeed.
With the aspect now present in the system, each time that MessageCommunicator.deliver() is executed, the advice code that prints “Hello!” will execute before the method.
17) AspectJ: under the hood
a) Aspects are mapped to classes, with each data member and method becoming the members of the class representing the aspect.
b) Advice is usually mapped to one or more methods. The calls to these methods are then inserted into the join points matching the pointcut specified within the advice. Advice may also be mapped to code that is directly inserted inside an advised join point.
c) Pointcuts are intermediate elements that instruct how advice is woven and usually aren’t mapped to any program element, but they may have auxiliary methods to help perform matching at runtime.
d) Introductions are mapped by making the required modification, such as adding the introduced fields to the target classes.
e) Compile-time warnings and errors have no effect on byte code. They simply cause the compiler to print warnings or abort the compilation when producing an error.
18) Not all of the join points in a system are available for your use. The join points that you can select in pointcuts are called exposed join points.
19) There are two types of join points that AspectJ exposes for each method: execution and call join points. The execution join point is on the method body itself,whereas the call join points are on other parts of the program, which are usually the methods that are calling this method.
20) The join points for field access capture the read and write access to an instance or class member of a class. Note that only access to the data member of a class or aspect is exposed in AspectJ. In other words, join points corresponding to access to local variables in a method are not exposed.
21) we will use only a within() pointcut along with a negation operator to capture all the join points occurring outside the aspect itself.. thisJoinPoint is a special object that contains information about the join point.
Example:
public aspect JoinPointTraceAspect {
private int _callDepth = -1;
pointcut tracePoints() : !within(JoinPointTraceAspect);
before() : tracePoints() {
_callDepth++;
print("Before", thisJoinPoint);
}
after() : tracePoints() {
print("After", thisJoinPoint);
_callDepth--;
}
private void print(String prefix, Object message) {
for(int i = 0, spaces = _callDepth * 2; i < spaces; i++) {
System.out.print(" ");
}
System.out.println(prefix + ": " + message);
}
}
22) Aspects can declare themselves to be abstract.
With abstract aspects, you can create reusable units of crosscutting by deferring
some of the implementation details to the concrete subaspects. An abstract aspect can mark any pointcut or method as abstract, which allows a base aspect to implement the crosscutting logic without needing the exact details that only a system-specific aspect can provide. Note that an abstract aspect by itself does not cause any weaving to occur; you must provide concrete subaspects to do so.
An aspect that contains any abstract pointcut or method must declare
itself as an abstract aspect. In this respect, aspects resemble classes. Any subaspect of an abstract aspect that does not define every abstract pointcut and
method in the base aspect, or that adds additional abstract pointcuts or
methods, must also declare itself abstract.
23) Aspects cannot be directly instantiated.
It is the system that instantiates the aspect objects appropriately. In other words, you never use “new” to create an object for an aspect. AspectJ doesn’t guarantee anything except that the object will be instantiated at or before you use it. Further, it is possible that in some cases, the system won’t instantiate an object for an aspect at all!
By default, an aspect is associated with the virtual machine—only one instance will be created for the whole system. However, there are ways to associate aspects with a set of objects and join points, and have multiple instances in the system.
24) Aspects cannot inherit from concrete aspects.
Although aspects can inherit from abstract aspects, they cannot inherit from concrete aspects. The reason for this limit is to reduce complexity. For example,with this rule in place, the AspectJ compiler considers only the concrete aspects for the purpose of weaving. If subaspects of a concrete aspect were
allowed, the language would have to specify how such subaspects interact with the weaving specified by their base aspect. In practice, this restriction usually does not pose any significant problem.
25) You can also use an anonymous pointcut as part of another pointcut. For example,the following pointcut uses an anonymous within() pointcut to limit the join points captured by calls to accountOperations() that are made from classes with banking as the root package:
pointcut internalAccountOperations()
: accountOperations() && within(banking..*);
26) Three wildcard notations are available in AspectJ:
a) * denotes any number of characters except the period.
b) .. denotes any number of characters including any number of periods.
c) + denotes any subclass or subinterface of a given type.
27) Just like in Java, where unary and binary operators are used to form complex conditional expressions composed of simpler conditional expressions, AspectJ provides a unary negation operator (!) and two binary operators (|| and &&) to form complex matching rules by combining simple pointcuts:
a) Unary operator—AspectJ supports only one unary operation—! (the negation)— that allows the matching of all join points except those specified by the pointcut. For example, we used !within(JoinPointTraceAspect) to exclude all the join points occurring inside the JoinPointTraceAspect’s body.
b) Binary operators—AspectJ offers || and && to combine pointcuts. Combining two pointcuts with the || operator causes the selection of join points that match either of the pointcuts, whereas combining them with the && operator causes the selection of join points matching both the pointcuts.
28) When we specify patterns that will match these signatures in pointcuts, we refer to them as signature patterns.
29) For example, to capture all public methods in the class, you use a call() pointcut along with one of the signatures to encode the pointcut as follows:
This example calls all public methods from Account class.
call(public * Account.*())
30) Similar to after returning advice, AspectJ offers “after throwing” advice,except such advice is executed only when the advised join point throws an exception.This is the form for after advice that returns after throwing an exception:
after() throwing : call(* Account.*(..)) {
... log the failure
}
31) If within the around advice you want to execute the operation that is at the join point, you must use a special keyword—proceed()—in the body of the advice. Unless you call proceed(), the captured join point will be bypassed.
32) The before and after advice cannot return anything, while the around advice does and therefore has a return type.
33) Aspect precedence
When a system includes multiple aspects, it’s possible that advice in more than one aspect applies to a join point. In such situations, it may be important to control the order in which the advice is applied. To understand the need for controlling the advice execution order.
Ordering of advice
As you have just seen, with multiple aspects present in a system, pieces of advice in the different aspects can often apply to a single join point. When this happens,AspectJ uses the following precedence rules to determine the order in which the advice is applied. Later, we will see how to control precedence:
a) The aspect with higher precedence executes its before advice on a join point before the one with lower precedence.
b) The aspect with higher precedence executes its after advice on a join point after the one with lower precedence.
c) The around advice in the higher-precedence aspect encloses the around advice in the lower-precedence aspect. This kind of arrangement allows the higher-precedence aspect to control whether the lower-precedence advice will run at all by controlling the call to proceed(). In fact, if the higher-precedence aspect does not call proceed() in its advice body, not only will the lower-precedence aspects not execute, but the advised join point also will not be executed.
34) Explicit aspect precedence
It is often necessary to change the precedence of advice as it is applied to a join point. AspectJ provides a construct—declare precedence—for controlling aspect precedence. The declare precedence construct must be specified inside an aspect.
The construct takes the following form:
declare precedence : TypePattern1, TypePattern2, ..;
The result of this kind of declaration is that aspects matching the type pattern on the left dominate the ones on the right, thus taking a higher precedence.
35) Since declare precedence takes a type list, you can specify a sequence of domination.
For example, the following declaration causes aspects whose names start with Auth to dominate both PoolingAspect and LoggingAspect, while also causing PoolingAspect to dominate LoggingAspect:
declare precedence : Auth*, PoolingAspect, LoggingAspect;
It is common for certain aspects to dominate all other aspects. You can use a * wildcard to indicate such an intention. The following declaration causes AuthenticationAspect to dominate all the remaining aspects in the system:
declare precedence : AuthenticationAspect, *;
It is also common for certain aspects to be dominated by all other aspects. You can use a wildcard to achieve this as well. The following declaration causes Caching-Aspect to have the lowest precedence:
declare precedence : *, CachingAspect;
36) Besides explicitly controlling aspect precedence using the declare precedence construct, AspectJ implicitly determines the precedence of two aspects related by a base-derived aspect relationship.
37) It is also possible to have multiple pieces of advice in one aspect that you want to apply to a pointcut. Since the advice resides in the same aspect, aspect precedence rules can no longer apply. In such cases, the advice that appears first lexically inside the aspect executes first. Note that the only way to control precedence between multiple advice in an aspect is to arrange them lexically.
38) In rare cases, when multiple aspects introduce data members with the same name or methods with the same signature, the members introduced by the aspect with the higher precedence will be retained and the matching members introduced by other aspects will be eliminated. For example, if you have introduced a method and its implementation in one aspect, and another implementation for the same method in another aspect, only the dominating aspect’s implementation will survive. The same is true for data members. If two aspects introduce a member with the same name, type, and initial value, only the member from the dominating aspect will survive.
39) Aspect association
By default, only one instance of an aspect exists in a virtual machine (VM)—much like a singleton class. All the entities inside the VM then share the state of such an aspect. For example, all objects share a resource pool inside a pooling aspect. Usually, this kind of sharing is fine and even desirable. However, there
are situations, especially when creating reusable aspects, where you want to associate the aspect’s state with an individual object or control flow.The aspect associations can be classified into three categories:
a) Per virtual machine (default)
b) Per object
c) Per control-flow association
40) Default association is in effect when you do not include an association specification in the aspect declaration.This type of association creates one instance of the aspect for the VM, thus making its state shared.
41) The softening feature helps to modularize the crosscutting concerns of exception handling. For example, you can soften a RemoteException thrown in a Remote Method Invocation (RMI)-based system to avoid handling the exception at each level. This may be a useful strategy in some situations.
42) For the most part, aspects have the same standard Java access-control rules as classes. For example, an aspect normally cannot access any private members of other classes. This is usually sufficient and, in fact, desirable on most occasions.
However, in a few situations, an aspect may need to access certain data members or operations that are not exposed to outsiders. You can gain such access by marking the aspect “privileged.”
Example:
package chapter4_Example9;
privileged public aspect PrivilegeTestAspect {
before(TestPrivileged callee) : call(void TestPrivileged.method1())
&& target(callee) {
System.out.println("<PrivilegeTestAspect:before objectId=\""
+ callee._id + "\"");
}
}
package chapter4_Example9;
//Listing 4.22 TestPrivileged.java
public class TestPrivileged {
private static int _lastId = 0;
private int _id;
public static void main(String[] args) {
TestPrivileged test = new TestPrivileged();
test.method1();
}
public TestPrivileged() {
_id = _lastId++;
}
public void method1() {
System.out.println("TestPrivileged.method1");
}
}
43) Often, you not only want to log the method calls but also the invoked object and the method parameters. Implementing this requirement is easily accomplished by using the thisJoinPoint reference. In each advice body, a special thisJoin- Point object is available that contains the information about the captured join point and its associated context.
Below is one example implementation
package chapter5_Example8;
import org.aspectj.lang.*;
public aspect TraceAspectV1 {
pointcut traceMethods()
: (execution(* *.*(..))
|| execution(*.new(..))) && !within(TraceAspectV1);
before() : traceMethods()&& !execution(String *.toString()){
Signature sig = thisJoinPointStaticPart.getSignature();
System.err.println("Entering ["
+ sig.getDeclaringType().getName() + "."
+ sig.getName() + "]"
+ createParameterMessage(thisJoinPoint));
}
private String createParameterMessage(JoinPoint joinPoint) {
StringBuffer paramBuffer = new StringBuffer("\n\t[This: ");
paramBuffer.append(joinPoint.getThis());
Object[] arguments = joinPoint.getArgs();
paramBuffer.append("]\n\t[Args: (");
for (int length = arguments.length, i = 0; i < length; ++i) {
Object argument = arguments[i];
paramBuffer.append(argument);
if (i != length-1) {
paramBuffer.append(',');
}
}
paramBuffer.append(")]");
return paramBuffer.toString();
}
}
We use the !execution(String *.toString())pointcut to avoid the recursion that will be caused by the execution of the toString() methods. Without this pointcut, the logger will prepare the parameter string in createParameter- Message() when it calls toString() for each object. However, when toString() executes, it first attempts to log the operation, and the logger will prepare a parameter string for it again when it calls toString() on the same object, and so on, causing an infinite recursion. By avoiding the join points for toString() execution, we avoid infinite recursion, leading to a stack overflow. Note that the !within(TraceAspectV1) pointcut is not sufficient here because it will only capture the calls to toString() methods; the execution of the methods will still be advised.
The createParameterMessage() helper method returns a formatted string containing the object and arguments.
Hope you enjoy reading the book.
About the Author
Ramnivas Laddad is a well-known expert in enterprise Java, especially in the area of AOP. He is the author of AspectJ in Action, the best-selling book on AOP and AspectJ that has been lauded by industry experts for its presentation of practical and innovative AOP applications to solve real-world problems. Ramnivas, a Spring framework committer, is also a very active presenter at leading industry events, and has been an active member of both the AspectJ and Spring communities from their beginnings.
No comments:
Post a Comment