2

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

3 Answers 3

4

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.)

1

I dont believe you can achieve want you are want. With ITD's, you need to specify the concrete class name, eg:

private Logger MyClass.logger;

Where MyClass is the class to have the logger field added to. So you would have to add one of these for every single class (spring roo style).

You could add this logger field to a parent class, and make all classes extend this class, but this would not work for classes that already extend other classes.

You could add this field to an interface and make all classes implemente the interface as you have in your example, but this wont work as fields in interfaces are static final, so you cant call getClass().

You could use the alternative logger creation:

Logger Slf4jLogger.logger = LoggerFactory.getLogger(Slf4jLogger.class);

But this will then use the same category for all logging events, thereby rendering logging useless.

A radical approach could be to add it on the Object class:

final Logger Object.logger = LoggerFactory.getLogger(getClass());

But that could really mess things up. Just tried this, and says "affected type is not exposed to weaver".

Also, I dont believe its a good practice to add a logging field through aspects. I dont see any problem with adding a Logger field at the top of each class in the traditional manner. AOP should be used sparingly.

0

Just because user tgharold was mentioning mix-ins: In AspectJ 1.6.4 the feature @DeclareMixin was introduced - see also ticket #266552. Maybe it helps, I have not tried, but it seems promising to solve your mix-in type of problem.

Not the answer you're looking for? Browse other questions tagged or ask your own question.