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.
Nice article, thanks.
ReplyDeleteWhy does the aspect NameableAspect in approach #2 declare Nameable and NameableSupport as parents and not just Nameable?