views:

1342

answers:

3

Is there a way to make this code work?

LogonControl.java

@Audit(AuditType.LOGON)
public void login(String username, String password) {
 // do login
}

AuditHandler.java

public void audit(AuditType auditType) {
 // persist audit
}

Endgame being, that each time login() is called, audit() is also called, with the appropriate audittype.

I imagine AOP is probably the solution to this, but I would like it to be as simple as possible (the AspectJ tutorials I've looked at normally have very convoluted annotations).

Note: I don't want to have to predefine the methods that will call audit, I'm writing this for an extensible framework, and others may need to use it.

+4  A: 

It's done - use Spring or Guice.

Rolling your own makes sense if you want to know how wheels work, or if you think that you can do something that's significantly lighter. Just be sure that both are true before you undertake it.

duffymo
Trust me - I have no desire to roll my own, but Spring is OTT for my uses. I'll look into Guice, initially it looks good.
Robert Wilson
My point would be that you don't have to swallow ALL of Spring to get benefit from it. Spring AOP is pretty light weight compared to AspectJ (less powerful, as well). It might serve your needs just fine. You don't have to deal with all of Spring, just the small subset that pertains to AOP. I've found that Spring works well for these situations. It's possible to introduce it into a legacy app for a narrow purpose without demanding a total rewrite. Try it.
duffymo
There's already a built-in trace aspect in Spring. You might be able to do what you want with a little configuration and no new code.
duffymo
@duffymo, can you post a link to the trace aspect please.
Rich Seller
Have a look at the Interceptors in the org.springframework.aop.interceptor package: http://static.springsource.org/spring/docs/3.0.x/javadoc-api/. I'm thinking of the SimpleTraceInterceptor or PerformanceMonitorInterceptor.
duffymo
+4  A: 

Using reflection is easy just annotate a method with @Audit, just like test runners in JUnit:

public interface Login {

    void login(String name, String password);
 }

public class LoginImpl implements Login {

    @Audit(handler = LoginHandler.class)
    public void login(String name, String password) {
        System.out.println("login");
    }

}

@Audit is defined as:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Audit {

   Class<? extends Handler> handler();
}

where Handler is:

interface Handler {

    void handle();
}

class LoginHandler implements Handler {

    public void handle() {
        System.out.println("HANDLER CALLED!");
    }
}

and now the real code:

public class LoginFactory {

    private static class AuditInvocationHandler implements InvocationHandler {

        private final Login realLogin;

        public AuditInvocationHandler(Login realLogin) {
            this.realLogin = realLogin;
        }

        public Object invoke(Object proxy, Method method, Object[] args) 
                      throws Throwable {
            Method realMethod = realLogin.getClass().getMethod(
                                        method.getName(), 
                                        method.getParameterTypes());
            Audit audit = realMethod.getAnnotation(Audit.class);

            if (audit != null) {
                audit.handler().newInstance().handle();
            }

            return method.invoke(realLogin, args);
        }
    }

    public static Login createLogin() {
        return (Login) Proxy.newProxyInstance(
                LoginFactory.class.getClassLoader(),
                new Class[]{Login.class},
                new AuditInvocationHandler(new LoginImpl()));
    }
}

@Test:

    Login login = LoginFactory.createLogin();
    login.login("user", "secret");
    login.logout();

output:

HANDLER CALLED!
login
logout
dfa
Seems like the best solution. I was hoping to be able to keep my code as-is and just add annotations, but after a lot of research I don't think that's possible.Cheers.
Robert Wilson
+1  A: 

Have a look at intercepting methods in Guice: http://code.google.com/p/google-guice/wiki/AOP

A similar approach should work with any AOP framework.

Esko Luontola
Thanks for that.This line scares me a little:"It is not possible to use method interception on instances that aren't constructed by Guice."By my understanding, this would mean that I'd have to use Guice.getInstance(LogonController.class) before the method intercepting will work?I'll look into whether this is possible in another AOP framework.
Robert Wilson
It means that Guice will have to construct the object, but it doesn't mean that you will have to ask Guice to construct it. The object could be injected into another object that is constructed by Guice.Essentially, Guice would replace the Factory in the solution that dfa gave.
NamshubWriter