Aop

  • November 2019
  • PDF

This document was uploaded by user and they confirmed that they have the permission to share it. If you are author or own the copyright of this book, please report to us by using this DMCA report form. Report DMCA


Overview

Download & View Aop as PDF for free.

More details

  • Words: 7,579
  • Pages: 27
Separate software concerns with aspect-oriented programming By Ramnivas Laddad, JavaWorld.com, 01/18/02 A concern is a particular goal, concept, or area of interest. In technology terms, a typical software system comprises several core and system-level concerns. For example, a credit card processing system's core concern would process payments, while its system-level concerns would handle logging, transaction integrity, authentication, security, performance, and so on. Many such concerns -- known as crosscutting concerns -- tend to affect multiple implementation modules. Using current programming methodologies, crosscutting concerns span over multiple modules, resulting in systems that are harder to design, understand, implement, and evolve. Read the whole "I Want My AOP" series: • • •

Part 1. Separate software concerns with aspect-oriented programming Part 2. Learn AspectJ to better understand aspect-oriented programming Part 3. Use AspectJ to modularize crosscutting concerns in real-world problems

Aspect-oriented programming (AOP) better separates concerns than previous methodologies, thereby providing modularization of crosscutting concerns. In this article, the first of three covering AOP, I first explain problems caused by crosscutting concerns in any even moderately complex software system. I then introduce AOP core concepts and show how AOP can solve problems with crosscutting concerns. The second article in the series will present a tutorial on AspectJ, a free AOP implementation for Java from Xerox PARC. The last article will present several AspectJ examples to illustrate AOP in creating software systems that are easier to understand, implement, and evolve.

Evolution of software programming methodology In the early days of computer science, developers wrote programs by means of direct machine-level coding. Unfortunately, programmers spent more time thinking about a particular machine's instruction set than the problem at hand. Slowly, we migrated to higher-level languages that allowed some abstraction of the underlying machine. Then came structured languages; we could now decompose our problems in terms of the procedures necessary to perform our tasks. However, as complexity grew, we needed better techniques. Object-oriented programming (OOP) let us view a system as a set of collaborating objects. Classes allow us to hide implementation details beneath interfaces. Polymorphism provided a common behavior and interface for related concepts, and allowed more specialized components to change a particular behavior without needing access to the implementation of base concepts.

Programming methodologies and languages define the way we communicate with machines. Each new methodology presents new ways to decompose problems: machine code, machine-independent code, procedures, classes, and so on. Each new methodology allowed a more natural mapping of system requirements to programming constructs. Evolution of these programming methodologies let us create systems with ever increasing complexity. The converse of this fact may be equally true: we allowed the existence of ever more complex systems because these techniques permitted us to deal with that complexity. Currently, OOP serves as the methodology of choice for most new software development projects. Indeed, OOP has shown its strength when it comes to modeling common behavior. However, as we will see shortly, and as you may have already experienced, OOP does not adequately address behaviors that span over many -- often unrelated -modules. In contrast, AOP methodology fills this void. AOP quite possibly represents the next big step in the evolution of programming methodologies.

View the system as a set of concerns We can view a complex software system as a combined implementation of multiple concerns. A typical system may consist of several kinds of concerns, including business logic, performance, data persistence, logging and debugging, authentication, security, multithread safety, error checking, and so on. You'll also encounter development-process concerns, such as comprehensibility, maintainability, traceability, and evolution ease . Figure 1 illustrates a system as a set of concerns implemented by various modules.

Figure 1. Implementation modules as a set of concerns

Figure 2 presents a set of requirements as a light beam passing through a prism. We pass a requirements light beam through a concern-identifier prism, which separates each concern. The same view also extends towards development-process concerns.

Figure 2. Concern decomposition: The prism analogy

Crosscutting concerns in a system A developer creates a system as a response to multiple requirements. We can broadly classify these requirements as core module-level requirements and system-level requirements. Many system-level requirements tend to be orthogonal (mutually independent) to each other and to the module-level requirements. System-level requirements also tend to crosscut many core modules. For example, a typical enterprise application comprises crosscutting concerns such as authentication, logging, resource pooling, administration, performance, and storage management. Each crosscuts several subsystems. For example, a storage-management concern affects every stateful business object. Let's consider a simple, but more concrete, example. Consider a skeleton implementation of a class encapsulating some business logic: public class SomeBusinessClass extends OtherBusinessClass { // Core data members // Other data members: Log stream, data-consistency flag // Override methods in the base class public void performSomeOperation(OperationInformation info) { // Ensure authentication // Ensure info satisfies contracts // Lock the object to ensure data-consistency in case other // threads access it // Ensure the cache is up to date // Log the start of operation // ==== Perform the core operation ==== // Log the completion of operation // Unlock the object } // More operations similar to above public void save(PersitanceStorage ps) {

} public void load(PersitanceStorage ps) { }

}

In the code above, we must consider at least three issues. First, other data members do not belong to this class's core concern. Second, implementation of performSomeOperation() seems to do more than perform the core operation; it seems to handle the peripheral logging, authentication, multithread safety, contract validation, and cache management concerns. In addition, many of these peripheral concerns would likewise apply to other classes. Third, it is not clear if save() and load() performing persistence management should form the core part of the class.

Crosscutting concern problems Although crosscutting concerns span over many modules, current implementation techniques tend to implement these requirements using one-dimensional methodologies, forcing implementation mapping for the requirements along a single dimension. That single dimension tends to be the core module-level implementation. The remaining requirements are tagged along this dominant dimension. In other words, the requirement space is an n-dimensional space, whereas the implementation space is one-dimensional. Such a mismatch results in an awkward requirements-to-implementation map.

Symptoms A few symptoms can indicate a problematic implementation of crosscutting concerns using current methodologies. We can broadly classify those symptoms into two categories: •



Code tangling: Modules in a software system may simultaneously interact with several requirements. For example, oftentimes developers simultaneously think about business logic, performance, synchronization, logging, and security. Such a multitude of requirements results in the simultaneous presence of elements from each concern's implementation, resulting in code tangling. Code scattering: Since crosscutting concerns, by definition, spread over many modules, related implementations also spread over all those modules. For example, in a system using a database, performance concerns may affect all the modules accessing the database.

Implications Combined, code tangling and code scattering affect software design and developments in many ways:











Poor traceability: Simultaneously implementing several concerns obscures the correspondence between a concern and its implementation, resulting in a poor mapping between the two. Lower productivity: Simultaneous implementation of multiple concerns shifts the developer's focus from the main concern to the peripheral concerns, leading to lower productivity. Less code reuse: Since, under these circumstances, a module implements multiple concerns, other systems requiring similar functionality may not be able to readily use the module, further lowering productivity. Poor code quality: Code tangling produces code with hidden problems. Moreover, by targeting too many concerns at once, one or more of those concerns will not receive enough attention. More difficult evolution: A limited view and constrained resources often produce a design that addresses only current concerns. Addressing future requirements often requires reworking the implementation. Since the implementation is not modularized, that means touching many modules. Modifying each subsystem for such changes can lead to inconsistencies. It also requires considerable testing effort to ensure that such implementation changes have not caused bugs.

The current response Since most systems include crosscutting concerns, it's no surprise that a few techniques have emerged to modularize their implementation. Such techniques include mix-in classes, design patterns, and domain-specific solutions. With mix-in classes, for example, you can defer a concern's final implementation. The primary class contains a mix-in class instance and allows the system's other parts to set that instance. For example, in a credit card processing example, the class implementing business logic composes a logger mix-in. Another part of the system could set this logger to get the appropriate logging type. For example, the logger could be set to log using a filesystem or messaging middleware. Although the nature of logging is now deferred, the composer nevertheless contains code to invoke logging operations at all log points and controls the logging information. Behavioral design patterns, like Visitor and Template Method, let you defer implementation. However, just as in case with mix-ins, the control of the operation -invoking visiting logic or invoking template methods -- stays with the main classes. Domain-specific solutions, such as frameworks and application servers, let developers address some crosscutting concerns in a modularized way. The Enterprise JavaBeans (EJB) architecture, for example, addresses crosscutting concerns such as security, administration, performance, and container-managed persistence. Bean developers focus on the business logic, while the deployment developers focus on deployment issues, such as bean-data mapping to a database. The bean developer remains, for the most part,

oblivious to the storage issues. In this case, you implement the crosscutting concern of persistence using an XML-based mapping descriptor. The domain-specific solution offers a specialized mechanism for solving the specific problem. As a downside to domain-specific solutions, developers must learn new techniques for each such solution. Further, because these solutions are domain specific, the crosscutting concerns not directly addressed require an ad hoc response.

The architect's dilemma Good system architecture considers present and future requirements to avoid a patchylooking implementation. Therein lies a problem, however. Predicting the future is a difficult task. If you miss future crosscutting requirements, you'll need to change, or possibly reimplement, many parts of the system. On the other hand, focusing too much on low-probability requirements can lead to an overdesigned, confusing, bloated system. Thus a dilemma for system architects: How much design is too much? Should I lean towards underdesign or overdesign? For example, should an architect include a logging mechanism in a system that does not initially need it? If so, where should the logging points be, and what information should be logged? A similar dilemma occurs for optimization-related requirements -- with performance, we seldom know the bottlenecks in advance. The usual approach is to build the system, profile it, and retrofit it with optimization to improve the performance. This approach requires potentially changing many system parts indicated by profiling. Further, over time, new bottlenecks may appear due to changed usage patterns. The reusable library architect's task is even more difficult because he finds it harder to imagine all the usage scenarios for the library. In summary, the architect seldom knows every possible concern the system may need to address. Even for requirements known beforehand, the specifics needed to create an implementation may not be fully available. Architecture thus faces the under/overdesign dilemma.

The fundamentals of AOP The discussion so far suggests that it can be helpful to modularize the implementation of crosscutting concerns. Researchers have studied various ways to accomplish that task under the more general topic of "separation of concerns." AOP represents one such method. AOP strives to cleanly separate concerns to overcome the problems discussed above. AOP, at its core, lets you implement individual concerns in a loosely coupled fashion, and combine these implementations to form the final system. Indeed, AOP creates systems using loosely coupled, modularized implementations of crosscutting concerns. OOP, in contrast, creates systems using loosely coupled, modularized implementations of

common concerns. The modularization unit in AOP is called an aspect, just as a common concern's implementation in OOP is called a class. AOP involves three distinct development steps: 1. Aspectual decomposition: Decompose the requirements to identify crosscutting and common concerns. You separate module-level concerns from crosscutting system-level concerns. For example, in the aforementioned credit card module example, you would identify three concerns: core credit card processing, logging, and authentication. 2. Concern implementation: Implement each concern separately. For the credit card processing example, you'd implement the core credit card processing unit, logging unit, and authentication unit. 3. Aspectual recomposition: In this step, an aspect integrator specifies recomposition rules by creating modularization units -- aspects. The recomposition process, also known as weaving or integrating, uses this information to compose the final system. For the credit card processing example, you'd specify, in a language provided by the AOP implementation, that each operation's start and completion be logged. You would also specify that each operation must clear authentication before it proceeds with the business logic.

Figure 3. AOP development stages

AOP differs most from OOP in the way it addresses crosscutting concerns. With AOP, each concern's implementation remains unaware that other concerns are "aspecting" it. For example, the credit card processing module doesn't know that the other concerns are logging or authenticating its operations. That represents a powerful paradigm shift from OOP.

Note: An AOP implementation can employ another programming methodology as its base methodology, thus keeping the base system's benefits intact. For example, an AOP implementation could choose OOP as the base system to pass on benefits of better implementation of common concerns with OOP. With such an implementation, individual concerns could employ OOP techniques for each identified concern. That is analogous to a procedural language acting as the base language for many OOP languages.

Weaving example The weaver, a processor, assembles an individual concern in a process known as weaving. The weaver, in other words, interlaces different execution-logic fragments according to some criteria supplied to it. To illustrate code weaving, let's return to our credit card processing system example. For brevity, consider only two operations: credit and debit. Also assume that a suitable logger is available. Consider the following credit card processing module: public class CreditCardProcessor { public void debit(CreditCard card, Currency amount) throws InvalidCardException, NotEnoughAmountException, CardExpiredException { // Debiting logic } public void credit(CreditCard card, Currency amount) throws InvalidCardException { // Crediting logic } }

Also, consider the following logging interface: public interface Logger { public void log(String message); }

The desired composition requires the following weaving rules, expressed here in natural language (a programming language version of these weaving rules is provided later in this article): 1. Log each public operation's beginning 2. Log each public operation's completion 3. Log any exception thrown by each public operation

The weaver would then use these weaving rules and concern implementations to produce the equivalent of the following composed code: public class CreditCardProcessorWithLogging { Logger _logger; public void debit(CreditCard card, Money amount) throws InvalidCardException, NotEnoughAmountException, CardExpiredException { _logger.log("Starting CreditCardProcessor.credit(CreditCard, Money) " + "Card: " + card + " Amount: " + amount); // Debiting logic _logger.log("Completing CreditCardProcessor.credit(CreditCard, Money) " + "Card: " + card + " Amount: " + amount); } public void credit(CreditCard card, Money amount) throws InvalidCardException { System.out.println("Debiting"); _logger.log("Starting CreditCardProcessor.debit(CreditCard, Money) " + "Card: " + card + " Amount: " + amount); // Crediting logic _logger.log("Completing CreditCardProcessor.credit(CreditCard, Money) " + "Card: " + card + " Amount: " + amount); } }

Anatomy of AOP languages Just like any other programming methodology implementation, an AOP implementation consists of two parts: a language specification and an implementation. The language specification describes language constructs and syntax. The language implementation verifies the code's correctness according to the language specification and converts it into a form that the target machine can execute. In this section, I explain the parts and pieces of an aspect-oriented language.

The AOP language specification At a higher level, an AOP language specifies two components: •



Implementation of concerns: Mapping an individual requirement into code so that a compiler can translate it into executable code. Since implementation of concerns takes the form of specifying procedures, you can to use traditional languages like C, C++, or Java with AOP. Weaving rules specification: How to compose independently implemented concerns to form the final system. For this purpose, an implementation needs to use or create a language for specifying rules for composing different

implementation pieces to form the final system. The language for specifying weaving rules could be an extension of the implementation language, or something entirely different.

AOP language implementation AOP language compilers perform two logical steps: 1. Combine the individual concerns 2. Convert the resulting information into executable code

An AOP implementation can implement the weaver in various ways, including source-tosource translation. Here, you preprocess source code for individual aspects to produce weaved source code. The AOP compiler then feeds this converted code to the base language compiler to produce final executable code. For instance, using this approach, a Java-based AOP implementation would convert individual aspects first into Java source code, then let the Java compiler convert it into byte code. The same approach can perform weaving at the byte code level; after all, byte code is still a kind of source code. Moreover, the underlying execution system -- a VM implementation, say -- could be aspect aware. Using this approach for Java-based AOP implementation, for example, the VM would load weaving rules first, then apply those rules to subsequently loaded classes. In other words, it could perform just-in-time aspect weaving.

AOP benefits AOP helps overcome the aforementioned problems caused by code tangling and code scattering. Here are other specific benefits AOP offers: •





Modularized implementation of crosscutting concerns: AOP addresses each concern separately with minimal coupling, resulting in modularized implementations even in the presence of crosscutting concerns. Such an implementation produces a system with less duplicated code. Since each concern's implementation is separate, it also helps reduce code clutter. Further, modularized implementation also results in a system that is easier to understand and maintain. Easier-to-evolve systems: Since the aspected modules can be unaware of crosscutting concerns, it's easy to add newer functionality by creating new aspects. Further, when you add new modules to a system, the existing aspects crosscut them, helping create a coherent evolution. Late binding of design decisions: Recall the architect's under/overdesign dilemma. With AOP, an architect can delay making design decisions for future requirements, since she can implement those as separate aspects.



More code reuse: Because AOP implements each aspect as a separate module, each individual module is more loosely coupled. For example, you can use a module interacting with a database in a separate logger aspect with a different logging requirement. In general, a loosely coupled implementation represents the key to higher code reuse. AOP enables more loosely coupled implementations than OOP.

AspectJ: An AOP implementation for Java AspectJ, a freely available AOP implementation for Java from Xerox PARC, is a generalpurpose aspect-oriented Java extension. AspectJ uses Java as the language for implementing individual concerns, and it specifies extensions to Java for weaving rules. These rules are specified in terms of pointcuts, join points, advice, and aspects. Join points define specific points in a program's execution, a pointcut is the language construct that specifies join points, advice defines pieces of an aspect implementation to be executed at pointcuts, and an aspect combines these primitives. In addition, AspectJ also allows the "aspecting" of other aspects and classes in several ways. Indeed, you can introduce new data members and new methods, as well as declare a class to implement additional base classes and interfaces. AspectJ's weaver -- an aspect compiler -- combines different aspects together. Because the final system created by the AspectJ compiler is pure Java byte code, it can run on any conforming JVM. AspectJ also features tools such as a debugger and selected IDE integration. I'll present AspectJ in more detail in Parts 2 and 3 of this series. Below you'll find an AspectJ implementation of the logging aspect for the weaver I described in natural language above. Since I will present an AspectJ tutorial in Part 2, do not worry if you don't understand it in detail. The point that you should really notice is that the credit card processing itself does not know anything about logging: public aspect LogCreditCardProcessorOperations { Logger logger = new StdoutLogger(); pointcut publicOperation(): execution(public * CreditCardProcessor.*(..)); pointcut publicOperationCardAmountArgs(CreditCard card, Money amount): publicOperation() && args(card, amount); before(CreditCard card, Money amount): publicOperationCardAmountArgs(card, amount) { logOperation("Starting", thisjoin point.getSignature().toString(), card, amount); } after(CreditCard card, Money amount) returning: publicOperationCardAmountArgs(card, amount) { logOperation("Completing",

thisjoin point.getSignature().toString(), card, amount); } after (CreditCard card, Money amount) throwing (Exception e): publicOperationCardAmountArgs(card, amount) { logOperation("Exception " + e, thisjoin point.getSignature().toString(), card, amount); } private void logOperation(String status, String operation, CreditCard card, Money amount) { logger.log(status + " " + operation + " Card: " + card + " Amount: " + amount); } }

Do I need AOP? Does AOP merely address design shortcomings? In AOP, the each concern's implementation is oblivious to the fact that it is being aspected by other concerns. This obliviousness sets AOP apart from OOP techniques. In AOP, the flow of composition runs from crosscutting concerns to the main concern, whereas in OOP techniques the flow runs in the opposite direction. Note, however, that OOP and AOP can merrily coexist. For example, you could employ a mix-in class as a composition using either AOP or OOP, depending upon your AOP comfort level. In either case, the mix-in class implementing a crosscutting concern doesn't need to know whether it is a mix-in class using hand composition or a part of an aspect using rule-based composition. For example, you could use a logger interface as a mix-in for some classes or as a logging aspect for others. Thus, there's an evolutionary path from OOP to AOP.

Get your AOP! In this article you discovered the problems with crosscutting concerns, the current ways to implement them, and their shortcomings. You also examined how AOP helps overcome those problems. AOP methodology seeks to modularize crosscutting concern implementations, and it produces a better and faster software implementation. If your software system comprises several crosscutting concerns, consider learning more about AOP, its implementations, and its benefits. AOP quite possibly represents the next big thing. Stay tuned for more in Parts 2 and 3 in future months.

I want my AOP!, Part 2 Learn AspectJ to better understand aspect-oriented programming By Ramnivas Laddad, JavaWorld.com, 03/01/02 In Part 1 of this three-part series on aspect-oriented programming (AOP), I introduced AOP concepts and briefly gave AOP implementation examples. Continuing that trend in this article, I present a concrete AOP implementation for Java: AspectJ, a free implementation and language specification from Xerox PARC. Moreover, I aim to impart familiarity with the AspectJ concepts you will need for Part 3. Read the whole "I Want My AOP" series: • • •

Part 1. Separate software concerns with aspect-oriented programming Part 2. Learn AspectJ to better understand aspect-oriented programming Part 3. Use AspectJ to modularize crosscutting concerns in real-world problems

In this article, I lean towards conciseness rather than completeness. For a comprehensive tutorial, I highly recommend the AspectJ Group's official AspectJ Programming Guide. Note: You can download this article's complete source code in Resources. The sample code works with AspectJ 1.0.3 -- the latest available version at the time of publication.

AspectJ overview AspectJ is a language specification as well as an AOP language implementation. The language specification defines various constructs and their semantics to support aspectoriented concepts. The language implementation offers tools for compiling, debugging, and documenting code. AspectJ's language constructs extend the Java programming language, so every valid Java program is also a valid AspectJ program. The AspectJ compiler produces class files that comply with Java byte code specification, enabling any compliant JVM to interpret the produced class files. By choosing Java as the base language, AspectJ passes on all the benefits of Java and makes it easy for Java programmers to use it. AspectJ, as one of its strong points, features helpful tools. It provides an aspect weaver in the form of a compiler, an aspect-aware debugger and documentation generator, and a standalone aspect browser to visualize how an advice crosscuts a system's parts. Moreover, AspectJ offers good integration with popular IDEs, including Sun Microsystems' Forte, Borland's JBuilder, and Emacs, making AspectJ a useful AOP implementation for Java developers.

AspectJ language overview To support AOP, AspectJ adds to the Java language concepts: • • •

Joinpoints: Points in a program's execution. For example, joinpoints could define calls to specific methods in a class Pointcuts: Program constructs to designate joinpoints and collect specific context at those points Advices: Code that runs upon meeting certain conditions. For example, an advice could log a message before executing a joinpoint

Pointcut and advice together specify weaving rules. Aspect, a class-like concept, puts pointcuts and advices together to form a crosscutting unit. The pointcuts specify execution points and the weaving rule's context, whereas the advices specify actions, operations, or decisions performed at those points. You can also look at joinpoints as a set of events in response to which you execute certain advices. I explain joinpoints, pointcuts, and advices in more detail below

AspectJ HelloWorld To keep with the tradition of introducing a new programming language with a HelloWorld program, let's write one in AspectJ. First, here's a simple class containing methods to print a message: // HelloWorld.java public class HelloWorld { public static void say(String message) { System.out.println(message); } public static void sayToPerson(String message, String name) { System.out.println(name + ", " + message); } }

Let's now write an implementation for adding greeting and gratitude manners. Before the program prints a message, it should print "Good day!"; and should follow the message with "Thank you!": // MannersAspect.java public aspect MannersAspect { pointcut callSayMessage() : call(public static void HelloWorld.say*(..)); before() : callSayMessage() {

System.out.println("Good day!"); } after() : callSayMessage() { System.out.println("Thank you!"); }

}

The MannersAspect.java file, in a process similar to class declaration in Java, declares a MannersAspect aspect. The aspect defines a callSayMessage() pointcut that captures calls to all public static methods with names that start with say. In our example, it would capture say() and sayToPerson() in a HelloWorld class taking any arguments. Then you define two advices for before and after reaching the callSayMessage() pointcut: printing "Good day!" and "Thank you!" When you compile the program with the AspectJ compiler, then run it, you'll see "Good day!" printed before each message, and "Thank you!" after each message. You now have excellent manners!

AspectJ language concepts and constructs Now that you have a feel for the AspectJ language, let's examine its core constructs in greater detail. This section explains AspectJ's available joinpoints and various pointcut syntax. I also take a closer look at advice constructs.

Joinpoints Joinpoints, a central concept in AspectJ, are well-defined points in a program's execution. Candidate joinpoints include calls to a method, a conditional check, a loop's beginning, or an assignment. Joinpoints also have a context associated with them. For example, a method-call joinpoint could have the target object and its argument as part of the context. Although any identifiable point in a program's execution is a joinpoint, AspectJ limits the available joinpoints to those usable in a systematic manner. AspectJ makes the following pointcuts available: • • • • •

Method call and execution Constructor call and execution Read/write access to a field Exception handler execution Object and class initialization execution

AspectJ does not expose finer execution structures such as if checks or for loops.

Pointcuts

Pointcuts, program constructs to designate joinpoints, let you specify a joinpoint collection. Pointcuts also let you expose context at the joinpoint to an advice implementation. In the HelloWorld example above, I used the following pointcut: pointcut callSayMessage() : call(public static void HelloWorld.say*(..));

In the code above, pointcut declares that what follows is a declaration of a named pointcut. Next, callSayMessage(), the pointcut's name, resembles a method declaration. The trailing empty () suggests that the pointcut collects no context. Moving on, call(public static void HelloWorld.say*(..)) captures needed joinpoints. call indicates the pointcut captures a call to, as opposed to execution of, designated methods. The public static void HelloWorld.say*(..) is the signature for methods to be captured. public static indicates that the methods must have public access and be declared a static method. void says that methods captured must have a void return type. HelloWorld.say* specifies the to-be-captured method's class and name. Here, we are specifying HelloWorld as a class. uses the * wildcard to indicate the capture of methods with names starting with "say." Finally, (..) specifies arguments to the captured methods. In this case, you specify the .. wildcard to capture methods regardless of type and number of arguments they take. say*

Now that you know how to specify pointcuts to capture joinpoints, let's look briefly at other pointcuts types.

Call to methods and constructors pointcuts First, call to methods and constructors pointcuts capture execution points after they evaluate a method's arguments, but before they call the method itself. They take the form of call(MethodOrConstructorSignature). Table 1 shows examples of such pointcuts. Table 1. Call to methods and constructors pointcuts

Pointcut

Description

call(public void MyClass.myMethod(String))

Call to myMethod() in MyClass taking a String argument, returning void, and with public access

call(void MyClass.myMethod(..))

Call to myMethod() in MyClass taking any arguments, with void return type, and any access modifiers

call(* MyClass.myMethod(..))

Call to myMethod() in MyClass taking any arguments returning any type

call(* MyClass.myMethod*(..))

Call to any method with name starting in "myMethod" in MyClass

call(* MyClass.myMethod*(String,..))

Call to any method with name starting in "myMethod" in MyClass and the first argument is of String type

call(* *.myMethod(..))

Call to myMethod() in any class in default package

call(MyClass.new())

Call to any MyClass' constructor taking no arguments

call(MyClass.new(..))

Call to any MyClass' constructor with any arguments

call(MyClass+.new(..))

Call to any MyClass or its subclass's constructor. (Subclass indicated by use of '+' wildcard)

call(public * com.mycompany..*.*(..))

All public methods in all classes in any package with com.mycompany the root package

Execution of methods and constructors pointcuts Next, execution of methods and constructors pointcuts capture the method's execution. In contrast to call pointcuts, execution pointcuts represent the method or constructor body itself. They take the form of execution(MethodOrConstructorSignature). Table 2. Execution of methods and constructors pointcuts

Pointcut

Description

execution(public void MyClass.myMethod(String))

Execution of myMethod() in MyClass taking a String argument, returning void, and with public access

execution(void MyClass.myMethod(..))

Execution of myMethod() in MyClass taking any arguments, with void return type, and any access modifiers

execution(* MyClass.myMethod(..))

Execution of myMethod() in MyClass taking any arguments returning any type

execution(* MyClass.myMethod*(..))

Execution of any method with name starting in "myMethod" in MyClass

execution(* MyClass.myMethod*(String,..))

Execution of any method with name starting in "myMethod" in MyClass and the first argument is of String type

execution(* *.myMethod(..))

Execution of myMethod() in any class in default package

execution(MyClass.new())

Execution of any MyClass' constructor taking no arguments

execution(MyClass.new(..))

Execution of any MyClass' constructor with any arguments

execution(MyClass+.new(..))

Execution of any MyClass or its subclass's constructor. (Subclass indicated by use of '+' wildcard)

execution(public * com.mycompany..*.*(..))

All public methods in all classes in any package with com.mycompany the root package

Field-access pointcuts Third, field-access pointcuts capture read and write access to a class's field. For example, you can capture all access to the out field inside the System class (as in System.out). You can capture either read or write access. For example, you can capture writing into field x of MyClass, as in MyClass.x = 5. The read-access pointcut takes the form of get(FieldSignature), whereas the write-access pointcut takes the form of set(FieldSignature). FieldSignature can use wildcards in the same manner as MethodOrConstructor in call and execution pointcuts. Table 3. Field-access pointcuts

Pointcut

Description

get(PrintStream System.out)

Execution of read-access to field out of type PrintStream in System class

set(int MyClass.x)

Execution of write-access to field x of type int in MyClass

Exception-handler pointcuts Fourth, exception-handler pointcuts capture the execution of exception handlers of specified types. They take the form of handler(ExceptionTypePattern).

Pointcut

Table 4. Exception-handler pointcuts Description

handler(RemoteException)

Execution of catch-block handling RemoteException type

handler(IOException+)

Execution of catch-block handling IOException or its subclasses

handler(CreditCard*)

Execution of catch-block handling exception types with names that start with CreditCard

Class-initialization pointcuts Fifth, class-initialization pointcuts capture the execution of static-class initialization, code specified in static blocks inside class definitions, of specified types. They take the form of staticinitialization(TypePattern). Table 5. Class-initialization pointcuts Pointcut Description staticinitialization(MyClass)

Execution of static block of MyClass

Staticinitialization(MyClass+)

Execution of static block of MyClass or its subclasses

Lexical-structure-based pointcuts Next, lexical-structure-based pointcuts capture all joinpoints inside a class or method's lexical structure. The pointcut capturing the code lexically inside a class, including an inner class, takes the form of within(TypePattern). The pointcut capturing the code lexically inside a method or constructor, including any local classes, takes the form of withincode(MethodOrConstructorSignature).

Table 6. Lexical-structure-based pointcuts Pointcut Description within(MyClass)

Any pointcut inside MyClass's lexical scope

within(MyClass*)

Any pointcut inside lexical scope of classes with a name that starts with "MyClass"

withincode(* MyClass.myMethod(..))

Any pointcut inside lexical scope of any myMethod() of MyClass

Control-flow-based pointcuts Seventh, control-flow-based pointcuts capture pointcuts based on other pointcuts' control flow (the flow of program instructions). For example, if in an execution, method a() calls method b(), then b() is said to be in a()'s control flow. With control-flow-based pointcuts, you can, for example, capture all methods, field access, and exception handlers caused by invoking a method. The pointcut that captures the pointcuts in control flow of some other pointcut, including the specified pointcut itself, takes the form of cflow(pointcut), whereas the one that excludes the specified pointcut itself takes the form of cflowbelow(pointcut). Table 7. Control-flow-based pointcuts Pointcut Description cflow(call(* MyClass.myMethod(..))

All the joinpoints in control flow of call to any myMethod() in MyClass including call to the specified method itself

cflowbelow(call(* MyClass.myMethod(..))

All the joinpoints in control flow of call to any myMethod() in MyClass excluding call to the specified method itself

Self-, target-, and arguments-type pointcuts Eighth, self-, target-, and arguments-type pointcuts capture joinpoints based on the selfobject, target-object, and argument types. These are the only constructs that can capture context at the joinpoints. The pointcut that captures the pointcuts based on the self object takes the form of this(TypePattern or ObjectIdentifier), whereas the one based on target takes the form of target(TypePattern or ObjectIdentifier). Argumentbased pointcuts take the form of args(TypePattern or ObjectIdentifier, ..). Table 8. Self-, target-, and arguments-type pointcuts

Pointcut

Description

this(JComponent+)

All the joinpoints where this is instanceof JComponent

target(MyClass)

All the joinpoints where the object on which the method is called is of type MyClass

args(String,..,int)

All the joinpoints where the first argument is of String type and the last argument is of int type

args(RemoteException)

All the joinpoints where the type of argument or exception handler type is RemoteException

Conditional-test pointcuts The ninth and last pointcut we'll look at, conditional test, captures joinpoints based on some conditional check at the joinpoint. It takes the form of if(BooleanExpression). Table 9. Conditional-test pointcuts Pointcut Description All the joinpoints where

if(EventQueue.isDispatchThread()) EventQueue.isDispatchThread()

evaluates to true

Named and anonymous pointcuts Named pointcuts explicitly specify their names. You can reuse such pointcuts for defining other pointcuts, defining part of an advice, overriding a pointcut, and so on. Anonymous pointcuts, like anonymous classes, become defined at the point of their usage, specifically in the advice specification or as a part of some other pointcut's definition. As with anonymous classes, anonymous pointcuts cannot be reused. In the examples above, I used an anonymous pointcut with cflow().

Use ||, &&, and ! operators with pointcuts You can use || and && operators to combine named and anonymous pointcuts. For example, to designate a pointcut for a call to method m1() or m2() in MyClass, you could use call(* MyClass.m1()) || call(* MyClass.m2()). To designate a pointcut for a call to method m1() in MyClass that is in control flow of a call to m2 in MyClass, you could use call(* MyClass.m1()) && cflow(call(* MyClass.m2()). You can use broad pointcuts such as this(), within(), and cflow() with other pointcuts using && to capture a smaller joinpoint subset.

Use ! to specify joinpoints not captured by the specified pointcut. For example, to designate a call to all methods, except m1() in MyClass, you could use a !call(* MyClass.m1()) pointcut.

Exposing context Advice implementations often require access to data at the joinpoint. For example, to log certain operations, an advice might need information, called context, about a method and arguments to it. Pointcuts, therefore, allow exposure of the context at the execution point to pass to an advice implementation. AspectJ offers target(), this(), and args() pointcuts to collect the context. For example, the following pointcut collects all arguments to method executions associated with it: pointcut publicOperationCardAmountArgs(CreditCard card, Money amount): execution(public * CreditCardProcessor.*(..)) && args(card, amount);

Reflection AspectJ supports a limited form of reflection. With AspectJ's reflection, you can examine information at any pointcut's execution point. Each advice body has access to a special object, thisJoinPoint, which contains the information about the joinpoint. Such reflective access proves particularly important in logging and debugging aspects.

Advices Advices specify the executable code when reaching certain pointcuts. AspectJ provides three ways to associate an advice with a joinpoint: before, after, and around a joinpoint's execution. A before advice runs just before the joinpoint, whereas an after advice runs just after. With an after advice, you can specify after normal return, after throwing an exception, or after returning either way from a joinpoint. An around advice surrounds a joinpoint and has control if the joinpoint's execution should proceed. It can also decide to proceed with a different argument set. Let's look at a few simple advice examples. The following advice example prints thisJoinPoint and the current system time before and after calling any public method in MyClass: before() : call(public * MyClass.*(..)) { System.out.println("Before: " + thisJoinPoint + " " + System.currentTimeMillis()); } after() : call(public * MyClass.*(..)) { System.out.println("After: " + thisJoinPoint + " " + System.currentTimeMillis());

}

The next advice example captures all calls to Connection.close(), and, if you've enabled connection pooling, puts the connection into a resource pool instead; otherwise it uses proceed() to proceed with the captured operation. This advice also uses context collected by target(): void around(Connection conn) : call(Connection.close()) && target(conn) { if (enablePooling) { connectionPool.put(conn); } else { proceed(); } }

Aspects Aspects act as AspectJ's unit of modularization, just as classes do in Java. An aspect puts together pointcuts and advices. Aspects resemble classes: an aspect can contain methods and fields, extend other classes or aspects, and implement interfaces. However, aspects differ from classes in that you cannot create an object for an aspect using new. AspectJ allows classes to declare pointcuts. You must declare static pointcuts declared inside a class. However, AspectJ does not allow classes to contain advices; only aspects can contain advices. An aspect can mark itself and any pointcut as abstract. Abstract pointcuts act similarly to a class's abstract methods: they let you defer the details to the derived aspects. A concrete aspect extending an abstract aspect can then provide concrete definitions of abstract pointcuts.

AspectJ examples With your new AspectJ knowledge, it's time to look at a few examples implemented in AspectJ. (For another AOP implementation, review Part 1's credit card example. In Part 3, I will present more complex AOP examples.)

Example 1: Detect a Swing UI (user interface) update from a nonevent dispatch thread The following aspect detects any call to a Swing model that updates the UI. (For more information about when to use such detection, see Resources.) The test code that exercises this aspect is in Test.java:

// SwingThreadSafetyCheckAspect.java import java.awt.*; /** * Aspect that warns if Swing components are updated from a * nonevent dispatch thread. * * Because AspectJ currently does not allow byte-code weaving, the * implementation is short. It is required to manually * encode each method that updates model. Besides, it does not take care of * custom models. * * The implementation is for illustration only. It takes care of only * part of DefaultTableModel and DefaultListModel. */ public aspect SwingThreadSafetyCheckAspect { private static final String WARNING_MESSAGE = "BEWARE: Swing update called from non-AWT thread\n" + "Change code to use EvenetQueue.invokeLater() or invokeAndWait()"; // Define a pointcut to capture calls to TableModel and its subclass's // method that would update the UI. Note, this list is incomplete. pointcut swingTableUpdateCalls() : call(* javax..*TableModel+.set*(..)) || call(* javax..*TableModel+.add*(..)) || call(* javax..*TableModel+.remove*(..)); // Define a pointcut to capture calls to ListModel and its subclass's // method that would update the UI. Note, this list is incomplete. pointcut swingListUpdateCalls() : call(* javax..*ListModel+.set*(..)) || call(* javax..*ListModel+.insert*(..)); // Pointcuts for tree, text, and so on models // Implementation left out... // Pointcut for updates to any Swing model (use || to add pointcuts // to be defined above for tree, text, and so on). pointcut swingUpdateCalls() : swingTableUpdateCalls() || swingListUpdateCalls(); /** * Advice to print a warning and stack trace that led to a call * to Swing update methods from nonevent dispatch thread. */ before() : swingUpdateCalls() && !if(EventQueue.isDispatchThread()) { System.err.println(WARNING_MESSAGE); System.err.println("Joinpoint captured: " + thisJoinPoint); Thread.dumpStack(); System.err.println(); }

}

Example 2: Authentication The next example, found in AbstratcAuthenticationAspect.java, illustrates abstract pointcuts and abstract aspects, as well as crosscutting multiple methods. Other classes are available in DatabaseServer.java, DatabaseClient.java, Authenticator.java, and Test.java: // AbstratcAuthenticationAspect.java public abstract aspect AbstractAuthenticationAspect { public abstract pointcut operationsNeeddingAuthentication(); before() : operationsNeeddingAuthentication() { // Perform authentication. If it could not be authenticated, // let the exception thrown by authenticate propagate. Authenticator.authenticate(); } }

DatabaseAuthenticationAspect extends the abstract AbstractAuthenticationAspect aspect. It specifies the definition for the operationsNeeddingAuthentication() pointcut capturing operations needing authentication -- in our case calls to the DatabaseServer.connect() method: // DatabaseAuthenticationAspect.java public aspect DatabaseAuthenticationAspect extends AbstractAuthenticationAspect { public pointcut operationsNeeddingAuthentication(): call(* DatabaseServer.connect()); }

Example 3: Add Japanese manners to HelloWorld Now that you've seen the around() advice and collecting context, let's use it to add manners, Japanese style, by adding a "-san" suffix to each person's name to show respect. We simply add another aspect: JapaneseMannersAspect. This aspect advises the call to HelloWorld.sayToPerson(String, String) and proceeds with a modified person argument by adding "-san": // JapaneseMannersAspect.java public aspect JapaneseMannersAspect { pointcut callSayMessageToPerson(String person) : call(* HelloWorld.sayToPerson(String, String)) && args(*, person); void around(String person) : callSayMessageToPerson(person) { proceed(person + "-san"); } }

Type-modification constructs Sometimes it is necessary to affect the static structure in a crosscutting manner, in contrast to affecting dynamic behavior using an advice. AspectJ allows several staticcrosscutting types: introduction of new methods and fields, introduction of supertypes, and alteration of a method's exception specification.

Method and field introduction AspectJ lets you introduce methods or fields to classes and interfaces. Interestingly, AspectJ lets you introduce method implementations and fields into interfaces. You can, for example, provide a default implementation of a few methods in an interface. The following aspect introduces a foo() method and an instanceCount field of int type in MyClass: aspect IntroduceMethodIllustration { private void MyClass.foo() { System.out.println("This is foo"); } private static int MyClass.instanceCount = 0; }

Inheritance hierarchy restructuring With AspectJ, you can modify an existing class's inheritance hierarchy. Using these constructs, you can declare super classes and interfaces of an existing class or interface. The following aspect declares that MyClass is a Serializable: aspect MakeMyClassSerializable { declare parents : MyClass implements Serializable; }

Soften an exception AspectJ can soften a checked exception into an unchecked exception for a particular method set. Such methods then behave as if they've thrown an unchecked exception of type org.aspectj.lang.SoftException, which wraps the original exception. The next aspect example softens a RemoteException thrown by a call to any public method in DateFetcher's subclasses or DataFetcherImpl's constructor if such a call is made from within Test class's code: aspect SoftenDateFetcherRemoteException { declare soft :

RemoteException : (call(public * DateFetcher.*(..)) || call(public DateFetcherImpl.new(..))) && within(Test);

}

Note: Since such a declaration affects compile-time behavior, the pointcut must be statically determinable.

The rest in short In this section, I briefly cover additional AspectJ features: • •

• •

You can declare one aspect as dominant over others to control precedence when multiple aspects affect a certain pointcut. By default, Aspect J associates aspects with other classes and aspects, and therefore all aspectee instances share the aspect's state. Beyond the default association type, AspectJ offers additional types: perthis, pertarget, percflow, and percflowbelow. Each associates a separate aspect instance with a self object, target object, control flow, and control flow excluding the pointcut, respectively. You can mark an aspect privileged, which gives it access to the private members of aspected classes. AspectJ also lets you introduce compile-time crosscutting behavior by allowing compile-time warnings and errors (similar to, but more powerful than, #error and #warning preprocessor directives supported by some C/C++ compilers).

AspectJ in all its glory AspectJ provides a powerful and useful AOP implementation for Java. AspectJ offers modularizing crosscutting concerns by adding new joinpoint, pointcut, and advice concepts. Since AspectJ extends Java, Java programmers will find it an easy language to learn. The byte code produced by the AspectJ compiler is standard Java byte code, therefore it keeps Java's advantages. AspectJ could evolve into a far more powerful language and implementation. As more people use it, the experience gained should guide changes to the language specification and implementation. In Part 3, I will demonstrate AspectJ's power via several examples. Those examples, taken from various domains, will also show how AspectJ/AOP offers an elegant solution to otherwise complex problems.

Related Documents

Aop
November 2019 25
Aop Handbook
December 2019 18
Aop Presentation
May 2020 22
Observacion Aves (aop)
December 2019 22
Assessment Of Firms Aop
November 2019 28