Search code examples
javaaopguice

Incorporating Guice and AOP


I'm building a package that is trying to intercept a function's return value based on a flag. My design involves some AOP. The idea is that a class FirstIntercept intercepts a call firstCall and stores parameters in a Parameters object. Then later, a second class SecondIntercept intercepts another call secondCall and does some logic based on what is populated in Parameters:

// pseudoish code 
public class FirstIntercept {
   private Parameters param;

   @AfterReturning(pointcut = "execution(* ...firstCall(..))", returning = "payload")
   public void loadParam(Joinpoint joinPoint, Object payload) {
      // logic handling payload returned from firstCall()
      // logic provides a Boolean flag
      this.param = new Parameters(flag);
   }
}



public class Parameters {
   @Getter
   private Boolean flag;

   public Parameters(Boolean flag) {
      this.flag = flag;
   }
}



public class SecondIntercept {
   private static Parameters params;
   
   @Around("execution(* ...secondCall(..))")
   public void handleSecondCallIntercept(ProceedingJoinPoint joinPoint) {
      // want to do logic here based on what params contains
   }
}

What I want to achieve is that the Parameters object is loaded once and for all when FirstIntercept.loadParam is invoked through AOP. I'm not too sure how I can go about with this persistence. I looked online and Google guice seems to be promising. I believe a first step would to use dependency injection on the Parameters, but I'm really not sure. Can someone help point me in the right direction?


edit:

So I tried this setup:

public class FirstIntercept implements MethodInterceptor {
   public Object invoke(MethodInvocation invocation) throws Throwable {
      System.out.println("invoked!");
      return invocation.proceed();
   }

   @AfterReturning(pointcut = "execution(* ...firstCall(..))", returning = "payload")
   public void loadParam(Joinpoint joinPoint, Object payload) {
      // do stuff
   }

   public String firstCall() {
      return "hello";
   }
}

public class InterceptionModule extends AbstractModule {
   protected void configure() {
      FirstIntercept first = new FirstIntercept();
      bindInterceptor(Matchers.any(), Matchers.annotatedWith(AfterReturning.class), first);
   }
}

public class FirstIterceptTest {
   @Test
   public void dummy() {
      Injector injector = Guice.createInjector(new InterceptionModule());
      FirstIntercept intercept = injector.getInstance(FirstIntercept.class);

      intercept.firstCall();
   }
}

When I do .firstCall(), I can see the @AfterReturning running but the invoke is not being called.


Solution

  • If you expand upon the documentation for AOP https://github.com/google/guice/wiki/AOP you should get something close to:

    public class FirstInterceptor implements MethodInterceptor {
    
      @Inject Parameters parameters;  // Injected with singleton Parameter
    
      public Object invoke(MethodInvocation invocation) throws Throwable {
        Object result = invocation.proceed();
        // your logic based on result to set parameters.setFlag()
        return result;
      }
    }
    

    Then the second:

    public class SecondInterceptor implements MethodInterceptor {
    
      @Inject Parameters parameters;  // Injected with singleton Parameter
    
      public Object invoke(MethodInvocation invocation) throws Throwable {
        boolean flag = parameters.getFlag();
        // your logic here
        return invocation.proceed(); // maybe maybe not?
      }
    }
    

    Your parameters is the key, you'll need to ensure it's thread safe, which is another topic. But to inject these you need:

    public class InterceptionModule extends AbstractModule {
      protected void configure() {
        // Ensure there is only ever one Parameter injected
        bind(Parameter.class).in(Scopes.SINGLETON);
    
        // Now inject and bind the first interceptor
        FirstInterceptor firstInterceptor = new FirstInterceptor();
        requestInjection(firstInterceptor );
        bindInterceptor(Matchers.any(), Matchers.annotatedWith(AfterReturning.class),
            firstInterceptor);
    
        // Now inject and bind the second interceptor
        SecondInterceptor SecondInterceptor = new SecondInterceptor ();
        requestInjection(firstInterceptor);
        bindInterceptor(Matchers.any(), Matchers.annotatedWith(AfterReturning.class),
            SecondInterceptor);
      }
    }
    

    Edit Look at what you're doing.

    1. You're telling Guice to wrap a method with @AfterReturn with the FirstInterceptor
    2. Then you're calling interceptor.firstCall()

    First call does not have @AfterReturn annotation, so why would it be matched against that configuration?

    I'm guessing if you called:

    intercept.loadParam();
    

    you would see the invoke method. Also, this is great for a test, but in real life you want to have a Service level class have the @AfterReturn which is then Injected into another Api/Job/Etc that will call LoadParam.

    edit Oh no. Take a look at this line

    bindInterceptor(Matchers.any(),  // a class with this matcher
       Matchers.annotatedWith(AfterReturning.class),  // a method with this 
            firstInterceptor); 
    

    This means that the injector only fires on the loadParams. You need to annotate the method of the class youw ish to cause the interception with @AfterReturning. And you want the loadParams to be the invoke method.