CSC/ECE 517 Summer 2008/wiki1 5 bk
Introduction
Hooks and aspect-oriented programming are both techniques which allow cross-cutting of code to take place. Both concepts endeavor to provide a single point of modification in order to eliminate the need to make subtle changes scattered throughout the code. I will start by explaining these concepts in detail and then proceed to look at two implementations of the aspect-oriented approach for both Ruby and Java. A comparison of the two implementations will also be provided. Example code for will help to clarify certain points. I will also address why one would choose one approach over the other with respect to hooking and aspect-oriented programming.
Hooking Concept
The hooking mechanism in Ruby allows functional events inside a running program to be identified while providing an action that will occur upon detection of the specified event. In this way, hooks serve as a detector of actions within the code, when event x is encountered, y will be performed. Examples where such a technique would prove useful include:
- Recording when or how many times a method is called.
- Recording when an object is created.
- Debugging problematic code.
Example of Hooking System Calls
The following is an example of a hook being inserted to perform a certain action when the program makes a system call. In order for this to occur, an alias must be created so that the original functionality can still be accessed. In this case, original_system is aliased to the unaltered system. Once this is done, we can freely modify the definition of system to perform just about any operation. This is similar to the way in which a nefarious hook, known as a rootkit, would work by still performing the intended operation of the code, but also appending something extra in the process... In our current non-malicious case, the system method has been modified to print the command being executed ("ls -al" in this case), the timestamp, and then finally executing the original instruction and sending the output to standard output <ref>CIA World Factbook, 2006.</ref>.
module Kernel alias_method :original_system, :system def system(*args) puts "Your program executed the \"#{args.join(', ')}\" command." puts "It was executed at: " + `date` puts "The command output the following: " original_system(*args) end end system("ls -al")
Output: Your program executed the "ls -al" command. It was executed at: Mon Jun 2 21:19:27 EDT 2008 The command output the following: total 20 drwxr-xr-x 2 brking brking 4096 2008-06-02 21:19 . drwxr-xr-x 18 brking brking 4096 2008-06-02 21:19 .. -rw-r--r-- 1 brking brking 134 2008-06-01 22:01 fib.rb -rw-r--r-- 1 brking brking 235 2008-06-02 20:29 hooking.rb -rw-r--r-- 1 brking brking 287 2008-06-02 21:19 moreHooking.rb
Example of Hooking a Class Method
Similarly, a method could be hooked to perform additional tasks upon calling the method. The method would still carry out its original functionality, but the new instructions would precede or follow. In this example, the puts method of the IO class is hooked in order to print an additional statement to standard output every time puts is called. As in the previous example, the original method is aliased so that it can still be referenced after making enhancements.
class IO alias_method :original_puts, :puts def puts(*args) original_puts(*args) original_puts("You didn't ask for permission to speak!") end end
puts "Hi there."
Output: Hi there. You didn't ask for permission to speak!
Aspect-Oriented Programming Concept
Aspect-oriented programming deals with abstracting operations in code which are independent of the main purpose or function of the code. As an illustration, a program may have two completely unrelated classes, an automobile class and a plant class. Setting aside for a moment trying to conceive of an application where the two would reside together, there could potentially be similar data desired from objects of both classes. For example, it might be worthwhile knowing the time in which each object gets instantiated, logging events on both objects, or even telling when they pass out of scope, These three commonalities are associated with each object of the class, but have nothing to do specifically with the functionality of the object. Clearly there are many other transcending pieces of information that could be attributed to either class.
In the aspect-oriented vernacular, code written to obtain this common data is said to be cross-cutting in that it applies to multiple facets of the code, but does not specifically deal with the business or core concerns of the code. This cross-cutting code is also known as advice. The specific places in the code where it is determined that advice is needed is referred to as a pointcut or jointpoint. From there, the aspect embodies the combination of the jointpoint and advice for use by the program. In the previous example, there would be aspects for timestamps, event logging, and scope tracking [1]. It is important to note that the creation of aspects leaves the original underlying class intact and simply provides a wrapper which will encompass it. This ability to work on existing code without altering has the added advantage of making it more adaptable to future changes and enhancement requests later in the application's life cycle. Aspect-orientation does not in any way undermine object-orientation. Aspect-oriented programming complements object-oriented programming in that OOP governs top-down relationships while AOP governs left-right relationships within the application [2].
Aspect-Oriented Programming Implementations
A non-aspect-oriented implementation of something as simple as logging would require logging statements to be manually inserted wherever needed. If you were looking for a return value of every method in every class to be output to a log file, this would become tedious, as a logging statement would have to be positioned prior to the return of each method call in the program. In addition to the insertion, if anything about the way logging was handled were changed, then each logging statement would have to also be changed in an error prone manner [3]. By identifying logging as a cross-cutting concern and assigning an aspect to address it, only the aspect need ever be changed. The aspect will reside completely separate from the original code which will keep the application from being riddled with logging statements. The primary aspect-oriented tools for Ruby and Java are AspectR and AspectJ respectively. Below are samples provided to illustrate aspects for logging in both AspectR and AspectJ.
Example Using AspectR
The following is a sample implementation of AspectR used for logging [4].
require 'aspectr' include AspectR class Logger < Aspect def tick; "#{Time.now.strftime('%Y-%m-%d %X')}"; end def log_enter(method, object, exitstatus, *args) $stderr.puts "#{tick} #{self.class}##{method}: args = #{args.inspect}" end def log_exit(method, object, exitstatus, *args) $stderr.print "#{tick} #{self.class}##{method}: exited " if exitstatus.kind_of?(Array) $stderr.puts "normally returning #{exitstatus[0].inspect}" elsif exitstatus == true $stderr.puts "with exception '#{$!}'" else $stderr.puts "normally" end end end if $0 == __FILE__ class SomeClass def some_method sleep 1 puts "Hello!" [:t, "sd"] end def some_other_method(*args) raise NotImplementedError end end Logger.new.wrap(SomeClass, :log_enter, :log_exit, /some/) SomeClass.new.some_method begin SomeClass.new.some_other_method(1, true) rescue Exception end end
Example Using AspectJ
The following is a sample implementation of AspectJ used for logging [5]. As in the AspectR example, upon closer examination it is fairly easy to follow what is taking place. Immediately within the TraceAspect aspect, the Java logger is initialized and a call is made to the point-cut traceMethods. The "!within(TraceAspect)" tells the code to not perform the logging trace within the aspect code since it will be performing it everywhere else as indicated by the wildcards. TraceMethods will reach every method and output the timestamp, class and method name, along with an indication that the method is being "entered."
import java.util.logging.*; import org.aspectj.lang.*; public aspect TraceAspect { private Logger _logger = Logger.getLogger("trace"); pointcut traceMethods() : execution(* *.*(..)) && !within(TraceAspect); before() : traceMethods() { Signature sig = thisJoinPointStaticPart.getSignature(); _logger.logp(Level.INFO, sig.getDeclaringType().getName(), sig.getName(), "Entering"); } }
Conclusions
Hooks versus Aspect-Oriented Programming Tools
Given the nature of the aspect-oriented approach, it is easy draw parallels with hooking techniques. Ruby hooks can accomplish the same objective as an aspect-oriented tool. In addition to interceptors and mixins, Ruby hooks manage to cover a significant amount of what is present in separate AOP tools [6]. The main function present in aspect-oriented tools which is not natively present in Ruby, is the ability to directly specify point-cuts [7][8]. By using native techniques built into Ruby, you can accomplish much of what an AOP tool can without having to learn the syntax and requiring additional overhead [9]. Two key differences between hooking and using non-native AOP tools are that aspects do not require editing of a class and a single aspect has the ability to affect different classes in different ways [10].
AspectR versus ApectJ
AspectR and AspectJ both facilitate the use of aspect-oriented programming in their respective languages. How they go about doing it does differ. With AspectJ, the AOP code is preprocessed prior to being compiled into bytecode. Essentially there are two compilations. In the case of AspectR, the AOP wrapping is performed dynamically at runtime, which is a tremendous adavantage.
Another important factor to consider is that AspectR is still in an infant stage, as it is an alpha release, and is therefore not as well developed as AspectJ. The creators of AspectR admit that it is currently missing features present in AspectJ such as [11]. It is important to note that while AspectR may be lacking in some of the features present in AspectJ, its code base is considerable smaller [12].
References and Resources for Additional Information
1. Programming Ruby: The Pragmatic Programmers' Guide 2. http://aosd.net/ <references/>