CSC/ECE 517 Fall 2010/ch3 3d mr
Aspect-oriented programming and AspectR
AspectR is a programming library that enables Aspect-oriented programming for Ruby programs by providing ways to wrap code around existing methods in a program. It is simpler but similar in functionality to the AspectJ library for Java, but less well known. AspectR is distributed under the GNU General Public License.
Aspect-Oriented Programming
Aspect-Oriented Programming is an approach to modularizing concerns that cut across the core concerns or business logic, of a program.
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, etc. This 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.
Terminology
- 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:
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)
Methods for controlling dispatching
disable_advice_dispatching
- 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).
dispatch?
- Indicates if dispatching to advice methods is enabled.
Specifying methods that should never be wrapped
Other utility methods
The following methods are used by the API itself but also made public:
get_methods (targret, args)
prepare (target)
all_classes (regexp = /^.+$/)
- Returns all classes whose class name matches a given regular expression.
Example
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 #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