Your standard CDI/EJB Interceptor example uses a logger as an around advice. Generally this gives you an interceptor that looks like so:
@Log public class LoggingInterceptor { private java.util.logging.Logger logger = java.util.logging.Logger.getLogger("theLogger"); @AroundInvoke public Object intercept(InvocationContext context) throws Exception { logger.info("" + context.getMethod().getName()); return context.proceed(); } }
As of the Interceptors 1.1 spec, you can bind that to a bean via creating our own javax.interceptor.InterceptorBinding annotation. In our example, we've created one called @Log:
@InterceptorBinding @Target(value = {ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) public @interface Log { }
Now we just apply that to the bean as follows.
@Log public class FooBean { public void somethingCommon(){ //... } public void somethingImportant() { //... } public void somethingNoteworthy() { //... } }
Great! Now we are done. Every time that bean is invoked, the LoggerInterceptor will issue a log message on info level. Aren't interceptors wonderful! End of story, right? Not quite.
Fundamentally, our example is still very contrived. Who wants to log everything on the same level?
Here is a little pattern that you can use to better advise your LoggerInterceptor around advice. First, we create a couple new annotations for log levels: @Fine and @Info
@Target(value = {ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) public @interface Fine { } @Target(value = {ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) public @interface Info { }
Then we apply those to the bean...
@Log public class FooBean { public void somethingCommon(){ //... } @Info public void somethingImportant() { //... } @Fine public void somethingNoteworthy() { //... } }
Now we alter our LoggerInterceptor to check for our new annotations and alter its behavior.
@Log public class LoggingInterceptor { private java.util.logging.Logger logger = java.util.logging.Logger.getLogger("theLogger"); @AroundInvoke public Object intercept(InvocationContext context) throws Exception { final Method method = context.getMethod(); if (method.isAnnotationPresent(Info.class)) { return info(context); } else if (method.isAnnotationPresent(Fine.class)) { return fine(context); } else { return finest(context); } } public Object info(InvocationContext context) throws Exception { logger.info("" + context.getMethod().getName()); return context.proceed(); } public Object fine(InvocationContext context) throws Exception { logger.finest("" + context.getMethod().getName()); return context.proceed(); } public Object finest(InvocationContext context) throws Exception { logger.finest("" + context.getMethod().getName()); return context.proceed(); } }
Done! Now we have a pattern to advise our interceptor!
This will totally work today. But this pattern is so simple and elegant what if we could support it right inside the container? Imagine how cool it would be if we could just do this in our interceptor and the container would just figure it out.
@Log public class LoggingInterceptor { private java.util.logging.Logger logger = java.util.logging.Logger.getLogger("theLogger"); @Info public Object info(InvocationContext context) throws Exception { logger.info("" + context.getMethod().getName()); return context.proceed(); } @Fine public Object fine(InvocationContext context) throws Exception { logger.finest("" + context.getMethod().getName()); return context.proceed(); } @AroundInvoke public Object finest(InvocationContext context) throws Exception { logger.finest("" + context.getMethod().getName()); return context.proceed(); } }
Definitely something I plan to propose for the next round of specifications....
As always, comments welcome.