Blog

EJB Default Interceptor: Einer für alle

…aber eben nicht für viele. Neulich stellte ein Kollege die Frage, wie er einen Interceptor für viele EJBs konfigurieren könne. Wichtig ist, dass es sich nicht um alle EJBs der Applikation handelt. Schaut man in die Spezifikation, so lautet die Antwort: Nimm einen Default Interceptor, der für alle Beans konfiguriert wird.

<ejb-jar>
  ...
  <assembly-descriptor>
    <interceptor-binding>
      <ejb-name>*</ejb-name>
      <interceptor-class>
        de.holisticon.StatisticsInterceptor
      </interceptor-class>
    </interceptor-binding>
  </assembly-descriptor>
</ejb-jar>

Beans, die nicht in den Genuss des Interzeptoren kommen sollen, können mit @ExcludeDefaultInterceptors annotiert werden. Dies kann aber ein mühsames Geschäft werden, sobald eine nennenswerte Anzahl von EJBs ausgenommen werden soll. Wenn man sieht, dass ein Default Interceptor in der ejb-jar.xml mit einem * für alle Komponenten konfiguriert wird, dann kommt unweigerlich der Gedanke auf, das Problem mit einem geschickten regulären Ausdruck zu erschlagen. Da macht uns jedoch die Spezifikation einen Strich durch die Rechnung, denn diese Möglichkeit ist nicht vorgesehen. Eine Lösung unter Verwendung regulärer Ausdrücke möchte ich hier skizzieren.

Man nehme eine abstrakte Oberklasse, von der konkrete Interzeptoren erben, sowie eine Properties-Datei, welche im Verzeichnis META-INF der EJB-Applikation abgelegt wird. Die Properties-Datei (welche z.B. flexible-interceptors.properties heißt) enthält Key/Value-Paare, die sich aus dem Klassennnamen der Interzeptor-Klasse (Key) und einem regulären Ausdruck (Value) zusammensetzen. Wichtig ist hier, dass es sich um echte reguläre Ausdrücke handelt. Daher reicht z.B. ein * als Platzhalter für beliebig viele beliebige Zeichen nicht aus. Es muss also ein . für ein beliebiges Zeichen, gefolgt von einem * für beliebig viele Vorkommen sein.

de.holisticon.StatisticsInterceptor=.*VerwaltungBean
de.holisticon.HolisticInterceptor=de.holisticon..*

Im Konstruktor der Oberklasse werden diese Properties geladen.

...
public abstract class AbstractFlexibleInterceptor {

  protected Properties properties = new Properties();

  public AbstractSomeInterceptor() {
    try {
      properties.load(Thread.currentThread().getContextClassLoader()
          .getResourceAsStream("META-INF/flexible-interceptors.properties"));
    } catch (IOException e) {
      e.printStackTrace();
      // or some real exception handling
    }
  }
  ...

Daneben gibt es eine Methode, welche z.B. mit @AroundInvoke als Interceptor-Methode markiert wird. Diese Methode wertet aus, um welche Klasse es sich bei dem Interzeptor handelt, und ermittelt den entsprechenden regulären Ausdruck aus den Properties. Passt der Klassennname der auszuführenden EJB zu diesem, so wird an eine als abstract deklarierte Methode delegiert. Die Methode greift nicht weiter ein, wenn sie keinen regulären Ausdruck ermitteln kann oder der Klassennname der Bean nicht zu diesem passt.

  ...
  @AroundInvoke
  public Object onMethodCall(InvocationContext ctx) throws Exception {
    String regex = properties.getProperty(this.getClass()
        .getCanonicalName());
    try {
      if (regex != null
          && ctx.getTarget().getClass().getCanonicalName().matches(regex)) {
        return delegateMethodCall(ctx);
      }
    } catch (PatternSyntaxException e) {
      e.printStackTrace();
      // or some real exception handling
    }
    return ctx.proceed();
  }

  protected abstract Object delegateMethodCall(InvocationContext ctx)
      throws Exception;
}

Die erbende Klasse – also der konkrete Interceptor – trägt keine Annotation mehr, sondern implementiert in der delegierten Methode die eigentliche Logik des Interceptors. Es ist auch diese Klasse, die als Default Interceptor in der ejb-jar.xml konfiguriert wird. Grundsätzlich arbeitet sie für alle EJBs der Applikation. Die eigentliche Interceptor-Logik wird jedoch nur für die über den regulären Ausdruck festgelegten Beans ausgeführt.

...
public class StatisticsInterceptor extends AbstractFlexibleInterceptor {

  @Override
  protected Object delegateMethodCall(InvocationContext ctx) throws Exception {
    // do some intercepting stuff
    return ctx.proceed();
  }
}

Diese Lösung hat zweifelsohne ein paar Aspekte, die man als unschön betrachten kann. So setzt sie z.B. auf Vererbung statt Annotationen oder verwendet Properties statt XML. Einige Dinge davon können mit etwas mehr Aufwand sicher verbessert werden. Es wäre wünschenswert, dass ein vergleichbarer Mechanismus zur Konfiguration von Interzeptoren mit regulären Ausdrücken in die Spezifikation aufgenommen wird. Bis es soweit ist, kann diese pragmatische Lösung weiterhelfen.

Über den Autor

Antwort hinterlassen