I'm trying to use AspectJ to automatically add a logger field to all my classes to avoid me having to code boiler plate logger info in each class individually. This would seem to me like a fairly common practice, and yet I haven't been able to find any existing Aspects that accomplish this.
The aspect itself is fairly simplistic:
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public privileged aspect Slf4jLoggerAspect{
public interface Slf4jLogger{}
private final Logger Slf4jLogger.logger = LoggerFactory.getLogger(getClass());
declare parents: ????? extends Slf4jLogger;
}
My big question is what do I put for the ?????. I would like the TypePattern to indicate all classes, but not interfaces. If I try just using a * pattern, it picks up all my interfaces as well.
Is there something in AspectJ that allows me to pick out all my class defn's only?
Or does something like this already exist? I would have thought/expected to see this in Roo, but cannot find an slf4j roo addon or annotation.
Thanks!
Eric
First off, remember that in Java a class can only have a single super-class (using the "extends" keyword), but you can have multiple interfaces (implements). So you'll probably want to change to using "implements" isntead of "extends".
The following shows how to do a mix-in style approach as explained in AspectJ in Action, 2nd Edition by Ramnivas Laddad (chapter 5). This style works well when you are able to have classes implement an interface and you want to provide a default implementation.
package com.example.aop.attributes;
import java.util.Calendar;
import javax.persistence.Column;
import javax.persistence.PrePersist;
import javax.persistence.PreUpdate;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;
import javax.validation.constraints.NotNull;
/**
* Aspect that adds a timestamp attribute to the entity along with the logic to
* update that timestamp value just before it gets persisted to the database. It
* also adds a creation timestamp to track when the object was originally
* created.
*
* @author tgh
*
*/
public interface Timestamped {
public Calendar getCreationTimestamp();
public void setCreationTimestamp(Calendar creationTimestamp);
public Calendar getModificationTimestamp();
public void setModificationTimestamp(Calendar modificationTimestamp);
/**
* AspectJ MixIn for any class which implements the interface. Provides a
* default implementation using AspectJ. This is the style shown in
* Manning's AspectJ in Action (2nd edition) for providing a default
* implementation interface.
*
* @author tgh
*/
static aspect Impl {
@NotNull
@Temporal(TemporalType.TIMESTAMP)
@Column(name = "created", nullable=false)
private Calendar Timestamped.creationTimestamp = Calendar.getInstance();
@NotNull
@Temporal(TemporalType.TIMESTAMP)
@Column(name = "last_modified", nullable=false)
private Calendar Timestamped.modificationTimestamp = Calendar.getInstance();
public Calendar Timestamped.getCreationTimestamp() {
return this.creationTimestamp;
}
public void Timestamped.setCreationTimestamp(Calendar creationTimestamp) {
this.creationTimestamp = creationTimestamp;
}
public Calendar Timestamped.getModificationTimestamp() {
return this.modificationTimestamp;
}
public void Timestamped.setModificationTimestamp(Calendar modificationTimestamp) {
this.modificationTimestamp = modificationTimestamp;
}
@PrePersist
@PreUpdate
private void Timestamped.updateModificationTimestampDuringPrePersistAndPreUpdate() {
this.modificationTimestamp = Calendar.getInstance();
}
}
}
To use the above, you just add "implements Timestamped" to the entity class. That's the easiest way to do mix-ins where you introduce members (new attributes) to the class. You can also do mix-ins using annotations on the source.
In cases where you can't modify the source, you're going to have to play around with the declare parents. In the case of only tagging entities where they already had "@Entity" on their class declaration this would be:
declare parents: @Entity * implements Slf4jLoggerAspect;
Or:
declare parents: (@MakeLoggable *) implements Slf4jLogger;
If your interfaces are in a separate package, then you could try a TypePattern that only tags a specific package (and not the interfaces package):
declare parents: my.class.package.* implements Slf4jLogger;
And according to chapter 3.5 in AspectJ in Action (2nd Ed), you can also use binary operators on the Type Pattern element:
declare parents: (@Entity *) || (@MakeLoggable *) implements Slf4jLogger;
I'm not 100% sure that the above works, but there is an "||" binary operator and I've seen that used elsewhere (but can't find the link).
(And no, I don't think you can tell the Type Pattern to only look at classes and not interfaces. Although there is possibly hasMethod() and hasField() but those were marked as experimental in the book.)