Skip to content

2. Artikel 1 – Spring IoC

Ziele dieses Dokuments:

  • die Konfigurations- und Integrationsmöglichkeiten des Spring-Frameworks (http://www.springframework.org) zu untersuchen
  • das Konzept von IoC (Inversion of Control), auch bekannt als Dependency Injection, zu definieren und anzuwenden

2.1. Konfiguration einer 3-Tier-Anwendung mit Spring

Betrachten wir eine klassische 3-Schichten-Anwendung:

Wir gehen davon aus, dass der Zugriff auf die Geschäfts- und DAO-Schicht durch Java-Schnittstellen gesteuert wird:

  1. die Schnittstelle [IArticlesDao] für die Datenzugriffsebene
  1. die Schnittstelle [IArticlesManager] für die Geschäftsschicht

In der Datenzugriffsebene, auch DAO-Ebene (Data Access Object) genannt, ist es üblich, mit einem DBMS und somit mit einem JDBC-Treiber zu arbeiten. Betrachten wir das Grundgerüst einer Klasse, die auf eine Artikeltabelle in einem DBMS zugreift:

public class ArticlesDaoPlainJdbc implements IArticlesDao {

     // connection to data source
    private String driverClassName=null;
    private Connection connexion=null;
    private String url = null;
    private String user = null;
    private String pwd = null;
 ....

    public List getAllArticles() {
        // the list of items is requested
        try {
             // load the JDBC driver
            Class.forName(driverClassName);
            // create a connection to BD
            connexion = DriverManager.getConnection(url, user, pwd);
            ...
        } catch (SQLException ex) {
            ...
        } finally {
            ...
        }
    }

Um eine Operation im DBMS auszuführen, benötigt jede Methode ein [Connection]-Objekt, das die Verbindung zur Datenbank darstellt und über das Daten zwischen der Datenbank und dem Java-Code ausgetauscht werden. Um dieses Objekt zu erstellen, sind vier Informationen erforderlich:

String driverClassName
der Name der JDBC-Treiberklasse des DBMS
String url
die JDBC-URL der zu verwendenden Datenbank
String user
die Anmeldedaten, die zum Herstellen der Verbindung verwendet werden
String pwd
Das Passwort für diesen Benutzernamen

Wie kann unsere vorherige Klasse [ArticlesDaoPlainJdbc] diese Informationen abrufen? Es gibt mehrere Möglichkeiten:

Lösung 1 – Die Informationen sind fest in der Klasse hinterlegt:

1
2
3
4
5
6
7
8
public class ArticlesDaoPlainJdbc implements IArticlesDao {

     // connection to data source
    private final String driverClassName = "org.firebirdsql.jdbc.FBDriver";
    private String url = "jdbc:firebirdsql:localhost/3050:d:/databases/dbarticles.gdb";
    private String user = "someone";
    private String pwd = "somepassword";
 ....

Der Nachteil dieser Lösung ist, dass Sie den Java-Code jedes Mal ändern müssen, wenn sich eine dieser Informationen ändert, beispielsweise wenn das Passwort geändert wird.

Lösung 2 – die Informationen werden dem Objekt bei seiner Erstellung übergeben:

public class ArticlesDaoPlainJdbc implements IArticlesDao {

     // connection to data source
    private final String driverClassName;
    private String url;
    private String user;
    private String pwd;
 ....
    public ArticlesDaoPlainJdbc(String driverClassName,String url,String user,String pwd) {
      this.driverClassName=driverClassName;
    this.url=url;
    this.user=user;
    this.pwd=pwd;
    ...
    }

Hier erhält das Objekt die für seine Funktion erforderlichen Informationen bereits bei seiner Erstellung. Das Problem verlagert sich somit auf den Code, der ihm die vier Informationen übergeben hat. Wie hat dieser sie erhalten? Die folgende Klasse [ArticlesManagerWithDataBase] in der Geschäftslogik könnte ein Objekt [ArticlesDaoPlainJdbc] aus der Datenzugriffsschicht erstellen:

public class ArticlesManagerWithDataBase implements IArticlesManager {

    // a data access instance
    private IArticlesDao articlesDao;
 ....
    public ArticlesManagerWithDataBase (String driverClassName, String url, String user, String pwd, ...) {
        ... 
         // creation of a data access service
        articlesDao =(IArticlesDao)new ArticlesDaoPlainJdbc(driverClassName,url,user,pwd);
    ...
    }

    public ... doSomething(...){
        ...
    }
}

Wir sehen, dass erneut die zum Erstellen des [ArticlesDaoPlainJdbc]-Objekts benötigten Informationen an den Konstruktor des [ArticlesManagerWithDataBase]-Objekts übergeben werden. Wir können uns vorstellen, dass diese Informationen von einer höheren Schicht, beispielsweise der Benutzeroberflächenschicht, an dieses Objekt übergeben werden. So erreichen wir nach und nach die oberste Schicht der Anwendung. Aufgrund ihrer Position wird diese Schicht nicht von einer Schicht aufgerufen, die ihr die benötigten Konfigurationsinformationen übergeben könnte. Wir müssen daher eine Alternative zur konstruktbasierten Konfiguration finden. Der Standardansatz zur Konfiguration einer Anwendung auf ihrer obersten Schicht besteht darin, eine Datei zu verwenden, die alle Informationen enthält, die sich im Laufe der Zeit ändern können. Es kann mehrere solcher Dateien geben. Beim Start der Anwendung erstellt dann eine Initialisierungsschicht alle oder einen Teil der Objekte, die von den verschiedenen Schichten der Anwendung benötigt werden.

Es gibt eine Vielzahl von Konfigurationsdateien. Der aktuelle Trend geht zur Verwendung von XML-Dateien. Dies ist der von Spring gewählte Ansatz. Die Datei, die ein [ArticlesDaoPlainJdbc]-Objekt konfiguriert, könnte wie folgt aussehen:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans SYSTEM "http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
     <!-- data access class -->
    <bean id="articlesDao" class="istia.st.articles.dao.ArticlesDaoPlainJdbc">
        <constructor-arg index="0">
            <value>org.firebirdsql.jdbc.FBDriver</value>
        </constructor-arg>
        <constructor-arg index="1">
            <value>jdbc:firebirdsql:localhost/3050:d:/databases/dbarticles.gdb</value>
        </constructor-arg>
        <constructor-arg index="2">
            <value>someone</value>
        </constructor-arg>
        <constructor-arg index="3">
            <value>somepassword</value>
        </constructor-arg>
    </bean>
</beans>

Eine Anwendung ist eine Sammlung von Objekten, die Spring als „Beans“ bezeichnet, da sie dem JavaBean-Standard für die Benennung von Zugriffs- und Initialisierungsmethoden (Getter/Setter) der privaten Felder eines Objekts folgen. Objekte in einer Anwendung, die einem bestimmten Zweck dienen, werden oft als einzelne Instanz erstellt. Diese werden als Singletons bezeichnet. In unserem hier besprochenen Beispiel einer mehrschichtigen Anwendung wird der Zugriff auf die Artikeldatenbank daher von einer einzigen Instanz der Klasse [ArticlesDaoPlainJdbc] abgewickelt. Bei einer Webanwendung bedienen diese Serviceobjekte mehrere Clients gleichzeitig. Es wird nicht für jeden Client ein eigenes Serviceobjekt erstellt.

Die obige Spring-Konfigurationsdatei ermöglicht die Erstellung eines einzelnen Service-Objekts vom Typ [ArticlesDaoPlainJdbc] in einem Paket namens [istia.st.articles.dao]. Die vier Informationen, die der Konstruktor dieses Objekts benötigt, sind innerhalb eines <bean>...</bean>-Tags definiert. Es gibt so viele solcher <bean>-Tags, wie Singletons erstellt werden sollen.

Wann werden die in der Spring-Datei definierten Objekte instanziiert? Die Initialisierung der Anwendung kann über die main-Methode der Anwendung erfolgen, sofern eine solche vorhanden ist. Bei einer Webanwendung könnte dies die [init]-Methode des Haupt-Servlets sein. Jede Anwendung verfügt über eine Methode, die garantiert als erste ausgeführt wird. In der Regel erfolgt die Instanziierung von Singletons innerhalb dieser Methode.

Nehmen wir ein Beispiel. Angenommen, wir möchten die vorherige Klasse [ArticlesDaoPlainJdbc] mit einem JUnit-Test testen. Eine JUnit-Testklasse verfügt über eine [setUp]-Methode, die vor allen anderen Methoden ausgeführt wird. Hier werden wir das Singleton [ArticlesDaoPlainJdbc] erstellen.

Wenn wir den Ansatz verfolgen, Konfigurationsinformationen über den Konstruktor zu übergeben, erhalten wir die folgende Testklasse:

public class TestArticlesPlainJdbc extends TestCase {
    // tests the ArticlesDaoPlainJdbc item access class
     // the data source is defined in sprintest

     // an instance of the class under test
    private IArticlesDao articlesDao;

    protected void setUp() throws Exception{
        // retrieves a data access instance
        articlesDao =
            (IArticlesDao) new ArticlesDaoPlainJdbc("org.firebirdsql.jdbc.FBDriver",
                "jdbc:firebirdsql:localhost/3050:d:/databases/dbarticles.gdb","someone","somepassword");
    }

Die aufrufende Klasse [TestArticlesPlainJdbc] muss die vier Informationen kennen, die zur Initialisierung des zu erstellenden [ArticlesDaoPlainJdbc]-Singletons erforderlich sind.

Wenn wir den Ansatz verfolgen, Konfigurationsinformationen über eine Konfigurationsdatei zu übergeben, könnten wir die folgende Testklasse verwenden, die die oben beschriebene Spring-Datei nutzt.

public class TestSpringArticlesPlainJdbc extends TestCase {
    // tests the ArticlesDaoJdbc item access class
     // the data source is defined in sprintest

     // an instance of the class under test
    private IArticlesDao articlesDao;

    protected void setUp() throws Exception {
      // retrieves a data access instance
      articlesDao = (IArticlesDao) (new XmlBeanFactory(new ClassPathResource(
          "springArticlesPlainJdbc.xml"))).getBean("articlesDao");
    }

Hier muss die aufrufende Klasse [TestSpringArticlesPlainJdbc] die Informationen, die zur Initialisierung des zu erstellenden Singletons erforderlich sind, nicht kennen. Sie muss lediglich Folgendes wissen:

  1. [springArticlesPlainJdbc.xml]: den Namen der oben beschriebenen Spring-Konfigurationsdatei
  2. [articlesDao]: den Namen des zu erstellenden Singletons

Eine Änderung an der Konfigurationsdatei, die über diese beiden Elemente hinausgeht, hat keine Auswirkungen auf den Java-Code. Diese Methode zur Konfiguration der Objekte einer Anwendung ist sehr flexibel. Um sich selbst zu konfigurieren, muss die Anwendung nur zwei Dinge wissen:

  • den Namen der Spring-Datei, die die Definitionen der zu erstellenden Singletons enthält
  • die Namen dieser Singletons, die der Java-Code verwendet, um über die Konfigurationsdatei eine Referenz auf die Objekte zu erhalten, denen sie zugeordnet wurden

2.2. Dependency Injection und Inversion of Control

Lassen Sie uns nun das Konzept der Dependency Injection vorstellen, das von Spring zur Konfiguration von Anwendungen verwendet wird. Der Begriff Inversion of Control (IoC) wird ebenfalls verwendet. Betrachten wir die Erstellung des Singletons [ArticlesManagerWithDataBase] in der Geschäftsschicht unserer Anwendung:

Um auf Daten aus dem DBMS zuzugreifen, muss die Geschäftsschicht die Dienste eines Objekts nutzen, das die Schnittstelle [IArticlesDao] implementiert, beispielsweise ein Objekt vom Typ [ArticlesDaoPlainJdbc]. Der Code für die Klasse [ArticlesManagerWithDataBase] könnte wie folgt aussehen:

public class ArticlesManagerWithDataBase implements IArticlesManager {

     // a data access instance
    private IArticlesDao articlesDao;
 ....
    public ArticlesManagerWithDataBase (String driverClassName, String url, String user, String pwd, ...) {
        ... 
         // creation of a data access service
        articlesDao =(IArticlesDao)new ArticlesDaoPlainJdbc(driverClassName,url,user,pwd);
    ...
    }

    public ... doSomething(...){
        ...
    }
}

Die Klasse [ArticlesDaoPlainJdbc] soll hier die Schnittstelle [IArticlesDao] implementieren:

public class ArticlesDaoPlainJdbc implements IArticlesDao {...}

Um das für die Funktion der Klasse erforderliche Singleton [IArticlesDao] zu erstellen, verwendet dessen Konstruktor explizit den Namen der Klasse, die die Schnittstelle [IArticlesDao] implementiert:

articlesDao =(IArticlesDao) new ArticlesDaoPlainJdbc(...);

Wir haben daher eine fest codierte Abhängigkeit vom Klassennamen im Code. Sollte sich die Klasse, die die Schnittstelle [IArticlesDao] implementiert, ändern, müsste der Code im vorherigen Konstruktor angepasst werden. Zwischen den Objekten bestehen folgende Beziehungen:

Die Klasse [ArticlesManagerWithDataBase] selbst übernimmt die Initiative, das benötigte Objekt [ArticlesDaoPlainJdbc] zu erstellen. Um auf den Begriff „Inversion of Control“ zurückzukommen, können wir sagen, dass sie diejenige ist, die die „Kontrolle“ darüber hat, das benötigte Objekt zu erstellen.

Wenn wir eine JUnit-Testklasse für die Klasse [ArticlesManagerWithDataBase] schreiben würden, könnte diese etwa so aussehen:

public class TestArticlesManagerWithDataBase extends TestCase {
    // an instance of the business class under test
    private IArticlesManager articlesManager;

    protected void setUp() throws Exception {
        // creates an instance of the business class under test
        articlesManager =
            (IArticlesManager) new ArticlesManagerWithDataBase("org.firebirdsql.jdbc.FBDriver",
                "jdbc:firebirdsql:localhost/3050:d:/databases/dbarticles.gdb","someone","somepassword");
    }

Die Testklasse erstellt eine Instanz der Business-Klasse [ArticlesManagerWithDataBase], die wiederum in ihrem Konstruktor eine Instanz der Datenzugriffsklasse [ArticlesDaoPlainJdbc] erstellt.

Die Spring-Lösung macht es überflüssig, dass die Geschäftsklasse [ArticlesManagerWithDataBase] den Namen [ArticlesDaoPlainJdbc] der von ihr benötigten Datenzugriffsklasse kennt. Dadurch kann die Klasse geändert werden, ohne den Java-Code der Geschäftsklasse zu modifizieren. Spring ermöglicht die gleichzeitige Erstellung beider Singletons: eines für die Datenzugriffsebene und eines für die Geschäftsebene. Die Spring-Konfigurationsdatei definiert einen neuen Bean:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans SYSTEM "http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
     <!-- data access class -->
    <bean id="articlesDao" class="istia.st.articles.dao.ArticlesDaoPlainJdbc">
        <constructor-arg index="0">
            <value>org.firebirdsql.jdbc.FBDriver</value>
        </constructor-arg>
        <constructor-arg index="1">
            <value>jdbc:firebirdsql:localhost/3050:d:/databases/dbarticles.gdb</value>
        </constructor-arg>
        <constructor-arg index="2">
            <value>someone</value>
        </constructor-arg>
        <constructor-arg index="3">
            <value>somepassword</value>
        </constructor-arg>
    </bean>
    <bean id="articlesManager" class="istia.st.articles.domain.ArticlesManagerWithDataBase">
        <property name="articlesDao">
            <ref bean="articlesDao"/>
        </property>
    </bean>
</beans>

Die neue Funktion ist die Bean, die das Singleton der zu erstellenden Business-Klasse definiert:

    <bean id="articlesManager" class="istia.st.articles.domain.ArticlesManagerWithDataBase">
        <property name="articlesDao">
            <ref bean="articlesDao"/>
        </property>
    </bean>
  1. Die Klasse, die die [articlesManager]-Bean implementiert, ist definiert: [ArticlesManagerWithDataBase]
  2. Dem Feld [articlesDao] des Beans wird über das Tag <property name="articlesDao"> ein Wert zugewiesen. Dies ist das in der Klasse [ArticlesManagerWithDataBase] definierte Feld:
public class ArticlesManagerWithDataBase implements IArticlesManager {

  // data access interface
  private IArticlesDao articlesDao;

  public IArticlesDao getArticlesDao() {
    return articlesDao;
  }

  public void setArticlesDao(IArticlesDao articlesDao) {
    this.articlesDao = articlesDao;
  }

Damit das Feld [articlesDao] von Spring und seinem <property>-Tag initialisiert wird, muss das Feld dem JavaBean-Standard entsprechen und es muss eine [setArticlesDao]-Methode vorhanden sein, um das Feld [articlesDao] zu initialisieren. Beachten Sie, dass der Methodenname genau vom Feldnamen abgeleitet ist. Ebenso gibt es oft eine [get...]-Methode, um den Wert des Feldes abzurufen. Hier ist es die [getArticlesDao]-Methode. In dieser neuen Version hat die Klasse [ArticlesManagerWithDataBase] keinen Konstruktor mehr. Sie benötigt keinen mehr.

  • Der Wert, den Spring dem Feld [articlesDao] zuweist, ist derjenige des Beans [articlesDao], der in seiner Konfigurationsdatei definiert ist:
    <bean id="articlesManager" class="istia.st.articles.domain.ArticlesManagerWithDataBase">
        <property name="articlesDao">
            <ref bean="articlesDao"/>
        </property>
    </bean>
    <bean id="articlesDao" class="istia.st.articles.dao.ArticlesDaoPlainJdbc">
        <constructor-arg index="0">
    .............
    </bean>
  • Wenn Spring das Singleton [ArticlesManagerWithDataBase] erstellt, erstellt es auch das Singleton [ArticlesDaoPlainJdbc]:
    • Spring erstellt einen Abhängigkeitsgraphen der Beans und stellt fest, dass die [articlesManager]-Bean von der [articlesDao]-Bean abhängt
    • Es erstellt die [articlesDao]-Bean, d. h. ein Objekt vom Typ [ArticlesDaoPlainJdbc]
    • anschließend erstellt es die [articlesManager]-Bean vom Typ [ArticlesManagerWithDataBase]

Stellen wir uns nun einen JUnit-Test für die Klasse [ArticlesManagerWithDataBase] vor. Dieser könnte wie folgt aussehen:

public class TestSpringArticlesManagerWithDataBase extends TestCase {
    // test business class [ArticlesManagerWithDataBase]

    // an instance of the business class under test
    private IArticlesManager articlesManager;

    protected void setUp() throws Exception {
      // retrieves a data access instance
      articlesManager = (IArticlesManager) (new XmlBeanFactory(new ClassPathResource(
          "springArticlesManagerWithDataBase.xml"))).getBean("articlesManager");
    }

Verfolgen wir den Erstellungsprozess der beiden Singletons, die in der Spring-Datei namens [springArticlesManagerWithDataBase.xml] definiert sind.

  • Die obige [setUp]-Methode fordert eine Referenz auf das Bean namens [articlesManager] an
  • Spring konsultiert seine Konfigurationsdatei und findet die Bean [articlesManager]. Wenn diese bereits erstellt wurde, gibt es einfach eine Referenz auf das Objekt (Singleton) zurück; andernfalls erstellt es sie.
  • Spring erkennt die Abhängigkeit der [articlesManager]-Bean von der [articlesDao]-Bean. Daher erstellt es das [articlesDao]-Singleton vom Typ [ArticlesDaoPlainJdbc], falls dieses noch nicht (als Singleton) erstellt wurde.
  • Es erstellt das [articlesManager]-Singleton vom Typ [ArticlesManagerWithDataBase]

Dieser Mechanismus lässt sich wie folgt schematisch darstellen:

Erinnern wir uns an das Grundgerüst der Klasse [ArticlesManagerWithDataBase]:

public class ArticlesManagerWithDataBase implements IArticlesManager {

  // data access interface
  private IArticlesDao articlesDao;

  public IArticlesDao getArticlesDao() {
    return articlesDao;
  }

  public void setArticlesDao(IArticlesDao articlesDao) {
    this.articlesDao = articlesDao;
  }

Sobald Spring die Singletons fertiggestellt hat, verfügen wir über ein Objekt vom Typ [ArticlesManagerWithDataBase], dessen Feld [articlesDao] initialisiert ist, ohne dass es weiß, wie. Wir sagen, dass wir eine Abhängigkeit in das [ArticlesManagerWithDataBase]-Objekt injiziert haben. Wir sagen auch, dass wir die Steuerung umgekehrt haben: Es ist nicht mehr das [ArticlesManagerWithDataBase]-Objekt, das die Initiative ergreift, um das Objekt zu erstellen, das die benötigte [IArticlesDao]-Schnittstelle implementiert; vielmehr ist es die oberste Anwendungsebene (bei ihrer Initialisierung), die sich um die Erstellung aller benötigten Objekte kümmert, indem sie deren gegenseitige Abhängigkeiten verwaltet.

Der Hauptvorteil der Konfiguration des [ArticlesManagerWithDataBase]-Singletons über eine Spring-Datei besteht darin, dass wir nun die Implementierungsklasse ändern können, die dem Feld [articlesDao] der Klasse [ArticlesManagerWithDataBase] entspricht, ohne deren Code zu ändern. Wir müssen lediglich den Klassennamen in der Definition des [articlesDao]-Beans in der Spring-Datei ändern:

    <bean id="articlesDao" class="istia.st.articles.dao.ArticlesDaoPlainJdbc">
...
    </bean>

wird beispielsweise zu:

    <bean id="articlesDao" class="istia.st.articles.dao.ArticlesDaoIbatisSqlMap">
...
    </bean>

Die Bean [ArticlesManagerWithDataBase] wird mit dieser neuen Datenzugriffsklasse arbeiten, ohne es überhaupt zu merken.

2.3. Spring IoC in der Praxis

2.3.1. Beispiel 1

Betrachten Sie die folgende Klasse:

package istia.st.springioc.domain;

public class Personne {
  private String nom;
  private int age;

   // person display
  public String toString() {
    return "nom=[" + this.nom + "], age=[" + this.age + "]";
  }

   // init-close
  public void init() {
    System.out.println("init personne [" + this.toString() + "]");
  }

  public void close() {
    System.out.println("destroy personne [" + this.toString() + "]");
  }

   // getters-setters
  public int getAge() {
    return age;
  }

  public void setAge(int age) {
    this.age = age;
  }

  public String getNom() {
    return nom;
  }

  public void setNom(String nom) {
    this.nom = nom;
  }
}

Die Klasse hat:

  • zwei private Felder, name und age
  • Getter- und Setter-Methoden für diese beiden Felder
  • eine toString-Methode, um den Wert des [Person]-Objekts als Zeichenkette abzurufen
  • eine init-Methode, die von Spring beim Erstellen des Objekts aufgerufen wird, und eine close-Methode, die beim Löschen des Objekts aufgerufen wird

Um Objekte vom Typ [Person] zu erstellen, verwenden wir die folgende Spring-Datei:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" 
    "http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
    <bean id="personne1" class="istia.st.springioc.domain.Personne" 
        init-method="init" destroy-method="close">
        <property name="nom">
            <value>Simon</value>
        </property>
        <property name="age">
            <value>40</value>
        </property>
    </bean>
    <bean id="personne2" class="istia.st.springioc.domain.Personne" 
        init-method="init" destroy-method="close">
        <property name="nom">
            <value>Brigitte</value>
        </property>
        <property name="age">
            <value>20</value>
        </property>
    </bean>
</beans>

Diese Datei erhält den Namen config.xml.

  • Sie definiert zwei Beans mit den jeweiligen Schlüsseln „person1“ und „person2“ vom Typ [Person]
  • Sie initialisiert die Felder [name, age] für jede Person
  • Sie definiert die Methoden, die beim Erstellen des Objekts [init-method] und beim Löschen des Objekts [destroy-method] aufgerufen werden

Für unsere Tests verwenden wir eine einzige JUnit-Testklasse, der wir nach und nach Methoden hinzufügen werden. Die erste Version dieser Klasse sieht wie folgt aus:

package istia.st.springioc.tests;

import istia.st.springioc.domain.Personne;
import org.springframework.beans.factory.ListableBeanFactory;
import org.springframework.beans.factory.xml.XmlBeanFactory;
import org.springframework.core.io.ClassPathResource;
import junit.framework.TestCase;

public class Tests extends TestCase {

   // bean factory
  private ListableBeanFactory bf;

   // init tests
  public void setUp() {
    bf = new XmlBeanFactory(new ClassPathResource("config.xml"));
  }

  public void test1() {
    // retrieve [Person] bean keys from the Spring file
    Personne personne1 = (Personne) bf.getBean("personne1");
    System.out.println("personne1=" + personne1.toString());
    Personne personne2 = (Personne) bf.getBean("personne2");
    System.out.println("personne2=" + personne2.toString());
    personne2 = (Personne) bf.getBean("personne2");
    System.out.println("personne2=" + personne2.toString());
  }
}

Kommentare:

  • Um die in der Datei [config.xml] definierten Beans abzurufen, verwenden wir ein Objekt vom Typ [ListableBeanFactory]. Es gibt weitere Objekttypen, die den Zugriff auf Beans ermöglichen. Das [ListableBeanFactory]-Objekt wird in der Methode [setUp] der Testklasse abgerufen und in einer privaten Variablen gespeichert. Es steht somit allen Testmethoden zur Verfügung.
  • Die Datei [config.xml] wird im [ClassPath] der Anwendung abgelegt, d. h. in einem der Verzeichnisse, die von der Java Virtual Machine durchsucht werden, wenn sie nach einer von der Anwendung referenzierten Klasse sucht. Das [ClassPathResource]-Objekt wird verwendet, um im [ClassPath] einer Anwendung nach einer Ressource zu suchen, in diesem Fall nach der Datei [config.xml].
  • Spring kann Konfigurationsdateien in verschiedenen Formaten verwenden. Das [XmlBeanFactory]-Objekt dient dazu, eine Konfigurationsdatei im XML-Format zu parsen.
  • Die Verarbeitung einer Spring-Datei gibt ein Objekt vom Typ [ListableBeanFactory] zurück, hier das Objekt bf. Mit diesem Objekt wird über bf.getBean(C) ein Bean abgerufen, das durch den Schlüssel C identifiziert wird.
  • Die Methode [test1] ruft die Werte der Beans mit den Schlüsseln „person1“ und „person2“ ab und zeigt sie an.

Die Struktur des Eclipse-Projekts unserer Anwendung sieht wie folgt aus:

Image

Anmerkungen:

  • Der Ordner [src] enthält den Quellcode. Der kompilierte Code wird in einen Ordner [bin] kopiert, der hier nicht dargestellt ist.
  • Die Datei [config.xml] befindet sich im Stammverzeichnis des Ordners [src]. Beim Erstellen des Projekts wird sie automatisch in den Ordner [bin] kopiert, der Teil des [ClassPath] der Anwendung ist. Dort wird sie vom Objekt [ClassPathResource] gesucht.
  • Der Ordner [lib] enthält drei Java-Bibliotheken, die von der Anwendung benötigt werden:
      • commons-logging.jar und spring-core.jar für die Spring-Klassen
      • junit.jar für die JUnit-Klassen
  • Der Ordner [lib] ist ebenfalls Teil des [ClassPath] der Anwendung

Die Ausführung der Methode [test1] des JUnit-Tests liefert folgende Ergebnisse:

18 sept. 2004 11:28:53 org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions
INFO: Loading XML bean definitions from class path resource [config.xml]
18 sept. 2004 11:28:53 org.springframework.beans.factory.support.AbstractBeanFactory getBean
INFO: Creating shared instance of singleton bean 'personne1'
init personne [nom=[Simon], age=[40]]
personne1=nom=[Simon], age=[40]
18 sept. 2004 11:28:53 org.springframework.beans.factory.support.AbstractBeanFactory getBean
INFO: Creating shared instance of singleton bean 'personne2'
init personne [nom=[Brigitte], age=[20]]
personne2=nom=[Brigitte], age=[20]
personne2=nom=[Brigitte], age=[20]

Kommentare:

  • Spring protokolliert eine Reihe von Ereignissen mithilfe der Bibliothek [commons-logging.jar]. Diese Protokolle helfen uns, die Funktionsweise von Spring besser zu verstehen.
  • Die Datei [config.xml] wurde geladen und anschließend verarbeitet
  • Der Vorgang*
Personne personne1 = (Personne) bf.getBean("personne1");

Dies führte zur Erstellung der [person1]-Bean. Wir können das entsprechende Spring-Protokoll einsehen. Da wir in der Definition der [person1]-Bean [init-method="init"] angegeben hatten, wurde die [init]-Methode des erstellten [Person]-Objekts ausgeführt. Die entsprechende Meldung wird angezeigt.

  • Der Vorgang
System.out.println("personne1=" + personne1.toString());

zeigte den Wert des erstellten [Person]-Objekts an.

  • Das gleiche Phänomen tritt bei der Schlüssel-Bean [person2] auf.
  • Die letzte Operation
    personne2 = (Personne) bf.getBean("personne2");
    System.out.println("personne2=" + personne2.toString());

Dies führte nicht zur Erstellung eines neuen Objekts vom Typ [Person]. Wäre dies der Fall gewesen, wäre die Methode [init] angezeigt worden, was hier jedoch nicht der Fall ist. Dies ist das Prinzip des Singletons. Standardmäßig erstellt Spring nur eine einzige Instanz der Beans in seiner Konfigurationsdatei. Es handelt sich um einen Objektreferenzdienst. Wird nach der Referenz auf ein Objekt gefragt, das noch nicht erstellt wurde, erstellt es dieses und gibt eine Referenz zurück. Ist das Objekt bereits erstellt, gibt Spring einfach eine Referenz darauf zurück.

  • Beachten Sie, dass von der [close]-Methode des [Person]-Objekts keine Spur zu sehen ist, obwohl wir [destroy-method=close] in die Bean-Definition geschrieben hatten. Es ist möglich, dass diese Methode nur ausgeführt wird, wenn der vom Objekt belegte Speicher vom Garbage Collector freigegeben wird. Zu diesem Zeitpunkt ist die Anwendung bereits beendet, und das Schreiben auf den Bildschirm hat keine Wirkung. Zu überprüfen.

Nachdem wir nun die Grundlagen einer Spring-Konfiguration behandelt haben, können wir unsere Erläuterungen etwas schneller durchgehen.

2.3.2. Beispiel 2

Betrachten Sie die folgende neue [Car]-Klasse:

package istia.st.springioc.domain;

public class Voiture {
  private String marque;
  private String type;
  private Personne propriétaire;

   // manufacturers

  public Voiture() {
  }

  public Voiture(String marque, String type, Personne propriétaire) {
    this.marque = marque;
    this.type = type;
    this.propriétaire = propriétaire;
  }

   // toString
  public String toString() {
    return "Voiture : marque=[" + this.marque + "] type=[" + this.type
        + "] propriétaire=[" + this.propriétaire + "]";
  }

     // getters-setters
  public String getMarque() {
    return marque;
  }

  public void setMarque(String marque) {
    this.marque = marque;
  }

  public Personne getPropriétaire() {
    return propriétaire;
  }

  public void setPropriétaire(Personne propriétaire) {
    this.propriétaire = propriétaire;
  }

  public String getType() {
    return type;
  }

  public void setType(String type) {
    this.type = type;
  }

   // init-close
  public void init() {
    System.out.println("init voiture [" + this.toString() + "]");
  }

  public void close() {
    System.out.println("destroy voiture [" + this.toString() + "]");
  }

}

Die Klasse verfügt über:

  • drei private Felder: type, make und owner. Diese Felder können mithilfe der öffentlichen get- und set-Methoden initialisiert und ausgelesen werden. Sie können auch mithilfe des Konstruktors Car(String, String, Person) initialisiert werden. Die Klasse verfügt außerdem über einen Konstruktor ohne Argumente, um dem JavaBean-Standard zu entsprechen.
  • eine toString-Methode, um den Wert des [Car]-Objekts als Zeichenkette abzurufen
  • eine init-Methode, die von Spring unmittelbar nach der Erstellung des Objekts aufgerufen wird, eine close-Methode, die beim Löschen des Objekts aufgerufen wird

Um Objekte vom Typ [Car] zu erstellen, verwenden wir die folgende Spring-Datei [config.xml]:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" 
    "http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
    <bean id="personne1" class="istia.st.springioc.domain.Personne" 
        init-method="init" destroy-method="close">
        <property name="nom">
            <value>Simon</value>
        </property>
        <property name="age">
            <value>40</value>
        </property>
    </bean>
    <bean id="personne2" class="istia.st.springioc.domain.Personne" 
        init-method="init" destroy-method="close">
        <property name="nom">
            <value>Brigitte</value>
        </property>
        <property name="age">
            <value>20</value>
        </property>
    </bean>
    <bean id="voiture1" class="istia.st.springioc.domain.Voiture" 
        init-method="init" destroy-method="close">
        <constructor-arg index="0">
            <value>Peugeot</value>
        </constructor-arg>
        <constructor-arg index="1">
            <value>307</value>
        </constructor-arg>
        <constructor-arg index="2">
            <ref bean="personne2"></ref>
        </constructor-arg>
    </bean>
</beans>

Diese Datei fügt den vorherigen Definitionen eine Bean mit dem Schlüssel „car1“ vom Typ [Car] hinzu. Um diese Bean zu initialisieren, hätten wir schreiben können:

    <bean id="voiture1" class="istia.st.springioc.domain.Voiture" 
        init-method="init" destroy-method="close">
        <property name="marque">
            <value>Peugeot</value>
        </property>
        <property name="type">
            <value>307</value>
        </property>
        <property name="propriétaire">
            <ref bean="personne2"/>
        </property>
    </bean>

Anstelle der bereits vorgestellten Methode haben wir uns hier für die Verwendung des Konstruktors Car(String, String, Person) der Klasse entschieden. Darüber hinaus definiert die Bean [car1] die Methode, die während der anfänglichen Erstellung des Objekts aufgerufen werden soll [init-method], sowie die Methode, die während der Zerstörung des Objekts aufgerufen werden soll [destroy-method].

Für unsere Tests verwenden wir die bereits vorgestellte JUnit-Testklasse und fügen ihr die folgende [test2]-Methode hinzu:

1
2
3
4
5
  public void test2() {
     // recovery of bean [voiture1]
    Voiture Voiture1 = (Voiture) bf.getBean("voiture1");
    System.out.println("Voiture1=" + Voiture1.toString());
  }

Die Methode [test2] ruft die Bean [car1] ab und gibt sie aus.

Die Struktur des Eclipse-Projekts bleibt dieselbe wie im vorherigen Test. Die Ausführung der Methode [test2] des JUnit-Tests liefert folgende Ergebnisse:

18 sept. 2004 14:56:10 org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions
INFO: Loading XML bean definitions from class path resource [config.xml]
18 sept. 2004 14:56:10 org.springframework.beans.factory.support.AbstractBeanFactory getBean
INFO: Creating shared instance of singleton bean 'voiture1'
18 sept. 2004 14:56:10 org.springframework.beans.factory.support.AbstractBeanFactory getBean
INFO: Creating shared instance of singleton bean 'personne2'
init personne [nom=[Brigitte], age=[20]]
18 sept. 2004 14:56:10 org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory autowireConstructor
INFO: Bean 'voiture1' instantiated via constructor [public istia.st.springioc.domain.Voiture(java.lang.String,java.lang.String,istia.st.springioc.domain.Personne)]
init voiture [Voiture : marque=[Peugeot] type=[307] propriétaire=[nom=[Brigitte], age=[20]]]
Voiture1=Voiture : marque=[Peugeot] type=[307] propriétaire=[nom=[Brigitte], age=[20]]

Kommentare:

  1. Die Methode [test2] fordert eine Referenz auf die Bean [car1] an
  2. Zeile 4: Spring beginnt mit der Erstellung der [car1]-Bean, da diese Bean noch nicht erstellt wurde (Singleton)
  3. Zeile 6: Da die Bean [car1] auf die Bean [person2] verweist, wird diese wiederum instanziiert
  4. Zeile 7: Die Bean [person2] wurde erstellt. Anschließend wird ihre [init]-Methode ausgeführt.
  5. Zeile 9: Spring gibt an, dass es einen Konstruktor verwenden wird, um die Bean [car1] zu erstellen
  6. Zeile 10: Die Bean [car1] wurde erstellt. Anschließend wird ihre [init]-Methode ausgeführt.
  7. Zeile 11: Die Methode [test2] gibt den Wert der Bean [car1] aus

2.3.3. Beispiel 3

Wir führen die folgende neue Klasse [PersonGroup] ein:

package istia.st.springioc.domain;

import java.util.Map;

public class GroupePersonnes {
  private Personne[] membres;
  private Map groupesDeTravail;

   // getters - setters
  public Personne[] getMembres() {
    return membres;
  }

  public void setMembres(Personne[] membres) {
    this.membres = membres;
  }

  public Map getGroupesDeTravail() {
    return groupesDeTravail;
  }

  public void setGroupesDeTravail(Map groupesDeTravail) {
    this.groupesDeTravail = groupesDeTravail;
  }

   // display
  public String toString() {
    String liste = "membres : ";
    for (int i = 0; i < this.membres.length; i++) {
      liste += "[" + this.membres[i].toString() + "]";
    }
    return liste + ", groupes de travail = " + this.groupesDeTravail.toString();
  }

   // init-close
  public void init() {
    System.out.println("init GroupePersonnes [" + this.toString() + "]");
  }

  public void close() {
    System.out.println("destroy GroupePersonnes [" + this.toString() + "]");
  }
}

Seine beiden privaten Mitglieder sind:

members: ein Array von Personen, die Mitglieder der Gruppe sind

workGroups: ein Wörterbuch, das eine Person einer Arbeitsgruppe zuordnet

Beachten Sie hierbei, dass die Klasse [PeopleGroup] keinen Konstruktor ohne Argumente definiert, um dem JavaBean-Standard zu entsprechen. Erinnern Sie sich daran, dass es bei Fehlen eines Konstruktors einen „Standard“-Konstruktor gibt, bei dem es sich um den Konstruktor ohne Argumente handelt, der nichts tut.

Das Ziel hierbei ist es, zu zeigen, wie Spring die Initialisierung komplexer Objekte ermöglicht, beispielsweise solcher mit Array- oder Dictionary-Feldern. Wir fügen der bisherigen Spring-Datei [config.xml] eine neue Bean hinzu:

    <bean id="groupe1" class="istia.st.springioc.domain.GroupePersonnes" 
        init-method="init" destroy-method="close">
        <property name="membres">
            <list>
                <ref bean="personne1"/>
                <ref bean="personne2"/>
            </list>
        </property>
        <property name="groupesDeTravail">
            <map>
                <entry key="Brigitte">
                    <value>Marketing</value>
                </entry>
                <entry key="Simon">
                    <value>Ressources humaines</value>
                </entry>
            </map>
        </property>
    </bean>
  1. Mit dem <list>-Tag können Sie ein Feld vom Typ Array oder ein Feld, das die List-Schnittstelle implementiert, mit verschiedenen Werten initialisieren.
  2. Mit dem map-Tag können Sie dasselbe mit einem Feld tun, das die Map-Schnittstelle implementiert

Für unsere Tests verwenden wir die bereits vorgestellte JUnit-Testklasse und fügen ihr die folgende Methode [test3] hinzu:

1
2
3
4
5
  public void test3() {
    // bean retrieval [group1]]
    GroupePersonnes groupe1 = (GroupePersonnes) bf.getBean("groupe1");
    System.out.println("groupe1=" + groupe1.toString());
  }

Die Methode [test3] ruft die Bean [groupe1] ab und gibt sie aus.

Die Struktur des Eclipse-Projekts bleibt dieselbe wie im vorherigen Test. Die Ausführung der Methode [test3] des JUnit-Tests liefert folgende Ergebnisse:

18 sept. 2004 15:51:45 org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions
INFO: Loading XML bean definitions from class path resource [config.xml]
18 sept. 2004 15:51:45 org.springframework.beans.factory.support.AbstractBeanFactory getBean
INFO: Creating shared instance of singleton bean 'groupe1'
18 sept. 2004 15:51:45 org.springframework.beans.factory.support.AbstractBeanFactory getBean
INFO: Creating shared instance of singleton bean 'personne1'
init personne [nom=[Simon], age=[40]]
18 sept. 2004 15:51:45 org.springframework.beans.factory.support.AbstractBeanFactory getBean
INFO: Creating shared instance of singleton bean 'personne2'
init personne [nom=[Brigitte], age=[20]]
init GroupePersonnes [membres : [nom=[Simon], age=[40]][nom=[Brigitte], age=[20]], groupes de travail = {Brigitte=Marketing, Simon=Ressources humaines}]
groupe1=membres : [nom=[Simon], age=[40]][nom=[Brigitte], age=[20]], groupes de travail = {Brigitte=Marketing, Simon=Ressources humaines}

Kommentare:

  • Die Methode [test3] fordert eine Referenz auf die Bean [group1] an
  • Zeile 4: Spring beginnt mit der Erstellung dieser Bean
  • Da die [group1]-Bean auf die [person1]- und [person2]-Beans verweist, werden diese beiden Beans erstellt (Zeilen 6 und 9) und ihre init-Methoden ausgeführt (Zeilen 7 und 10)
  • Zeile 11: Die [group1]-Bean wurde erstellt. Ihre [init]-Methode wird nun ausgeführt.
  • Zeile 12: Anzeige, die von der Methode [test3] angefordert wurde.

2.4. Spring zur Konfiguration von dreischichtigen Webanwendungen

2.4.1. Allgemeine Anwendungsarchitektur

Wir möchten eine dreischichtige Anwendung mit folgender Struktur erstellen:

  • Die drei Schichten werden durch die Verwendung von Java-Schnittstellen voneinander unabhängig gemacht
  • Die Integration der drei Schichten wird von Spring übernommen
  • Wir werden für jede der drei Schichten separate Pakete erstellen, die wir Control, Domain und Dao nennen werden. Ein zusätzliches Paket wird die Testanwendungen enthalten.

Die Anwendungsstruktur in Eclipse könnte wie folgt aussehen:

Image

2.4.2. Die DAO-Datenzugriffsschicht

Die DAO-Schicht implementiert die folgende Schnittstelle:

package istia.st.demo.dao;

public interface IDao1 {
  public int doSometingInDaoLayer(int a, int b);
}
  • Schreiben Sie zwei Klassen, Dao1Impl1 und Dao1Impl2, die die Schnittstelle IDao1 implementieren. Die Methode Dao1Impl1.doSomethingInDaoLayer gibt a+b zurück, und die Methode Dao1Impl2.doSomethingInDaoLayer gibt a-b zurück.
  • Schreiben Sie eine JUnit-Testklasse, um die beiden vorherigen Klassen zu testen

2.4.3. Die Geschäftsschicht

Die Geschäftsschicht implementiert die folgende Schnittstelle:

package istia.st.demo.domain;

public interface IDomain1 {
  public int doSomethingInDomainLayer(int a, int b);
}
  • Schreiben Sie zwei Klassen, Domain1Impl1 und Domain1Impl2, die die Schnittstelle IDomain1 implementieren. Diese Klassen verfügen über einen Konstruktor, der einen Parameter vom Typ IDao1 entgegennimmt. Die Methode Domain1Impl1.doSomethingInDomainLayer erhöht a und b um eins und übergibt diese beiden Parameter anschließend an die Methode doSomethingInDaoLayer des empfangenen IDao1-Objekts. Die Methode Domain1Impl2.doSomethingInDomainLayer hingegen verringert a und b um eins, bevor sie dasselbe tut.
  • Schreiben Sie eine JUnit-Testklasse, um die beiden vorangegangenen Klassen zu testen

2.4.4. Die Benutzeroberflächenschicht

Die Benutzeroberflächenschicht implementiert die folgende Schnittstelle:

package istia.st.demo.control;

public interface IControl1 {
  public int doSometingInControlLayer(int a, int b);
}
  • Schreiben Sie zwei Klassen, Control1Impl1 und Control1Impl2, die die Schnittstelle IControl1 implementieren. Diese Klassen verfügen über einen Konstruktor, der einen Parameter vom Typ IDomain1 entgegennimmt. Die Methode Control1Impl1.doSomethingInControlLayer erhöht a und b um eins und übergibt diese beiden Parameter anschließend an die Methode doSomethingInDomainLayer des empfangenen IDomain1-Objekts. Die Methode Control1Impl2.doSomethingInControlLayer hingegen verringert a und b um eins, bevor sie dasselbe tut.
  • Schreiben Sie eine JUnit-Testklasse, um die beiden vorherigen Klassen zu testen

2.4.5. Integration mit Spring

  • Schreiben Sie eine Spring-Konfigurationsdatei, die festlegt, welche Klassen jede der drei vorherigen Schichten verwenden soll
  • Schreiben Sie eine JUnit-Testklasse unter Verwendung verschiedener Spring-Konfigurationen, um die Flexibilität der Anwendung hervorzuheben
  • Schreiben Sie eine eigenständige Anwendung (main-Methode), die zwei Parameter an die IControl1-Schnittstelle übergibt und das von der Schnittstelle zurückgegebene Ergebnis anzeigt.

2.4.6. Eine Lösung

2.4.6.1. Das Eclipse-Projekt

Image

Die Archive im Ordner [lib] wurden dem [ClassPath] des Projekts hinzugefügt.

2.4.6.2. Das Paket [istia.st.demo.dao]

Die Schnittstelle:

1
2
3
4
5
6
7
8
9
package istia.st.demo.dao;

/**
 * @author ST-ISTIA
 *  
 */
public interface IDao1 {
  public int doSometingInDaoLayer(int a, int b);
}

Eine erste Implementierungsklasse:

package istia.st.demo.dao;

/**
 * @author ST-ISTIA
 *  
 */
public class Dao1Impl1 implements IDao1 {

     // we do something in the [dao] layer
  public int doSometingInDaoLayer(int a, int b) {
    return a+b;
  }

}

Eine zweite Implementierungsklasse:

package istia.st.demo.dao;

/**
 * @author ST-ISTIA
 *
 */
public class Dao1Impl2 implements IDao1 {

     // we do something in the [dao] layer
  public int doSometingInDaoLayer(int a, int b) {
    return a-b;
  }
}

2.4.6.3. Das Paket [istia.st.demo.domain]

Die Schnittstelle:

package istia.st.demo.domain;

/**
 * @author ST-ISTIA
 *  
 */
public interface IDomain1 {

     // we do something in the [domain] layer
  public int doSomethingInDomainLayer(int a, int b);
}

Eine erste Implementierungsklasse:

package istia.st.demo.domain;

import istia.st.demo.dao.IDao1;

/**
 * @author ST-ISTIA
 *  
 */
public class Domain1Impl1 implements IDomain1 {

     // the [dao] layer access service
  private IDao1 dao1;

  public Domain1Impl1() {
     // constructor with no arguments
  }

     // memorizes the [dao] layer access service
  public Domain1Impl1(IDao1 dao1) {
    this.dao1 = dao1;
  }

     // we do something in the [domain] layer
  public int doSomethingInDomainLayer(int a, int b) {
    a++;
    b++;
    return dao1.doSometingInDaoLayer(a, b);
  }
}

Eine zweite Implementierungsklasse:

package istia.st.demo.domain;

import istia.st.demo.dao.IDao1;

/**
 * @author ST-ISTIA
 *  
 */
public class Domain1Impl2 implements IDomain1 {

     // the [dao] layer access service
  private IDao1 dao1;

  public Domain1Impl2() {
     // constructor with no arguments
  }

     // memorizes the [dao] layer access service
  public Domain1Impl2(IDao1 dao1) {
    this.dao1 = dao1;
  }

     // we do something in the [domain] layer
  public int doSomethingInDomainLayer(int a, int b) {
    a--;
    b--;
    return dao1.doSometingInDaoLayer(a, b);
  }
}

2.4.6.4. Das Paket [istia.st.demo.control]

Die Schnittstelle

1
2
3
4
5
6
7
8
9
package istia.st.demo.control;

/**
 * @author ST-ISTIA
 *  
 */
public interface IControl1 {
  public int doSometingInControlLayer(int a, int b);
}

Eine erste Implementierungsklasse:

package istia.st.demo.control;

import istia.st.demo.domain.IDomain1;

/**
 * @author ST-ISTIA
 *  
 */
public class Control1Impl1 implements IControl1 {
   // business class in layer [domain]
    private IDomain1 domain1;

  public Control1Impl1() {
     // constructor with no arguments
  }

     // domain] layer access service enhancement
  public Control1Impl1(IDomain1 domain1) {
    this.domain1 = domain1;
  }

     // we're doing something
  public int doSometingInControlLayer(int a, int b) {
    a++;
    b++;
    return domain1.doSomethingInDomainLayer(a, b);
  }

}

Eine zweite Implementierungsklasse:

package istia.st.demo.control;

import istia.st.demo.domain.IDomain1;

/**
 * @author ST-ISTIA
 *  
 */
public class Control1Impl2 implements IControl1 {

     // the [domain] layer access class
    private IDomain1 domain1;

  public Control1Impl2() {
     // constructor with no arguments
  }

     // stores the [domain] layer access class
  public Control1Impl2(IDomain1 domain1) {
    this.domain1 = domain1;
  }

     // we're doing something
  public int doSometingInControlLayer(int a, int b) {
    a--;
    b--;
    return domain1.doSomethingInDomainLayer(a, b);
  }

}

2.4.6.5. [Spring] Konfigurationsdateien

Eine erste [springMainTest1.xml]:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans SYSTEM "http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
     <!-- the dao class -->
    <bean id="dao" class="istia.st.demo.dao.Dao1Impl1">
    </bean>
     <!-- the trade class -->
    <bean id="domain" class="istia.st.demo.domain.Domain1Impl1">
        <constructor-arg index="0">
            <ref bean="dao"/>
        </constructor-arg>
    </bean>
     <!-- the control class -->
    <bean id="control" class="istia.st.demo.control.Control1Impl1">
        <constructor-arg index="0">
            <ref bean="domain"/>
        </constructor-arg>
    </bean>
</beans>

Eine zweite [springMainTest2.xml]:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans SYSTEM "http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
     <!-- the dao class -->
    <bean id="dao" class="istia.st.demo.dao.Dao1Impl2">
    </bean>
     <!-- the trade class -->
    <bean id="domain" class="istia.st.demo.domain.Domain1Impl2">
        <constructor-arg index="0">
            <ref bean="dao"/>
        </constructor-arg>
    </bean>
     <!-- the control class -->
    <bean id="control" class="istia.st.demo.control.Control1Impl2">
        <constructor-arg index="0">
            <ref bean="domain"/>
        </constructor-arg>
    </bean>
</beans>

2.4.6.6. Das Testpaket [istia.st.demo.tests]

Ein [main]-Test:

package istia.st.demo.tests;

import istia.st.demo.control.IControl1;

import org.springframework.beans.factory.xml.XmlBeanFactory;
import org.springframework.core.io.ClassPathResource;

/**
 * @author ST-ISTIA
 *  
 */
public class MainTest1 {
  public static void main(String[] arguments) {
     // we retrieve an implementation of the IControl1 interface
    IControl1 control = (IControl1) (new XmlBeanFactory(new ClassPathResource(
        "springMainTest1.xml"))).getBean("control");
     // we use the
    int a = 10, b = 20;
    int res = control.doSometingInControlLayer(a, b);
     // the result is displayed
    System.out.println("control(" + a + "," + b + ")=" + res);
  }
}

In der Eclipse-Konsole angezeigte Ergebnisse:

11 mars 2005 11:25:14 org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions
INFO: Loading XML bean definitions from class path resource [springMainTest1.xml]
11 mars 2005 11:25:14 org.springframework.beans.factory.support.AbstractBeanFactory getBean
INFO: Creating shared instance of singleton bean 'control'
11 mars 2005 11:25:14 org.springframework.beans.factory.support.AbstractBeanFactory getBean
INFO: Creating shared instance of singleton bean 'domain'
11 mars 2005 11:25:14 org.springframework.beans.factory.support.AbstractBeanFactory getBean
INFO: Creating shared instance of singleton bean 'dao'
11 mars 2005 11:25:14 org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory autowireConstructor
INFO: Bean 'domain' instantiated via constructor [public istia.st.demo.domain.Domain1Impl1(istia.st.demo.dao.IDao1)]
11 mars 2005 11:25:14 org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory autowireConstructor
INFO: Bean 'control' instantiated via constructor [public istia.st.demo.control.Control1Impl1(istia.st.demo.domain.IDomain1)]
control(10,20)=34

Ein weiterer Test unter Verwendung der zweiten [Spring]-Konfigurationsdatei:

package istia.st.demo.tests;

import istia.st.demo.control.IControl1;
import org.springframework.beans.factory.xml.XmlBeanFactory;
import org.springframework.core.io.ClassPathResource;

/**
 * @author ST-ISTIA
 *  
 */
public class MainTest2 {
  public static void main(String[] arguments) {
     // we retrieve an implementation of the IControl1 interface
    IControl1 control = (IControl1) (new XmlBeanFactory(new ClassPathResource(
        "springMainTest2.xml"))).getBean("control");
     // we use the
    int a = 10, b = 20;
    int res = control.doSometingInControlLayer(a, b);
     // the result is displayed
    System.out.println("control(" + a + "," + b + ")=" + res);
  }
}

In der Eclipse-Konsole angezeigte Ergebnisse:

11 mars 2005 11:28:52 org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions
INFO: Loading XML bean definitions from class path resource [springMainTest2.xml]
11 mars 2005 11:28:52 org.springframework.beans.factory.support.AbstractBeanFactory getBean
INFO: Creating shared instance of singleton bean 'control'
11 mars 2005 11:28:52 org.springframework.beans.factory.support.AbstractBeanFactory getBean
INFO: Creating shared instance of singleton bean 'domain'
11 mars 2005 11:28:52 org.springframework.beans.factory.support.AbstractBeanFactory getBean
INFO: Creating shared instance of singleton bean 'dao'
11 mars 2005 11:28:52 org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory autowireConstructor
INFO: Bean 'domain' instantiated via constructor [public istia.st.demo.domain.Domain1Impl2(istia.st.demo.dao.IDao1)]
11 mars 2005 11:28:52 org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory autowireConstructor
INFO: Bean 'control' instantiated via constructor [public istia.st.demo.control.Control1Impl2(istia.st.demo.domain.IDomain1)]
control(10,20)=-10

Zum Schluss noch ein JUnit-Test:

package istia.st.demo.tests;

import istia.st.demo.control.IControl1;
import org.springframework.beans.factory.xml.XmlBeanFactory;
import org.springframework.core.io.ClassPathResource;
import junit.framework.TestCase;

/**
 * @author ST-ISTIA
 *  
 */
public class JunitTest2Control1 extends TestCase {
  public void testControl1() {
     // we retrieve an implementation of the IControl1 interface
    IControl1 control1 = (IControl1) (new XmlBeanFactory(new ClassPathResource(
        "springMainTest1.xml"))).getBean("control");
     // we use the
    int a1 = 10, b1 = 20;
    int res1 = control1.doSometingInControlLayer(a1, b1);
    assertEquals(34, res1);
     // we retrieve another implementation of the IControl1 interface
    IControl1 control2 = (IControl1) (new XmlBeanFactory(new ClassPathResource(
        "springMainTest2.xml"))).getBean("control");
     // we use the
    int a2 = 10, b2 = 20;
    int res2 = control2.doSometingInControlLayer(a2, b2);
    assertEquals(-10, res2);
  }
}

2.5. Fazit

Das Spring-Framework bietet echte Flexibilität sowohl bei der Anwendungsarchitektur als auch bei der Konfiguration. Wir haben das IoC-Konzept verwendet, eine der beiden Säulen von Spring. Die andere Säule ist AOP (Aspect-Oriented Programming), auf die wir nicht eingegangen sind. Damit können Sie einer Klassenmethode durch Konfiguration „Verhalten“ hinzufügen, ohne den Code der Methode zu ändern. Einfach ausgedrückt ermöglicht AOP Ihnen, Aufrufe bestimmter Methoden zu filtern:

  • Der Filter kann vor oder nach der Zielmethode M oder beides ausgeführt werden.
  • Die Methode M hat keine Kenntnis von diesen Filtern. Sie werden in der Spring-Konfigurationsdatei definiert.
  • Der Code der Methode M wird nicht verändert. Filter sind Java-Klassen, die implementiert werden müssen. Spring stellt vordefinierte Filter bereit, insbesondere für die Verwaltung von DBMS-Transaktionen.
  • Filter sind Beans und werden als solche in der Spring-Konfigurationsdatei als Beans definiert.

Ein gängiger Filter ist der Transaktionsfilter. Betrachten wir eine Methode M in der Geschäftsschicht, die zwei untrennbare Operationen an Daten ausführt (eine Arbeitseinheit). Sie ruft zwei Methoden, M1 und M2, in der DAO-Schicht auf, um diese beiden Operationen auszuführen.

Da sich die Methode M in der Geschäftsschicht befindet, abstrahiert sie den zugrunde liegenden Datenspeicher. Sie muss beispielsweise nicht davon ausgehen, dass die Daten in einem DBMS gespeichert sind oder dass sie die Aufrufe der Methoden M1 und M2 in eine DBMS-Transaktion einschließen muss. Es ist Aufgabe der DAO-Schicht, diese Details zu handhaben. Eine Lösung für das vorgenannte Problem besteht darin, in der DAO-Schicht eine Methode zu erstellen, die selbst die Methoden M1 und M2 aufruft und diese Aufrufe in eine DBMS-Transaktion einbindet.

Die AOP-Filterlösung ist flexibler. Sie ermöglicht es Ihnen, einen Filter zu definieren, der vor dem Aufruf von M eine Transaktion startet und nach dem Aufruf je nach Bedarf ein Commit oder ein Rollback durchführt.

Dieser Ansatz bietet mehrere Vorteile:

  • Sobald der Filter definiert ist, kann er auf mehrere Methoden angewendet werden, beispielsweise auf alle, die eine Transaktion erfordern
  • die gefilterten Methoden müssen nicht umgeschrieben werden
  • da die zu verwendenden Filter über die Konfiguration definiert werden, können sie geändert werden

Zusätzlich zu den IoC- und AOP-Konzepten bietet Spring zahlreiche Hilfsklassen für dreischichtige Anwendungen:

  • für JDBC, SQLMap (iBatis), Hibernate und JDO (Java Data Object) in der DAO-Schicht
  • für das MVC-Modell in der Benutzeroberflächenschicht

Weitere Informationen: http://www.springframework.org.