5. Die Steuerberechnungsanwendung
5.1. Einführung
Hier greifen wir die IMPOTS-Anwendung wieder auf, die im Java-Handout desselben Autors wiederholt verwendet wird. Lassen Sie uns das Problem noch einmal betrachten. Das Ziel ist es, eine Anwendung zu schreiben, die die Steuerschuld eines Steuerzahlers berechnet. Wir betrachten den vereinfachten Fall eines Steuerzahlers, der nur ein einziges Gehalt zu melden hat:
- Wir berechnen die Anzahl der Steuerklassen des Arbeitnehmers als nbParts = nbEnfants / 2 + 1, wenn er unverheiratet ist, und als nbEnfants / 2 + 2, wenn er verheiratet ist, wobei nbEnfants die Anzahl der Kinder ist.
- Wenn er mindestens drei Kinder hat, erhält er einen zusätzlichen halben Anteil
- Wir berechnen sein zu versteuerndes Einkommen R = 0,72 * S, wobei S sein Jahresgehalt ist
- Wir berechnen den Familienkoeffizienten QF = R / nbParts
- Wir berechnen ihre Steuer I. Betrachten Sie die folgende Tabelle:
12620,0 | 0 | 0 |
13.190 | 0,05 | 631 |
15.640 | 0,1 | 1.290,5 |
24.740 | 0,15 | 2.072,5 |
31.810 | 0,2 | 3.309,5 |
39.970 | 0,25 | 4.900 |
48.360 | 0,3 | 6.898,5 |
55.790 | 0,35 | 9.316,5 |
92.970 | 0,4 | 12.106 |
127.860 | 0,45 | 16.754,5 |
151.250 | 0,50 | 23.147,5 |
172.040 | 0,55 | 30.710 |
195.000 | 0,60 | 39.312 |
0 | 0,65 | 49062 |
Jede Zeile enthält 3 Felder. Um die Steuer I zu berechnen, suchen Sie die erste Zeile, in der QF <= Feld1 ist. Wenn beispielsweise QF = 23.000 ist, lautet die gefundene Zeile
Steuer I ist dann gleich 0,15*R - 2072,5*nbParts. Wenn QF so ist, dass die Bedingung QF<=field1 nie erfüllt ist, werden die Koeffizienten aus der letzten Zeile verwendet. Hier:
was die Steuer I = 0,65*R - 49062*nbParts ergibt.
Die Daten, die die verschiedenen Steuerklassen definieren, werden in einer ODBC-MySQL-Datenbank gespeichert. MySQL ist ein Open-Source-DBMS, das auf verschiedenen Plattformen, darunter Windows und Linux, verwendet werden kann. Mit diesem DBMS wurde eine Datenbank namens dbimpots erstellt, die eine einzige Tabelle namens impots enthält. Der Zugriff auf die Datenbank wird durch einen Benutzernamen und ein Passwort gesteuert, in diesem Fall „admimpots“ und „mdpimpots“. Der folgende Screenshot zeigt, wie die Datenbank „dbimpots“ mit MySQL verwendet wird:
C:\Program Files\EasyPHP\mysql\bin>mysql -u admimpots -p
Enter password: *********
Welcome to the MySQL monitor. Commands end with ; or \g.
Your MySQL connection id is 18 to server version: 3.23.49-max-nt
Type 'help;' or '\h' for help. Type '\c' to clear the buffer.
mysql> use dbimpots;
Database changed
mysql> show tables;
+--------------------+
| Tables_in_dbimpots |
+--------------------+
| impots |
+--------------------+
1 row in set (0.00 sec)
mysql> describe impots;
+---------+--------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+---------+--------+------+-----+---------+-------+
| limites | double | YES | | NULL | |
| coeffR | double | YES | | NULL | |
| coeffN | double | YES | | NULL | |
+---------+--------+------+-----+---------+-------+
3 rows in set (0.02 sec)
mysql> select * from impots;
+---------+--------+---------+
| limites | coeffR | coeffN |
+---------+--------+---------+
| 12620 | 0 | 0 |
| 13190 | 0.05 | 631 |
| 15640 | 0.1 | 1290.5 |
| 24740 | 0.15 | 2072.5 |
| 31810 | 0.2 | 3309.5 |
| 39970 | 0.25 | 4900 |
| 48360 | 0.3 | 6898 |
| 55790 | 0.35 | 9316.5 |
| 92970 | 0.4 | 12106 |
| 127860 | 0.45 | 16754 |
| 151250 | 0.5 | 23147.5 |
| 172040 | 0.55 | 30710 |
| 195000 | 0.6 | 39312 |
| 0 | 0.65 | 49062 |
+---------+--------+---------+
14 rows in set (0.00 sec)
mysql>quit
Die Datenbank „dbimpots“ wird wie folgt in eine ODBC-Datenquelle konvertiert:
- Starten Sie den 32-Bit-ODBC-Datenquellen-Administrator

- Fügen Sie über die Schaltfläche [Hinzufügen] eine neue ODBC-Datenquelle hinzu

- Wählen Sie den MySQL-Treiber aus und klicken Sie auf [Fertigstellen]
54321

- Der MySQL-Treiber fordert bestimmte Informationen an:
1 | den DSN-Namen, der der ODBC-Datenquelle zugewiesen werden soll – dieser kann beliebig sein |
2 | den Rechner, auf dem das MySQL-DBMS läuft – hier „localhost“. Es ist anzumerken, dass es sich bei der Datenbank um eine Remote-Datenbank handeln könnte. Lokale Anwendungen, die die ODBC-Datenquelle nutzen, würden davon nichts bemerken. Dies wäre insbesondere bei unserer Java-Anwendung der Fall. |
3 | die zu verwendende MySQL-Datenbank. MySQL ist ein DBMS, das relationale Datenbanken verwaltet, also Gruppen von Tabellen, die durch Beziehungen miteinander verknüpft sind. Hier geben wir den Namen der zu verwaltenden Datenbank an. |
4 | Der Name eines Benutzers mit Zugriffsrechten auf diese Datenbank |
5 | sein Passwort |
Zur Berechnung der Steuer wurden zwei Klassen definiert: impots und impotsJDBC. Eine Instanz der Klasse impots wird unter Verwendung der Steuerklassen-Daten erstellt, die als Parameter in Arrays übergeben werden:
// creation of an impots class
public class impots{
// data required for tax calculation
// come from an external source
protected double[] limites=null;
protected double[] coeffR=null;
protected double[] coeffN=null;
// empty builder
protected impots(){}
// manufacturer
public impots(double[] LIMITES, double[] COEFFR, double[] COEFFN) throws Exception{
// check that the 3 arrays have the same size
boolean OK=LIMITES.length==COEFFR.length && LIMITES.length==COEFFN.length;
if (! OK) throw new Exception ("Les 3 tableaux fournis n'ont pas la même taille("+
LIMITES.length+","+COEFFR.length+","+COEFFN.length+")");
// it's good
this.limites=LIMITES;
this.coeffR=COEFFR;
this.coeffN=COEFFN;
}//manufacturer
// tAX CALCULATION
public long calculer(boolean marié, int nbEnfants, int salaire){
// calculating the number of shares
double nbParts;
if (marié) nbParts=(double)nbEnfants/2+2;
else nbParts=(double)nbEnfants/2+1;
if (nbEnfants>=3) nbParts+=0.5;
// calculation of taxable income & family quota
double revenu=0.72*salaire;
double QF=revenu/nbParts;
// tAX CALCULATION
limites[limites.length-1]=QF+1;
int i=0;
while(QF>limites[i]) i++;
// return result
return (long)(revenu*coeffR[i]-nbParts*coeffN[i]);
}//calculate
}//class
Die Klasse impotsJDBC leitet sich von der vorherigen Klasse impots ab. Eine Instanz der Klasse impotsJDBC wird unter Verwendung von Steuerklassen-Daten erstellt, die in einer Datenbank gespeichert sind. Die für den Zugriff auf diese Datenbank erforderlichen Informationen werden als Parameter an den Konstruktor übergeben:
// imported packages
import java.sql.*;
import java.util.*;
public class impotsJDBC extends impots{
// addition of a constructor for building
// limit tables, coeffr, coeffn from table
// database taxes
public impotsJDBC(String dsnIMPOTS, String userIMPOTS, String mdpIMPOTS)
throws SQLException,ClassNotFoundException{
// dsnIMPOTS: DSN database name
// userIMPOTS, mdpIMPOTS: database login/password
// data tables
ArrayList aLimites=new ArrayList();
ArrayList aCoeffR=new ArrayList();
ArrayList aCoeffN=new ArrayList();
// connection to base
Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
Connection connect=DriverManager.getConnection("jdbc:odbc:"+dsnIMPOTS,userIMPOTS,mdpIMPOTS);
// creation of a Statement object
Statement S=connect.createStatement();
// select request
String select="select limites, coeffr, coeffn from impots";
// query execution
ResultSet RS=S.executeQuery(select);
while(RS.next()){
// running line operation
aLimites.add(RS.getString("limites"));
aCoeffR.add(RS.getString("coeffr"));
aCoeffN.add(RS.getString("coeffn"));
}// next line
// closing resources
RS.close();
S.close();
connect.close();
// data transfer to bounded arrays
int n=aLimites.size();
limites=new double[n];
coeffR=new double[n];
coeffN=new double[n];
for(int i=0;i<n;i++){
limites[i]=Double.parseDouble((String)aLimites.get(i));
coeffR[i]=Double.parseDouble((String)aCoeffR.get(i));
coeffN[i]=Double.parseDouble((String)aCoeffN.get(i));
}//for
}//manufacturer
}//class
Sobald eine Instanz der Klasse impotsJDBC erstellt wurde, kann ihre Methode calculate wiederholt aufgerufen werden, um die Steuer zu berechnen:
Die drei erforderlichen Daten können auf verschiedene Weise ermittelt werden. Der Vorteil der Klasse *importsJDBC besteht darin, dass wir uns nur um die Beschaffung dieser Daten kümmern müssen. Sobald die drei Informationen (Familienstand, Anzahl der Kinder, Jahresgehalt) vorliegen, erhalten wir durch den Aufruf der Methode *calculer der Klasse importsJDBC den fälligen Steuerbetrag.
5.2. Version 1
Wir befinden uns im Kontext einer Webanwendung, die einem Benutzer eine HTML-Oberfläche bereitstellt, um die drei für die Steuerberechnung erforderlichen Parameter zu erfassen:
- Familienstand (verheiratet oder nicht)
- Anzahl der Kinder
- Jahreseinkommen

Das Formular wird auf der folgenden JSP-Seite angezeigt:
<%@ page import="java.util.*" %>
<%
// on récupère les attributs passés par la servlet principale
String chkoui=(String)request.getAttribute("chkoui");
String chknon=(String)request.getAttribute("chknon");
String txtEnfants=(String)request.getAttribute("txtEnfants");
String txtSalaire=(String)request.getAttribute("txtSalaire");
String txtImpots=(String)request.getAttribute("txtImpots");
ArrayList erreurs=(ArrayList)request.getAttribute("erreurs");
%>
<html>
<head>
<title>impots</title>
<script language="JavaScript" type="text/javascript">
function effacer(){
// raz du formulaire
with(document.frmImpots){
optMarie[0].checked=false;
optMarie[1].checked=true;
txtEnfants.value="";
txtSalaire.value="";
txtImpots.value="";
}//with
}//effacer
</script>
</head>
<body background="/impots/images/standard.jpg">
<center>
Calcul d'impôts
<hr>
<form name="frmImpots" action="/impots/main" method="POST">
<table>
<tr>
<td>Etes-vous marié(e)</td>
<td>
<input type="radio" name="optMarie" value="oui" <%= chkoui %>>oui
<input type="radio" name="optMarie" value="non" <%= chknon %>>non
</td>
</tr>
<tr>
<td>Nombre d'enfants</td>
<td><input type="text" size="5" name="txtEnfants" value="<%= txtEnfants %>"></td>
</tr>
<tr>
<td>Salaire annuel</td>
<td><input type="text" size="10" name="txtSalaire" value="<%= txtSalaire %>"></td>
</tr>
<tr>
<td><font color="green">Impôt</font></td>
<td><input type="text" size="10" name="txtImpots" value="<%= txtImpots %>" readonly></td>
</tr>
<tr></tr>
<tr>
<td><input type="submit" value="Calculer"></td>
<td><input type="button" value="Effacer" onclick="effacer()"></td>
</tr>
</table>
</form>
</center>
<%
// y-a-t-il des erreurs
if(erreurs!=null){
// affichage des erreurs
out.println("<hr>");
out.println("<font color=\"red\">");
out.println("Les erreurs suivantes se sont produites<br>");
out.println("<ul>");
for(int i=0;i<erreurs.size();i++){
out.println("<li>"+(String)erreurs.get(i));
}
out.println("</ul>");
out.println("</font>");
}
%>
</body>
</html>
Die JSP-Seite zeigt lediglich Informationen an, die ihr vom Haupt-Servlet der Anwendung übergeben werden:
// retrieve attributes passed by the main servlet
String chkoui=(String)request.getAttribute("chkoui");
String chknon=(String)request.getAttribute("chknon");
String txtEnfants=(String)request.getAttribute("txtEnfants");
String txtSalaire=(String)request.getAttribute("txtSalaire");
String txtImpots=(String)request.getAttribute("txtImpots");
ArrayList erreurs=(ArrayList)request.getAttribute("erreurs");
Die Attribute „checked“ und „unchecked“ von Optionsfeldern können die Werte „checked“ oder „unchecked“ annehmen, um anzugeben, ob das entsprechende Optionsfeld ausgewählt ist oder nicht | |
die Anzahl der Kinder des Steuerzahlers | |
sein Jahresgehalt | |
die Höhe der fälligen Steuern | |
eine Liste der Fehler, falls vorhanden, wenn errors!=null |
Die an den Client gesendete Seite enthält ein JavaScript-Skript mit einer „Clear“-Funktion, die der Schaltfläche „Clear“ zugeordnet ist und dazu dient, das Formular in seinen Ausgangszustand zurückzusetzen: nicht markierte Schaltflächen, leere Eingabefelder. Beachten Sie, dass dieses Ergebnis mit einer HTML-Schaltfläche „Zurücksetzen“ nicht erreicht werden könnte. Wenn diese Art von Schaltfläche verwendet wird, setzt der Browser das Formular nämlich in den Zustand zurück, in dem er es erhalten hat. In unserer Anwendung erhält der Browser jedoch Formulare, die möglicherweise nicht leer sind.
Das Haupt-Servlet der Anwendung heißt main.java, und sein Code lautet wie folgt:
import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
import java.util.regex.*;
import java.util.*;
public class main extends HttpServlet{
// instance variables
String msgErreur=null;
String urlAffichageImpots=null;
String urlErreur=null;
String DSNimpots=null;
String admimpots=null;
String mdpimpots=null;
impotsJDBC impots=null;
//-------- GET
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws IOException, ServletException{
// was the initialization successful?
if(msgErreur!=null){
// we hand over to the error page
request.setAttribute("msgErreur",msgErreur);
getServletContext().getRequestDispatcher(urlErreur).forward(request,response);
}
// query attributes
String chkoui=null;
String chknon=null;
String txtImpots=null;
// retrieve query parameters
String optMarie=request.getParameter("optMarie"); // marital status
String txtEnfants=request.getParameter("txtEnfants"); // no. of children
if(txtEnfants==null) txtEnfants="";
String txtSalaire=request.getParameter("txtSalaire"); // annual salary
if(txtSalaire==null) txtSalaire="";
// do we have all the expected parameters
if(optMarie==null || txtEnfants==null || txtSalaire==null){
// missing parameters
request.setAttribute("chkoui","");
request.setAttribute("chknon","checked");
request.setAttribute("txtEnfants","");
request.setAttribute("txtSalaire","");
request.setAttribute("txtImpots","");
// we hand over to the tax display url
getServletContext().getRequestDispatcher(urlAffichageImpots).forward(request,response);
}
// we have all the parameters - we check them
ArrayList erreurs=new ArrayList();
// marital status
if( ! optMarie.equals("oui") && ! optMarie.equals("non")){
// error
erreurs.add("Etat marital incorrect");
optMarie="non";
}
// number of children
txtEnfants=txtEnfants.trim();
if(! Pattern.matches("^\\d+$",txtEnfants)){
// error
erreurs.add("Nombre d'enfants incorrect");
}
// salary
txtSalaire=txtSalaire.trim();
if(! Pattern.matches("^\\d+$",txtSalaire)){
// error
erreurs.add("Salaire incorrect");
}
// if there are errors, they are passed as query attributes
if(erreurs.size()!=0){
request.setAttribute("erreurs",erreurs);
txtImpots="";
}else{
// you can calculate the tax payable
try{
int nbEnfants=Integer.parseInt(txtEnfants);
int salaire=Integer.parseInt(txtSalaire);
txtImpots=""+impots.calculer(optMarie.equals("oui"),nbEnfants,salaire);
}catch(Exception ex){}
}
// other query attributes
if(optMarie.equals("oui")){
request.setAttribute("chkoui","checked");
request.setAttribute("chknon","");
}else{
request.setAttribute("chknon","checked");
request.setAttribute("chkoui","");
}
request.setAttribute("txtEnfants",txtEnfants);
request.setAttribute("txtSalaire",txtSalaire);
request.setAttribute("txtImpots",txtImpots);
// we hand over to the tax display url
getServletContext().getRequestDispatcher(urlAffichageImpots).forward(request,response);
}
//-------- POST
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws IOException, ServletException{
doGet(request,response);
}
//-------- INIT
public void init(){
// retrieve initialization parameters
ServletConfig config=getServletConfig();
urlAffichageImpots=config.getInitParameter("urlAffichageImpots");
urlErreur=config.getInitParameter("urlErreur");
DSNimpots=config.getInitParameter("DSNimpots");
admimpots=config.getInitParameter("admimpots");
mdpimpots=config.getInitParameter("mdpimpots");
// parameters ok?
if(urlAffichageImpots==null || DSNimpots==null || admimpots==null || mdpimpots==null){
msgErreur="Configuration incorrecte";
return;
}
// create an instance of impotsJDBC
try{
impots=new impotsJDBC(DSNimpots,admimpots,mdpimpots);
}catch(Exception ex){
msgErreur=ex.getMessage();
}
}
}
- Die init-Methode des Servlets führt zwei Aufgaben aus:
- Es ruft seine Initialisierungsparameter ab. Diese ermöglichen es ihm, eine Verbindung zur ODBC-Datenbank herzustellen, die die Daten für die verschiedenen Steuerklassen (DSNimpots, admimpots, mdpimpots) sowie die URLs der mit der Anwendung verbundenen Seiten enthält: urlAffichageImpots für das Formular, urlErreur für die Fehlerseite.
- Es erstellt eine Instanz der Klasse impotsJDBC
In beiden Fällen werden mögliche Fehler behandelt und die Fehlermeldung in der Variablen msgErreur gespeichert.
- Die Methode doGET
- prüft zunächst, ob das Servlet korrekt initialisiert wurde. Ist dies nicht der Fall, wird die Fehlerseite angezeigt
- ruft die erwarteten Parameter aus dem Steuerformular ab: optMarie, txtEnfants, txtSalaire. Fehlt einer dieser Parameter (==null), wird ein leeres Steuerformular übermittelt. Man könnte meinen, dass eine Validierung des Parameters optMarie unnötig ist. Dieser Parameter ist der Wert eines Optionsfelds und kann hier nur einen der Werte „yes“ oder „no“ annehmen. Dabei wird jedoch übersehen, dass nichts ein Programm daran hindert, das Servlet direkt abzufragen, indem es ihm die gewünschten Parameter übermittelt. Man kann nie sicher sein, dass man tatsächlich mit einem Browser verbunden ist. Das Übersehen dieses Punktes kann zu Sicherheitslücken in der Anwendung führen, und tatsächlich sind solche Probleme sogar in kommerziellen Anwendungen häufig anzutreffen.
- Die Gültigkeit jedes der drei abgerufenen Parameter wird überprüft. Alle gefundenen Fehler werden einer Fehlerliste (ArrayList errors) hinzugefügt. Wenn keine Fehler vorliegen, wird der Steuerbetrag berechnet; andernfalls nicht.
- Die zur Anzeige der Seite erforderlichen Informationen werden als Request-Attribute gesetzt, und das Steuerformular wird anschließend angezeigt
Die JSP-Fehlerseite sieht wie folgt aus:
<%
// jspService
// une erreur s'est produite
String msgErreur= (String)request.getAttribute("msgErreur");
if(msgErreur==null) msgErreur="Erreur non identifiée";
%>
<!-- top of page HTML -->
<html>
<head>
<title>impots</title>
</head>
<body>
<h3>calcul d'impots</h3>
<hr>
Application indisponible(<%= msgErreur %>)
</body>
</html>
Die Webanwendung heißt „impots“ und ist in der Datei „server.xml“ von Tomcat wie folgt konfiguriert:
Das Anwendungsverzeichnis enthält die folgenden Verzeichnisse und Dateien:




Die Konfigurationsdatei web.xml für die Anwendung „imports“ sieht wie folgt aus:
<?xml version="1.0" encoding="ISO-8859-1"?>
<!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>main</servlet-name>
<servlet-class>main</servlet-class>
<init-param>
<param-name>urlAffichageImpots</param-name>
<param-value>/impots.jsp</param-value>
</init-param>
<init-param>
<param-name>DSNimpots</param-name>
<param-value>mysql-dbimpots</param-value>
</init-param>
<init-param>
<param-name>admimpots</param-name>
<param-value>admimpots</param-value>
</init-param>
<init-param>
<param-name>mdpimpots</param-name>
<param-value>mdpimpots</param-value>
</init-param>
<init-param>
<param-name>urlErreur</param-name>
<param-value>/erreur.jsp</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>main</servlet-name>
<url-pattern>/main</url-pattern>
</servlet-mapping>
</web-app>
Das Haupt-Servlet heißt „main“ und hat den Alias „/main“. Es ist daher über die URL http://localhost:8080/impots/main erreichbar.
Hier sind einige Anwendungsbeispiele:
Um korrekt initialisiert zu werden, muss das Servlet Zugriff auf die Datenbank „mysql-dbimpots“ haben. Hier ist die Seite, die angezeigt wird, wenn beispielsweise der MySQL-Server nicht läuft und die Datenbank „mysql-dbimpots“ daher nicht erreichbar ist:

Die Seite, die bei falscher Eingabe angezeigt wird, sieht wie folgt aus:

Sind die Angaben korrekt, wird die Steuer berechnet:

5.3. Version 2
Im vorherigen Beispiel überprüft der Server die Parameter „txtEnfants“ und „txtSalaire“ im Formular. Hier schlagen wir vor, diese mithilfe eines in die Formularseite eingebundenen JavaScript-Skripts zu überprüfen. Der Browser führt dann die Parameterüberprüfung durch. Der Server wird nur kontaktiert, wenn die Parameter gültig sind. Dies spart „Bandbreite“. Die JSP-Anzeigeseite sieht dann wie folgt aus:
<%@ page import="java.util.*" %>
............
<html>
<head>
<title>impots</title>
<script language="JavaScript" type="text/javascript">
function effacer(){
.......
}//effacer
function calculer(){
// vérification des paramètres avant de les envoyer au serveur
with(document.frmImpots){
//nbre d'enfants
champs=/^\s*(\d+)\s*$/.exec(txtEnfants.value);
if(champs==null){
// le modéle n'est pas vérifié
alert("Le nombre d'enfants n'a pas été donné ou est incorrect");
nbEnfants.focus();
return;
}//if
//salaire
champs=/^\s*(\d+)\s*$/.exec(txtSalaire.value);
if(champs==null){
// le modéle n'est pas vérifié
alert("Le salaire n'a pas été donné ou est incorrect");
salaire.focus();
return;
}//if
// c'est bon - on envoie le formulaire au serveur
submit();
}//with
}//calculer
</script>
</head>
<body background="/impots/images/standard.jpg">
........
<td><input type="button" value="Calculer" onclick="calculer()"></td>
<td><input type="button" value="Effacer" onclick="effacer()"></td>
.....
</body>
</html>
Beachten Sie die folgenden Änderungen:
- Die Schaltfläche „Calculate“ ist nun keine Absenden-Schaltfläche mehr, sondern eine normale Schaltfläche, die mit einer Funktion namens „calculate“ verknüpft ist. Diese Funktion überprüft die Felder „txtEnfants“ und „txtSalaire“ auf Gültigkeit. Sind diese gültig, werden die Formulardaten an den Server übermittelt; andernfalls wird eine Fehlermeldung angezeigt.
Hier ist ein Beispiel dafür, was im Falle eines Fehlers angezeigt wird:

5.4. Version 3
Wir nehmen eine geringfügige Änderung an der Anwendung vor, um das Konzept einer Sitzung einzuführen. Wir betrachten die Anwendung nun als ein Tool zur Simulation von Steuerberechnungen. Ein Benutzer kann dann verschiedene „Szenarien“ für Steuerzahler simulieren und sehen, wie hoch die Steuerschuld in jedem einzelnen Fall wäre. Die folgende Webseite zeigt ein Beispiel dafür, was damit erreicht werden könnte:

Das Haupt-Servlet wurde geändert und heißt nun „simulations“. Es ist innerhalb der Anwendung „impots“ wie folgt konfiguriert:
<?xml version="1.0" encoding="ISO-8859-1"?>
<!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>
<servlet-name>simulations</servlet-name>
<servlet-class>simulations</servlet-class>
<init-param>
<param-name>urlSimulationImpots</param-name>
<param-value>/simulationsImpots.jsp</param-value>
</init-param>
<init-param>
<param-name>DSNimpots</param-name>
<param-value>mysql-dbimpots</param-value>
</init-param>
<init-param>
<param-name>admimpots</param-name>
<param-value>admimpots</param-value>
</init-param>
<init-param>
<param-name>mdpimpots</param-name>
<param-value>mdpimpots</param-value>
</init-param>
<init-param>
<param-name>urlErreur</param-name>
<param-value>/erreur.jsp</param-value>
</init-param>
</servlet>
......
<servlet-mapping>
<servlet-name>simulations</servlet-name>
<url-pattern>/simulations</url-pattern>
</servlet-mapping>
</web-app>
Das Haupt-Servlet heißt „simulations“ und basiert auf der Datei „simulations.class“. Es hat den Alias „/simulations“, wodurch es über die URL „http://localhost:8080/impots/simulations“ erreichbar ist. Es verfügt über dieselben Initialisierungsparameter wie das zuvor besprochene Haupt-Servlet in Bezug auf den Datenbankzugriff. Ein neuer Parameter kommt hinzu: „urlSimulationsImports“, bei dem es sich um die URL der Simulations-JSP-Seite handelt (die oben gerade vorgestellte).
Das Servlet „simulations.java“ ähnelt dem Servlet „main.java“. Es unterscheidet sich von diesem im Wesentlichen in folgenden Punkten:
- Das Haupt-Servlet berechnet einen Wert für `txtImpots` auf der Grundlage der Parameter `optmarie`, `txtEnfants` und `txtSalaire` und übergibt diesen Wert an die JSP-Anzeigeseite
- Das Servlet „simulations“ berechnet den Wert „txtImpots“ auf die gleiche Weise und speichert die Parameter (optMarie, txtEnfants, txtsalaire, txtImpots) in einer Liste namens „simulations“. Diese Liste wird als Parameter an die JSP-Anzeigeseite übergeben. Um sicherzustellen, dass diese Liste alle vom Benutzer durchgeführten Simulationen enthält, wird sie als Attribut der aktuellen Sitzung gespeichert.
Das Simulations-Servlet sieht wie folgt aus (es wurden nur die Codezeilen aufgenommen, die sich von denen in der vorherigen Anwendung unterscheiden):
import java.io.*;
.......
public class simulations extends HttpServlet{
// instance variables
String msgErreur=null;
String urlSimulationImpots=null;
String urlErreur=null;
...........
//-------- GET
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws IOException, ServletException{
...........
// retrieve previous simulations from the session
HttpSession session=request.getSession();
ArrayList simulations=(ArrayList)session.getAttribute("simulations");
if(simulations==null) simulations=new ArrayList();
// put the simulations in the current query
request.setAttribute("simulations",simulations);
// other query attributes
...........
// do we have all the expected parameters
if(optMarie==null || txtEnfants==null || txtSalaire==null){
........
// we hand over to the url for displaying tax calculation simulations
getServletContext().getRequestDispatcher(urlSimulationImpots).forward(request,response);
}
// we have all the parameters - we check them
...........
// if there are errors, they are passed as query attributes
if(erreurs.size()!=0){
request.setAttribute("erreurs",erreurs);
}else{
try{
// you can calculate the tax payable
int nbEnfants=Integer.parseInt(txtEnfants);
int salaire=Integer.parseInt(txtSalaire);
txtImpots=""+impots.calculer(optMarie.equals("oui"),nbEnfants,salaire);
// the current result is added to the previous simulations
String[] simulation={optMarie.equals("oui") ? "oui" : "non",txtEnfants, txtSalaire, txtImpots};
simulations.add(simulation);
// the new simulations value is added to the session
session.setAttribute("simulations",simulations);
}catch(Exception ex){}
}
// other query attributes
..........
// we hand over to the simulations display url
getServletContext().getRequestDispatcher(urlSimulationImpots).forward(request,response);
}
//-------- POST
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws IOException, ServletException{
doGet(request,response);
}
//-------- INIT
public void init(){
// retrieve initialization parameters
ServletConfig config=getServletConfig();
urlSimulationImpots=config.getInitParameter("urlSimulationImpots");
urlErreur=config.getInitParameter("urlErreur");
DSNimpots=config.getInitParameter("DSNimpots");
admimpots=config.getInitParameter("admimpots");
mdpimpots=config.getInitParameter("mdpimpots");
// parameters ok?
.........................
}
}
Die JSP-Anzeigeseite simulationsImpots.jsp sieht nun wie folgt aus (es wurde nur der Code beibehalten, der sich von der JSP-Anzeigeseite der vorherigen Anwendung unterscheidet).
<%@ page import="java.util.*" %>
<%
// on récupère les attributs passés par la servlet principale
...........
ArrayList simulations=(ArrayList)request.getAttribute("simulations");
%>
<html>
<head>
<title>impots</title>
<script language="JavaScript" type="text/javascript">
........
</script>
</head>
<body background="/impots/images/standard.jpg">
<center>
Calcul d'impôts
<hr>
<form name="frmImpots" action="/impots/simulations" method="POST">
.......................
</form>
</center>
<hr>
<%
// y-a-t-il des erreurs ?
if(erreurs!=null){
..................
}else if(simulations.size()!=0){
// résultats des simulations
out.println("<h3>Résultats des simulations<h3>");
out.println("<table \"border=\"1\">");
out.println("<tr><td>Marié</td><td>Enfants</td><td>Salaire annuel (F)</td><td>Impôts à payer (F)</td></tr>");
for(int i=0;i<simulations.size();i++){
String[] simulation=(String[])simulations.get(i);
out.println("<tr><td>"+simulation[0]+"</td><td>"+simulation[1]+"</td><td>"+simulation[2]+"</td><td>"+simulation[3]+"</td></tr>");
}
out.println("</table>");
}
%>
</body>
</html>
5.5. Version 4
Wir werden nun eine eigenständige Anwendung erstellen, die als Web-Client für die vorherige Anwendung /impots/simulations dienen soll. Diese Anwendung wird über die folgende grafische Benutzeroberfläche verfügen:
![]() |
Nr. | Typ | Name | Rolle |
1 | JTextField | txtTaxServiceUrl | URL des Dienstes zur Simulation der Steuerberechnung |
2 | JRadioButton | rdYes | Angekreuzt, wenn verheiratet |
3 | JRadioButton | rdNo | Markiert, wenn unverheiratet |
4 | JSpinner | spinChildren | Anzahl der Kinder des Steuerpflichtigen (Minimum=0, Maximum=20, Schrittweite=1) |
5 | JTextField | txtSalary | Jahresgehalt des Steuerpflichtigen in F |
6 | JList in JScrollPane | lstSimulations | Liste der Simulationen |
Das Menü „Steuern“ enthält die folgenden Optionen:
Hauptmenü Hauptmenü | Option sekundär | Name | Rolle |
Steuern | |||
Berechnen | mnuCalculate | Berechnet die fällige Steuer, wenn alle für die Berechnung erforderlichen Daten vorhanden und korrekt sind | |
Löschen | mnuClear | Setzt das Formular auf den Ausgangszustand zurück | |
Beenden | mnuExit | schließt die Anwendung |
Bedienungsregeln
- Die Menüoption „Berechnen“ bleibt deaktiviert, wenn entweder Feld 1 oder 5 leer ist
- In Feld 1 wird eine syntaktisch falsche URL erkannt

- ein falscher Lohn festgestellt wird

- ein Serververbindungsfehler gemeldet wird (in Beispiel 1 unten ist der Port falsch; in Beispiel 2 existiert die angeforderte URL nicht; in Beispiel 3 lief die MySQL-Datenbank nicht)



- Wenn alles korrekt ist, werden die Simulationen angezeigt

Beim Schreiben eines programmierten Web-Clients muss man genau wissen, was der Server als Antwort auf die verschiedenen möglichen Anfragen eines Clients sendet. Der Server sendet eine Reihe von HTML-Zeilen, die nützliche Informationen sowie andere Elemente enthalten, die ausschließlich dem HTML-Layout dienen. Mit regulären Ausdrücken in Java können wir die nützlichen Informationen in dem vom Server gesendeten Datenstrom finden. Dazu müssen wir das genaue Format der verschiedenen Antworten des Servers kennen. Hier verwenden wir den zuvor vorgestellten Web-Client, mit dem wir die Antwort eines Servers auf eine URL-Anfrage auf dem Bildschirm anzeigen können. Die angeforderte URL ist die unseres Dienstes zur Steuerberechnungssimulation, http://localhost:8080/impots/simulations, an den wir Parameter in der Form http://localhost:8080/impots/simulations?param1=vam1¶m2=val2&... übergeben können
Lassen Sie uns die URL anfordern, während die MySQL-Datenbank, die zur Erstellung eines Steuerobjekts verwendet wird, nicht läuft:
Dos>java clientweb http://localhost:8080/impots/simulations GET
HTTP/1.1 200 OK
Content-Type: text/html;charset=ISO-8859-1
Date: Fri, 16 Aug 2002 16:31:04 GMT
Connection: close
Server: Apache Tomcat/4.0.3 (HTTP/1.1 Connector)
Set-Cookie: JSESSIONID=9DEC8B27966A1FBE3D4968A7B9DF3331;Path=/impots
<!-- dÚbut from page HTML -->
<html>
<head>
<title>impots</title>
</head>
<body>
<h3>calcul d'impôts</h3>
<hr>
Application indisponible([TCX][MyODBC]Can't connect to MySQL server on 'localhost' (10061))
</body>
</html>
Um den Fehler abzurufen, muss der Web-Client die Antwort des Web-Servers nach der Zeile durchsuchen, die den Text „Anwendung nicht verfügbar“ enthält. Starten wir nun die MySQL-Datenbank und rufen dieselbe URL auf, wobei wir ihr die erwarteten Werte übergeben (sie akzeptiert diese gleichermaßen als GET- oder POST-Anfrage – siehe den Java-Code des Servers):
Dos>java clientweb "http://localhost:8080/impots/simulations?ptMarie=oui&txtEnfants=2&txtSalaire=200000" GET
HTTP/1.1 200 OK
Content-Type: text/html;charset=ISO-8859-1
Date: Fri, 16 Aug 2002 16:42:36 GMT
Connection: close
Server: Apache Tomcat/4.0.3 (HTTP/1.1 Connector)
Set-Cookie: JSESSIONID=C2A707600E98A37A343611D80DD5C8A2;Path=/impots
<html>
<head>
<title>impots</title>
<script language="JavaScript" type="text/javascript">
function effacer(){
...................................................
}//effacer
function calculer(){
...................................................
}//calculer
</script>
</head>
<body background="/impots/images/standard.jpg">
<center>
Calcul d'imp¶ts
<hr>
<form name="frmImpots" action="/impots/simulations" method="POST">
...................................................
</form>
</center>
<hr>
<h3>Résultats des simulations<h3>
<table "border="1">
<tr><td>Marié</td><td>Enfants</td><td>Salaire annuel (F)</td><td>Impôts à payer (F)</td></tr>
<tr><td>oui</td><td>2</td><td>200000</td><td>22504</td></tr>
</table>
</body>
</html>
Hier ist das gesamte vom Server gesendete HTML-Dokument. Die Ergebnisse der verschiedenen Simulationen finden sich in der einzigen Tabelle in diesem Dokument. Der reguläre Ausdruck, mit dem wir die relevanten Informationen aus dem Dokument extrahieren können, könnte wie folgt lauten:
wobei die vier Ausdrücke in Klammern die vier zu extrahierenden Informationen darstellen.
Wir haben nun die Richtlinien dafür, was zu tun ist, wenn der Benutzer über die vorherige grafische Benutzeroberfläche eine Steuerberechnung anfordert:
- Überprüfen Sie, ob alle Daten in der Oberfläche gültig sind, und melden Sie etwaige Fehler.
- Stellen Sie eine Verbindung zu der in Feld 1 angegebenen URL her. Dazu folgen wir dem Modell des generischen Web-Clients, das bereits vorgestellt und
- im Server-Antwort-Stream verwenden wir reguläre Ausdrücke, um entweder:
- die Fehlermeldung zu finden, falls vorhanden
- die Simulationsergebnisse zu finden, falls keine Fehler vorliegen
Der Code für das Menü „Berechnen“ lautet wie folgt:
void mnuCalculer_actionPerformed(ActionEvent e) {
// tAX CALCULATION
// verification URL service
URL urlImpots=null;
try{
urlImpots=new URL(txtURLServiceImpots.getText().trim());
String query=urlImpots.getQuery();
if(query!=null) throw new Exception();
}catch (Exception ex){
// error msg
JOptionPane.showMessageDialog(this,"URL incorrecte. Recommencez","Erreur",JOptionPane.ERROR_MESSAGE);
// focus on wrong field
txtURLServiceImpots.requestFocus();
// back to interface
return;
}
// salary verification
int salaire=0;
try{
salaire=Integer.parseInt(txtSalaire.getText().trim());
if(salaire<0) throw new Exception();
}catch (Exception ex){
// error msg
JOptionPane.showMessageDialog(this,"Salaire incorrect. Recommencez","Erreur",JOptionPane.ERROR_MESSAGE);
// focus on wrong field
txtSalaire.requestFocus();
// back to interface
return;
}
// no. of children
Integer nbEnfants=(Integer)spinEnfants.getValue();
try{
// tax calculation
calculerImpots(urlImpots,rdOui.isSelected(),nbEnfants.intValue(),salaire);
}catch (Exception ex){
// error is displayed
JOptionPane.showMessageDialog(this,"L'erreur suivante s'est produite : " + ex.getMessage(),"Erreur",JOptionPane.ERROR_MESSAGE);
}
}//mnuCalculer
public void calculerImpots(URL urlImpots,boolean marié, int nbEnfants, int salaire)
throws Exception{
// tAX CALCULATION
// urlImpots : URL of the tax department
// married: true if married, false otherwise
// nbEnfants : number of children
// salary: annual salary
// remove from urlImpots the info needed to connect to the tax server
String path=urlImpots.getPath();
if(path.equals("")) path="/";
String query="?"+"optMarie="+(marié ? "oui":"non")+"&txtEnfants="+nbEnfants+"&txtSalaire="+salaire;
String host=urlImpots.getHost();
int port=urlImpots.getPort();
if(port==-1) port=urlImpots.getDefaultPort();
// local data
Socket client=null; // the customer
BufferedReader IN=null; // the customer's reading flow
PrintWriter OUT=null; // the customer's writing flow
String réponse=null; // server response
// the model searched in HTTP headers
Pattern modèleCookie=Pattern.compile("^Set-Cookie: JSESSIONID=(.*?);");
// the model for a correct answer
Pattern réponseOK=Pattern.compile("^.*? 200 OK");
// the result of the model comparison
Matcher résultat=null;
try{
// connect to the server
client=new Socket(host,port);
// create customer input/output flows TCP
IN=new BufferedReader(new InputStreamReader(client.getInputStream()));
OUT=new PrintWriter(client.getOutputStream(),true);
// request URL - send HTTP headers
OUT.println("GET " + path + query + " HTTP/1.1");
OUT.println("Host: " + host + ":" + port);
if(! JSESSIONID.equals("")){
OUT.println("Cookie: JSESSIONID="+JSESSIONID);
}
OUT.println("Connection: close");
OUT.println("");
// read the 1st line of the answer
réponse=IN.readLine();
// compare the HTTP line with the model of the correct answer
résultat=réponseOK.matcher(réponse);
if(! résultat.find()){
// we have a URL problem
throw new Exception("Le serveur a répondu : URL ["+ txtURLServiceImpots.getText().trim() + "] inconnue");
}//if(result)
// we read the response through to the end of the headers, looking for any cookies
while((réponse=IN.readLine())!=null){
// empty line?
if(réponse.equals("")) break;
// line HTTP not empty
// if you don't have the session token, look for it
if (JSESSIONID.equals("")){
// compare the HTTP line with the cookie template
résultat=modèleCookie.matcher(réponse);
if(résultat.find()){
// we found the token cookie
JSESSIONID=résultat.group(1);
}//if(result)
}//if(JSESSIONID)
}//while
// that's it for HTTP headers - move on to HTML code
// to retrieve simulations
ArrayList listeSimulations=getSimulations(IN,OUT,simulations);
simulations.clear();
for (int i=0;i<listeSimulations.size();i++){
simulations.addElement(listeSimulations.get(i));
}
// it's over
client.close();
}catch (Exception ex){
throw new Exception(ex.getMessage());
}
}//calculerImpots
private ArrayList getSimulations(BufferedReader IN, PrintWriter OUT, DefaultListModel simulations) throws Exception{
// the model of a line in the simulation table
Pattern ptnSimulation=Pattern.compile("<tr>\\s*<td>(.*?)</td>\\s*<td>(.*?)</td>\\s*<td>(.*?)</td>\\s*<td>(.*?)</td>\\s*</tr>");
// the template for a line in the error list
Pattern ptnErreur=Pattern.compile("(Application indisponible.*?)\\s*$");
// the result of the model comparison
Matcher résultat=null;
// simulations
ArrayList listeSimulations=new ArrayList();
// read all the lines to the end
String ligne=null;
boolean simulationRéussie=false;
while((ligne=IN.readLine())!=null){
// follow-up
// the line is compared with the error model if the simulation part has not yet been encountered
if(! simulationRéussie){
résultat=ptnErreur.matcher(ligne);
if(résultat.find()){
// error msg
JOptionPane.showMessageDialog(this,résultat.group(1),"Erreur",JOptionPane.ERROR_MESSAGE);
// it's over
return listeSimulations;
}//if
}//if
// the line is compared with the simulation model
résultat=ptnSimulation.matcher(ligne);
if(résultat.find()){
// we found a row in the
listeSimulations.add(résultat.group(1)+":"+résultat.group(2)+":"+résultat.group(3)+
":"+résultat.group(4));
// the simulation was a success
simulationRéussie=true;
}//if
}//while
// end
return listeSimulations;
}
Lassen Sie uns diesen Code kurz erläutern:
- Die Prozedur „mnuCalculer_actionPerformed“ prüft, ob die Schnittstellendaten gültig sind. Ist dies nicht der Fall, wird eine Fehlermeldung angezeigt und die Prozedur wird beendet. Sind die Daten gültig, wird die Prozedur „calculerImpots“ ausgeführt.
- Die Prozedur „calculerImpots“ beginnt mit der Erstellung der URL, die sie für die Anfrage benötigt
// on retire d'urlImpots les infos nécessaire à la connexion au serveur d'impôts
String path=urlImpots.getPath();
if(path.equals("")) path="/";
String query="?"+"optMarie="+(marié ? "oui":"non")+"&txtEnfants="+nbEnfants+"&txtSalaire="+salaire;
String host=urlImpots.getHost();
int port=urlImpots.getPort();
if(port==-1) port=urlImpots.getDefaultPort();
........................
- und stellt dann eine Verbindung zu dieser URL her, indem die entsprechenden HTTP-Header gesendet werden:
// connect to the server
client=new Socket(host,port);
// create customer input/output flows TCP
IN=new BufferedReader(new InputStreamReader(client.getInputStream()));
OUT=new PrintWriter(client.getOutputStream(),true);
// request URL - send HTTP headers
OUT.println("GET " + path + query + " HTTP/1.1");
OUT.println("Host: " + host + ":" + port);
if(! JSESSIONID.equals("")){
OUT.println("Cookie: JSESSIONID="+JSESSIONID);
}
OUT.println("Connection: close");
OUT.println("");
- Sobald die Anfrage gestellt wurde, wartet unser Web-Client auf die Antwort. Er empfängt zunächst die HTTP-Header aus der Antwort des Servers. Er analysiert diese Header, um das Session-Token zu finden. Tatsächlich muss er dieses Token an den Server zurücksenden, damit der Server den Überblick über die verschiedenen durchgeführten Simulationen behalten kann. Beachten Sie hierbei, dass es für den Client einfacher gewesen wäre, die verschiedenen vom Benutzer durchgeführten Simulationen selbst zu speichern. Dennoch halten wir an der Idee fest, das Token zurückzusenden, um ein neues Beispiel für die Sitzungsverwaltung zu liefern. Die erste Zeile der Antwort wird separat behandelt. Sie muss die Form HTTP/Version 200 OK haben, um anzuzeigen, dass die angeforderte URL tatsächlich existiert. Ist dies nicht der Fall, schließen wir daraus, dass der Benutzer eine falsche URL angefordert hat, und informieren ihn entsprechend.
// the model searched in HTTP headers
Pattern modèleCookie=Pattern.compile("^Set-Cookie: JSESSIONID=(.*?);");
// the model of a correct answer
Pattern réponseOK=Pattern.compile("^.*? 200 OK");
..........
// read the 1st line of the answer
réponse=IN.readLine();
// compare the HTTP line with the model of the correct answer
résultat=réponseOK.matcher(réponse);
if(! résultat.find()){
// we have a URL problem
throw new Exception("Le serveur a répondu : URL ["+ txtURLServiceImpots.getText().trim() + "] inconnue");
}//if(result)
// we read the response through to the end of the headers, looking for any cookies
while((réponse=IN.readLine())!=null){
// empty line?
if(réponse.equals("")) break;
// line HTTP not empty
// if you don't have the session token, look for it
if (JSESSIONID.equals("")){
// compare the HTTP line with the cookie template
résultat=modèleCookie.matcher(réponse);
if(résultat.find()){
// we found the token cookie
JSESSIONID=résultat.group(1);
}//if(result)
}//if(JSESSIONID)
}//while
- Sobald die HTTP-Header verarbeitet wurden, fahren wir mit dem HTML-Teil der Antwort fort
// that's it for HTTP headers - move on to HTML code
// to retrieve simulations
ArrayList listeSimulations=getSimulations(IN,OUT,simulations);
simulations.clear();
for (int i=0;i<listeSimulations.size();i++){
simulations.addElement(listeSimulations.get(i));
}
- Die Prozedur getSimulations gibt die Liste der Simulationen zurück, sofern vorhanden; diese Liste ist leer, wenn der Server eine Fehlermeldung zurückgibt. In diesem Fall wird die Fehlermeldung in einem Meldungsfeld angezeigt. Ist die Liste nicht leer, wird sie in der Dropdown-Liste der grafischen Benutzeroberfläche angezeigt.
- Die Prozedur „getSimulations“ vergleicht jede Zeile der HTML-Antwort mit dem regulären Ausdruck, der die Fehlermeldung (Anwendung nicht verfügbar...) darstellt, sowie mit dem regulären Ausdruck, der eine Simulation darstellt. Wird die Fehlermeldung gefunden, wird sie angezeigt und die Prozedur wird beendet. Wird das Ergebnis einer Simulation gefunden, wird es der Liste der Simulationen hinzugefügt. Am Ende der Prozedur wird diese Liste als Ergebnis zurückgegeben.
private ArrayList getSimulations(BufferedReader IN, PrintWriter OUT, DefaultListModel simulations) throws Exception{
// the model of a line in the simulation table
Pattern ptnSimulation=Pattern.compile("<tr>\\s*<td>(.*?)</td>\\s*<td>(.*?)</td>\\s*<td>(.*?)</td>\\s*<td>(.*?)</td>\\s*</tr>");
// the template for a line in the error list
Pattern ptnErreur=Pattern.compile("(Application indisponible.*?)\\s*$");
5.6. Version 5
Hier wandeln wir die vorherige eigenständige grafische Anwendung in ein Java-Applet um. Die grafische Oberfläche unterscheidet sich geringfügig. Bei der eigenständigen Anwendung gab der Benutzer die URL des Dienstes zur Steuerberechnungssimulation selbst ein, und die Anwendung stellte dann eine Verbindung zu dieser URL her. Hier ist die Client-Anwendung ein Browser, und der Benutzer fordert die URL des HTML-Dokuments an, das das Applet enthält. Es ist wichtig zu beachten, dass ein Java-Applet nur eine Netzwerkverbindung zu dem Server herstellen kann, von dem es heruntergeladen wurde. Die URL des Simulationsdienstes befindet sich daher auf demselben Server wie das HTML-Dokument, das das Applet enthält. In unserem Beispiel handelt es sich dabei um einen Applet-Initialisierungsparameter, der im Feld „txtUrlServiceImpots“ platziert wird und vom Benutzer nicht bearbeitet werden kann. Dies führt zu folgendem Client:

Das HTML-Dokument, das das Applet enthält, heißt simulations.htm und sieht wie folgt aus:
<html>
<head>
<title>Simulations de calculs d'impôts</title>
</head>
<body background="/impots/images/standard.jpg">
<center>
<h3>Simulations de calculs d'impôts</h3>
<hr>
<applet code="appletImpots.class" width="400" height="360">
<param name="urlServiceImpots" value="simulations">
</applet>
</center>
</body>
</html>
Das Applet verfügt über einen Parameter namens *urlServiceImpots, bei dem es sich um die URL des Dienstes zur Simulation der Steuerberechnung handelt. Diese URL ist relativ zur URL des HTML-Dokuments simulations.htm. Wenn ein Browser dieses Dokument also über die URL http://localhost:8080/impots/simulations.htm abruft, lautet die URL des Simulationsdienstes http://localhost:8080/impots/simulations. Wäre diese URL http://stahe:8080/impots/simulations.htm gewesen, hätte die URL des Simulationsdienstes http://stahe:8080/impots/simulations* gelautet.
Das Applet appletImpots.java enthält den gesamten Code der vorherigen eigenständigen grafischen Anwendung und hält dabei die Regeln für die Umwandlung einer grafischen Anwendung in ein Applet ein.
public class appletImpots extends JApplet {
// window components
JPanel contentPane;
JMenuBar jMenuBar1 = new JMenuBar();
JMenu jMenu1 = new JMenu();
JMenuItem mnuCalculer = new JMenuItem();
.............
//Building the frame
public void init() {
try {
jbInit();
}
catch(Exception e) {
e.printStackTrace();
}
// other initializations
moreInit();
}
// form initialization
private void moreInit(){
// retrieve the urlServiceImpots parameter
String urlServiceImpots=getParameter("urlServiceImpots");
if(urlServiceImpots==null){
// missing parameter
JOptionPane.showMessageDialog(this,"Le paramètre urlServiceImpots de l'applet n'a pas été défini","Erreur",JOptionPane.ERROR_MESSAGE);
// end
return;
}
// put the URL in its field
String codeBase=""+getCodeBase();
if(codeBase.endsWith("/"))
txtURLServiceImpots.setText(codeBase+urlServiceImpots);
else txtURLServiceImpots.setText(codeBase+"/"+urlServiceImpots);
// calculate menu disabled
mnuCalculer.setEnabled(false);
// spinner Children - between 0 and 20 children
spinEnfants=new JSpinner(new SpinnerNumberModel(0,0,20,1));
spinEnfants.setBounds(new Rectangle(130,140,50,27));
contentPane.add(spinEnfants);
}//moreInit
//Initialize component
private void jbInit() throws Exception {
contentPane = (JPanel) this.getContentPane();
contentPane.setLayout(null);
...............
}
Wenn ein Browser ein Applet lädt, führt er zunächst dessen init-Methode aus. In dieser Methode haben wir den Wert des Parameters urlServiceImpots des Applets abgerufen, die vollständige URL des Simulationsdienstes berechnet und diesen Wert in das Feld txtURLServiceImpots eingefügt, als hätte der Benutzer ihn selbst eingegeben. Sobald dies geschehen ist, gibt es keinen Unterschied mehr zwischen den beiden Anwendungen. Insbesondere ist der Code, der mit dem Menü „Calculate“ verbunden ist, identisch. Hier ist ein Beispiel für die Ausführung:

5.7. Fazit
Wir haben verschiedene Versionen unserer Client-Server-Anwendung zur Steuerberechnung vorgestellt:
- Version 1: Der Dienst wird durch eine Reihe von Servlets und JSP-Seiten bereitgestellt; der Client ist ein Browser. Er führt eine einzelne Simulation durch und speichert keine Historie früherer Simulationen.
- Version 2: Wir fügen einige browserbasierte Funktionen hinzu, indem wir JavaScript-Skripte in das vom Browser geladene HTML-Dokument einbetten. Diese validieren die Formularparameter.
- Version 3: Wir ermöglichen es dem Dienst, sich die verschiedenen von einem Client durchgeführten Simulationen zu merken, indem wir eine Sitzung verwalten. Die HTML-Oberfläche wird entsprechend angepasst, um diese Simulationen anzuzeigen.
- Version 4: Der Client ist nun eine eigenständige grafische Anwendung. Dies ermöglicht es uns, die Entwicklung programmierter Web-Clients erneut zu betrachten.
- Version 5: Der Client wird zu einem Java-Applet. Wir verfügen nun über eine vollständig in Java programmierte Client-Server-Anwendung, sowohl auf der Server- als auch auf der Client-Seite.
An dieser Stelle lassen sich einige Beobachtungen anstellen:
- Die Versionen 1 bis 3 unterstützen Browser, die keine anderen Funktionen als die Ausführung von JavaScript-Skripten bieten. Beachten Sie, dass ein Benutzer jederzeit die Möglichkeit hat, die Ausführung dieser Skripte zu deaktivieren. Die Anwendung funktioniert dann in Version 1 nur teilweise (die Option „Löschen“ funktioniert nicht) und in den Versionen 2 und 3 überhaupt nicht (die Optionen „Löschen“ und „Berechnen“ funktionieren nicht). Es könnte sich lohnen, eine Version des Dienstes zu entwickeln, die keine JavaScript-Skripte verwendet.
- Version 4 setzt voraus, dass der Client-Computer über eine Java 2 Virtual Machine verfügt.
- Für Version 5 muss der Client-Computer über einen Browser mit einer Java 2 Virtual Machine verfügen.
Bei der Entwicklung eines Webdienstes müssen Sie berücksichtigen, welche Arten von Clients Sie ansprechen möchten. Wenn Sie eine möglichst große Anzahl von Clients erreichen möchten, sollten Sie eine Anwendung entwickeln, die ausschließlich HTML an Browser sendet (kein JavaScript oder Applets). Wenn Sie in einem Intranet arbeiten und die Konfiguration der Arbeitsplätze kontrollieren können, können Sie auf der Client-Seite höhere Anforderungen stellen, und die vorherige Version 5 könnte dann akzeptabel sein.
Die Versionen 4 und 5 sind Web-Clients, die die benötigten Informationen aus dem vom Server gesendeten HTML-Stream abrufen. Sehr oft haben wir keine Kontrolle über diesen Stream. Dies ist der Fall, wenn wir einen Client für einen bestehenden Webdienst im Netzwerk geschrieben haben, der von jemand anderem verwaltet wird. Nehmen wir ein Beispiel. Angenommen, unser Dienst zur Simulation von Steuerberechnungen wurde von Unternehmen X geschrieben. Derzeit sendet der Dienst die Simulationen in einer HTML-Tabelle, und unser Client nutzt diese Tatsache, um sie abzurufen. Er vergleicht daher jede Zeile der Serverantwort mit dem regulären Ausdruck:
// the model of a line in the simulation table
Pattern ptnSimulation=Pattern.compile("<tr>\\s*<td>(.*?)</td>\\s*<td>(.*?)</td>\\s*<td>(.*?)</td>\\s*<td>(.*?)</td>\\s*</tr>");
Nehmen wir nun an, der Anwendungsentwickler ändert das visuelle Erscheinungsbild der Antwort, indem er die Simulationen nicht in einer Tabelle, sondern in einer Liste im folgenden Format anordnet:
In diesem Fall muss unser Web-Client neu geschrieben werden. Dies ist die ständige Gefahr, der Web-Clients für Anwendungen ausgesetzt sind, die wir nicht selbst kontrollieren. XML kann eine Lösung für dieses Problem bieten:
- Anstatt HTML zu generieren, erzeugt der Simulationsdienst XML. In unserem Beispiel könnte dies
<simulations>
<entetes marie="marié" enfants="enfants" salaire="salaire" impot="impôt"/>
<simulation marie="oui" enfants="2" salaire="200000" impot="22504" />
<simulation marie="non" enfants="2" salaire="200000" impot="33388" />
</simulations>
- Dieser Antwort könnte ein Stylesheet zugeordnet werden, das den Browsern Anweisungen zur visuellen Darstellung dieser XML-Antwort gibt
- Programmierte Web-Clients würden dieses Stylesheet ignorieren und die Informationen direkt aus dem XML-Stream der Antwort abrufen
Wenn der Dienstentwickler die visuelle Darstellung der bereitgestellten Ergebnisse ändern möchte, ändert er das Stylesheet und nicht das XML. Dank des Stylesheets zeigen Browser das neue visuelle Format an, und programmgesteuerte Web-Clients müssen nicht angepasst werden. Neue Versionen unseres Simulationsdienstes könnten daher wie folgt geschrieben werden:
- Version 6: Der Dienst liefert eine XML-Antwort zusammen mit einem Stylesheet, das für Browser bestimmt ist
- Version 7: Der Client ist eine eigenständige grafische Anwendung, die die XML-Antwort des Servers verarbeitet
- Version 8: Der Client ist ein Java-Applet, das die XML-Antwort des Servers verarbeitet
