Spring mit AOP (Tutorial)

Klassendiagramm dieses Beispiels

Dieser Beitrag ist Teil einer (Tutorial-) Serie über die Einführung in das Spring Framework und beschreibt das Arbeiten mit AOP im Spring Framework.

Die Struktur des Projektes

Dieses Beispiel baut auf dem vorherigem Beispiel Spring an einem einfachem Beispiel in der Version 1.x auf. Die verwendeten Frameworks und Werkzeuge sind hier beschrieben. Dieses Beispiel ist eine Konsolenanwendung, die den Service eines Taschenrechners aufruft. In diesem Beispiel werden die folgenden Technologien des Spring Frameworks vorgestellt:

  • Die Definition eines Aspektes mit Advices und Join Points.
  • Die XML Konfiguration eines Aspektes im Spring Framework.
  • Die Konfiguration des Loggings.

Im folgendem Bild sind die benötigten Bibliotheken dargestellt.

Die benötigten Bibliotheken (Dependencies)

Die benötigten Bibliotheken - Dependencies (© Frank Rahn)

Die Literaturempfehlungen für dieses Beispiel

Der Aspect

Zunächst definieren wir eine einfache Spring Bean Trace (Aspect). Diese Klasse definiert ein paar Methoden (Advices), die Vor, Nach bzw. Um den Aufruf der Zielmethode (Join Point) gesetzt bzw. ausgeführt werden.

/**
 * Eine Tracing Aspect.
 * @author Frank W. Rahn
 */
public class Trace {

    /** Der Logger für das Tracing. */
    private static final Logger logger = getLogger(Trace.class);

    /**
     * Eine Methode (Advice), die vor der Zielmethode (Join Point) aufgerufen
     * wird.
     */
    public void traceBefore() {
        logger.trace("TRACE-Logausgabe vor Aufruf der Methode");
    }

    /**
     * Führe diese Methode (Advice) um die Zielmethode (Join Point) herum aus.
     * @param joinPoint ein Objekt, welches den Join Point beschreibt und
     * zusätzlich eine {@link ProceedingJoinPoint#proceed()}-Methode anbietet
     * @return das gewünschte Ergebnis
     * @throws Throwable falls ein Fehler auftritt
     */
    public Object debugAround(ProceedingJoinPoint joinPoint) throws Throwable {
        logger.debug("DEBUG-Logausgabe vor Ausführung der Methode");

        Object result = null;
        try {
            result = joinPoint.proceed();
        } catch (Exception exception) {
            logger.error(
                "ERROR-Logausgabe, weil während der Ausführung ein Fehler aufgetreten ist",
                exception);

            // Jetzt weiter werfen...
            throw exception;
        }

        logger.debug(
            "DEBUG-Logausgabe nach der Ausführung der Methode mit Ergebnis={}",
            result);
        return result;
    }

    /**
     * Eine Methode (Advice), die nach der Zielmethode (Join Point) aufgerufen
     * wird.
     * @param result das Ergebnis der Zielmethode
     */
    public void traceAfter(Object result) {
        logger.trace(
            "TRACE-Logausgabe nach Aufruf der Methode mit Ergebnis={}",
            result);
    }

}

Die XML Konfiguration für das AOP Modul.

<?xml version="1.0" encoding="UTF-8"?>

<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans
            http://www.springframework.org/schema/beans/spring-beans.xsd
    ">

    <description>Dieses ist die Konfiguration der Aspekte.</description>

    <!-- Das Tracing Bean definieren -->
    <bean id="trace" class="de.rahn.aop.Trace" />

</beans>

Die notwendige Erweiterungen am Projekt

Die XML Konfiguration, für das vorherige Beispiel Spring an einem einfachem Beispiel, zum Einstieg in die Anwendung ist

  • um die AOP Beans in Zeile 23
  • und das Enabling des AspectJ Supports in Zeile 5, 9 und 18 zu erweitern.

Der Aspekt wird hier aktiviert, weil er Anwendungsspezifisch eingesetzt wird.

  • Der Aspekt wird ab Zeile 26 definiert.
  • Der Pointcut ist in Zeile 28 definiert und enthält alle Methoden der Implementierung des Services. Der Pointcut wird in den Zeile 31 bis 33 den Advices zugewiesen.
  • In Zeile 27 wird das Spring Bean referenziert, welches die Implementierung des Aspekts bereitstellt.
  • In Zeile 18 und 26 wurde proxy-target-class="true" gesetzt. Dadurch wird nicht der Dynamic Proxy-Mechanismus des JDK’s verwendet, sondern mit dem Generator CGLIB Ableitungen generiert. Diese abgeleiteten Klassen werden mit zusätzlicher Funktionalität zur Laufzeit bzw. Ladezeitpunkt der Klasse generiert. Bei der Verwendung von JDK Proxies muss darauf geachtet werden, dass die Spring Beans, auf die Aspekte angewendet werden, auf jeden Fall eine Interface definieren müssen. Ansonsten muss CGLIB verwendet werden.
<?xml version="1.0" encoding="UTF-8"?>

<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:aop="http://www.springframework.org/schema/aop"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans
            http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/aop
            http://www.springframework.org/schema/aop/spring-aop.xsd
    ">

    <description>
        Dieses ist die zentrale Konfiguration für die Anwendungen.
    </description>

    <!-- Enabling den AspectJ Support -->
    <aop:aspectj-autoproxy proxy-target-class="true" />

    <!-- Die projektspezifischen Konfigurationen laden -->
    <import resource="classpath:/de/rahn/services/calculator/calculator.xml" />
    <import resource="classpath:/de/rahn/app/application.xml" />
    <import resource="classpath:/de/rahn/aop/aop.xml" />

    <!-- Das Tracing per AOP ermöglichen -->
    <aop:config proxy-target-class="true">
        <aop:aspect ref="trace">
            <aop:pointcut id="pointcut1"
                expression="execution(* de.rahn.services.calculator.standard.SimpleCalculator.*(..))" />

            <aop:before method="traceBefore" pointcut-ref="pointcut1" />
            <aop:around method="debugAround" pointcut-ref="pointcut1" />
            <aop:after-returning method="traceAfter"
                returning="result" pointcut-ref="pointcut1" />
        </aop:aspect>
    </aop:config>

</beans>

Für die Ausgaben des Aspekts müssen einige Erweiterungen an der log4j Konfiguration durchgeführt werden.

  • Diese sind in Zeile 17 bis 19 zu finden.
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE log4j:configuration PUBLIC "-//APACHE//DTD LOG4J 1.2//EN" "log4j.dtd">
<log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/">

    <!-- Appenders -->
    <appender name="console" class="org.apache.log4j.ConsoleAppender">
        <param name="Target" value="System.out" />
        <layout class="org.apache.log4j.PatternLayout">
            <param name="ConversionPattern" value="%-5p: %c - %m%n" />
        </layout>
    </appender>

    <!-- Application Loggers -->
    <logger name="de.rahn">
        <level value="info" /<
    </logger>
    <logger name="de.rahn.aop.Trace">
        <level value="trace" />
    </logger>

    <!-- 3rd Party Loggers -->
    <logger name="org.springframework">
        <level value="info" />
    </logger>

    <!-- Root Logger -->
    <root>
        <priority value="warn" />
        <appender-ref ref="console" />
    </root>

</log4j:configuration>

Die Ausgaben

Hier die Ausgabe der Konsole von Eclipse ohne AOP.

INFO : org.springframework.context.support.ClassPathXmlApplicationContext - Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@63238bd2: startup date [Tue May 10 22:29:46 CEST 2011]; root of context hierarchy
INFO : org.springframework.beans.factory.xml.XmlBeanDefinitionReader - Loading XML bean definitions from class path resource [META-INF/spring/context-app.xml]
INFO : org.springframework.beans.factory.xml.XmlBeanDefinitionReader - Loading XML bean definitions from class path resource [de/rahn/services/calculator/calculator.xml]
INFO : org.springframework.beans.factory.xml.XmlBeanDefinitionReader - Loading XML bean definitions from class path resource [de/rahn/app/application.xml]
INFO : org.springframework.beans.factory.support.DefaultListableBeanFactory - Pre-instantiating singletons in org.springframework.beans.factory.support.DefaultListableBeanFactory@6a340101: defining beans [simpleCalculator,application,org.springframework.context.annotation.internalConfigurationAnnotationProcessor,org.springframework.context.annotation.internalAutowiredAnnotationProcessor,org.springframework.context.annotation.internalRequiredAnnotationProcessor,org.springframework.context.annotation.internalCommonAnnotationProcessor]; root of factory hierarchy
INFO : de.rahn.app.Application - Ergebnis 1 = 6.0
Exception in thread "main" java.lang.IllegalArgumentException: Die Summanden sind null
    at org.springframework.util.Assert.notNull(Assert.java:112)
    at de.rahn.services.calculator.standard.SimpleCalculator.add(SimpleCalculator.java:36)
    at de.rahn.app.Application.run(Application.java:33)
    at Starter.main(Starter.java:21)

Hier die Ausgabe der Konsole von Eclipse mit AOP.

  • Die Ausgaben durch die Advices erscheinen ab der Zeilen 7 bis 41, dabei ist die Zeile 11 (vorher in Zeile 6) noch aus der Anwendung.
  • In der alten Stacktrace Ausgabe ab Zeile 42 sind die Änderungen durch das AOP sichtbar.
INFO : org.springframework.context.support.ClassPathXmlApplicationContext - Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@63238bd2: startup date [Tue May 10 21:43:48 CEST 2011]; root of context hierarchy
INFO : org.springframework.beans.factory.xml.XmlBeanDefinitionReader - Loading XML bean definitions from class path resource [META-INF/spring/context-app.xml]
INFO : org.springframework.beans.factory.xml.XmlBeanDefinitionReader - Loading XML bean definitions from class path resource [de/rahn/services/calculator/calculator.xml]
INFO : org.springframework.beans.factory.xml.XmlBeanDefinitionReader - Loading XML bean definitions from class path resource [de/rahn/app/application.xml]
INFO : org.springframework.beans.factory.xml.XmlBeanDefinitionReader - Loading XML bean definitions from class path resource [de/rahn/aop/aop.xml]
INFO : org.springframework.beans.factory.support.DefaultListableBeanFactory - Pre-instantiating singletons in org.springframework.beans.factory.support.DefaultListableBeanFactory@77addb59: defining beans [org.springframework.aop.config.internalAutoProxyCreator,simpleCalculator,org.springframework.aop.aspectj.AspectJPointcutAdvisor#0,org.springframework.aop.aspectj.AspectJPointcutAdvisor#1,org.springframework.aop.aspectj.AspectJPointcutAdvisor#2,pointcut1,application,org.springframework.context.annotation.internalConfigurationAnnotationProcessor,org.springframework.context.annotation.internalAutowiredAnnotationProcessor,org.springframework.context.annotation.internalRequiredAnnotationProcessor,org.springframework.context.annotation.internalCommonAnnotationProcessor,trace]; root of factory hierarchy
TRACE: de.rahn.aop.Trace - TRACE-Logausgabe vor Aufruf der Methode
DEBUG: de.rahn.aop.Trace - DEBUG-Logausgabe vor Ausführung der Methode
DEBUG: de.rahn.aop.Trace - DEBUG-Logausgabe nach der Ausführung der Methode mit Ergebnis=6.0
TRACE: de.rahn.aop.Trace - TRACE-Logausgabe nach Aufruf der Methode mit Ergebnis=6.0
INFO : de.rahn.app.Application - Ergebnis 1 = 6.0
TRACE: de.rahn.aop.Trace - TRACE-Logausgabe vor Aufruf der Methode
DEBUG: de.rahn.aop.Trace - DEBUG-Logausgabe vor Ausführung der Methode
ERROR: de.rahn.aop.Trace - ERROR-Logausgabe, weil während der Ausführung ein Fehler aufgetreten ist
java.lang.IllegalArgumentException: Die Summanden sind null
    at org.springframework.util.Assert.notNull(Assert.java:112)
    at de.rahn.services.calculator.standard.SimpleCalculator.add(SimpleCalculator.java:36)
    at de.rahn.services.calculator.standard.SimpleCalculator$FastClassByCGLIB$52e8fcfd.invoke(<generated>)
    at net.sf.cglib.proxy.MethodProxy.invoke(MethodProxy.java:191)
    at org.springframework.aop.framework.Cglib2AopProxy$CglibMethodInvocation.invokeJoinpoint(Cglib2AopProxy.java:688)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:150)
    at org.springframework.aop.aspectj.MethodInvocationProceedingJoinPoint.proceed(MethodInvocationProceedingJoinPoint.java:80)
    at de.rahn.aop.Trace.debugAround(Trace.java:37)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:616)
    at org.springframework.aop.aspectj.AbstractAspectJAdvice.invokeAdviceMethodWithGivenArgs(AbstractAspectJAdvice.java:621)
    at org.springframework.aop.aspectj.AbstractAspectJAdvice.invokeAdviceMethod(AbstractAspectJAdvice.java:610)
    at org.springframework.aop.aspectj.AspectJAroundAdvice.invoke(AspectJAroundAdvice.java:65)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172)
    at org.springframework.aop.framework.adapter.AfterReturningAdviceInterceptor.invoke(AfterReturningAdviceInterceptor.java:50)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172)
    at org.springframework.aop.framework.adapter.MethodBeforeAdviceInterceptor.invoke(MethodBeforeAdviceInterceptor.java:50)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172)
    at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:89)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172)
    at org.springframework.aop.framework.Cglib2AopProxy$DynamicAdvisedInterceptor.intercept(Cglib2AopProxy.java:621)
    at de.rahn.services.calculator.standard.SimpleCalculator$EnhancerByCGLIB$43e651bb.add(<generated>)
    at de.rahn.app.Application.run(Application.java:33)
    at Starter.main(Starter.java:21)
Exception in thread "main" java.lang.IllegalArgumentException: Die Summanden sind null
    at org.springframework.util.Assert.notNull(Assert.java:112)
    at de.rahn.services.calculator.standard.SimpleCalculator.add(SimpleCalculator.java:36)
    at de.rahn.services.calculator.standard.SimpleCalculator$FastClassByCGLIB$52e8fcfd.invoke(<generated>)
    at net.sf.cglib.proxy.MethodProxy.invoke(MethodProxy.java:191)
    at org.springframework.aop.framework.Cglib2AopProxy$CglibMethodInvocation.invokeJoinpoint(Cglib2AopProxy.java:688)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:150)
    at org.springframework.aop.aspectj.MethodInvocationProceedingJoinPoint.proceed(MethodInvocationProceedingJoinPoint.java:80)
    at de.rahn.aop.Trace.debugAround(Trace.java:37)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:616)
    at org.springframework.aop.aspectj.AbstractAspectJAdvice.invokeAdviceMethodWithGivenArgs(AbstractAspectJAdvice.java:621)
    at org.springframework.aop.aspectj.AbstractAspectJAdvice.invokeAdviceMethod(AbstractAspectJAdvice.java:610)
    at org.springframework.aop.aspectj.AspectJAroundAdvice.invoke(AspectJAroundAdvice.java:65)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172)
    at org.springframework.aop.framework.adapter.AfterReturningAdviceInterceptor.invoke(AfterReturningAdviceInterceptor.java:50)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172)
    at org.springframework.aop.framework.adapter.MethodBeforeAdviceInterceptor.invoke(MethodBeforeAdviceInterceptor.java:50)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172)
    at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:89)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172)
    at org.springframework.aop.framework.Cglib2AopProxy$DynamicAdvisedInterceptor.intercept(Cglib2AopProxy.java:621)
    at de.rahn.services.calculator.standard.SimpleCalculator$EnhancerByCGLIB$43e651bb.add(<generated>)
    at de.rahn.app.Application.run(Application.java:33)
    at Starter.main(Starter.java:21)

Der Quellcode und Download des Beispiels

Quellcode ansehen bei GitHub:
Spring mit AOP

Download einer ZIP-Datei von GitHub:
Spring mit AOP

Die Maven Befehle

Eclipse Konfiguration neu erzeugen: $ mvn eclipse:clean eclipse:eclipse

Anwendung bauen: $ mvn clean install

Anwendung ausführen: $ mvn initialize exec:exec

Update am 28.09.2012

Es wurden folgende Änderungen an allen Projekten vorgenommen.

  1. Die Projekte wurden aus meinem lokalen Apache Subversion Repository in meine öffentlichen GitHub Repositories verschoben.
  2. Die folgenden typischen Anpassungen an Git wurden an allen Projekten durchgeführt.
    • .directory gelöscht
    • .gitignore hinzugefügt
    • README.md hinzugefügt
    • COPYRIGHT.md hinzugefügt
    • In jedem Repository wurde für jeden Beitrag der Serie ein Branch angelegt.
      • develop-spring-an-einem-einfachen-beispiel
      • develop-spring-mit-aop
      • develop-spring-mit-jpa-und-hibernate
      • develop-spring-mit-einer-einfachen-webanwendung
      • develop-spring-mit-einer-webanwendung-mit-jpa-und-validierung
      • develop-spring-security-mit-einer-webanwendung
      • develop-spring-mit-restful-webservice

Updates von 06.10.2012 bis zum 14.10.2012

Es wurden folgende Änderungen an allen Projekten vorgenommen.

  1. Aktualisierung des OpenJDK auf die Version 1.7.0.
  2. Aktualisierung der Entwicklungsumgebung Eclipse auf die Version 4.2.1.
    Die benötigten Plugins aus dem Eclipse Marketplace:
  3. Aktualisierung der Datei pom.xml:
    • Anpassungen an die OpenJDK Version
    • GitHub Einträge (SCM und URL) hinzugefügt
    • Aktualisierung der Libraries auf aktuellerer Versionen (z. B. Spring Version 3.1.2.RELEASE, Hibernate Version 4.1.7.Final, JUnit Version 4.10, mockito Version 1.9.0, …)
      Die genaueren Versionen bitte aus den jeweiligen pom.xml auf GitHub entnehmen.
Frank Rahn
Letzte Artikel von Frank Rahn (Alle anzeigen)
0 Kommentare

Hinterlasse einen Kommentar

An der Diskussion beteiligen?
Hinterlasse uns deinen Kommentar!

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert

Ihre E-Mail-Adresse wird nicht veröffentlicht. Ihr Kommentar wird verschlüsselt an meinen Server gesendet. Erforderliche Felder sind mit * markiert.

Weitere Informationen und Widerrufshinweise finden Sie in meiner Datenschutzerklärung.