0

I would like to write a tool able to intercept all the method calls during the usage of an application that use CDI (Weld).

I have already tried with CDI interceptors, however, since I want to keep the beans unawares of the interceptor, I do not want to use the interceptor binding to observe the method calls.

Is there a way to intercept calls that satisfies this constraint?

Bonus question: with this vision in mind, is there any way of tracing the cascade of calls from a method? it is fine with both top down (retrieve all the other methods called from the body of a specific method) and bottom up strategy (retrieve the method the caller method of a specific method).

2 Answers2

1

The way (I know) to add an interceptor on any (or all) methods without touching the code is a CDI portable extension. This sounds intimidating at first but in reality it isn't. You will find plenty of resources online, but a high-level overview of the steps is:

  • Create the extension class, a simple class that implements the marker interface javax.enterprise.inject.spi.Extension
  • Add a file under /META-INF/services/javax.enterprise.inject.spi.Extension that contains the name of the class implementing the extension

Done setting up!

Now for the interesting part: extension are simply CDI beans that contain observer methods for a set of events fired by CDI itself during the various phases of its initialization. Look here. Many of these events provide methods to tweak the container or what CDI knows for a bean. The idea is that your method @Observes these events and adds an interceptor annotation to all bean classes.

So the next step, specific to your problem is to create the interceptor with the logic you want and the interceptor annotation, that goes with it, let's call it @MyInterceptor. A very simple implementation of the extension class would be:

// There is a little ceremony for the annotation...
import javax.enterprise.util.AnnotationLiteral;

public class MyInterceptorLiteral extends AnnotationLiteral<MyInterceptor>
  implements MyInterceptor {
  // nothing more needed
}
// WARNING DEMO CODE, UNTESTED & SEE BELOW FOR POSSIBLE GOTCHAS
public class MyInterceptorExtension implements Extension {
  public void onProcessAnnotatedType(@Observes ProcessAnnotatedType<?> event) {
    event.configureAnnotatedType().add(new MyInterceptorLiteral());
  }
}

This may need some tweaking, but the principle is to add your interceptor annotation on all bean classes discovered by CDI. I do not know if this catches bean instances produced by producer methods/fields, these might be trickier to handle.

As for tracing the cascade of calls, this is the job of your interceptor. Not trivial, but there are ways (e.g. ThreadLocal context objects).

Nikos Paraskevopoulos
  • 39,514
  • 12
  • 85
  • 90
  • thanks for the suggestion, I understand the logic but I have doubts about how to add my binding: the snippet `event.configureAnnotatedType().add(MyInterceptor.class);` does not work since `The method add(Annotation) in the type Set is not applicable for the arguments (Class)`. How can I solve this error? – Leonardo Scommegna Mar 02 '22 at 10:29
  • Hi @LeonardoScommegna, yes this is a funny (irritating) ceremony I keep forgetting about annotations in JEE - it involves the `AnnotationLiteral`. Please check edit! – Nikos Paraskevopoulos Mar 02 '22 at 10:54
1

What you are asking for is a lot like what Weld does for its Probe extension that monitors all beans - it is possible to certain extent, but not fully. What I mean is that you can do this for any class based beans but not for producers (method/field) because by CDI definition those cannot by intercepted or decorated in standard ways. The only way to achieve that is via interception factory and that requires some special tweaking.

As for how to do this, here is few basic steps you'd need:

  1. Create an enabled interceptor class with required logic
  2. Create an interceptor binding (say, MyBinding for the sake of this answer) and place it on the interceptor
  3. Create a CDI extension that will add this interceptor binding to all the beans that you want to intercept. This can be done at several points in the extension lifecycle, but I'd recommend looking at ProcessBeanAttributes<?> where you can configure the attributes and add the MyBinding interceptor binding to each bean.
  4. Don't forget to enable the extension via META-INF entry.

You could take a look at the aforementioned Weld's Probe extension but note that it does more things so it will be more complex than what you are asking for in this question. Here are some links that might help:

  • Probe extension class
  • Interceptor class, note that the class is @Vetoed whereas yours shouldn't be! Weld has it vetoed because of optional enablement for integrators.
  • MonitoredComponent, in this case it is a stereotype (that then contains the binding) which is added to all the beans via extension. But you should be able to just add binding directly.

Last but not least, there is one big gotcha that you need to be aware of. Adding interception to all beans means that you might attempt to add it to some bean that is unproxyable (which is an interception requirement). This would result in a error. In short, final methods and final classes will cause problems, but see this specification part for more information. This is also why ProbeExtension does some special checking to avoid that. Look at this code for inspiration.

however, since I want to keep the beans unawares of the interceptor

I should note that with this or any other approach, bean metadata (the Bean<?> object) will contain information on the added binding so it is "visible" that the bean has been tweaked.

Siliarus
  • 6,393
  • 1
  • 14
  • 30
  • Thank you for the details, however I can not figure out how to add an interceptor binding to my beans. In the Probe source code I assume that this is done through the method `event.addAnnotatedType` during the BeforeBeanDiscovery event but some additional method are also used e.g., `event.addAnnotatedType(VetoedSuppressedAnnotatedType.from(Monitored.class, beanManager), Monitored.class.getName());` – Leonardo Scommegna Mar 02 '22 at 10:50
  • Yes, but you can use the same approach as is shown in the other answer by @Nikos - observer `ProcessAnnotatedType> event` and then do `event.configureAnnotatedType()` from there you can temper what will be in the final AT. In my answer I suggested using `ProcessBeanAttributes> event` instead which follows similar logic - `event,configureBeanAttributes()` but then you'd have to register a stereotype (that contains the interceptor binding) because the binding is not a direct attribute of the bean. – Siliarus Mar 03 '22 at 15:21