Caching mit Spring AOP

Mit der Aspektorientierte Programmierung (AOP) in Spring lässt sich ein Cache implementieren, der die Ergebnisse beliebiger Methoden speichern kann. Der Quellcode der Methoden muss dafür nicht verändert werden. Der Cache kann nachträglich in fertige Spring Projekte integriert werden.


Die Implementierung des Cache besteht aus einem MethodCacheInterceptor, der vor der Ausführung von Methoden aufgerufen wird. Der Interceptor prüft anhand von Klasse, Methode und Parametern ob der Rückgabewert schon gespeichert wurde und gibt ihn ggf. zurück ohne die Methode auszuführen. Die Werte werden mit Ehcache gespeichert. Wird kein Wert gefunden, so wird die Ausführung de Methode fortgesetzt.

In der Bean-Konfiguration von Spring wird konfiguriert, für welche Methoden der Interceptor aufgerufen wird, das heißt welche Methoden Gecachet werden.

In der Konfiguration von Ehcache wird eingestellt, wie viele Werte gespeichert werden, wann die Werte ungültig werden und wie gespeichert wird, im Ram oder auf der Festplatte.

Es folgen Details der Implementierung und Konfiguration. Der MethodCacheInterceptor implementiert die Methode invoke aus dem Interface MethodInterceptor der AOP Alliance:

public Object invoke( MethodInvocation invocation ) throws Throwable {
  Object[] arguments = invocation.getArguments();
  String invocationClassName = invocation.getThis().getClass().getName();
  Object result = null;
  Cache cache = getCache( invocation );
  Element element = null;
  String cacheKey = getCacheKey( arguments );
  if( cache!=null ) {
    element = cache.get( cacheKey );
  }
  if( element == null ) {
    // call target/sub-interceptor
    result = invocation.proceed();
    boolean isEmptyCollection = false;
    if( result!=null && result instanceof Collection && ((Collection) result).isEmpty() ) {
      isEmptyCollection = true;
    }
    if( cache!=null && result!=null && !isEmptyCollection ) {
      // cache method result
      element = new Element( cacheKey, ( Serializable )result );
      cache.put( element );
    }
  }
  else {
    result = element.getObjectValue();
  }
  return result;
}

Zuerst wird der Cache mit der Methode getCache( MethodInvocation invocation ) geladen. Die Klasse MethodInvocation enthält Laufzeitinformationen über die Methode, die aufgerufen wird. Mit diesen Informationen wird ein Ehcache mit dem Namen:
pac.kage.name.Klasse.methode(pac.kage.name.Param1,pac.kage.name.Param2,..) gesucht. Ein Cache mit diesem Namen muss in der Ehcache Konfiguration enthalten sein. Wird kein Cache gefunden, dann wird auch nach Caches mit den Namen der Interfaces gesucht, die implementiert werden.

private Cache getCache( MethodInvocation invocation ) {
  String methodName = invocation.getMethod().getName();
  Class[] argumentsClasses = invocation.getMethod().getParameterTypes();
  Class clazz = invocation.getThis().getClass();
  String name = getCacheName( clazz, methodName, argumentsClasses );
  Cache cache = getCacheManager().getCache( name );
  if( cache==null ) {
    Class[] interfaces = clazz.getInterfaces();
    for( int i = 0; i < interfaces.length && cache==null; i++ ) {
      name = getCacheName( interfaces[i], methodName, argumentsClasses );
      cache = getCacheManager().getCache( name );
    }
  }
  return cache;
}

Wenn ein Cache gefunden wird, dann wird im Cache ein Wert gesucht mit Hilfe eines Keys, der aus den Parametern der Methode erzeugt wird:

private String getCacheKey( Object[] arguments ) {
  StringBuffer sb = new StringBuffer( "arguments" );
  if( ( arguments != null ) && ( arguments.length != 0 ) ) {
    for( int i = 0; i < arguments.length; i++ ) {
      sb.append( "." ).append( arguments[ i ] );
    }
  }
  return sb.toString();
}

In der Spring Konfiguration wird mit Hilfe der AOP definiert für welche Methoden der MethodInterceptor aufgerufen wird. Zuerst wird der Cache-Manager konfiguriert und der MethodCacheInterceptor als sogenannter Advice.

<bean id="cacheManager" class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean">
    <property name="configLocation">
        <value>classpath:ehcache-impl.xml</value>
    </property>
    <property name="shared">
        <value>true</value>
    </property>
</bean>
<bean id="methodCacheAdvice" class="com.carano.aop.MethodCacheInterceptor">
    <property name="cacheManager" ref="cacheManager" />
</bean>

Der Advice wird dann mit einen AOP Pointcut verknüpft. Ein Pointcut definiert ein Aufrufereignis für den Advice, das heißt den Aufruf einer Methode, die gecachet werden soll.

<aop:config  proxy-target-class="false">
 <aop:pointcut id="dictionaryHandlerCache"
  expression="execution(* com.carano.ml.handler.*HandlerImpl.find*(..))" />
 <aop:advisor advice-ref="methodCacheAdvice" pointcut-ref="dictionaryHandlerCache" />
</aop:config>

Im Beispiel gilt der MethodInterceptor für alle Methoden aus dem Package com.carano.ml.handler, die in Klassen enthalten sind, dessen Name mit HandlerImpl endet und deren Namen mit find beginnen.

Wer neugierig ist oder den Cache benutzen will, findet hier den Quellcode:

  1. No trackbacks yet.

Schreibe einen Kommentar

Trage deine Daten unten ein oder klicke ein Icon um dich einzuloggen:

WordPress.com-Logo

Du kommentierst mit Deinem WordPress.com-Konto. Abmelden / Ändern )

Twitter-Bild

Du kommentierst mit Deinem Twitter-Konto. Abmelden / Ändern )

Facebook-Foto

Du kommentierst mit Deinem Facebook-Konto. Abmelden / Ändern )

Google+ Foto

Du kommentierst mit Deinem Google+-Konto. Abmelden / Ändern )

Verbinde mit %s

%d Bloggern gefällt das: