JPz'log Coin Coin and Plop da Plop

11Feb/105

Revisiting Guice and AOP with AspectJ

I decided to revisit my article on Guice and AOP with AspectJ instead of the AOP Alliance API that Guice comes with.

The full source code is available on GitHub.

To refresh your memory, we had implemented a declarative approach for controlling the access to a class methods through annotations:

@WithUserProfileVerification
public class InMemoryContactManager implements ContactManager {
 
    private final Set<Person> contacts = new HashSet<Person>();
 
    @RequiresProfile(ADMIN)
    public ContactManager add(Person person) {
        contacts.add(person);
        return this;
    }
 
    @RequiresProfile(ADMIN)
    public ContactManager remove(Person person) {
        contacts.remove(person);
        return this;
    }
 
    @RequiresProfile(USER)
    public Person lookup(String name) {
        for (Person person : contacts) {
            if (person.getName().equals(name)) {
                return person;
            }
        }
        return null;
    }
 
}

First off AspectJ is way more expressive than the AOP Alliance API. Using Guice with AspectJ is not very different. If your aspects do not need injection then you fall back to plain AspectJ development, as Guice and AspectJ live apart from each other. Things are a little more subtile if you need to connect Guice with your aspects.

In our case we had one aspect that needed injection, hence the trick is to ask Guice to perform injection on the aspect instance. By default, an AspectJ aspect is a singleton, so all we need is to grab an access to the instance, which is as simple as calling the Aspects#aspectOf() static method.

Now let's see some code that differs from the Guice / AOP Alliance sample I showed you.

Here is how you can define a simple dirty console logger that hooks itself onto any implementation of the ContactManager interface:

package info.ponge.julien.hacks.guiceaspectj.aspects;
 
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
 
@Aspect
public class ContactManagerLogger {
 
    @Before("call( * info.ponge.julien.hacks.guiceaspectj.contact.ContactManager.*(..) )")
    public void methodCalled(JoinPoint thisJoinPoint) {
        System.out.println("Calling: " + thisJoinPoint.getSignature().getName());
    }
 
}

The aspect that checks for the user permissions is implemented as follows:

package info.ponge.julien.hacks.guiceaspectj.aspects;
 
import com.google.inject.Inject;
import info.ponge.julien.hacks.guiceaspectj.auth.RequiresProfile;
import info.ponge.julien.hacks.guiceaspectj.auth.UserProfile;
import info.ponge.julien.hacks.guiceaspectj.auth.UserProfileChecker;
import info.ponge.julien.hacks.guiceaspectj.auth.WithUserProfileVerification;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
 
import static info.ponge.julien.hacks.guiceaspectj.auth.UserProfile.ADMIN;
import static info.ponge.julien.hacks.guiceaspectj.auth.UserProfile.USER;
 
@Aspect
public class ProfileVerification {
 
    @Inject
    UserProfileChecker userProfileChecker;
 
    @Before("execution( * *(..) ) && @annotation( required ) && within( @WithUserProfileVerification * )")
    public void verify(RequiresProfile required) {
        UserProfile expected = required.value();
        UserProfile current = userProfileChecker.getCurrentUserProfile();
 
        if (insufficientProfile(expected, current)) {
            throw new RuntimeException("The current user profile (" + current + ") is not sufficient: " + required);
        }
    }
 
    private boolean insufficientProfile(UserProfile required, UserProfile current) {
        return (required == ADMIN && current != ADMIN)
                || (required == USER && (current != USER && current != ADMIN));
    }
 
}

Finally our Guice module is configured to perform the injection on the singleton that corresponds to the previous aspect:

package info.ponge.julien.hacks.guiceaspectj;
 
import com.google.inject.*;
import info.ponge.julien.hacks.guiceaspectj.aspects.ProfileVerification;
import info.ponge.julien.hacks.guiceaspectj.auth.UserProfileChecker;
import info.ponge.julien.hacks.guiceaspectj.auth.dumb.DumbUserProfileChecker;
import info.ponge.julien.hacks.guiceaspectj.contact.ContactManager;
import info.ponge.julien.hacks.guiceaspectj.contact.Person;
import info.ponge.julien.hacks.guiceaspectj.contact.simple.InMemoryContactManager;
import org.aspectj.lang.Aspects;
 
import static org.aspectj.lang.Aspects.*;
 
public class Main {
 
    public static void main(String[] args) {
        new Main().execute();
    }
 
    private Module guiceModule = new AbstractModule() {
        @Override
        protected void configure() {
 
            bind(ContactManager.class)
                    .to(InMemoryContactManager.class)
                    .in(Singleton.class);
 
            bind(UserProfileChecker.class)
                    .to(DumbUserProfileChecker.class)
                    .in(Singleton.class);
 
            requestInjection(aspectOf(ProfileVerification.class));
 
        }
    };
 
    private Injector injector = Guice.createInjector(guiceModule);
 
    private void execute() {
        ContactManager contacts = injector.getInstance(ContactManager.class);
        UserProfileChecker profileChecker = injector.getInstance(UserProfileChecker.class);
 
        profileChecker.login("Julien", "secret");
 
        contacts.add(new Person("Julien Ponge", "julien.ponge@gmail.com"));
        contacts.add(new Person("Jean-Jacques", "jean.jacques@gmail.com"));
 
        profileChecker.logout();
        profileChecker.login("Jean-Jacques", "1234");
 
        System.out.println(contacts.lookup("Julien Ponge"));
 
        profileChecker.logout();
        contacts.add(new Person("Mr Bean", "mrbean@gmail.com"));
    }
 
}

As one would expect, trying to add Mr Bean fails due to insufficient permissions:

Calling: add
Calling: add
Calling: lookup
Julien Ponge 
Calling: add
Exception in thread "main" java.lang.RuntimeException: The current user profile (ANONYMOUS) is not sufficient: @info.ponge.julien.hacks.guiceaspectj.auth.RequiresProfile(value=ADMIN)
	at info.ponge.julien.hacks.guiceaspectj.aspects.ProfileVerification.verify(ProfileVerification.java:27)
	at info.ponge.julien.hacks.guiceaspectj.contact.simple.InMemoryContactManager.add(InMemoryContactManager.java:21)
	at info.ponge.julien.hacks.guiceaspectj.Main.execute(Main.java:54)
	at info.ponge.julien.hacks.guiceaspectj.Main.main(Main.java:17)

(full code on GitHub)

Share this post:
  • Digg
  • del.icio.us
  • Facebook
  • Google Bookmarks
  • DZone
  • Live
  • Netvibes
  • StumbleUpon
  • Technorati
  • FriendFeed
  • Wikio
  • Twitter
  • Identi.ca
  • Reddit
  • RSS
  • Slashdot

Related posts:

  1. Guice it up (or AOP can be made simple sometimes)
  2. Playing with Berkeley DB Java Edition and Guice
  3. Initialization blocks in Java
  4. DecoratorInjector in Python by Yannick
  5. Rails-style Ruby meta-programming

Comments (5) Trackbacks (0)
  1. Hi,
    Can you explain why the code would not work without the “requestInjection(aspectOf(ProfileVerification.class));” statement.
    Is this somekind of disciplined aspect deployment to prevent that other aspects code cannot iintervene inside a guice module?

    I also read our AOP alliance article and there you requested the injection of the ProfileInterceptor, but not of the LoggingInterceptor. Why is that?

    Thanks in advance for an answer.

  2. Hi Eddy,

    In either case (AspectJ or AOP Alliance API) the access verification aspect needs an injection of a UserProfileChecker (see the @Inject annotation).

    Guice does not manage the aspects lifecycle, hence you have to manually ask for injection (or bind through a Guice provider). You would have exactly the same problem with Spring BTW.

    Finally, the logging aspect does not need injection, hence we can leave it out.

    Hope it helps!

  3. I did something similiar but instead of requesting injection “manually”, i wrote the following:

    pointcut createInjector(): call(Injector Guice.createInjector(..));

    after() returning (Injector injector): createInjector() {
    LOG.trace(“Injecting members on {}”, this);
    injector.injectMembers(this);
    }

    This code in my abstract base aspect makes all my aspects part of the “guice club”.
    What do you think?

    • Nice trick which keeps your aspects configuration out of the Guice module configuration. This makes it actually easier to plug / unplug aspects without recompiling… great idea.


Leave a comment


No trackbacks yet.

JPz'log is Digg proof thanks to caching by WP Super Cache