JEE6 Interceptors - Java Enterprise Edition 6

09.09.2012

JEE6 Interceptors - Java Enterprise Edition 6

In diesem Artikel wird erklärt, was ein Interceptor ist und wie man ihn bei der Entwicklung von Enterprise Java Beans (EJBs) sinnvoll einsetzen kann.

Mit einem Interceptor ist es möglich sich in den "Callstack" von EJBs zu "hängen". D.h. wenn die Methode eines EJBs aufgerufen wird, dann kann man den Aufruf über einen Interceptor laufen lassen. Die Idee dahinter ist, dass man herausfinden möchte, wann die Methoden eines EJBs aufgerufen werden und welche Parameter sie erhalten und welche Rückgabewerte sie an den Aufrufenden zurückliefern.

Wenn wir das auf das EJB UrlManagerBean aus dem JEE6-Tutorial beziehen, dann soll festgestellt werden, wann die URLs abgefragt und wann neue hinzugefügt werden. Das einfachste wäre natürlich einen Breakpoint zu setzen oder Log-Ausgaben zu ergänzen. Allerdings bietet JEE6 mit dem Konzept des Interceptors eine elegante Möglichkeit dies auch anders zu bewerkstelligen.

Ein weiterer Vorteil von Interceptors ist, dass die Funktionsaufrufe nicht nur überwacht, sondern auch die Parameter und Rückgabewerte der Methoden zu beeinflussen werden können. Wie das genau funktioniert, wird in den nächsten Abschnitten beschrieben.

Es wird im Folgenden vorausgesetzt, dass das Beispiel aus JEE6-Tutorials durchgearbeitet wurde und die Klassen bekannt sind.

Als erstes wird eine neue Klasse UrlManagerInterceptor angelegt.

In der Klasse wird eine Methode public Object intercept(InvocationContext context) throws Exception angelegt. Dabei spielt der Name der Methode keine Rolle. Wichtig ist, dass der Rückgabewert vom Type Object ist und die Methode eine Exception wirft. Der Rückgabewert wird mittels proceed vom context abgefragt. Damit wir sehen, welche der beiden Methode (getUrls(), addUrl()) aufgerufen wird, geben wir den Methodennamen auf der Konsole aus. Wichtig ist noch die Annotation @AroundInvoke. Durch sie wird die intercept-Methode erst aufgerufen.

package org.hameister.urlmanager;

import javax.interceptor.InvocationContext;

/**
 *
 * @author Hameister
 */
public class UrlManagerInterceptor {

    @AroundInvoke
    public Object intercept(InvocationContext context) throws Exception {
        System.out.println("intercepted " + context.getMethod());

        return context.proceed();
    }
}

Damit die Methode aufgerufen wird, muss der Interceptor in der Bean-Klasse per Annotation angemeldet werden. Dazu wird @Interceptors(UrlManagerInterceptor.class) oberhalb des Klassennamens ergänzt.

package org.hameister.urlmanager;

import java.util.ArrayList;
import java.util.List;
import javax.ejb.Stateless;
import javax.inject.Named;
import javax.interceptor.Interceptors;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.Query;

/**
 *
 * @author Hameister
 */
@Interceptors(UrlManagerInterceptor.class)
@Stateless
@Named
public class UrlManagerBean {

    @PersistenceContext
    EntityManager em;

    public void addUrl(UrlManagerEntity entity) {
        System.out.println("Added");
        em.persist(entity);
    }

    public List<String> getUrls() {
        List<String> urls = new ArrayList<String>();

         Query query = em.createQuery("SELECT url FROM UrlManagerEntity url");
         List<UrlManagerEntity> resultList = query.getResultList();
         for(UrlManagerEntity entity : resultList) {
             urls.add(entity.getUrl());
         }
        return urls;
    }
}

Wenn nun die Urls abgefragt werden oder neue Urls hinzugefügt werden, dann wird automatisch die Methode intercept aufgerufen.

Da in der Methode intercept der komplette InvocationContext übergeben wird, besteht die Möglichkeit auf eine ganze Menge Informationen zuzugreifen.

Beispielsweise kann mittels getTarget() direkt auf das Bean zugegriffen werden. In dem Beispiel unten wird die Methode getUrls() aufgerufen und die enthaltenen Urls auf der Konsole ausgegeben.

Außerdem ist es möglich den Rückgabewert der Methode mittels proceed() abzufragen und auszugeben.

package org.hameister.urlmanager;

import java.util.List;
import javax.interceptor.AroundInvoke;
import javax.interceptor.InvocationContext;

/**
 *
 * @author Hameister
 */
public class UrlManagerInterceptor {

    @AroundInvoke
    public Object intercept(InvocationContext context) throws Exception {
        System.out.println("intercepted " + context.getMethod()+"  "+context.toString());

        if(context.getTarget() instanceof  UrlManagerBean) {

            UrlManagerBean target = (UrlManagerBean)context.getTarget();
            List<String> urls = target.getUrls();
            for(String url : urls) {
                System.out.println(url);
            }
        }

        if(context.proceed() instanceof  List) {
            List<String> urls = (List<String>)context.proceed();
            for(String url : urls) {
                System.out.println(url);
            }
        }
        return context.proceed();
    }
}

Bisher haben wir nur Werte von der Bean abgefragt und auf der Konsole ausgegeben. Allerdings ist es auch möglich Werte in der Bean zu ändern. Im folgenden Beispiel wird eine URL in der Ergebnisliste ergänzt, wenn die Methode getUrls aufgerufen wird.

package org.hameister.urlmanager;

import java.util.ArrayList;
import java.util.List;
import javax.interceptor.AroundInvoke;
import javax.interceptor.InvocationContext;

/**
 *
 * @author Hameister
 */
public class UrlManagerInterceptor {

    @AroundInvoke
    public Object intercept(InvocationContext context) throws Exception {
        System.out.println("intercepted " + context.getMethod()+"  "+context.toString());

        if(context.getMethod().getName().equals("getUrls")) {
            ArrayList<String> urls = (ArrayList<String>)context.proceed();
            urls.add("www.myURL.com");
            return urls;
        }

        return context.proceed();
    }
}

An dem kurzen Beispiel sollte klar geworden sein, dass es mit Interceptors sehr einfach möglich ist das Verhalten von Komponenten zur Laufzeit zu beeinflussen. Gerade für Debugging und Test-Zwecke sind Interceptors sehr gut zu gebrauchen.

Es soll nicht verheimlicht werden, dass ein Interceptor auch an einzelne Methoden eines EJBs gebunden werden kann. D.h. er wird nur dann aufgerufen, wenn die eine spezielle Methode angesprochen wird.Um das zu erreichen, muss die Annotation @Interceptors(UrlManagerInterceptor.class) einfach über die entsprechende Methode geschrieben werden.

Außerdem existieren Life-Cycle-Interceptors. Mit ihnen ist es möglich gezielt auf Änderungen in Life-Cycle des EJBs zu reagieren. Es stehen dazu folgenden Annotationen zur Verfügung: @PostConstruct, @PrePassivate, @PostActivate, @PreDestroy.

Weitere Informationen

Die Interceptors sind Teil der Enterprise JavaBean-Spezifikation 3.1 und sind im JSR-318 genauer beschrieben. Hier findet man das PDF dazu.