Search code examples
javaannotationsaop

Java: Simple technique for annotation-based code injection?


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.


Solution

  • 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