CSC/ECE 517 Fall 2012/ch1 1w27 ms

From PG_Wiki
Jump to: navigation, search

Contents

Aspect Oriented Programming (AOP): AspectJ and AspectR

Aspect Oriented Programming (AOP) refers to a new way of designing software. It aims to increase modularity by allowing the separation of cross-cutting concerns. AOP includes programming methods and tools that support the modularization of concerns at the level of the source code. It is not a replacement to popular Object Oriented Programming (OOP), but is complimentary to it.

Overview

Aspect-oriented programming entails breaking down program logic into distinct parts, called concerns. Nearly all programming paradigms support some level of grouping and encapsulation of concerns into separate, independent entities by providing abstractions (e.g., procedures, modules, classes, methods) that can be used for implementing, abstracting and composing these concerns. But some concerns defy these forms of implementation and are called crosscutting concerns because they "cut across" multiple abstractions in a program. An example to this is logging. In an normal object oriented language program we might need to invoke a logger function from everywhere we this functionality.

Why do we need AOP? The banking example

Typically, an aspect is scattered or tangled as code, making it harder to understand and maintain. It is scattered by virtue of the function (such as logging) being spread over a number of unrelated functions that might use its function, possibly in entirely unrelated systems, different source languages, etc. That means to change logging can require modifying all affected modules. Aspects become tangled not only with the mainline function of the systems in which they are expressed but also with each other. That means changing one concern entails understanding all the tangled concerns or having some means by which the effect of changes can be inferred.

Consider a simple banking application written in Ruby.

require 'insufficient_funds'
class BankAccount
attr_accessor :balance
def initialize @balance = 0.0 end
def deposit(amount) @balance += amount end
def withdraw(amount) if @balance < amount raise InsufficientFunds end @balance -= amount end
end

This is simple enough. But is not very useful. What if we need persistence too?

require 'insufficient_funds_error'
class BankAccount attr_reader :balance
def balance=(amount) @balance = amount persist_balance end
def initialize @balance = retrieve_balance end
def deposit(amount) @balance += amount persist_balance end
def withdraw(amount) if @balance < amount raise InsufficientFundsError end @balance -= amount persist_balance end
def retrieve_balance # Get from database end
def persist_balance # Save to database end
end

We can see that now other interests have become tangled with the basic functionality (sometimes called the business logic concern). Now add:

Also this code will be repeated across other domain objects which need the same behavior. Finally, attempt to reuse this code in a new application with a different persistence store, transaction engine, security model etc.

The Problem

The Solution: Aspect Oriented Programming

AOP attempts to solve this problem by allowing the programmer to express cross-cutting concerns in stand-alone modules called aspects. Then it provides mechanisms for fine-grained modification of target component behavior to incorporate the aspects’ behaviors. Aspects can contain advice (code joined to specified points in the program) and inter-type declarations (structural members added to other classes). For example, a security module can include advice that performs a security check before accessing a bank account.

For example in the above program, the behavior is modified by adding a 'Persistence aspect' to the program. We specify separately where these aspect should be invoked in the banking account application. Now the banking account remains agnostic and the aspect code is maintained in one place. Also, now it is easy to change to a different solution or to remove the concern altogether.

We will see how this is implemented exactly in various implementation of AOP such as AspectJ and AspectR below.

AOP Concepts

The advice-related component of an aspect-oriented language defines a join point model (JPM). A JPM defines three things:

Join-point models can be compared based on the join points exposed, how join points are specified, the operations permitted at the join points, and the structural enhancements that can be expressed.

AspectJ: AOP in Java

AspectJ extend the Java programming language with constructs to support AOP. It is a superset of Java such that each valid Java program is also a valid AspectJ program. Also, it is designed as a general purpose language as opposed to a domain specific language. AspectJ is currently the most notable AOP technology.

AspectJ was created at PARC for the Java programming language. It is available in Eclipse Foundation open-source projects, both stand-alone and integrated into Eclipse. AspectJ has become the widely used de-facto standard for AOP by emphasizing simplicity and usability for end users. It has included IDE integrations for displaying crosscutting structure since its initial public release in 2001.

History and contributors

Gregor Kiczales started and led the Xerox PARC team that eventually developed AspectJ; he coined the term "crosscutting". Fourth on the team, Chris Maeda coined the term "aspect-oriented programming." Jim Hugunin and Erik Hilsdale (Xerox PARC team members 12 and 13) were the original compiler and weaver engineers, Mik Kersten implemented the IDE integration and started the Eclipse AJDT project with Adrian Colyer (current lead of the AspectJ project) and Andrew Clement (current compiler engineer).

The AspectBench Compiler was developed and is being maintained as a joint effort of the Programming Tools Group at the Oxford University Computing Laboratory, the Sable Research Group at McGill University and the Institute for Basic Research in Computer Science (BRICS).

Lanugage description

All valid Java programs are also valid AspectJ programs, but AspectJ also allows programmers to define special constructs called aspects. Aspects can contain several entities unavailable to standard classes. These are:

Inter-type declarations

These allow a programmer to add methods, fields, or interfaces to existing classes from within the aspect. This example adds an acceptVisitor (see visitor pattern) method to the Point class:

 aspect VisitAspect {
   void Point.acceptVisitor(Visitor v) {
     v.visit(this);
   }
 }

Pointcuts

Pointcuts allow a programmer to specify join points. All pointcuts are expressions (quantifications) that determine whether a given join point matches. A call joinpoint captures an execution event after it evaluates a method calls' arguments, but before it calls the method itself.

For example, this point-cut specifies all calls to GetBalance method in the class BankingAccount that takes no arguments and returns long

 call(public long BankingAccount.GetBalance())

We may also use logical operators in the definition of pointcuts inorder to combine pointcut expressions.

  1. || (OR operator) - Matches a joinpoint if either left pointcut expression or right pointcut expression matches
  2. && (AND operator) - Matches a joinpoint if both left pointcut expression and right pointcut expression matches
  3.  ! (NOT operator) - Matches all joinpoints NOT specified by the pointcut expression

Example of a pointcut which matches both mutator methods of the BankingAccount class is

 pointcut mutators(): call(public long BankingAccount.Withdraw(long)) || call(public long BankingAccount.Deposit(long))

Various types of joinpoints that are possible in AspectJ are:

  1. Calls to methods and constructors
  2. Execution of methods and constructors
  3. Field access
  4. Exception handling
  5. Class initializaiton
  6. Lexical structure
  7. Control flow
  8. Self-target and argument type
  9. Conditional test

Patterns in pointcuts
Patterns can also be used to specify events in pointcuts. This would be useful to specify a set of events concisely.

The use of * character is highly overloaded.

Examples

Advice

Advice allows a programmer to specify code to run at a join point matched by a pointcut. The actions can be performed before, after, or around the specified join point. An advice must be define with respect to a pointcut. Given below is an advice defined with respect to the mutators pointcut.

 before() : mutators() {
   System.out.println("Mutator called");
 }

This is a special type of mutator called the before mutator which is called before the joinpoint specified by the pointcut is executed.

There are three ways to associate an advice with a pointcut:

  1. Before - run just before the pointcut
  2. After - run just after the pointcut
  3. Around - Runs instead of the pointcut with the provision for the pointcut to resume execution through a method called proceed().

AspectJ also supports limited forms of pointcut-based static checking and aspect reuse (by inheritance). Pointcuts and advices together define composition (weaving) rules.

Aspect

Much like a class, an Aspect is a unit of modularity. It is defined in terms of pointcuts, advices and ordinary java fields and methods. Pointcuts say which event to match and advice body says what to execute when it matches.

 public aspect Tracer {
   pointcut mutators(): call(public long BankingAccount.Withdraw(long)) ||
                          call(public long BankingAccount.Deposit(long));
before() : mutators() { System.out.println("Mutator called"); } }

Banking example in Java using AOP

Now let us look at how persistence may be added to the BankingAccount example using AspectJ

 class BankAccount {
float balance;
public BankAccount(float balance) { this.balance = balance; }
public void deposit(float amount) { balance += amount }
public void withdraw(float amount) { if (balance < amount) { throw new InsufficientFundsException(); } balance -= amount; } }
 aspect PersistenceAspect {
pointcut stateChange(BankAccount ba): (call(void BankAccount.deposit(*)) || call(void BankAccount.withdraw(*))) && target(ba);
after(BankAccount ba) returning: stateChange(ba) { // persist ba.getBalance() value to database } }

We define the pointcut stateChange as calls to the mutator functions of the BankAccount class, which are the deposit and withdraw functions in the PersistenceAspect. The persistence action should be performed after the above mentioned pointcut. So we define an advice returning, after the pointcut stateChange. So this code will execute everytime after the withdraw and deposit functions and the balance will be persisted.

Thus the BankAccount class contains code only related to the BankAccount. It is completely free of any cross-cutting concerns such as persistence. Other concerns such as user authentication might be implemented similarly.

AOP in Ruby

Ruby does not have native AOP support. But Ruby's metaprogramming features cover many of the aspect requirements. 'method_missing' and reflection in Ruby are classis examples of that. 'method_missing' allows the code to inspect the method call at runtime and redirect to some default method or define a new method if the called method does not exists. Similarly Ruby provides naive support to implement pointcuts and wrapper methods. They are discussed below.

Pointcut equivalent in Ruby

There are no pointcuts in Ruby. But they can be simulated by the inspect method that Ruby provides.

 # List all the classes
 ObjectSpace.each_object(Class) do |o|
   puts o.inspect
 end
# List all the modules ObjectSpace.each_object(Module) do |o| puts o.inspect end
# List all the instances of class String ObjectSpace.each_object(String) do |o| puts o.inspect end

Adding advice in Ruby

In Ruby, alias can be used to wrap methods with advice.

 foo.rb
 class Foo
   def bar
     "bar"
   end
 end
foo_advice.rb class Foo alias old_bar bar def bar "{[< "+ old_bar+ " >]}” end end

In the above code, the alias will be executed eveytime a call to method 'bar' is made. The output of the above code will be as follows:

 puts Foo.new.bar
 => "{[< bar >]}"

AspectR

We saw how AOP can be implemented in Ruby without any extra support in the above sections. AspectR is free library which provides API's to the same for Ruby. Essentially it allows you to wrap code around existing methods in your classes. This can be a good thing (and even affect how you design your code) since it separates different concerns into different parts of the code. The code for AspectR can be downloaded from here. AspectR was originally called advice.rb and written by Avi Bryant. Later Robert Feldt changed/tweaked/extended it.

Main features of AspectR

AspectR Syntax

Advice methods are passed a variable number of parameters:

Here is the sample syntax:

 require 'aspectr'
 include AspectR
 class MyAspect < Aspect
   def someAdviceMethod(method, object, exitstatus, *args)
     ...
   end
     ... some other advice methods ...
 end
ma = MyAspect.new ma.wrap(someClassorObject, :preAdvice, :postAdvice, ... methods to wrap...) or ma.wrap(someClassorObject, :preAdvice, :postAdvice, /patternToWrap/) or AspectR.wrap_classes(ma, :preAdvice, :postAdvice, [Class1, Class2], ...methods to wrap...)

Banking account example in AspectR

 class BankAccount
   attr_reader :balance
   def balance=(amount)
     @balance = amount
     persist_balance 
   end
def initialize @balance = retrieve_balance end
def deposit(amount) @balance += amount persist_balance end
def withdraw(amount) if @balance < amount raise InsufficientFundsError end @balance -= amount persist_balance end end
class Persist < Aspect def retrieve_balance # Get from database .. end def persist_balance # Save to database .. end end
Persist.new.wrap(BankingAccount, :retrive_balance, :persist_balance, /[withdraw|deposit]/)

In the above code class persists is a subclass of Aspect and has the before(retrive_balance) and after(persist_advice) advice defined in it. Then the last line of the code wraps the this persist class to the BankingAccount's withdraw and deposit methods. It also specifies what pre-advice and what post-advice should be used. The API for wrap class is as follows:

 def wrap(target, pre, post, *args)		
   get_methods(target, args).each do |method_to_wrap|
     add_advice(target, PRE, method_to_wrap, pre)
     add_advice(target, POST, method_to_wrap, post)
   end
 end

Thus as we can see from API, the wrap method directly adds the pre and post advice to the methods (Join-points in AspectJ terminology).

How AspectR is different from AspectJ

Conclusion

Aspect Oriented Programming is a very powerful concept which enables programmers to separate the cross-cutting concerns. If provides flexibility in decomposing source code into modules. But it lacks popularity. There is no native support for this in popular programming languages like Java. Thus it requires learning new framework. Though there is some native support for APO in Ruby, it does not offer all features of AOP. The AspectR library has these limitations.

References

  1. Aspect Oriented Programming - wikipedia
  2. An introduction to Aspect-Oriented Programming with AspectJ by Constantinos Constantinides, McGill University
  3. AspectJ - wikipedia
  4. AspectR - Simple aspect-oriented programming in Ruby
  5. AspectR - Ruby documentation
  6. Aspect-Oriented Programming in Ruby by Dean Wampler
Personal tools
Namespaces
Variants
Actions
Navigation
Toolbox