Tuesday, February 16, 2010

Four Approaches of Mixins in Java with AspectJ

Aspect Oriented Programming (AOP) is used to introduce cross-cutting concerns in several OO classes at once, for example mixins. In Java, AspectJ is the most popular tool for doing aspect oriented programming and domain-driven development.

If you're not familiar with AOP or AspectJ, the book Aspectj in Action: Enterprise AOP with Spring Applications is an excellent resource for learning AOP.

One use case of AOP is for implementing mixins or traits. Its purpose is to introduce members aka methods, properties, and fields to an existing class, and also implement interfaces. This is called static structure weaving.

There are four approaches to do static structure weaving.

Here I list them all with their benefits and weaknesses, and conclusion of when you should use it:

1. non-AOP: proxy to interface Impl

+ weaved API completion is available from consumer object
+ no need for external tool (AspectJ) nor any IDE and build configuration/plugin
+ mixin Impl can be reused like approach #3 and #4 below
- needs @Embedded for JPA
- overriden aspect methods do not take effect inside aspect methods (must take the aspect Impl as a whole)
- need to code proxy methods manually

Conclusion: just say good bye to this programming model from hell. Thank me later.

Example:
public class Note implements Nameable {

 @Embedded
 private Nameable nameable = new NameableImpl();

 @Override
 @Transient
 public String getName() {
  return nameable.getName();
 }

 @Override
 public void setName(String name) {
  nameable.setName(name);
 }
 
 @Override
 public boolean hasName() {
  return nameable.hasName();
 }

} 

2. AspectJ-only

+ can override aspect methods at will
 + no need for @Embedded for JPA
 - require AspectJ IDE
+ weaved API completion is available from consumer object

Conclusion: bBest choice for developing aspects, need AspectJ IDE. Resulting classes can be used normally (even with non-AspectJ IDE), especially if you use build-time weaving.

Example class:

@NameableMixin
public class Ticket { }

Example aspect:
public aspect NameableAspect {

 declare parents : @NameableMixin * implements Nameable;
 declare parents : @NameableMixin * implements NameableSupport;

 public String NameableSupport.name;
 
 public String NameableSupport.getName() {
  return name;
 }
 
 public void NameableSupport.setName(String name) {
  this.name = name;
 }

 public boolean NameableSupport.hasName() {
  return getName() != null && !getName().isEmpty();
 }
}

3. @AspectJ annotations

+ can override aspect methods at will
 + no need for @Embedded for JPA
 + usable in any IDE
 - weaved API completion not available from consumer object

Conclusion: if you definitely can't use AspectJ IDE, start from this  so you can develop aspects fast. beware that usage from client objects is not so convenient.

Example class:

@NameableMixin2
public class Project { }
Example aspect using @AspectJ notation:
@Aspect
public class NameableAspect2 {

 @DeclareMixin("@NameableMixin2 *")
 public Nameable nameableMixin() {
  return new NameableImpl();
 }
 
}
Example mixin Impl:
@Embeddable
public class NameableImpl implements Nameable {

 private String name;
 
 @Override
 @Basic
 public String getName() {
  return name;
 }

 @Override
 public void setName(String name) {
  this.name = name;
 }

 @Override
 public boolean hasName() {
  return getName() != null && !getName().isEmpty();
 }

}
Using the aspected class with typecasting: (pretty bad huh?)

  Project project = new Project();
  Nameable named = (Nameable)project;
  named.setName("Get coffee");
  System.out.println(named.getName());

4. AspectJ + Impl hybrid

Hybrid here means using the exact semantics of a non-AOP approach (proxying). Implementation of the aspect is a combination of AspectJ syntax for the aspect "proxy", plus standard Java for actual mixin implementation.

In essence, this is a modified approach #3 that replaces the @AspectJ-annotation aspect, with AspectJ syntax aspect to introduce interface members that delegates all calls to a separate concrete implementation (in plain Java).

+ single Impl for both non-AOP (approach #1), @AspectJ (approach #3), and hybrid (this one)
+ weaved API completion is available from consumer object
+ Impl editable from any IDE. aspect code is simple thus AspectJ IDE not so required.
+ Impl can be marked as @Embeddable, but weaved class does not need to use @Embedded
- overriden aspect methods do not take effect inside aspect methods (must take the aspect Impl as a whole)
- code twice for each method: the Impl and the aspect proxy methods

Conclusion: if you don't have AspectJ IDE and you need to access the API easily. beware that aspect methods won't be overridable. you can also start from @AspectJ annotations first then switch to this when the aspect implementation is "stable". Don't start with this approach because you'll be burdened with synchronizing the AspectJ aspect with the mixin Impl.

If you want to learn more about AspectJ and AOP in general, the book Aspectj in Action: Enterprise AOP with Spring Applications by Ramnivas Laddad has the most comprehensive coverage on this subject.

1 comment:

  1. Nice article, thanks.
    Why does the aspect NameableAspect in approach #2 declare Nameable and NameableSupport as parents and not just Nameable?

    ReplyDelete