CSC/ECE 517 Fall 2010/ch3 3d mr: Difference between revisions

From Expertiza_Wiki
Jump to navigation Jump to search
No edit summary
 
(24 intermediate revisions by the same user not shown)
Line 1: Line 1:
= Aspect-oriented programming and AspectR =
= Aspect-oriented programming and AspectR =


AspectR is a programming library that enables [http://en.wikipedia.org/wiki/Aspect-oriented_programming Aspect-oriented programming] for [http://en.wikipedia.org/wiki/Ruby_%28programming_language%29 Ruby] programs by providing ways to wrap code around existing methods in a program. It is simpler but similar in functionality to the [http://en.wikipedia.org/wiki/Aspectj AspectJ] library for Java, but less well known. AspectR is distributed under the [http://en.wikipedia.org/wiki/GPL GNU General Public License].
'''AspectR''' is a programming library that enables [http://en.wikipedia.org/wiki/Aspect-oriented_programming Aspect-Oriented Programming] (AOP) for Ruby programs by providing ways to wrap code around existing methods in a program. It is similar in functionality to and shares terminology with the [http://en.wikipedia.org/wiki/AspectJ AspectJ] library for Java.


== Aspect-Oriented Programming ==
AspectR was developed by Avi Bryant and Robert Feldt; it is distributed under the GNU General Public License [1].


Aspect-Oriented Programming is an approach to modularizing concerns that cut across the core concerns or business logic, of a program.
== Overview of AOP ==
 
Aspect-Oriented Programming is an approach to modularizing code that ''cross-cuts'' the core components or business logic of a program [2]. Logging is a classical example of a ''concern'' which cross-cuts the core components of a program, and thus violates the widely held principle on Separation of Concerns. AOP solves this problem and is complimentary to Object Oriented Programming because it allows the concerns which cuts across the program to be encapsulated into their own classes, called aspects.
 
[[Image:Aop.GIF]]


==== Motivation ====
==== Motivation ====


The model code in a typical program will often contain not only the business logic but also secondary concerns such as transactions, security, logging, etcThis clutters the code in the model, making it hard to distinguish the business logic from the secondary code, complicating maintenance and reuse. Furthermore, these secondary concerns cut across multiple core concerns in the program, which violates the separation of concerns. As a result, changing to a different transaction API, for example, results in code modifications throughout the program.
Consider the following exercise...
 
: 1. Take a shopping cart application with a Cart class and method add_to_cart():
 
class Cart
  has_many :items
  def add_to_cart(item)
    items << item
  end
end
 
This code is easy to understand and maintain. 
 
: 2. Now add security, database transactions, and logging code:
 
class Cart
  has_many :items
  def add_to_cart(item)
    log('entering method')
    check_permissions
    conn = new_connection
    begin
      items << item
    rescue
      log('exception')
    ensure
      conn.close
    end
    log('leaving method')   
  end
  end
 
: 3. Make the same changes to other business logic methods throughout the program.
 
: 4. Finally, try reusing this code in another application which has a different transaction framework, uses a different logging library, or handles security differently.
 
Aspect-Oriented Programming resolves this problem by encapsulating the concerns that cut across the program.


=== Terminology ===
=== Terminology ===
AspectR uses a lot of the same terminology as AspectJ.


* Aspects - Classes that implement functionality needed in many parts of a program, but are not part of the business logic or core concern of the program.
* Aspects - Classes that implement functionality needed in many parts of a program, but are not part of the business logic or core concern of the program.


* Advice - Aspect methods or code to be applied around existing business logic.
* Advice - Borrowed from AspectJ terminology, refers to Aspect methods or code to be applied around existing business logic.


* Join Point - Places in the core business logic that advice should be applied or wrapped around.
* Join Point - Places in the core business logic that advice should be applied or wrapped around.
Line 21: Line 63:
== The AspectR API ==
== The AspectR API ==


AspectR provides a simple mechanism for wrapping methods in a program. One begins by creating an "aspect" class that inherits from the AspectR Aspect class. You then define the wrapper methods which will be called at the join points in the program. These methods are called Advice methods. AspectR currently supports only two method join points, <code>PRE</code> and <code>POST</code>. The inherited instance method <code>wrap</code> is then used to specify the wrapper methods to be called before and after a method invocation, along with the target class and methods to be intercepted.
AspectR provides a simple mechanism for wrapping methods in a program. One begins by creating an "aspect" class that inherits from the AspectR Aspect class. For example, we define a new aspect class to implement a code profiler:


=== Methods for wrapping ===
require aspectr.rb
include AspectR
class Profiler < Aspect
end


To add an advice method to be invoked before or after a single target method, use the <code>add_advice</code> and <code>remove_advice</code> instance methods on the Aspect class:
We then define the wrapper methods which will be called at the join points in the program. These methods are called Advice methods.


  add_advice (target, joinpoint, method, advice)
  def method_start(method, object, exitstatus, *args)
  @begin = Time.now
end
   
   
  remove_advice (target, joinpoint, method, advice)
  def method_end(method, object, exitstatus, *args)
  timeElapsed = Time.now - @begin
  puts "#{object.class}.#{method} took #{timeElapsed} secs"
end


Parameters:
AspectR currently supports only two method join points, <code>PRE</code> and <code>POST</code>.
** <code>target</code> - target class
** <code>joinpoint</code> - must be either <code>PRE</code> or <code>POST</code>
** <code>method</code> - target method
** <code>advice</code> - advice method in the aspect class


To wrap both the before and after join points, and specify multiple target methods, use the <code>wrap</code> and <code>unwrap</code> instance methods on the Aspect class:
Given the simple example,


  wrap (target, pre, post, *args)
  class SomeClass
  def some_method
    puts "hello"
    sleep 5
  end
end
 
One can add advice to be invoked before or after a single target method using the <code>add_advice</code> method, for example:
 
profiler = Profiler.new
   
   
  unwrap (target, pre, post, *args)
  profiler.add_advice(SomeClass, :PRE, :some_method, :method_start)
profiler.add_advice(SomeClass, :POST, :some_method, :method_end)


Parameters:
This advice can be removed using <code>remove_advice</code>:
* <code>target</code> - target class
* <code>pre</code> - advice method to call before target method invocations
* <code>post</code> - advice method to call after target method invocations
* <code>*args</code> - target methods, or regular expression matching method names in the target class


Alternatively, you may wrap target methods to blocks of advice code using the <code>wrap_with_code</code> instance method. Has performance advantages but cannot be unwrapped:
profiler.remove_advice(SomeClass, :PRE, :some_method, :method_start)
profiler.remove_advice(SomeClass, :POST, :some_method, :method_end)


wrap_with_code (target, preCode, postCode, *args)
We can wrap both the before and after join points, and specify multiple target methods, for example the following code will wrap all the methods from the class <code>SomeClass</code> which match the regular expression "/some/" with our advice methods for the before and after join points:


To wrap methods from multiple target classes, the AspectR library defines a global <code>wrap_classes</code> method. This is an experimental method and the API is likely to change:
profiler.wrap(SomeClass, :method_start, :method_end, /some/)


wrap_classes (aspect, pre, post, classes, *methods)
Similarly, this can be unwrapped:


Parameters:
profiler.unwrap(SomeClass, :method_start, :method_end, /some/)
* <code>aspect</code> - aspect class
* <code>pre</code> - advice method to call before target method invocations
* <code>post</code> - advice method to call after target method invocations
* <code>classes</code> - regular expression to match target class names
* <code>*methods</code> - target methods, or regular expression matching method names in the target classes


=== Methods for controlling dispatching ===
Alternatively, you can have code directly injected into the beginning and ending of the target methods . This has performance advantages but cannot be unwrapped:


* <code>disable_advice_dispatching</code> - Disables all dispatching to advice methods by the API. Provided as an instance method so must be called on an instance of the aspect class (any instance).
profiler.wrap_with_code(SomeClass, '@begin = Time.now', 'timeElapsed = Time.now - @begin; puts "took #{timeElapsed} secs"' , /some/)


* <code>dispatch?</code> - Indicates if dispatching to advice methods is enabled.
To wrap methods from multiple target classes, the AspectR library defines a  <code>wrap_classes</code> method. This is an experimental method and the API is likely to change [3]:


=== Specifying methods that should never be wrapped ===
wrap_classes(profiler, :method_start, :method_end, /Some/, /some/)


* <code>new (never_wrap = "^$ ")</code> - Class method to create a new instance of the aspect class and specify methods that should never be wrapped.
One can specify that one or more methods which should never be wrapped by passing in the beginning of the method name to the constructor. There is also a <code>wrappable?</code> method to test if a method can be wrapped. For example this code will result in the wrap being ignored and return true:


* <code>wrappable? (method)</code> - Indicates if the specified method can be wrapped. See [new] below.
profiler = Profiler.new("some")
profiler.wrap(SomeClass, :method_start, :method_end, /some/)
profiler.wrappable?(:some_method)


=== Other utility methods ===
Code can be executed with dispatching by the API disabled using the <code>disable_advice_dispatching</code> instance method on the aspect class. Although temporary, the effect is global to the API. For example the following code will NOT result in the advice methods being called:


The following methods are used by the API itself but also made public:
  profiler.wrap(SomeClass, :method_start, :method_end, /some/)
  profiler.disable_advice_dispatching { SomeClass.new.some_method }


* <code>get_methods (targret, args)</code>
One can test to see if the API dispatching is disabled using the Aspect class method <code>dispatch?</code>, for example:


* <code>prepare (target)</code>
Aspect.dispatch?


* <code>all_classes (regexp = /^.+$/)</code> - Returns all classes whose class name matches a given regular expression.
== Examples ==
 
== Example ==


The following code examples implement a code [http://en.wikipedia.org/wiki/Profiling_%28computer_programming%29 profiler] to measure the duration of method calls.
The following code examples implement a code [http://en.wikipedia.org/wiki/Profiling_%28computer_programming%29 profiler] to measure the duration of method calls.
Line 96: Line 149:
   
   
  class Profiler < Aspect
  class Profiler < Aspect
   def method_start(method, object, exitstatus, *args)
   def method_start(method, object, exitstatus, *args)
     @begin = Time.now
     @begin = Time.now
   end
   end
   def method_end(method, object, exitstatus, *args)
   def method_end(method, object, exitstatus, *args)
     timeElapsed = Time.now - @begin
     timeElapsed = Time.now - @begin
Line 104: Line 159:
   end
   end
  end
  end
 
The following code defines a class <code>SomeClass</code> and tests the aspect:
 
  #if $0 == __FILE__
  #if $0 == __FILE__
   class SomeClass
   class SomeClass
Line 118: Line 175:


=== AspectJ ===
=== AspectJ ===
Unlike AspectR, the aspects in AspectJ programs must be transformed into valid Java code. 
import java.util.Date;
aspect Profiler {
  private Date beginTime;
 
  pointcut myMethod(SomeClass s): target(s) && call(public * some*(..));
  before(SomeClass s): myMethod(s) {
    this.beginTime = new Date();
  }
  after(SomeClass s): myMethod(s) {
    Date newTime = new Date();
    int timeElapsed = this.beginTime.getTime() - newTime.getTime();
    System.out.println(thisJoinPointStaticPart + " took " + timeElapsed + " secs");
  }
}
== Summary ==
Although the library is quite small, AspectR is a useful tool for implementing Aspect-Oriented Programming in Ruby programs. The main limitation of the library is that it only supports wrapping methods with join points either before or after the method calls. More complex join points are not supported at the time of this writing. 
Unlike AspectJ, however, AspectR does not require a separate language processor to coordinate the composition of program from the aspects and the business logic code.


== References ==
== References ==
# AspectR README. http://aspectr.sourceforge.net/ January 29, 2002
# Kiczales, Gregor; John Lamping, Anurag Mendhekar, Chris Maeda, Cristina Lopes, Jean-Marc Loingtier, and John Irwin (1997). "Aspect-Oriented Programming". Proceedings of the European Conference on Object-Oriented Programming, vol.1241. pp. 220–242.
# AspectR Ruby documentation (RDoc), AspectR module


== External Links ==
== External Links ==
* [http://en.wikipedia.org/wiki/Aspect-oriented_programming Aspect-Oriented Programming on Wikipedia]
* [http://ruby-doc.org/ Ruby Documentation]
* [http://aspectr.sourceforge.net/ AspectR home page]
* [http://www.eclipse.org/aspectj/doc/released/progguide/index.html AspectJ Programming Guide]
* [http://ruby-doc.org/docs/ProgrammingRuby/html/ Programming Ruby - The Pragmatic Programmer's Guide]
* [http://en.wikipedia.org/wiki/GPL GNU Public License]
* [http://en.wikipedia.org/wiki/Separation_of_concerns Separation of Concerns on Wikipedia]

Latest revision as of 15:50, 13 October 2010

Aspect-oriented programming and AspectR

AspectR is a programming library that enables Aspect-Oriented Programming (AOP) for Ruby programs by providing ways to wrap code around existing methods in a program. It is similar in functionality to and shares terminology with the AspectJ library for Java.

AspectR was developed by Avi Bryant and Robert Feldt; it is distributed under the GNU General Public License [1].

Overview of AOP

Aspect-Oriented Programming is an approach to modularizing code that cross-cuts the core components or business logic of a program [2]. Logging is a classical example of a concern which cross-cuts the core components of a program, and thus violates the widely held principle on Separation of Concerns. AOP solves this problem and is complimentary to Object Oriented Programming because it allows the concerns which cuts across the program to be encapsulated into their own classes, called aspects.

Motivation

Consider the following exercise...

1. Take a shopping cart application with a Cart class and method add_to_cart():
class Cart
  has_many :items
  def add_to_cart(item)
    items << item
  end
end

This code is easy to understand and maintain.

2. Now add security, database transactions, and logging code:
class Cart
  has_many :items
  def add_to_cart(item)
    log('entering method')
    check_permissions
    conn = new_connection
    begin
      items << item
    rescue
      log('exception')
    ensure
      conn.close
    end
    log('leaving method')     
  end
end
3. Make the same changes to other business logic methods throughout the program.
4. Finally, try reusing this code in another application which has a different transaction framework, uses a different logging library, or handles security differently.

Aspect-Oriented Programming resolves this problem by encapsulating the concerns that cut across the program.

Terminology

AspectR uses a lot of the same terminology as AspectJ.

  • Aspects - Classes that implement functionality needed in many parts of a program, but are not part of the business logic or core concern of the program.
  • Advice - Borrowed from AspectJ terminology, refers to Aspect methods or code to be applied around existing business logic.
  • Join Point - Places in the core business logic that advice should be applied or wrapped around.

The AspectR API

AspectR provides a simple mechanism for wrapping methods in a program. One begins by creating an "aspect" class that inherits from the AspectR Aspect class. For example, we define a new aspect class to implement a code profiler:

require aspectr.rb
include AspectR

class Profiler < Aspect
end

We then define the wrapper methods which will be called at the join points in the program. These methods are called Advice methods.

def method_start(method, object, exitstatus, *args)
  @begin = Time.now
end

def method_end(method, object, exitstatus, *args)
  timeElapsed = Time.now - @begin
  puts "#{object.class}.#{method} took #{timeElapsed} secs"
end

AspectR currently supports only two method join points, PRE and POST.

Given the simple example,

class SomeClass
  def some_method
    puts "hello"
    sleep 5
  end
end

One can add advice to be invoked before or after a single target method using the add_advice method, for example:

profiler = Profiler.new

profiler.add_advice(SomeClass, :PRE, :some_method, :method_start)
profiler.add_advice(SomeClass, :POST, :some_method, :method_end)

This advice can be removed using remove_advice:

profiler.remove_advice(SomeClass, :PRE, :some_method, :method_start)
profiler.remove_advice(SomeClass, :POST, :some_method, :method_end)

We can wrap both the before and after join points, and specify multiple target methods, for example the following code will wrap all the methods from the class SomeClass which match the regular expression "/some/" with our advice methods for the before and after join points:

profiler.wrap(SomeClass, :method_start, :method_end, /some/)

Similarly, this can be unwrapped:

profiler.unwrap(SomeClass, :method_start, :method_end, /some/)

Alternatively, you can have code directly injected into the beginning and ending of the target methods . This has performance advantages but cannot be unwrapped:

profiler.wrap_with_code(SomeClass, '@begin = Time.now', 'timeElapsed = Time.now - @begin; puts "took #{timeElapsed} secs"' , /some/)

To wrap methods from multiple target classes, the AspectR library defines a wrap_classes method. This is an experimental method and the API is likely to change [3]:

wrap_classes(profiler, :method_start, :method_end, /Some/, /some/)

One can specify that one or more methods which should never be wrapped by passing in the beginning of the method name to the constructor. There is also a wrappable? method to test if a method can be wrapped. For example this code will result in the wrap being ignored and return true:

profiler = Profiler.new("some")

profiler.wrap(SomeClass, :method_start, :method_end, /some/)

profiler.wrappable?(:some_method)

Code can be executed with dispatching by the API disabled using the disable_advice_dispatching instance method on the aspect class. Although temporary, the effect is global to the API. For example the following code will NOT result in the advice methods being called:

 profiler.wrap(SomeClass, :method_start, :method_end, /some/)

 profiler.disable_advice_dispatching { SomeClass.new.some_method }

One can test to see if the API dispatching is disabled using the Aspect class method dispatch?, for example:

Aspect.dispatch?

Examples

The following code examples implement a code profiler to measure the duration of method calls.

AspectR

require aspectr.rb
include AspectR

class Profiler < Aspect

  def method_start(method, object, exitstatus, *args)
    @begin = Time.now
  end

  def method_end(method, object, exitstatus, *args)
    timeElapsed = Time.now - @begin
    puts "#{object.class}.#{method} took #{timeElapsed} secs"
  end
end

The following code defines a class SomeClass and tests the aspect:

#if $0 == __FILE__
  class SomeClass
    def some_method
      puts "hello"
      sleep 5
    end
  end

  Profiler.new.wrap(SomeClass, :method_start, :method_end, /some/)
  SomeClass.new.some_method
#end

AspectJ

Unlike AspectR, the aspects in AspectJ programs must be transformed into valid Java code.

import java.util.Date;

aspect Profiler {
  private Date beginTime;
  
  pointcut myMethod(SomeClass s): target(s) && call(public * some*(..));

  before(SomeClass s): myMethod(s) {
    this.beginTime = new Date();
  }

  after(SomeClass s): myMethod(s) {
    Date newTime = new Date();
    int timeElapsed = this.beginTime.getTime() - newTime.getTime();
    System.out.println(thisJoinPointStaticPart + " took " + timeElapsed + " secs");
  }
}

Summary

Although the library is quite small, AspectR is a useful tool for implementing Aspect-Oriented Programming in Ruby programs. The main limitation of the library is that it only supports wrapping methods with join points either before or after the method calls. More complex join points are not supported at the time of this writing.

Unlike AspectJ, however, AspectR does not require a separate language processor to coordinate the composition of program from the aspects and the business logic code.

References

  1. AspectR README. http://aspectr.sourceforge.net/ January 29, 2002
  2. Kiczales, Gregor; John Lamping, Anurag Mendhekar, Chris Maeda, Cristina Lopes, Jean-Marc Loingtier, and John Irwin (1997). "Aspect-Oriented Programming". Proceedings of the European Conference on Object-Oriented Programming, vol.1241. pp. 220–242.
  3. AspectR Ruby documentation (RDoc), AspectR module

External Links