CSC/ECE 517 Fall 2010/ch3 3d mr
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
- 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