Spring 2.x: MVC Web Framework

+ andere TechDocs
+ Java, JEE, EJB
+ Spring 2.x: DI + AOP
+ Spring 2.x: DB + TX
+ Spring 2.x: Remote
+ Springframework.org
+


Vierter Teil zu Spring 2.x:
Spring MVC Web Framework

    

Weitere Spring-Themen:



Inhalt

  1. Das MVC-Pattern (Model - View - Controller)
  2. Schichtenmodell einer Spring-MVC-Webanwendung
  3. Programmierbeispiel für das Spring MVC Web Framework
  4. Erläuterungen zur typischen Ablauf-Sequenz
  5. Erweiterung des Beispiels um JUnit-Tests
  6. Weitere Technologien für das Spring MVC Web Framework


Das MVC-Pattern (Model - View - Controller)

Das Spring Web Framework implementiert das MVC-Pattern (genauer: das MVC2-Pattern für Webanwendungen).
Vereinfacht dargestellt wird der Presentation-Layer dabei aufgeteilt in:
- Model: Datencontainer ohne weitere Logikfunktionen (z.B. HashMap)
- View: Zeigt die Daten aus dem Model in einer GUI-Oberfläche an (z.B. per JSP für HTML-Webbrowser)
- Controller: Steuert die Abfolge, generiert und bearbeitet die Daten des Models und startet den View


                  («HTML-Webbrowser») 
                           |
            ┌--------------┴--------------┐
            |                             |
     «Controller» -------------------> «View»     
                                       (z.B. JSP) 
        |       └----> «Model»    <----┘
        |              (z.B. Map) 
        |
     («BusinessLogic») 
        |
     («DAO») 
        |
     («DB») 


Schichtenmodell einer Spring-MVC-Webanwendung

Das folgende Diagramm zeigt beispielhaft das Schichtenmodell der im Folgenden vorgestellten Spring-MVC-Webanwendung. Dabei ist die Einbindung des DispatcherServlets etwas vereinfacht dargestellt, um das Diagramm nicht zu kompliziert werden zu lassen.
Das Diagramm ist kein UML-Klassendiagramm, sondern eher ein Objekte-Vernetzungsdiagramm. Es zeigt die Abhängigkeiten der Objekte. Insbesondere die Abhängigkeiten vom und zum Business-Logic-Layer werden größtenteils durch Spring Dependency Injection definiert.
Erläuterungen zur typischen Ablauf-Sequenz finden Sie weiter unten.


              «HTML-Webbrowser» 
                      |(HTTP)                                        ↑ Client-Tier
......................|...........................................................
          ┌-----------┴---------------┐                         JSP-/Servlet-Layer
    «StartRedirect»           «Dispatcher»      
    index.jsp                 DispatcherServlet 
    |                         ┌-------┴--------┐
    |redirect=         «View»              «FormularView» 
    |start.disp        anzeige.jsp         eingabe.jsp    
    |                 ↑             |Link=               ↑
    |                 |             |eingabe.disp        |
....|.................|.............|....................|........................
    |UrlMapping=      |View=        |UrlMapping=         |formView=      Web-Layer
    |idAnzeigeContr.  |anzeige      |idEingabeFormContr. |eingabe
    v                 |             v                    |
    «Controller»                    «FormController»       
    AnzeigeController  <----------- EingabeFormController  
    geschaeftslogik -┐ successView= command -------------┐ 
                     |   start.disp validator ---------┐ | 
                     |              geschaeftslogik -┐ | | 
.....................|...............................|.|.|.......................
Business-Logic-Layer |                               | | └-> «CommandClass» 
                     |                               | |     CommandTO      
                     |                               | |
                     |                               | └---> «Validator»      
                     v                               v       EingabeValidator 
                     «BusinessLogic»                   
                     MeineGeschaeftslogik              
                     List datenObjekte --------------┐ 
                     MeinDaoIf meinDao ------------┐ | 
...................................................|.|............................
DAO-Layer                                          | └-----> «TO»          
                                                   |         DatenObjektTO 
                                                   |
                                                   |-------> («TestDaoMock»)   
                                                   |         (MeinTestDaoMock) 
                                                   |
                                                   └-------> «DaoImpl»   
                                                             MeinDaoImpl 
...............................................................|..................
↓ EIS-Tier (Datenbanken, JMS, ERP, Legacy)  «DB»  


Programmierbeispiel für das Spring MVC Web Framework

  1. Das folgende Beispiel verwendet Spring-DI (Dependency Injection) und das Spring-JdbcTemplate. Sehen Sie sich hierzu die Vorbemerkungen und Programmierbeispiele an unter Spring-DI und Spring-JdbcTemplate. Das Spring-API finden Sie unter http://www.springframework.org/docs/api/
  2. Installieren Sie ein aktuelles Java SE JDK wie beschrieben unter java-install.htm.
  3. Die folgende Beschreibung geht davon aus, dass Sie Tomcat verwenden. Sie können auch beliebige andere JEE Application Server einsetzen, allerdings müssen Sie dann an einigen Stellen Anpassungen vornehmen.
    Installieren Sie Tomcat (z.B. Version 5.5.9, Datei 'jakarta-tomcat-5.5.9.zip') zum Beispiel nach 'D:\Tools\Tomcat', so wie beschrieben unter jsp-install.htm#InstallationUnterWindows.
    Für eine Entwicklungsumgebung sollten Sie vorzugsweise nicht von der Tomcat-.exe-Datei sondern besser von der Tomcat-.zip-Datei installieren.
  4. Achten Sie auf korrekt gesetzte Umgebungsvariablen (Environment-Variablen) (passen Sie die Pfadangaben an Ihre Java-SDK- und '<tomcat-root>'-Verzeichnisse an):
    Benutzervariablen:
    JAVA_HOMEC:\Program Files\Java\jdk1.6
    CATALINA_HOMED:\Tools\Tomcat
  5. Installieren Sie zusätzlich Ant wie zum Beispiel hier beschrieben.
  6. Erstellen Sie ein Projektverzeichnis 'SpringWebApp' und darin die im Folgenden in eckigen Klammern dargestellten Unterverzeichnisse:

    [\MeinWorkspace]
      '- [SpringWebApp]
           |- [src]
           |    |- [bsnss]
           |    |- [db]
           |    '- [web]
           |- [tstsrc]
           |    '- [tests]
           '- [webapp]
                '- [WEB-INF]
                     |- [lib]
                     |     |- commons-collections.jar
                     |     |- commons-dbcp.jar
                     |     |- commons-logging.jar
                     |     |- commons-pool.jar
                     |     |- hsqldb.jar
                     |     |- jstl.jar
                     |     |- junit.jar
                     |     |- log4j-1.2.13.jar
                     |     |- servlet-api.jar
                     |     |- spring.jar
                     |     '- standard.jar
                     |- [views]
                     '- spring.tld
    
  7. Downloaden Sie das Spring Framework von http://www.springframework.org (z.B. 'spring-framework-2.0-m4-with-dependencies.zip') und kopieren Sie daraus folgende Dateien:
    'spring.tld' und 'spring.jar' aus 'spring-framework.../dist/',
    'hsqldb.jar' aus 'spring-framework.../lib/hsqldb/',
    'jstl.jar' und 'servlet-api.jar' aus 'spring-framework.../lib/j2ee/',
    'commons-collections.jar', 'commons-dbcp.jar', 'commons-logging.jar' und 'commons-pool.jar' aus 'spring-framework.../lib/jakarta-commons/',
    'standard.jar' aus 'spring-framework.../lib/jakarta-taglibs/',
    'junit.jar' aus 'spring-framework.../lib/junit/',
    'log4j-1.2.13.jar' aus 'spring-framework.../lib/log4j/'.
  8. Erzeugen Sie im 'src'-Verzeichnis folgende Projekt-Properties-Datei 'project.properties':

    appserver.home=D:/Tools/Tomcat
    db.test.drv=org.hsqldb.jdbcDriver
    db.test.url=jdbc:hsqldb:file:/MeineHsqldbDaten/SpringWebApp/test/db
    db.test.usr=sa
    db.test.pwd=
    db.prod.drv=org.hsqldb.jdbcDriver
    db.prod.url=jdbc:hsqldb:file:/MeineHsqldbDaten/SpringWebApp/prod/db
    db.prod.usr=sa
    db.prod.pwd=
    db.sql1=DROP TABLE if exists Personen;
    db.sql2=CREATE TABLE Personen ( Name varchar(255) not null primary key, Plz char(5), Ort varchar(255) );
    db.sql3=CREATE INDEX Personen_Name ON Personen(Name);
    db.sql4=INSERT INTO Personen (Name, Plz, Ort) values('Hinz',    '00815', 'Entenhausen');
    db.sql5=INSERT INTO Personen (Name, Plz, Ort) values('Kunz',    '47110', 'Pusemuckel');
    db.sql6=INSERT INTO Personen (Name, Plz, Ort) values('Torsten', '52072', 'Aachen');
    db.sql7=COMMIT;
    db.sql8=SHUTDOWN;
    

    Passen Sie 'appserver.home' an Ihr Application-Server-Installationsverzeichnis, 'db.test.url' an Ihr gewünschtes Verzeichnis für hSqlDb-Testdaten für JUnit-Tests und 'db.prod...' an Ihre Webserver-Datenbank an. Letzteres ist oben ebenfalls für hSqlDb definiert, Sie können aber die Einstellungen leicht zum Beispiel für MySQL umdefinieren. Bitte beachten Sie, dass eventuell bereits vorhandene gleichnamige Datenbanktabellen kommentarlos gelöscht werden.

  9. Erzeugen Sie im 'src'-Verzeichnis folgende Log4j-Properties-Datei 'log4j.properties':

    # For JBoss: Avoid to setup Log4J outside $JBOSS_HOME/server/default/deploy/log4j.xml!
    # For other servers: Comment out the Log4j listener in web.xml to activate Log4j.
    log4j.rootLogger=INFO, stdout
    log4j.appender.stdout=org.apache.log4j.ConsoleAppender
    log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
    log4j.appender.stdout.layout.ConversionPattern=%d %p [%c] - <%m>%n
    log4j.logger.org.springframework=WARN
    
  10. Erzeugen Sie im 'src'-Verzeichnis folgende Textressourcendatei 'messages.properties':

    app.title=Spring-Web-Applikation
    anzeige.Ueberschrift=SpringWebApp: Anzeige der gespeicherten Datenobjekte
    anzeige.Datum=Datum:
    anzeige.Link.eingabe=Aenderung oder Neueingabe
    eingabe.Ueberschrift=SpringWebApp: Aenderung oder Neueingabe eines Datenobjekts
    eingabe.Fehler=Bitte die Fehler beseitigen.
    eingabe.SubmitButton=Ausfuehren
    eingabe.Link.Startseite=Zur Startseite
    dbTabelle.Spalte1=Name
    dbTabelle.Spalte2=Plz
    dbTabelle.Spalte3=Ort
    error.CommandNull=Command-Objekt ist 'null'.
    error.ValueEmpty=Darf nicht leer sein.
    error.ValueLength=Laenge muss {0} sein.
    error.ValueRange=Nur Ziffern, Wert muss zwischen {0} und {1} liegen.
    
  11. Erzeugen Sie im Package-Verzeichnis 'src/db' folgende TransferObjekt-Datei 'DatenObjektTO.java':

    package db;
    
    public class DatenObjektTO implements java.io.Serializable
    {
      private String name;
      private String plz;
      private String ort;
    
      public DatenObjektTO()
      {
      }
    
      public DatenObjektTO( String name, String plz, String ort )
      {
        this.name = name;
        this.plz  = plz;
        this.ort  = ort;
      }
    
      public String getName() { return name; }
      public String getOrt()  { return ort; }
      public String getPlz()  { return plz; }
      public void setName( String name ) { this.name = name; }
      public void setOrt(  String ort )  { this.ort  = ort; }
      public void setPlz(  String plz )  { this.plz  = plz; }
    }
    
  12. Erzeugen Sie im Package-Verzeichnis 'src/db' folgende DAO-Interface-Datei 'MeinDaoIf.java':

    package db;
    
    import java.util.List;
    
    public interface MeinDaoIf
    {
      public List findAllDatenObjekte();
      public List findDatenObjekteByName( String name );
      public void updateDatenObjekt( DatenObjektTO datenObjekt );
      public void insertDatenObjekt( DatenObjektTO datenObjekt );
    }
    
  13. Erzeugen Sie im Package-Verzeichnis 'src/db' folgende DAO-Implementierungsdatei 'MeinDaoImpl.java':

    package db;
    
    import java.sql.ResultSet;
    import java.sql.SQLException;
    import java.util.List;
    import org.springframework.jdbc.core.*;
    
    public class MeinDaoImpl implements MeinDaoIf
    {
      private JdbcTemplate jdbcTemplate;
    
      public List findAllDatenObjekte()
      {
        return findDatenObjekteBySql( "Select * from Personen", null );
      }
    
      public List findDatenObjekteByName( String name )
      {
        return findDatenObjekteBySql( "Select * from Personen where Name like ?", new Object[] { name } );
      }
    
      public void updateDatenObjekt( DatenObjektTO datenObjekt )
      {
        jdbcTemplate.update( "Update Personen set Plz = ?, Ort = ? where Name = ?",
            new Object[] { datenObjekt.getPlz(), datenObjekt.getOrt(), datenObjekt.getName() } );
      }
    
      public void insertDatenObjekt( DatenObjektTO datenObjekt )
      {
        jdbcTemplate.update( "Insert into Personen ( Name, Plz, Ort ) values (?, ?, ?)",
            new Object[] { datenObjekt.getName(), datenObjekt.getPlz(), datenObjekt.getOrt() } );
      }
    
      private List findDatenObjekteBySql( String sql, Object[] args )
      {
        return jdbcTemplate.query( sql, args,
            new RowMapper() {
              public Object mapRow( ResultSet rs, int rowNum ) throws SQLException {
                return new DatenObjektTO(
                    rs.getString( "Name" ), rs.getString( "Plz" ), rs.getString( "Ort" ) );
              }
            } );
      }
    
      public void setJdbcTemplate( JdbcTemplate jdbcTemplate )
      {
        this.jdbcTemplate = jdbcTemplate;
      }
    }
    
  14. Erzeugen Sie im Package-Verzeichnis 'src/bsnss' folgende Java-Datei 'MeineGeschaeftslogik.java':

    package bsnss;
    
    import java.io.Serializable;
    import java.util.List;
    import db.DatenObjektTO;
    import db.MeinDaoIf;
    
    public class MeineGeschaeftslogik implements Serializable
    {
      private MeinDaoIf meinDao;
    
      public void setMeinDao( MeinDaoIf meinDao )
      {
        this.meinDao = meinDao;
      }
    
      public List findAllDatenObjekte()
      {
        return meinDao.findAllDatenObjekte();
      }
    
      public void updateOrInsertDatenObjekt( DatenObjektTO datenObjekt )
      {
        if( datenObjekt == null || datenObjekt.getName() == null
            || datenObjekt.getName().trim().length() == 0 )
          return;
        List lst = meinDao.findDatenObjekteByName( datenObjekt.getName().trim() );
        if( lst == null || lst.size() == 0 )
          meinDao.insertDatenObjekt( datenObjekt );
        else if( lst.size() == 1 )
          meinDao.updateDatenObjekt( datenObjekt );
      }
    }
    
  15. Erzeugen Sie im Package-Verzeichnis 'src/bsnss' folgende TransferObjekt-Datei 'CommandTO.java':

    package bsnss;
    
    import db.DatenObjektTO;
    
    public class CommandTO extends DatenObjektTO
    {
      public CommandTO( String name, String plz, String ort )
      {
        super( name, plz, ort );
      }
    }
    
  16. Erzeugen Sie im Package-Verzeichnis 'src/bsnss' folgende Validator-Datei 'EingabeValidator.java':

    package bsnss;
    
    import org.springframework.validation.*;
    
    public class EingabeValidator implements Validator
    {
      public boolean supports( Class clazz )
      {
        return clazz.equals( CommandTO.class );
      }
    
      public void validate( Object obj, Errors errors )
      {
        CommandTO command = (CommandTO) obj;
        if( command == null ) {
          errors.reject( "error.CommandNull", null, "Command object is 'null'." );
        } else {
          if( command.getName() == null || command.getName().trim().length() == 0 ) {
            errors.rejectValue( "name", "error.ValueEmpty", null, "Must be filled." );
          }
          if( command.getOrt() == null || command.getOrt().trim().length() == 0 ) {
            errors.rejectValue( "ort", "error.ValueEmpty", null, "Must be filled." );
          }
          if( command.getPlz() == null || command.getPlz().trim().length() == 0 ) {
            errors.rejectValue( "plz", "error.ValueEmpty", null, "Must be filled." );
          } else if( command.getPlz().trim().length() != 5 ) {
              errors.rejectValue( "plz", "error.ValueLength",
                  new Object[] { new Integer(5) }, "Length must be {0}." );
          } else {
            int i = 0;
            try { i = Integer.parseInt(command.getPlz()); } catch( Exception e ) { }
            if( i < 100 || 99999 < i ) {
              errors.rejectValue( "plz", "error.ValueRange",
                  new Object[] { new Integer(100), new Integer(99999) },
                  "Only numbers, value must be between {0} and {1}." );
            }
          }
        }
      }
    }
    
  17. Erzeugen Sie im Package-Verzeichnis 'src/web' folgende MVC-Controller-Datei 'AnzeigeController.java':

    package web;
    
    import java.io.IOException;
    import java.text.SimpleDateFormat;
    import java.util.*;
    import javax.servlet.ServletException;
    import javax.servlet.http.*;
    import org.springframework.web.servlet.ModelAndView;
    import org.springframework.web.servlet.mvc.Controller;
    import bsnss.MeineGeschaeftslogik;
    
    public class AnzeigeController implements Controller
    {
      private MeineGeschaeftslogik geschaeftslogik;
    
      public ModelAndView handleRequest( HttpServletRequest request,
          HttpServletResponse response ) throws ServletException, IOException
      {
        String datum = (new SimpleDateFormat("yyyy-MM-dd HH:mm:ss")).format(new Date());
        Map myModel = new HashMap();
        myModel.put( "datum", datum );
        myModel.put( "datenObjekte", geschaeftslogik.findAllDatenObjekte() );
        return new ModelAndView( "anzeige", "model", myModel );
      }
    
      public void setGeschaeftslogik( MeineGeschaeftslogik geschaeftslogik )
      {
        this.geschaeftslogik = geschaeftslogik;
      }
    }
    
  18. Erzeugen Sie im Package-Verzeichnis 'src/web' folgende MVC-SimpleFormController-Datei 'EingabeFormController.java':

    package web;
    
    import javax.servlet.ServletException;
    import javax.servlet.http.HttpServletRequest;
    import org.springframework.web.servlet.ModelAndView;
    import org.springframework.web.servlet.mvc.SimpleFormController;
    import org.springframework.web.servlet.view.RedirectView;
    import bsnss.CommandTO;
    import bsnss.MeineGeschaeftslogik;
    
    public class EingabeFormController extends SimpleFormController
    {
      private MeineGeschaeftslogik geschaeftslogik;
    
      protected Object formBackingObject( HttpServletRequest request )
          throws ServletException
      {
        CommandTO command = new CommandTO( null, null, null );
        return command;
      }
    
      public ModelAndView onSubmit( Object command ) throws ServletException
      {
        geschaeftslogik.updateOrInsertDatenObjekt( (CommandTO) command );
        return new ModelAndView( new RedirectView( getSuccessView() ) );
      }
    
      public void setGeschaeftslogik( MeineGeschaeftslogik geschaeftslogik )
      {
        this.geschaeftslogik = geschaeftslogik;
      }
    }
    
  19. Erzeugen Sie im 'webapp'-Verzeichnis folgende Start-JSP-Datei 'index.jsp':

    <%@ page session="false"%>
    <%@ taglib prefix="c" uri="http://java.sun.com/jstl/core" %>
    
    <c:redirect url="/start.disp" />
    
  20. Erzeugen Sie im 'webapp/WEB-INF/views'-Verzeichnis folgende View-JSP-Datei 'anzeige.jsp':

    <%@ page session="false"%>
    <%@ taglib prefix="c"   uri="http://java.sun.com/jstl/core" %>
    <%@ taglib prefix="fmt" uri="http://java.sun.com/jstl/fmt"  %>
    
    <html>
    <head><title><fmt:message key="app.title"/></title></head>
    <body>
    <h2><fmt:message key="anzeige.Ueberschrift"/></h2>
    <p><fmt:message key="anzeige.Datum"/> <c:out value="${model.datum}"/></p>
    <table bgcolor="#EBEEEE" border="0" cellspacing="0" cellpadding="5">
      <tr><th><fmt:message key="dbTabelle.Spalte1"/></th>
          <th><fmt:message key="dbTabelle.Spalte2"/></th>
          <th><fmt:message key="dbTabelle.Spalte3"/></th></tr>
      <c:forEach items="${model.datenObjekte}" var="datenObjekt">
        <tr><td><c:out value="${datenObjekt.name}"/></td>
            <td><c:out value="${datenObjekt.plz}"/></td>
            <td><c:out value="${datenObjekt.ort}"/></td></tr>
      </c:forEach>
    </table>
    <br>
    <a href='<c:url value="eingabe.disp"/>'><fmt:message key="anzeige.Link.eingabe"/></a>
    <br>
    </body>
    </html>
    
  21. Erzeugen Sie im 'webapp/WEB-INF/views'-Verzeichnis folgende View-JSP-Datei 'eingabe.jsp':

    <%@ page session="false"%>
    <%@ taglib prefix="c"      uri="http://java.sun.com/jstl/core" %>
    <%@ taglib prefix="fmt"    uri="http://java.sun.com/jstl/fmt"  %>
    <%@ taglib prefix="spring" uri="/spring" %>
    
    <html>
    <head><title><fmt:message key="app.title"/></title></head>
    <body>
    <h2><fmt:message key="eingabe.Ueberschrift"/></h2>
    <form method="post">
      <table bgcolor="#EBEEEE" border="0" cellspacing="0" cellpadding="5">
        <tr>
          <td><fmt:message key="dbTabelle.Spalte1"/>:</td>
          <spring:bind path="MeinCommand.name">
            <td><input type="text" name="name" value="<c:out value="${status.value}"/>"></td>
            <td><font color="red"><c:out value="${status.errorMessage}"/></font></td>
          </spring:bind>
        </tr>
        <tr>
          <td><fmt:message key="dbTabelle.Spalte2"/>:</td>
          <spring:bind path="MeinCommand.plz">
            <td><input type="text" name="plz" value="<c:out value="${status.value}"/>"></td>
            <td><font color="red"><c:out value="${status.errorMessage}"/></font></td>
          </spring:bind>
        </tr>
        <tr>
          <td><fmt:message key="dbTabelle.Spalte3"/>:</td>
          <spring:bind path="MeinCommand.ort">
            <td><input type="text" name="ort" value="<c:out value="${status.value}"/>"></td>
            <td><font color="red"><c:out value="${status.errorMessage}"/></font></td>
          </spring:bind>
        </tr>
      </table>
      <br>
      <spring:hasBindErrors name="MeinCommand">
        <font color="red"><b><fmt:message key="eingabe.Fehler"/></b></font>
      </spring:hasBindErrors>
      <br><br>
      <input type="submit" alignment="center" value='<fmt:message key="eingabe.SubmitButton"/>'>
    </form>
    <a href='<c:url value="start.disp"/>'><fmt:message key="eingabe.Link.Startseite"/></a>
    </body>
    </html>
    
  22. Erzeugen Sie im 'webapp/WEB-INF'-Verzeichnis folgende Webanwendungskonfigurationsdatei 'web.xml':

    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE web-app PUBLIC
      '-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN'
      'http://java.sun.com/dtd/web-app_2_3.dtd'>
    <web-app>
      <servlet>
        <servlet-name>springapp</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <load-on-startup>1</load-on-startup>
      </servlet>
      <servlet-mapping>
        <servlet-name>springapp</servlet-name>
        <url-pattern>*.disp</url-pattern>
      </servlet-mapping>
      <welcome-file-list>
        <welcome-file>index.jsp</welcome-file>
      </welcome-file-list>
      <taglib>
        <taglib-uri>/spring</taglib-uri>
        <taglib-location>/WEB-INF/spring.tld</taglib-location>
      </taglib>
    </web-app>
    
  23. Erzeugen Sie im 'webapp/WEB-INF'-Verzeichnis folgende Spring-Servlet-Konfigurationsdatei 'springapp-servlet.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 class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
        <property name="location" value="WEB-INF/classes/project.properties" />
      </bean>
      <bean id="idAnzeigeController" class="web.AnzeigeController">
        <property name="geschaeftslogik" ref="idMeineGeschaeftslogik" />
      </bean>
      <bean id="idEingabeFormController" class="web.EingabeFormController">
        <property name="sessionForm"     value="true" />
        <property name="commandName"     value="MeinCommand" />
        <property name="commandClass"    value="bsnss.CommandTO" />
        <property name="validator"       ref="idEingabeValidator" />
        <property name="formView"        value="eingabe" />
        <property name="successView"     value="start.disp" />
        <property name="geschaeftslogik" ref="idMeineGeschaeftslogik" />
      </bean>
      <bean id="idEingabeValidator" class="bsnss.EingabeValidator">
      </bean>
      <bean id="idMeineGeschaeftslogik" class="bsnss.MeineGeschaeftslogik">
        <property name="meinDao" ref="idMeinDaoImpl" />
      </bean>
      <bean id="idMeinDaoImpl" class="db.MeinDaoImpl">
        <property name="jdbcTemplate" ref="idJdbcTemplate" />
      </bean>
      <bean id="idJdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="idDataSource" />
      </bean>
      <bean id="idDataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
        <property name="driverClassName" value="${db.prod.drv}" />
        <property name="url"             value="${db.prod.url}" />
        <property name="username"        value="${db.prod.usr}" />
        <property name="password"        value="${db.prod.pwd}" />
      </bean>
      <bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource">
        <property name="basename" value="messages" />
      </bean>
      <bean id="urlMapping" class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
        <property name="mappings">
          <props>
            <prop key="/start.disp">idAnzeigeController</prop>
            <prop key="/eingabe.disp">idEingabeFormController</prop>
          </props>
        </property>
      </bean>
      <bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="viewClass" value="org.springframework.web.servlet.view.JstlView" />
        <property name="prefix"    value="/WEB-INF/views/" />
        <property name="suffix"    value=".jsp" />
      </bean>
    </beans>
    
  24. Erzeugen Sie im 'SpringWebApp'-Projektverzeichnis folgende Ant-Build-Datei 'build.xml':

    <project name="MeinWebAppProjekt" default="Usage">
      <property file="src/project.properties" />
      <!-- <property name="appserver.home" value="${CATALINA_HOME}" /> -->
      <property name="app.name"   value="springwebapp" />
      <property name="dist.dir"   value="dist" />
      <property name="src.dir"    value="src" />
      <property name="tstsrc.dir" value="tstsrc" />
      <property name="tstbin.dir" value="tstbin" />
      <property name="webapp.dir" value="webapp" />
      <property name="WEBINF.dir" value="${webapp.dir}/WEB-INF" />
      <path id="compile.classpath">
        <fileset dir="${appserver.home}/common/lib" includes="servlet*.jar" />
        <fileset dir="${WEBINF.dir}/lib" />
        <pathelement path="${WEBINF.dir}/classes" />
      </path>
      <path id="junit.classpath">
        <path refid="compile.classpath" />
        <pathelement path="${tstbin.dir}" />
      </path>
      <target name="Usage">
        <echo message="Commandline" />
        <echo message="  ant -Dappserver.home=%CATALINA_HOME% target" />
        <echo message="Availiable Targets" />
        <echo message="  clean     : Delete ${WEBINF.dir}/classes, ${tstbin.dir}, ${dist.dir}" />
        <echo message="  compile   : Compile from ${src.dir} to ${WEBINF.dir}/classes" />
        <echo message="  create-war: Create ${dist.dir}/${app.name}.war" />
        <echo message="  deploy-war: Copy ${app.name}.war to ${appserver.home}/webapps" />
        <echo message="  preparedb : Prepare Production Database" />
        <echo message="  junit     : JUnit Tests" />
      </target>
      <target name="clean"
              description="Delete ${WEBINF.dir}/classes, ${tstbin.dir}, ${dist.dir}">
        <delete dir="${WEBINF.dir}/classes" />
        <delete dir="${tstbin.dir}" />
        <delete dir="${dist.dir}" />
      </target>
      <target name="compile"
              description="Compile from ${src.dir} to ${WEBINF.dir}/classes">
        <mkdir dir="${WEBINF.dir}/lib" />
        <mkdir dir="${WEBINF.dir}/classes" />
        <javac srcdir="${src.dir}" destdir="${WEBINF.dir}/classes">
          <classpath refid="compile.classpath" />
        </javac>
        <copy todir="${WEBINF.dir}/classes">
          <fileset dir="${src.dir}" excludes="**/*.java" />
        </copy>
      </target>
      <target name="create-war" depends="compile"
              description="Create ${dist.dir}/${app.name}.war">
        <mkdir dir="${dist.dir}" />
        <war destfile="${dist.dir}/${app.name}.war" webxml="${WEBINF.dir}/web.xml">
          <fileset dir="${webapp.dir}" includes="**/*.*"
                   excludes="**/web.xml, **/servlet*.jar, **/junit*.jar" />
        </war>
      </target>
      <target name="deploy-war" depends="create-war"
              description="Copy ${app.name}.war to ${appserver.home}/webapps">
        <copy todir="${appserver.home}/webapps" preservelastmodified="true">
          <fileset dir="${dist.dir}" includes="${app.name}.war" />
        </copy>
      </target>
      <target name="preparedb"
              description="Prepare Production Database">
        <sleep seconds="1" />
        <sql driver="${db.prod.drv}" url="${db.prod.url}"
             userid="${db.prod.usr}" password="${db.prod.pwd}" onerror="continue">
          <classpath refid="compile.classpath"/>
          ${db.sql1} ${db.sql2} ${db.sql3} ${db.sql4}
          ${db.sql5} ${db.sql6} ${db.sql7} ${db.sql8}
        </sql>
      </target>
      <target name="preparetestdb"
              description="Prepare Test Database">
        <sleep seconds="1" />
        <sql driver="${db.test.drv}" url="${db.test.url}"
             userid="${db.test.usr}" password="${db.test.pwd}" onerror="continue">
          <classpath refid="compile.classpath"/>
          ${db.sql1} ${db.sql2} ${db.sql3} ${db.sql4}
          ${db.sql5} ${db.sql6} ${db.sql7} ${db.sql8}
        </sql>
      </target>
      <target name="junit" depends="compile, preparetestdb"
              description="Run JUnit Tests">
        <sleep seconds="1" />
        <mkdir dir="${tstbin.dir}" />
        <javac srcdir="${tstsrc.dir}" destdir="${tstbin.dir}">
          <classpath refid="junit.classpath" />
        </javac>
        <copy todir="${tstbin.dir}">
          <fileset dir="${src.dir}" excludes="**/*.java" />
        </copy>
        <copy todir="${tstbin.dir}">
          <fileset dir="${tstsrc.dir}" excludes="**/*.java" />
        </copy>
        <junit printsummary="on" fork="false" showoutput="true"
               failureproperty="tests.failed" haltonfailure="false">
          <classpath refid="junit.classpath" />
          <formatter type="brief" usefile="false" />
          <batchtest>
            <fileset dir="${tstbin.dir}" includes="**/Test*.*" />
          </batchtest>
        </junit>
        <fail if="tests.failed">*
        ******************************
        ****  JUnit Test failed!  ****
        ******************************
        </fail>
      </target>
    </project>
    
  25. Ihr Projektverzeichnis sollte jetzt in etwa folgendermaßen aussehen:

    [\MeinWorkspace]
      '- [SpringWebApp]
           |- [src]
           |    |- [bsnss]
           |    |    |- CommandTO.java
           |    |    |- EingabeValidator.java
           |    |    '- MeineGeschaeftslogik.java
           |    |- [db]
           |    |    |- DatenObjektTO.java
           |    |    |- MeinDaoIf.java
           |    |    '- MeinDaoImpl.java
           |    |- [web]
           |    |    |- AnzeigeController.java
           |    |    '- EingabeFormController.java
           |    |- log4j.properties
           |    |- messages.properties
           |    '- project.properties
           |- [tstsrc]
           |    '- [tests]
           |- [webapp]
           |    |- [WEB-INF]
           |    |    |- [lib]
           |    |    |     |- commons-collections.jar
           |    |    |     |- commons-dbcp.jar
           |    |    |     |- commons-logging.jar
           |    |    |     |- commons-pool.jar
           |    |    |     |- hsqldb.jar
           |    |    |     |- jstl.jar
           |    |    |     |- junit.jar
           |    |    |     |- log4j-1.2.13.jar
           |    |    |     |- servlet-api.jar
           |    |    |     |- spring.jar
           |    |    |     '- standard.jar
           |    |    |- [views]
           |    |    |     |- anzeige.jsp
           |    |    |     '- eingabe.jsp
           |    |    |- spring.tld
           |    |    |- springapp-servlet.xml
           |    |    '- web.xml
           |    '- index.jsp
           '- build.xml
    
  26. Überprüfen Sie noch einmal genau die Voreinstellungen und Pfade in 'project.properties', insbesondere die Werte für 'appserver.home=...', 'db.test.url=...' und 'db.prod.url=...'. Insbesondere wenn Sie einen anderen JEE Application Server als Tomcat verwenden, müssen Sie auch die 'build.xml' überprüfen.

  27. Falls Sie nicht hSqlDb, sondern eine andere Datenbank verwenden wollen, starten Sie diese Datenbank.
  28. Starten Sie Tomcat (bzw. Ihren JEE Application Server) und testen Sie die Funktion mit dem Aufruf http://localhost:8080.
  29. Öffnen Sie ein Kommandozeilenfenster, verzweigen Sie in das Projektverzeichnis 'SpringWebApp' und führen Sie folgende Kommandos aus:

    cd \MeinWorkspace\SpringWebApp

    ant

    Sie erhalten eine Kurzerläuterung zu den wichtigsten Ant-Targets.

    Um in der Datenbank die benötigte Tabelle einzurichten und mit ersten Testdaten zu füllen, geben Sie ein (Vorsicht: Vorhandene Daten gehen verloren):

    ant preparedb

    Falls Sie die Anwendung auf einem lokal installierten Tomcat betreiben wollen, geben Sie ein:

    ant -Dappserver.home=%CATALINA_HOME% deploy-war

    Falls Sie in der 'project.properties' in 'appserver.home=...' Ihren Tomcat-Pfad korrekt eingetragen haben, genügt auch:

    ant deploy-war

    Falls Sie nicht Tomcat, sondern einen anderen JEE Application Server einsetzen, müssen Sie ebenfalls diese letztere Variante verwenden.

    Falls Sie spezielle Deployment-Prozeduren benötigen oder auf einen entfernten Server deployen wollen, geben Sie ein:

    ant create-war

    Falls Sie auf einen entfernten Unix- oder Linux-Server deployen wollen: Beachten Sie die Hinweise zu 'PuTTY' und 'PSCP'.

    Beobachten Sie die Konsole von Tomcat (bzw. Ihres JEE Application Servers). Es dürfen keine Fehler gemeldet werden.

  30. Rufen Sie über http://localhost:8080/springwebapp die Startseite der Webanwendung auf.

    Merken Sie sich einen der angezeigten Namen. Klicken Sie auf 'Aenderung oder Neueingabe'. Geben Sie auf der folgenden Webseite als 'Name' den gemerkten Namen ein und tragen Sie eine neue Postleitzahl und einen neuen Ort ein. Wenn Sie 'Ausfuehren' betätigen, wird die Änderung gespeichert und auf der Anzeigeseite angezeigt.

    Klicken Sie wieder auf 'Aenderung oder Neueingabe'. Geben Sie diesmal als 'Name' einen neuen Namen ein, den es noch nicht in der angezeigten Liste gibt, und tragen Sie eine Postleitzahl und einen Ort ein. Wenn Sie 'Ausfuehren' betätigen, wird der neue Datensatz gespeichert und auf der Anzeigeseite angezeigt.

    Klicken Sie wieder auf 'Aenderung oder Neueingabe'. Geben Sie nichts ein und betätigen Sie 'Ausfuehren'. Geben Sie als 'Plz' Buchstaben oder weniger als 5 Ziffern ein. Sie erhalten jeweils entsprechende Fehlermeldungen.

    Fahren Sie Tomcat herunter und wieder hoch: Die geänderten und neuen Datensätze erscheinen weiterhin.



Erläuterungen zur typischen Ablauf-Sequenz

  1. Öffnen Sie in einem parallelem Webbrowserfenster die Grafik zum Schichtenmodell der Spring-MVC-Webanwendung, um die folgenden Erläuterungen besser verfolgen zu können.

  2. Wenn Sie im Webbrowser die URL http://localhost:8080/springwebapp eingeben, sucht Tomcat (oder ein anderer JEE Application Server) nach einem '<welcome-file>'-Eintrag in der 'web.xml', die zur Webanwendung 'springwebapp' gehört (also im Tomcat-Verzeichnis 'webapps/springwebapp/WEB-INF').

    Der Eintrag '<welcome-file>index.jsp</welcome-file>' verweist auf 'index.jsp'.

  3. 'index.jsp' enthält die Umleitung: '<c:redirect url="/start.disp"/>'.

  4. Zu allen URLs nach dem Muster '<url-pattern>*.disp</url-pattern>' enthält die 'web.xml' eine Umleitung auf das Servlet '<servlet-name>springapp</servlet-name>'.

    Dem Servlet mit dem Namen 'springapp' ist in der 'web.xml' die Servlet-Klasse 'DispatcherServlet' zugeordnet.

  5. Wenn nichts anderes definiert ist, sucht das Spring-'DispatcherServlet' nach einer Spring-XML-Konfigurationsdatei, deren Name sich zusammensetzt aus dem Servlet-Namen und der Endung '-servlet.xml', im Beispiel also 'springapp-servlet.xml'.

  6. Die Spring-XML-Konfigurationsdatei 'springapp-servlet.xml' enthält für die Spring-'SimpleUrlHandlerMapping'-Bean die Anweisung, für den Key '/start.disp' die Bean mit der ID 'idAnzeigeController' aufzurufen, also die 'AnzeigeController'-Bean.

  7. Jetzt wird die MVC-Struktur sichtbar:
    Die 'AnzeigeController'-Bean stellt den 'Controller' dar, der dafür sorgt, dass das Datenmodell gefüllt wird (im Beispiel die Map 'myModel'), und der die geeignete 'View' auswählt (im Beispiel über den 'ModelAndView'-Parameter '"anzeige"').

  8. Die Spring-XML-Konfigurationsdatei 'springapp-servlet.xml' enthält für die Spring-'InternalResourceViewResolver'-Bean die Anweisung, dem 'View'-Namen das Präfix '/WEB-INF/views/' und den Suffix '.jsp' hinzuzufügen, um die View-URL zu bilden, und diese URL aufzurufen. Für den View-Namen 'anzeige' wird die URL '/WEB-INF/views/anzeige.jsp' aufgerufen.

    Übrigens können unterhalb von 'WEB-INF' angeordnete JSP- oder HTML-Seiten nicht direkt vom externen Webbrowser aus aufgerufen werden, sondern nur von den internen Servlets aus.

  9. Die 'anzeige.jsp' wertet die Daten des Modells aus, indem es sie anzeigt.

    Über den Link '<a href='<c:url value="eingabe.disp"/>'>...</a>' kann fortgefahren werden.

  10. Da dieser Link zum 'web.xml'-Umleitungs-'url-pattern' '*.disp' passt, wird er wieder zur Servlet-Klasse 'DispatcherServlet' umgeleitet.

  11. Die Spring-XML-Konfigurationsdatei 'springapp-servlet.xml' enthält für die Spring-'SimpleUrlHandlerMapping'-Bean die Anweisung, für den Key '/eingabe.disp' die Bean mit der ID 'idEingabeFormController' aufzurufen, also die 'EingabeFormController'-Bean.

  12. Die 'EingabeFormController'-Bean ist vom Spring-'SimpleFormController' abgeleitet, so dass dessen Funktionalität genutzt wird.

  13. Die 'EingabeFormController'-Bean bereitet ein Datentransferobjekt vor, mit der über den 'commandClass'-Eintrag in der 'springapp-servlet.xml' definierten Klasse, im Beispiel also 'CommandTO'.

    Der 'formView'-Eintrag in der 'springapp-servlet.xml' definiert, welche 'View' das Eingabeformular enthält (im Beispiel 'eingabe').

  14. 'SimpleFormController' ruft die 'formView' auf. Der Name 'eingabe' wird wieder über den 'InternalResourceViewResolver' zu '/WEB-INF/views/eingabe.jsp' erweitert.

  15. Die 'eingabe.jsp' zeigt das Eingabeformular und initialisiert die Eingabefelder mit den im 'CommandTO'-Objekt vorgegebenen Werten.

    Die vom Benutzer eingetragenen Werte werden im 'CommandTO'-Objekt übernommen.

  16. Wird der 'submit'-Button betätigt, überprüft der 'SimpleFormController' über die als 'validator' eingetragene Klasse (im Beispiel 'idEingabeValidator') die Werte. Im Fehlerfall werden im Eingabeformular Fehlermeldungen angezeigt. Ist die Eingabe fehlerfrei, wird die 'onSubmit'-Methode der 'EingabeFormController'-Klasse aufgerufen.

  17. In der 'onSubmit'-Methode werden die neuen Daten über 'geschaeftslogik.updateOrInsertDatenObjekt( (CommandTO) command )' in der Datenbank gespeichert.
    Anschließend wird wieder ein 'ModelAndView'-Objekt erzeugt. Als 'View' wird der in der 'springapp-servlet.xml' definierte 'successView'-Wert herangezogen, also 'start.disp'.
    Der Ausdruck 'return new ModelAndView( new RedirectView( getSuccessView() ) )' wirkt etwas umständlich, aber er verhindert, dass bei einem Refresh im Webbrowser ("Neu laden", Strg+R, F5) der 'Submit' erneut ausgeführt wird.

  18. 'start.disp' verzweigt wieder zur Startseite, welche die geänderten oder erweiterten Einträge anzeigt.



Erweiterung des Beispiels um JUnit-Tests

  1. Sehen Sie sich die verschiedenen grundsätzlichen Testverfahren an. Im Folgenden werden Unit-Tests mit JUnit implementiert.
  2. Damit die JUnit-Tests von Ant ausgeführt werden können, muss die 'junit.jar'-Bibliothek in das Ant-'lib'-Verzeichnis kopiert werden, also zum Beispiel aus 'spring-framework.../lib/junit/' nach 'D:\Tools\ant\lib'.
  3. Die folgenden JUnit-Tests demonstrieren unterschiedliche Verfahren. Alle kommen ohne einen Webserver aus. Zwei verwenden auch keine Datenbank und einer verwendet eine Test-hSqlDb-Datenbank, die für den Test instantiiert und gefüllt wird.
  4. Einige Tests sollen ohne Datenbank auskommen, obwohl eine DAO-Implementierung benötigt wird. Hierfür wird ein Mock erstellt, der eine Datenbank-DAO-Implementierung simuliert.
    Erzeugen Sie im Verzeichnis 'tstsrc/tests' folgende DAO-Mock-Datei 'MeinTestDaoMock.java':

    package tests;
    
    import java.util.*;
    import db.DatenObjektTO;
    import db.MeinDaoIf;
    
    public class MeinTestDaoMock implements MeinDaoIf
    {
      private List datenObjekte;
    
      public void setDatenObjekte( List datenObjekte )
      {
        this.datenObjekte = datenObjekte;
      }
    
      public List findAllDatenObjekte()
      {
        return datenObjekte;
      }
    
      public List findDatenObjekteByName( String name )
      {
        if( datenObjekte == null || name == null || name.length() == 0 )
          return null;
        List lst = new ArrayList();
        for( int i = 0; i < datenObjekte.size(); i++ ) {
          DatenObjektTO p = (DatenObjektTO) datenObjekte.get( i );
          if( p != null && p.getName() != null
              && p.getName().equalsIgnoreCase( name ) ) {
            lst.add( datenObjekte.get( i ) );
          }
        }
        return lst;
      }
    
      public void updateDatenObjekt( DatenObjektTO datenObjekt )
      {
        if( datenObjekte == null || datenObjekt == null
            || datenObjekt.getName() == null )
          return;
        for( int i = 0; i < datenObjekte.size(); i++ ) {
          DatenObjektTO p = (DatenObjektTO) datenObjekte.get( i );
          if( p != null && p.getName() != null
              && p.getName().equalsIgnoreCase( datenObjekt.getName() ) ) {
            datenObjekte.set( i, datenObjekt );
            break;
          }
        }
      }
    
      public void insertDatenObjekt( DatenObjektTO datenObjekt )
      {
        if( datenObjekte == null || datenObjekt == null
            || datenObjekt.getName() == null )
          return;
        datenObjekte.add( datenObjekt );
      }
    }
    
  5. Der DAO-Mock kann entweder per Java-Code mit Testdaten versorgt werden oder per Spring-XML-Konfigurationsdatei. Letzteres wird folgendermaßen realisiert:
    Erzeugen Sie im Verzeichnis 'tstsrc/tests' folgende Spring-Test-XML-Konfigurationsdatei 'springapp-test.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="idAnzeigeController" class="web.AnzeigeController">
        <property name="geschaeftslogik" ref="idMeineGeschaeftslogik" />
      </bean>
      <bean id="idMeineGeschaeftslogik" class="bsnss.MeineGeschaeftslogik">
        <property name="meinDao" ref="idMeinTestDaoMock" />
      </bean>
      <bean id="idMeinTestDaoMock" class="tests.MeinTestDaoMock">
        <property name="datenObjekte">
          <list>
            <ref bean="datenObjekt1" />
            <ref bean="datenObjekt2" />
            <ref bean="datenObjekt3" />
          </list>
        </property>
      </bean>
      <bean id="datenObjekt1" class="db.DatenObjektTO">
        <property name="name" value="Hinz" />
        <property name="plz"  value="1" />
        <property name="ort"  value="Ort1" />
      </bean>
      <bean id="datenObjekt2" class="db.DatenObjektTO">
        <property name="name" value="Kunz" />
        <property name="plz"  value="2" />
        <property name="ort"  value="Ort2" />
      </bean>
      <bean id="datenObjekt3" class="db.DatenObjektTO">
        <property name="name" value="Torsten" />
        <property name="plz"  value="3" />
        <property name="ort"  value="Ort3" />
      </bean>
    </beans>
    
  6. Der folgende Test testet den AnzeigeController und verwendet dafür obige Spring-Test-XML-Konfigurationsdatei 'springapp-test.xml', die wiederum den eben erstellten DAO-Mock 'MeinTestDaoMock.java' verwendet.
    Erzeugen Sie im Verzeichnis 'tstsrc/tests' folgenden JUnit-Test 'TestAnzeigeController.java':

    package tests;
    
    import java.io.IOException;
    import java.util.*;
    import javax.servlet.ServletException;
    import javax.servlet.http.*;
    import junit.framework.TestCase;
    import org.springframework.context.ApplicationContext;
    import org.springframework.context.support.FileSystemXmlApplicationContext;
    import org.springframework.web.servlet.ModelAndView;
    import db.DatenObjektTO;
    import web.AnzeigeController;
    
    /**
     * Testet: @see AnzeigeController
     * Testet ohne Datenbank, Daten stammen aus springapp-test.xml
     */
    public class TestAnzeigeController extends TestCase
    {
      private ApplicationContext ctx;
    
      public void setUp() throws IOException
      {
        ctx = new FileSystemXmlApplicationContext( "tstsrc/tests/springapp-test.xml" );
      }
    
      public void testHandleRequest() throws ServletException, IOException
      {
        AnzeigeController anzeigeController =
          (AnzeigeController) ctx.getBean( "idAnzeigeController" );
        ModelAndView mav = anzeigeController.handleRequest(
          (HttpServletRequest) null, (HttpServletResponse) null );
        Map mdl = mav.getModel();
        List pl = (List) ((Map) mdl.get( "model" )).get( "datenObjekte" );
        DatenObjektTO p1 = (DatenObjektTO) pl.get( 0 );
        assertEquals( "Hinz", p1.getName() );
        DatenObjektTO p2 = (DatenObjektTO) pl.get( 1 );
        assertEquals( "Kunz", p2.getName() );
        DatenObjektTO p3 = (DatenObjektTO) pl.get( 2 );
        assertEquals( "Torsten", p3.getName() );
      }
    }
    
  7. Das folgende Beispiel verwendet wieder den DAO-Mock 'MeinTestDaoMock.java', aber die Testdaten werden diesmal per Java-Code erzeugt.
    Erzeugen Sie im Verzeichnis 'tstsrc/tests' folgenden JUnit-Test 'TestGeschaeftslogik.java':

    package tests;
    
    import java.util.*;
    import db.DatenObjektTO;
    import junit.framework.TestCase;
    import bsnss.MeineGeschaeftslogik;
    
    /**
     * Testet: @see MeineGeschaeftslogik
     * Testet ohne Datenbank, verwendet: @see MeinTestDaoMock
     * Testdaten werden per Java-Code erzeugt
     */
    public class TestGeschaeftslogik extends TestCase
    {
      private MeineGeschaeftslogik geschaeftslogik;
    
      public void setUp()
      {
        DatenObjektTO p = new DatenObjektTO( "Hinz", "00815", "Ort1" );
        List lst = new ArrayList();
        lst.add( p );
        p = new DatenObjektTO( "Kunz", "47110", "Ort2" );
        lst.add( p );
        MeinTestDaoMock dao = new MeinTestDaoMock();
        dao.setDatenObjekte( lst );
        geschaeftslogik = new MeineGeschaeftslogik();
        geschaeftslogik.setMeinDao( dao );
      }
    
      public void testFindAllDatenObjekte()
      {
        List lst = geschaeftslogik.findAllDatenObjekte();
        assertEquals( "lst.size", 2, lst.size() );
        DatenObjektTO p0 = (DatenObjektTO) lst.get( 0 );
        DatenObjektTO p1 = (DatenObjektTO) lst.get( 1 );
        assertEquals( "Hinz", p0.getName() );
        assertEquals( "Kunz", p1.getName() );
        assertEquals( "00815", p0.getPlz() );
        assertEquals( "47110", p1.getPlz() );
      }
    
      public void testUpdateDatenObjekt()
      {
        List lst = geschaeftslogik.findAllDatenObjekte();
        assertEquals( "lst.size", 2, lst.size() );
        DatenObjektTO p02 = new DatenObjektTO( "Hinz", "01010", "Ort1" );
        geschaeftslogik.updateOrInsertDatenObjekt( p02 );
        lst = geschaeftslogik.findAllDatenObjekte();
        assertEquals( "lst.size", 2, lst.size() );
        DatenObjektTO p03 = (DatenObjektTO) lst.get( 0 );
        assertEquals( "01010", p03.getPlz() );
      }
    
      public void testInsertDatenObjekt()
      {
        List lst = geschaeftslogik.findAllDatenObjekte();
        assertEquals( "lst.size", 2, lst.size() );
        DatenObjektTO p91 = new DatenObjektTO( "Neu", "54321", "Ort2" );
        geschaeftslogik.updateOrInsertDatenObjekt( p91 );
        lst = geschaeftslogik.findAllDatenObjekte();
        assertEquals( "lst.size", 3, lst.size() );
        DatenObjektTO p92 = (DatenObjektTO) lst.get( 2 );
        assertEquals( "54321", p92.getPlz() );
      }
    }
    
  8. Dieser Test testet die echte DAO-Implementierung und verwendet dafür die Test-hSqlDb-Datenbank.
    Ein weiteres Beispiel zum verwendeten JdbcTemplate finden Sie unter jee-spring-db-tx.htm.
    Erzeugen Sie im Verzeichnis 'tstsrc/tests' folgenden JUnit-Test 'TestMeinDaoImpl.java':

    package tests;
    
    import java.io.*;
    import java.util.*;
    import junit.framework.TestCase;
    import org.springframework.jdbc.core.JdbcTemplate;
    import org.springframework.jdbc.datasource.DriverManagerDataSource;
    import db.DatenObjektTO;
    import db.MeinDaoImpl;
    
    /**
     * Testet: @see MeinDaoImpl
     * Testet mit hSqlDb-Test-Datenbank
     * Datenbank muss vorbereitet werden mit 'ant preparetestdb'
     */
    public class TestMeinDaoImpl extends TestCase
    {
      private MeinDaoImpl dao;
    
      public void setUp() throws FileNotFoundException, IOException
      {
        Properties prop = new Properties();
        prop.load( new FileInputStream( "src/project.properties" ) );
        DriverManagerDataSource ds = new DriverManagerDataSource();
        ds.setDriverClassName( (String) prop.get( "db.test.drv" ) );
        ds.setUrl(             (String) prop.get( "db.test.url" ) );
        ds.setUsername(        (String) prop.get( "db.test.usr" ) );
        ds.setPassword(        (String) prop.get( "db.test.pwd" ) );
        JdbcTemplate jdbcTemplate = new JdbcTemplate();
        jdbcTemplate.setDataSource(ds);
        dao = new MeinDaoImpl();
        dao.setJdbcTemplate( jdbcTemplate );
      }
    
      public void testFindAllDatenObjekte()
      {
        List lst = dao.findAllDatenObjekte();
        assertEquals( "lst.size", 3, lst.size() );
        DatenObjektTO p0 = (DatenObjektTO) lst.get( 0 );
        DatenObjektTO p1 = (DatenObjektTO) lst.get( 1 );
        assertEquals( "Hinz", p0.getName() );
        assertEquals( "Kunz", p1.getName() );
        assertEquals( "00815", p0.getPlz() );
        assertEquals( "47110", p1.getPlz() );
      }
    
      public void testFindDatenObjekteByName()
      {
        List lst = dao.findDatenObjekteByName("Kunz");
        assertEquals( "lst.size", 1, lst.size() );
        DatenObjektTO p = (DatenObjektTO) lst.get( 0 );
        assertEquals( "47110", p.getPlz() );
      }
    
      public void testUpdateDatenObjekt()
      {
        List lst = dao.findAllDatenObjekte();
        assertEquals( 3, lst.size() );
        DatenObjektTO p01 = (DatenObjektTO) lst.get( 0 );
        p01.setPlz("11225");
        dao.updateDatenObjekt(p01);
        lst = dao.findAllDatenObjekte();
        assertEquals( 3, lst.size() );
        DatenObjektTO p02 = (DatenObjektTO) lst.get( 0 );
        assertEquals( "11225", p02.getPlz() );
      }
    
      public void testInsertDatenObjekt()
      {
        List lst = dao.findAllDatenObjekte();
        assertEquals( 3, lst.size() );
        DatenObjektTO p91 = new DatenObjektTO( "Neu", "52211", "Ort3" );
        dao.insertDatenObjekt(p91);
        lst = dao.findDatenObjekteByName("Neu");
        assertEquals( 1, lst.size() );
        DatenObjektTO p92 = (DatenObjektTO) lst.get( 0 );
        assertEquals( "52211", p92.getPlz() );
      }
    }
    
  9. Ihr Projektverzeichnis ist jetzt um folgenden Zweig erweitert:

    [\MeinWorkspace]
      '- [SpringWebApp]
           |- ...
           |- [tstsrc]
           |    '- [tests]
           |         |- MeinTestDaoMock.java
           |         |- springapp-test.xml
           |         |- TestAnzeigeController.java
           |         |- TestGeschaeftslogik.java
           |         '- TestMeinDaoImpl.java
           |- ...
    
  10. Öffnen Sie ein Kommandozeilenfenster, verzweigen Sie in das Projektverzeichnis 'SpringWebApp' und führen Sie folgende Kommandos aus:

    cd \MeinWorkspace\SpringWebApp

    ant junit

    Sie können die JUnit-Tests auch in Eclipse starten. Allerdings müssen Sie dann für den letzten Test die hSqlDb-Datenbank vorbereiten mit:

    ant preparetestdb



Weitere Technologien für das Spring MVC Web Framework

Vorgabewerte

Tragen Sie beim 'CommandTO'-Konstruktor in der 'EingabeFormController.formBackingObject()'-Methode Werte ein, die dann im Eingabeformular als Startwerte erscheinen.

Mehrsprachigkeit

Erzeugen Sie im 'src'-Verzeichnis zusätzlich zur Textressourcendatei 'messages.properties' weitere Textressourcendateien für andere Sprachen, also zum Beispiel 'messages_en.properties' für englisch und 'messages_fr.properties' für französisch.
Schalten Sie in Ihrem Webbrowser die bevorzugte Sprache auf diese Sprachen um, um die angezeigten Texte in der anderen Sprache zu sehen.
Sehen Sie sich die verwendete Spring-'ResourceBundleMessageSource'-Klasse und das Java-'ResourceBundle'-Konzept an.

Datenbank

Tragen Sie bei den 'db.prod...'-Einträgen in der 'project.properties' im 'src'-Verzeichnis eine andere Datenbankverbindung ein. Vergessen Sie nicht, den passenden JDBC-Treiber dem 'webapp/WEB-INF/lib'-Verzeichnis hinzuzufügen.
Wenn Sie keine Datenbank installiert haben, installieren Sie MySQL wie beschrieben unter: mysql.htm#InstallationUnterWindows.

Für MySQL könnten die 'db.prod...'-Einträge zum Beispiel so aussehen:

db.prod.drv=com.mysql.jdbc.Driver
db.prod.url=jdbc:mysql://localhost:3306/MeineDb
db.prod.usr=root
db.prod.pwd=mysqlpwd

Innerhalb eines JEE Application Servers können Sie die DataSource auch über folgenden Eintrag in der Spring-XML-Konfigurationsdatei per JNDI-Lookup erfragen:

  <bean id="idDataSource" class="org.springframework.jndi.JndiObjectFactoryBean">
    <property name="jndiName" value="java:comp/env/jdbc/myDataSource" />
  </bean>

Denken Sie daran, dass gleichnamige Datenbanktabellen kommentarlos gelöscht werden.

Datei-Upload

Implementieren Sie eine Datei-Upload-Funktion mit Hilfe von 'CommonsMultipartResolver' oder 'CosMultipartResolver'.

Wizard, Web Flow

Erzeugen Sie eine einfache Sequenz von zusammengehörenden Formularen als Wizard mit Hilfe des 'AbstractWizardFormController'.

Implementieren Sie komplexere Abläufe mit Fallunterscheidungen mit 'Spring Web Flow'.

Andere Views

Erzeugen Sie andere View-Arten:
- XSLT-View mit 'AbstractXsltView'
- Excel-Datei mit 'AbstractExcelView' (mit Hilfe von Apache POI)
- PDF-Datei mit 'AbstractPdfView' (mit Hilfe von iText)

ResourceBundleViewResolver

Verwenden Sie den 'ResourceBundleViewResolver' statt des 'InternalResourceViewResolver', um außer auf Views, die als Datei vorliegen (wie z.B. JSP), auch auf Views, die als Java-Klasse realisiert sind, verweisen zu können.





Weitere Themen: andere TechDocs | EJB | SQL | Hibernate
© 1998-2007 Torsten Horn, Aachen