Skip to content
Ruedi Steinmann edited this page Mar 30, 2015 · 4 revisions

Intercepting methods with Salta

Aspect Oriented Programming

To compliment dependency injection, Salta supports method interception. This feature enables you to write code that is executed each time a matching method is invoked. It's suited for cross cutting concerns ("aspects"), such as transactions, security and logging. Because interceptors divide a problem into aspects rather than objects, their use is called Aspect Oriented Programming (AOP).

Salta does not attempt to replace a full fledged AOP framework like aspectj. But the provided AOP support is simple and sufficient in many cases.

To implement an aspect, you will need to select the matching methods, create an interceptor, and configure it in a module.

Matcher is a simple interface that either accepts or rejects a value. For Salta AOP, you need two matchers: one that defines which classes participate, and another for the methods of those classes. To make this easy, there's factory class to satisfy the common scenarios.

MethodInterceptors are executed whenever a matching method is invoked. They have the opportunity to inspect the call: the method, its arguments, and the receiving instance. They can perform their cross-cutting logic and then delegate to the underlying method. Finally, they may inspect the return value or exception and return. Since interceptors may be applied to many methods and will receive many calls, their implementation should be efficient and unintrusive.

Example: Forbidding method calls on weekends

To illustrate how method interceptors work with Salta, we'll forbid calls to our pizza billing system on weekends. The delivery guys only work Monday thru Friday so we'll prevent pizza from being ordered when it can't be delivered! This example is structurally similar to use of AOP for authorization.

To mark select methods as weekdays-only, we define an annotation:

@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD)
@interface NotOnWeekends {}

...and apply it to the methods that need to be intercepted:

public class RealBillingService implements BillingService {

  @NotOnWeekends
  public Receipt chargeOrder(PizzaOrder order, CreditCard creditCard) {
    ...
  }
}

Next, we define the interceptor by implementing the com.github.ruediste.salta.standard.binder.SaltaMethodInterceptor interface. When we need to call through to the underlying method, we do so by calling proxy.invoke():

private class WeekendBlocker implements SaltaMethodInterceptor {
  @Override
  public Object intercept(Object delegate, Method method, Object[] args,
      MethodProxy proxy) throws Throwable {
    Calendar today = new GregorianCalendar();
    if (today.getDisplayName(Calendar.DAY_OF_WEEK, Calendar.LONG,
        Locale.ENGLISH).startsWith("S")) {
      throw new IllegalStateException(method.getName()
          + " not allowed on weekends!");
    }
    return proxy.invoke(delegate, args);
  }
}

Finally, we configure everything. This is where we create matchers for the classes and methods to be intercepted. In this case we match any class, but only the methods with our @NotOnWeekends annotation:

public class NotOnWeekendsModule extends AbstractModule {
  protected void configure() {
    bindInterceptor(Matchers.any(), Matchers.annotatedWith(NotOnWeekends.class), 
        new WeekendBlocker());
  }
}

Putting it all together, (and waiting until Saturday), we see the method is intercepted and our order is rejected:

java.lang.IllegalStateException: chargeOrder not allowed on weekends!
  at com.github.ruediste.salta.jsr330.wikiChecks.AopTest$WeekendBlocker.intercept(AopTest.java:40)
  at com.github.ruediste.salta.standard.binder.StandardBinder$1$5.intercept(StandardBinder.java:362)
  at com.github.ruediste.salta.jsr330.wikiChecks.AopTest$RealBillingService$$EnhancerByCGLIB$$304f242a.chargeOrder(<generated>)

Limitations

Behind the scenes, method interception is implemented by generating bytecode at runtime. Salta dynamically creates a proxy that applies interceptors by overriding methods.

This approach imposes limits on what classes and methods can be intercepted:

  • Classes must be public or package-private.
  • Classes must be non-final
  • Methods must be public, package-private or protected
  • Methods must be non-final

Injecting Interceptors

If you need to inject dependencies into an interceptor, use the requestInjection API.

public class NotOnWeekendsModule extends AbstractModule {
  protected void configure() {
    WeekendBlocker weekendBlocker = new WeekendBlocker();
    requestInjection(weekendBlocker);
    bindInterceptor(Matchers.any(), Matchers.annotatedWith(NotOnWeekends.class), 
       weekendBlocker);
  }
}

Another option is to use Binder.getProvider and pass the dependency in the constructor of the interceptor.

public class NotOnWeekendsModule extends AbstractModule {
  protected void configure() {
    bindInterceptor(any(),
                    annotatedWith(NotOnWeekends.class),
                    new WeekendBlocker(getProvider(Calendar.class)));
  }
}

Use caution when injecting interceptors. If your interceptor calls a method that it itself is intercepting, you may receive a StackOverflowException due to unending recursion.

Clone this wiki locally