For common concerns such as caching, auditing, and so on, you can use an interceptor-like API. It is enabled on services by the framework.
An interceptor defines an annotation marked with @Intercepts
, which defines the implementation of the interceptor (InterceptorHandler
).
For example:
@Intercepts(LoggingHandler.class)
@Target({ TYPE, METHOD })
@Retention(RUNTIME)
public @interface Logged {
String value();
}
The handler is created from its constructor and can take service injections (by type). The first parameter, however, can be BiFunction<Method, Object[], Object>
, which represents the invocation chain if your interceptor can be used with others.
If you make a generic interceptor, pass the invoker as first parameter. Otherwise you cannot combine interceptors at all. |
Here is an example of interceptor implementation for the @Logged
API:
public class LoggingHandler implements InterceptorHandler {
// injected
private final BiFunction<Method, Object[], Object> invoker;
private final SomeService service;
// internal
private final ConcurrentMap<Method, String> loggerNames = new ConcurrentHashMap<>();
public CacheHandler(final BiFunction<Method, Object[], Object> invoker, final SomeService service) {
this.invoker = invoker;
this.service = service;
}
@Override
public Object invoke(final Method method, final Object[] args) {
final String name = loggerNames.computeIfAbsent(method, m -> findAnnotation(m, Logged.class).get().value());
service.getLogger(name).info("Invoking {}", method.getName());
return invoker.apply(method, args);
}
}
This implementation is compatible with interceptor chains because it takes the invoker as first constructor parameter and it also takes a service injection. Then, the implementation simply does what is needed, which is logging the invoked method in this case.
The findAnnotation annotation, inherited from InterceptorHandler , is an utility method to find an annotation on a method or class (in this order).
|