CSC/ECE 517 Fall 2007/wiki1b 5 b4

From Expertiza_Wiki
Jump to navigation Jump to search

Assignment

AspectR is a very useful Ruby module, but it is not easy to find documentation on it that is appropriate for students taking this class. Find, or construct, documentation that explains what it does without presuming previous knowledge of AspectJ, that describes many or all methods of the module and how they work. Also find or produce an easy-to-understand example that does not involve logging.

Note to Reviewers: All prose contained in this wiki is original, including the RubyDoc.

Aspect-Oriented Programming and AspectR

AspectR is a Ruby module that allows you to easily and concisely use the Aspect-Oriented Programming (AOP) paradigm in your Ruby applications.

What is Aspect-Oriented Programming?

When designing and developing a software system, you must account for and handle the many concerns of the system. A concern is a requirement of, or action that must be performed by, the system. For example, concerns for a banking system include making deposits, making withdrawals, handling account inquiries, transferring funds between accounts, authenticating and authorizing the user, and synchronizing and logging all transactions. The concerns at the beginning of the list are the primary, or core, concerns of the banking system, and would likely be handled by separate modules or classes. The concerns at the end of the list are secondary, and are required across many of the core modules. For this reason they are referred to in AOP as cross-cutting concerns.

The standard example used to demonstrate the functionality and benefits of AOP involves logging. A well-designed object-oriented system would have a separate class for logging, and that class would be utilized by all other classes in the system that require logging. By creating a separate logging class, the code that actually formats the logged data and writes it to file only needs to be written once. However, the code that invokes the logging module is still repeated and scattered throughout the system. As a result, the core code is less readable, takes longer to develop, and is much more difficult and expensive to maintain. With AOP, you can weave the logging code into the rest of the system without actually modifying all of the other classes that need to use it. This would be done by creating a logging aspect and using special APIs to specify which classes and methods need to be logged. In this way, the logging concern is kept separate from all other concerns in the system.

Other concerns that are good candidates for AOP include security, database transactions, synchronization, and profiling.

Benefits of AOP include:

  • Shorter development time and reduced code size.
  • More readable and less cluttered code; this makes the system easier to understand and maintain.
  • Much easier to add or delete function as needed.
  • Different implementations of a cross-cutting concern can be easily swapped. No changes to the core concerns are necessary.
  • Increased productivity. Developers can focus on the primary concern and do not need to address the cross-cutting concerns.

What is AspectR?

The Ruby language has certain facilities built in that make AOP possible out-of-the-box, including aliasing, reflection, and metaprogramming. AspectR is a Ruby module that was written by Avi Bryant and Robert Feldt to make AOP in Ruby even easier. With AspectR, you can create aspects that contain methods (known as advice) which will be called before and after certain specified methods are invoked. In other words, your existing class methods can be "wrapped" with other code by using AspectR. See the AspectR RubyDoc section below for more information on how this works.

AspectR is similar to AspectJ, Java's AOP implementation. AspectR lacks many of the features of AspectJ, however, and the two work very differently. AspectJ works as a preprocessor that actually statically adds the advice code to the classes and methods being wrapped, and then passes the new code to a compiler or interpreter. AspectR, on the other hand, aliases the existing methods in the wrapped class and calls the specified advice methods before and/or after the original method is called. For a more thorough description of the differences between AspectR and AspectJ, see Ruby Developer's Guide.


Downloading and Installing AspectR

In order to take advantage of the features of AspectR, you must first download and install it.

  1. Go to http://aspectr.sourceforge.net/ and download the tarball aspectr-0-3-5.tar.gz.
  2. Unpack the tarball. If you are on Windows, this can be unzipped using Winzip.
  3. From the newly created directory aspectr-0-3-5, execute "ruby install.rb".

These steps will place the AspectR module (aspectr.rb) in your Ruby library.


AspectR RubyDoc

To use AspectR, you'll begin by creating a new subclass of AspectR's Aspect class. Let's call it BasicAspect. Then you'll add two methods to BasicAspect, one containing the code that you want to execute before the original method executes and one containing the code that you want to execute after. We'll call the former before_advice and the latter after_advice. Note that you don't need to have both; you can choose to just have before advice or just have after advice.

Your BasicAspect will look something like this:

require 'aspectr'
include AspectR
class BasicAspect < Aspect
   
  def before_advice(method, object, exitstatus, *args) 
    puts "do something before calling the existing method" 
   end
   
  def after_advice(method, object, exitstatus, *args) 
    puts "do something after calling the existing method"
  end
end

Now suppose you have a class, ExistingClass, containing a method that you want to wrap.

class ExistingClass
  def existing_method
    puts "executing existing method"
  end
end

Finally, you'll need to use the AspectR API to specify that you want to wrap ExistingClass.existing_method with the advice in BasicAspect.

BasicAspect.new.wrap(ExistingClass, :before_advice, :after_advice, :existing_method)
ExistingClass.new.existing_method

Executing this code will give the following output:

do something before calling the existing method
executing existing method
do something after calling the existing method

Under the covers, AspectR is aliasing existing_method with a new name. When existing_method is called, the following occurs:

  1. before_advice is called
  2. existing_method is called via the new name
  3. after_advice is called

Following is a description of all of the methods found in Aspect that you might want to use when utilizing AspectR.

wrap

Arguments: target, pre, post, *args

Adds the advice specified by pre and post to all methods specified in args on the class or object target. The pre and post arguments are symbols whose values are the names of the methods containing the desired pre advice and post advice. To specify more than one method, pass a regular expression to args.

Example:

BasicAspect.new.wrap(MyClass, :before_advice, :after_advice, /method/)

In this case, all calls to methods on MyClass with names containing "method" will be wrapped by calls to before_advice and after_advice.

The methods specified by before_advice and after_advice will be passed three or more parameters when they are called. The first parameter is the name of the method that is being wrapped. The second is the object on which the wrapped method is being called. The third is an exit status, which will contain nil for before advice (since the method hasn't exited yet). For after advice, the exit status will be true if the method exited with an exception or it will be an array of values returned by the method if the method exited normally. Any remaining parameters are the arguments that were specified when the wrapped method was called.

unwrap

Arguments: target, pre, post, *args

Removes the advice specified by pre and post from all methods specified in args on the class or object target. To specify more than one method, pass a regular expression to args.

Example:

BasicAspect.new.unwrap(MyClass, :before_advice, :after_advice, /method/)

In this case, the advice before_advice and after_advice will no longer be executed when calls to methods on MyClass with names containing "method" are made.

wrap_with_code

Arguments: target, preCode, postCode, *args

Wraps the code in preCode and postCode around the methods specified in args on the class or object target. This method results in faster execution than the wrap method and cannot be undone (i.e. there is no unwrap_with_code).

Example:

class SomeClass
  def method1(str)
    puts "executing SomeClass.method1 #{str}"
  end
end

Aspect.new.wrap_with_code(SomeClass, %q{puts "entering method"}, %q{puts "exiting method"}, /method/)

SomeClass.new.method1 "test"

Output:

entering method
executing SomeClass.method1 test
exiting method

add_advice

Arguments: target, joinpoint, method, advice

Adds the advice specified by advice to the method method on the class or object target. Use a joinpoint of Aspect::PRE if the advice should be executed prior to the base method; otherwise, use Aspect::POST to execute the advice after the base method returns.

Example:

BasicAspect.new.add_advice(MyClass, Aspect::PRE, :method1, :before_advice)

In this case, the advice before_advice will be executed just before the code in MyClass.method1.


remove_advice

Arguments: target, joinpoint, method, advice

Removes the advice specified by advice from the method method on the class or object target. Use the same value for joinpoint that was specified when add_advice was called (Aspect::PRE or Aspect::POST).

Example:

BasicAspect.new.remove_advice(MyClass, Aspect:PRE, :method1, :before_advice)

In this case, the advice before_advice will no longer be executed just before the code in MyClass.method1.

disable_advice_dispatching

This method takes no parameters but does expect a code block. No pre or post advice is called while the specified code block is executing.

Example:

BasicAspect.new.disable_advice_dispatching {MyClass.new.method1}

Example: Profiler Aspect

One common use of AOP is profiling. Profiling refers to the collection of data about the dynamic behavior of a system. It is done to determine a breakdown of where time and memory are being consumed at runtime and make optimizations based on the results. In the example below, which is based off a sample found in Ruby Developer's Guide, we create an aspect that will determine the number of (milli)seconds spent in a single method. It is kept very simple to clearly illustrate the functionality of AspectR; a full-featured profiler would need to account for non-serialized and nested method invocations, aggregate the results, etc.

The Profiler class, a subclass of Aspect, contains two methods: profiler_enter and profiler_exit. The before advice, profiler_enter, records the time immediately before the method is invoked. The after advice, profiler_exit, records the time immediately after the method exits and prints out the duration of the method invocation.

require 'aspectr'
include AspectR
class Profiler < Aspect
   
  def profiler_enter(method, object, exitstatus, *args) 
    @enter_time = Time.now
    puts "#{@enter_time.strftime('%Y-%m-%d %X')} #{self.class}##{method}: #{args.inspect}" 
   end
   
  def profiler_exit(method, object, exitstatus, *args) 
    @exit_time = Time.now
    print "#{@exit_time.strftime('%Y-%m-%d %X')} #{self.class}##{method}: exited " 
    if exitstatus.kind_of?(Array) 
      print "normally returning #{exitstatus[0].inspect} " 
    elsif exitstatus == true 
      print "with exception '#{$!}' " 
    else 
      print "normally " 
    end 
    puts "after #{@exit_time.to_f - @enter_time.to_f} seconds"
  end
end

A user of Profiler would need to wrap all methods that she is interested in profiling. The program below uses the Profiler aspect to calculate the amount of time spent in the SomeClass.method1 method call.

class SomeClass
  def method1(str)
    puts "entering SomeClass.method1 #{str}"
    sleep 5.3
    puts "exiting SomeClass.method1"
  end
end

Profiler.new.wrap(SomeClass, :profiler_enter, :profiler_exit, :method1)
SomeClass.new.method1 "test"

This program generates the following output:

2007-09-30 22:05:51 Profiler#method1: ["test"]
entering SomeClass.method1 test
exiting SomeClass.method1
2007-09-30 22:05:56 Profiler#method1: exited normally returning nil after 5.29699993133545 seconds

Because the method method1 was wrapped with before and after advice, the following occurs when method1 is called:

  1. profiler_enter is called. The current time is stored in the instance variable @enter_time, and the entry is logged.
  2. method1 is called, and the application sleeps for 5.3 seconds.
  3. profiler_exit is called. The current time is stored in the instance variable @exit_time, and the exit is logged. The log contains the duration of the call to method1, which is calculated by subtracting @enter_time from @exit_time.

You can see from the Profiler output that SomeClass.method1 took approximately 5.3 seconds to execute, as expected.

Note: A profiler for Ruby programs, RbProf, is included in the AspectR distribution.

References

  1. Aspect-oriented programming from Wikipedia
  2. AspectJ in Action
  3. AspectR README
  4. CSC517 Lecture 08: Reflection and metaprogramming
  5. Mastering AspectJ
  6. Ruby Cookbook
  7. Ruby Developer's Guide
  8. The Ruby Way

See Also

  1. aspectj