Writing Java Aspects ... with JRuby and Aquarium! 26
Aquarium V0.4.0, my AOP library for Ruby, now supports JRuby. Not only do the regular “pure Ruby” Aquarium specs run reliably under JRuby (V1.1RC2), but you can now write aspects for Java types with Aquarium!
There are some important limitations, though. Cartographers of old would mark dangerous or unknown territory on their maps with hic sunt dracones (“here be dragons”), a reference to the old practice of adorning maps with serpents around the edges.
This is true of Aqurium + Java types in JRuby, too, at least for now.
Aquarium uses Ruby’s metaprogramming API extensively and the JRuby team has done some pretty sophisticated work to integrate Java types with Ruby. Hence, it’s not too surprising there are some gotchas. Hopefully, workarounds will be possible for all of them.
The details are discussed on the JRuby page, the README on the Aquarium site, and of course the “specs” in the distribution’s jruby/spec
directory. I’ll summarize them here, after discussing the pros and cons of Aquarium vs. the venerable AspectJ and showing you an example of using Aquarium for Java.
Briefly, Aquarium’s advantages over AspectJ are these:
- You can add and remove advice dynamically at runtime. You can’t remove AspectJ advice.
- You can advise JDK types easily with Aquarium. AspectJ won’t do this by default, but this is really more of a legacy licensing issue than a real technical limitation.
- You can advise individual objects, not just types.
Aquarium’s disadvantages compared to AspectJ include:
- Aquarium will be slower than using AspectJ (although this has not been studied in depth yet).
- Aquarium’s pointcut language is not as full-featured as AspectJ’s.
- There are the bugs and limitations I mentioned above in this initial V0.4.0 release, which I’ll elaborate shortly.
Here is an example of adding tracing calls to a method doIt
in all classes that implement the Java interface com.foo.Work
.
Aspect.new :before, :calls_to => [:doIt, :do_it], :in_types_and_descendents => Java::com.foo.Work do |jp, obj, *args|
log "Entering: #{jp.target_type.name}##{jp.method_name}: object = #{object}, args = #{args.inspect}"
end
There are two important points to notice in this example:
- You can choose to refer to the method as
do_it
(Ruby style) ordoIt
, but these variants are effectively treated as separate methods; advice on one will not affect invocations of the other. So, if you want to be sure to catch all invocations, use both forms. There is a bug (18326) that happens in certain conditions if you use just the Java naming convention. - If the type is an interface, you must use
:types_and_descendents
(or one of the supported variants on the wordtypes
...). Since interfaces don’t have method implementations, you will match no join points unless you use the_and_descendents
clause. (By default, Aquarium warns you when no join points are matched by an aspect.) However, there is a bug (18325) with this approach if Java types are subtyped in Ruby.
Limitations and Bugs
Okay, here’s the “fine print”...
In this (V0.4.0) release, there are some important limitations.
- Aquarium advice on a method in a Java type will only be invoked when the method is called directly from Ruby.
- To have the advice invoked when the method is called from either Java or Ruby, it is necessary to create a Ruby subclass of the Java type and override the method(s) you want to advise. These overrides can just call
super
. Note that it will also be necessary for instances of this Ruby type to be used throughout the application, in both the Java and Ruby code. So, you’ll have to instantiate the object in your Ruby code.
Yea, this isn’t so great, but if you’re motivated… ;)
There are also a few outstanding Aquarium bugs (which could actually be JRuby bugs or quirks of the Aquarium-JRuby “interaction”; I’m not yet sure which).
- Bug #18325: If you have Ruby subclasses of Java types and you advise a Java method in the hierarchy using
:types_and_descendents => MyJavaBaseClassOrInterface
and you call unadvise on the aspect, the advice “infrastructure” is not correctly removed from the Ruby types. Workaround: Either don’t “unadvise” such Ruby types or only advise methods in such Ruby types where the method is explicitly overridden in the Ruby class. (The spec and the Rubyforge bug report provide examples.) - Bug #18326: Normally, you can use either Java- or Ruby-style method names (e.g.,
doSomething
vs.do_something
), for Java types. However, if you write an aspect using the Java-style for a method name and a Ruby subclass of the Java type where the method is actually defined (i.e., the Ruby class doesn’t override the method), Aquarium acts like the JoinPoint is advised, but the advice is never actually called. Workaround: Use the Ruby-style name in this scenario.
So, there is still some work to do, but it’s promising that you can use an aspect framework in one language with another. A primary goal of Aquarium is to make it easy to write simple aspects. My hope is that people who might find AspectJ daunting will still give Aquarium a try.
AOSD 2007 Conference 7
Last week, I attended the Aspect-Oriented Software Development 2007 Conference in Vancouver, BC, where I gave a tutorial on aspect-oriented design and a paper in the Industry Track, also about design.
AOSD and this particular conference are still mostly academic projects with some notable industry traction, especially in the Java world. It is also a technology that needs to break through to the next level of innovation and applicability, in my opinion.
The Industry Track had a number of interesting papers, including, for example, a paper that describes how aspects are used in the innovative Terracotta JVM clustering tool. Also, the last keynote by Adrian Colyer of Interface 21 recounted his personal involvement in the AspectJ and Spring communities, as well as the impact that aspects are having at major Interface 21 clients. It’s worth noting that many of the important Java middleware systems, e.g., Spring, JBoss, and Weblogic have embraced aspects to one degree or another.
AOSD tools like AspectJ and Spring AOP (AO Programming) solve obvious “cross-cutting concerns” (CCCs) like object-relational mapping, transactions, security, etc. in Java. However, I feel that AOSD needs some breakthrough innovations to move beyond its current role as a useful niche technology to a more central role in the software development process. I get the impression that industry interest in AOP has reached a plateau.
The academic community is doing some interesting work on fundamentals of AOSD theory (type theory, modeling, categorizing types of aspects, etc.), on “early aspects” (e.g., cross-cutting requirements), and on tooling. There is also a lot of minor iterating around the edges, but that’s the nature of academic research (speaking as one who has been there…). Most of the research work is a long ways from practical applicability.
However, I’m seeing too much emphasis on extending the work of AspectJ-like approaches in statically-typed languages, rather than innovating in new areas like new applications of aspects and the nature of AOSD in dynamic languages.
My recent work with Ruby has made me think about these two topics lately. I’ll blog about these topics later, but for now, I’ll just say that I anticipate a fruitful growth area for AOSD will be to facilitate the implementation of powerful DSLs (Domain Specific Languages), a popular topic in the Ruby community.
Here are some other observations from the conference.
All other engineering disciplines recognize cross-cutting concerns
Gregor Kiczales (the father of AspectJ and one of the fathers of AOSD) made this remark in a panel discussion on “early aspects”. He cited the examples of electrical engineers considering systemic issues in circuit design, like capacitance, current leakage, etc. and mechanical engineers who evaluate the stresses and strains of the entire structure they are designing (buildings, brake assemblies in cars, etc.).
Gregor also remarked that CCCs that are evident in the requirements may disappear in the implementation and vice-versa.
Gerald Sussman keynote
In the first keynote, Gerald Sussman argued that robust systems are adaptable for uses that were not anticipated by their designers. These systems often have multiple organizational ideas and a “component” structure that promotes “combinatorial behavior”.
He doesn’t like formalisms such as Dykstra’s A Discipline of Programming. Provable correctness and rigor aren’t very compatible with rich programs. Sussman prefers a more “exploratory” model, very much analogous to Test Driven Development (TDD), and what he calls Paranoid Programming, which he defined as “I won’t be at fault if it fails.”
More on AOSD
I will blog further about aspect-oriented design and aspects in dynamic languages.