13. Przykład 10 – Konwersja i walidacja liczb rzeczywistych
Nowa aplikacja umożliwia wprowadzanie liczb rzeczywistych:
![]() |
- w [1], formularz wprowadzania danych
- na [2], a zwracana odpowiedź
Aplikacja działa podobnie jak w przypadku wprowadzania liczb całkowitych, dlatego omówimy tylko te elementy, które się różnią.
13.1. Projekt NetBeans
Projekt NetBeans wygląda następująco:
![]() |
- w [1], widoki aplikacji
- [Accueil.JSP]: strona główna
- [FormDouble.JSP]: formularz wprowadzania danych
- [ConfirmationDouble.JSP]: strona potwierdzenia
- w [2], plik komunikatów [messages.properties] oraz główny plik konfiguracyjny Struts
- w [3]:
- [FormDouble.java]: akcja wyświetlająca i przetwarzająca formularz
- [FormDouble-validation.xml]: reguły walidacji akcji [FormDouble]. Plik ten przekazuje te walidacje do modelu zgodnie z metodą, którą właśnie omówiliśmy.
- [FormDoubleModel]: model akcji [FormDouble]
- [FormDoubleModel-validation.xml]: reguły walidacji modelu
- [FormDoubleModel.properties]: plik komunikatów modelu
- [example.xml]: dodatkowy plik konfiguracyjny Struts
13.2. Konfiguracja projektu
Projekt jest konfigurowany głównie za pomocą następującego pliku [example.xml]:
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE struts PUBLIC
"-//Apache Software Foundation//DTD Struts Configuration 2.0//EN"
"http://struts.apache.org/dtds/struts-2.0.dtd">
<struts>
<package name="example" namespace="/example" extends="struts-default">
<action name="Accueil">
<result name="success">/example/Accueil.JSP</result>
</action>
<action name="FormDouble" class="example.FormDouble">
<result name="input">/example/FormDouble.JSP</result>
<result name="cancel" type="redirect">/example/Accueil.JSP</result>
<result name="success">/example/ConfirmationFormDouble.JSP</result>
</action>
</package>
</struts>
Jest on analogiczny do tego, który został omówiony w przypadku wprowadzania liczb całkowitych.
13.3. Pliki komunikatów
Plik [messages.properties] ma następującą postać:
Accueil.titre=Accueil
Accueil.message=Struts 2 - Conversions et validations
Accueil.FormDouble=Saisie de nombres r\u00e9els
Form.titre=Conversions et validations
FormDouble.message=Struts 2 - Conversion et validation de nombres r\u00e9els
FormDouble.conseil=Tapez les nombres r\u00e9els avec une virgule comme 10,7
Form.submitText=Valider
Form.cancelText=Annuler
Form.clearModel=Raz mod\u00e8le
Confirmation.titre=Confirmation
Confirmation.message=Confirmation des valeurs saisies
Confirmation.champ=champ
Confirmation.valeur=valeur
Confirmation.lien=Formulaire de test
xwork.default.invalid.fieldvalue=Valeur invalide pour le champ "{0}".
Plik [FormDoubleModel.properties] ma następującą postać:
double.format={0,number}
double1.prompt=1-Nombre r\u00E9el
double1.error=Tapez un nombre r\u00E9el
double2.prompt=2-Nombre r\u00E9el
double2.error=Tapez un nombre r\u00E9el
double3.prompt=3-Nombre r\u00E9el >=2.64
double3.error=Tapez un nombre r\u00E9el >=2.64
double4.prompt=4-Nombre r\u00E9el <8.32
double4.error=Tapez un nombre r\u00E9el <8.32
double5.prompt=5-Nombre r\u00E9el dans l''intervalle [2.64,8.32[
double5.error=Tapez un nombre r\u00E9el dans l''intervalle [2.64,8.32[
double6.prompt=6-Nombre r\u00E9el dans l''intervalle [2.64,8.32]
double6.error=Tapez un nombre r\u00E9el dans l''intervalle [2.64,8.32]
Ważną rolę odgrywa wiersz 1. Wrócimy do niego później.
13.4. Formularz wprowadzania danych
Widok [FormDouble.JSP] wygląda następująco:
<%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib prefix="s" uri="/struts-tags" %>
<html>
<head>
<title><s:text name="Form.titre"/></title>
<s:head/>
</head>
<body background="<s:url value="/ressources/standard.jpg"/>">
<h2><s:text name="FormDouble.message"/></h2>
<h4><s:text name="FormDouble.conseil"/></h4>
<s:form name="formulaire" action="FormDouble">
<s:textfield name="double1" key="double1.prompt"/>
<s:textfield name="double2" key="double2.prompt" value="%{#parameters['double2']!=null ? #parameters['double2'] : double2==null ? '' :getText('double.format',{double2})}"/>
<s:textfield name="double3" key="double3.prompt" value="%{#parameters['double3']!=null ? #parameters['double3'] : double3==null ? '' :getText('double.format',{double3})}"/>
<s:textfield name="double4" key="double4.prompt" value="%{#parameters['double4']!=null ? #parameters['double4'] : double4==null ? '' :getText('double.format',{double4})}"/>
<s:textfield name="double5" key="double5.prompt" value="%{#parameters['double5']!=null ? #parameters['double5'] : double5==null ? '' :getText('double.format',{double5})}"/>
<s:textfield name="double6" key="double6.prompt"/>
<s:submit key="Form.submitText" method="execute"/>
</s:form>
<br/>
<s:url id="URL" action="FormDouble" method="cancel"/>
<s:a href="%{URL}"><s:text name="Form.cancelText"/></s:a>
<br/>
<s:url id="URL" action="FormDouble" method="clearModel"/>
<s:a href="%{URL}"><s:text name="Form.clearModel"/></s:a>
</body>
</html>
Wiersze od 13 do 18 to sześć pól do wprowadzania liczb rzeczywistych. Pola od double2 do double5 mają złożony atrybut value. Zazwyczaj sześć pól wprowadzania danych powinno wyglądać następująco:
<s:textfield name="double1" key="double1.prompt"/>
<s:textfield name="double2" key="double2.prompt"/>
<s:textfield name="double3" key="double3.prompt"/>
<s:textfield name="double4" key="double4.prompt"/>
<s:textfield name="double5" key="double5.prompt"/>
<s:textfield name="double6" key="double6.prompt"/>
Aby rozwiązać pewne problemy napotkane podczas testów, konieczne było zwiększenie złożoności systemu. Na razie czytelnik może zignorować tę złożoność. Wyjaśnimy ją w późniejszym terminie.
13.5. Ekran potwierdzenia
Ekran potwierdzenia [ConfirmationFormDouble.JSP] wygląda następująco:
![]() |
Jego kod wygląda następująco:
<%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib prefix="s" uri="/struts-tags" %>
<html>
<head>
<title><s:text name="Confirmation.titre"/></title>
<s:head/>
</head>
<body background="<s:url value="/ressources/standard.jpg"/>">
<h2><s:text name="Confirmation.message"/></h2>
<table border="1">
<tr>
<th><s:text name="Confirmation.champ"/></th>
<th><s:text name="Confirmation.valeur"/></th>
</tr>
<tr>
<td><s:text name="double1.prompt"/></td>
<td><s:text name="double1"/></td>
</tr>
<tr>
<td><s:text name="double2.prompt"/></td>
<td>
<s:text name="double.format">
<s:param value="double2"/>
</s:text>
</td>
</tr>
<tr>
<td><s:text name="double3.prompt"/></td>
<td>
<s:text name="double.format">
<s:param value="double3"/>
</s:text>
</td>
</tr>
<tr>
<td><s:text name="double4.prompt"/></td>
<td>
<s:text name="double.format">
<s:param value="double4"/>
</s:text>
</td>
</tr>
<tr>
<td><s:text name="double5.prompt"/></td>
<td>
<s:text name="double.format">
<s:param value="double5"/>
</s:text>
</td>
</tr>
<tr>
<td><s:text name="double6.prompt"/></td>
<td><s:text name="double6"/></td>
</tr>
</table>
<br/>
<s:url id="URL" action="FormDouble!input"/>
<s:a href="%{URL}"><s:text name="Confirmation.lien"/></s:a>
</body>
</html>
Widok [ConfirmationFormDouble.JSP] ogranicza się do wyświetlania szablonu [FormDoubleModel]. Wiersze 47–49 pokazują wyświetlanie liczby rzeczywistej. Chcemy, aby to wyświetlanie uwzględniało ustawienia localisation aplikacji. Zgodnie z tym modelem liczba rzeczywista nie będzie wyświetlana w ten sam sposób we Francji (10,7) i w Wielkiej Brytanii (10.7). W tym celu wykorzystujemy znacznik <s:text>, którego używaliśmy dotychczas do internacjonalizacji aplikacji. Znacznik ten służy zatem również do lokalizacji.
W tym przypadku używany jest klucz komunikatu double.format. Klucz ten znajduje się w pliku [FormDoubleModel.properties]:
double.format={0,number}
Wartość powiązana z tym kluczem nie jest w tym przypadku komunikatem, lecz formatem wyświetlania:
- 0 to parametr reprezentujący wartość, która ma zostać wyświetlona. W tym przypadku będzie to pole double5 w szablonie.
- number określa format liczbowy. Będzie on dostosowany do danego kraju. Bez tego formatu liczba 10,7 będzie zawsze wyświetlana jako 10,7, niezależnie od kraju.
Tag
<s:text name="double.format">
<s:param value="double5"/>
</s:text>
sprawia, że wyświetlany jest komunikat {0, number}, w którym parametr double5 (wiersz 2) zastąpi parametr 0 formatu. W ten sposób szablon double5 zostanie wyświetlony w zlokalizowanym formacie liczbowym.
13.6. Szablon [FormDoubleModel]
Pola od double1 do double6 z formularza [FormDouble.JSP] są wstawiane do następującego szablonu [FormDoubleModel]:
package example;
public class FormDoubleModel {
// konstruktor bez parametrów
public FormDoubleModel() {
}
// pola
private String double1;
private Double double2;
private Double double3;
private Double double4;
private Double double5;
private String double6;
// szablon
public void clearModel() {
double1 = null;
double2 = null;
double3 = null;
double4 = null;
double5 = null;
double6 = null;
}
// metody pobierające i ustawiające
...
}
- Pola double1 i double6 są typu String
- pozostałe pola są typu Double
13.7. Walidacja modelu
Walidacja modelu jest kontrolowana przez dwa pliki: [FormDouble-validation.xml] i [FormDoubleModel-validation.xml].
Plik [FormDouble-validation.xml] przekazuje zadania walidacji do pliku [FormDoubleModel-validation.xml]:
<!--
<!DOCTYPE validators PUBLIC "-//OpenSymphony Group//XWork Validator 1.0.2//
EN" "http://www.opensymphony.com/xwork/xwork-validator-1.0.2.dtd">
-->
<!DOCTYPE validators PUBLIC "-//OpenSymphony Group//XWork Validator 1.0.2//
EN" "http://localhost:8084/przykład-10/example/xwork-validator-1.0.2.dtd">
<validators>
<field name="model" >
<field-validator type="visitor">
<param name="appendPrefix">false</param>
<message/>
</field-validator>
</field>
</validators>
Ten plik już wcześniej spotkaliśmy.
Plik [FormDoubleModel-validation.xml] zawiera następujące reguły walidacji:
<!--
<!DOCTYPE validators PUBLIC "-//OpenSymphony Group//XWork Validator 1.0.2//
EN" "http://www.opensymphony.com/xwork/xwork-validator-1.0.2.dtd">
-->
<!DOCTYPE validators PUBLIC "-//OpenSymphony Group//XWork Validator 1.0.2//
EN" "http://localhost:8084/przykład-10/example/xwork-validator-1.0.2.dtd">
<validators>
<field name="double1" >
<field-validator type="requiredstring" short-circuit="true">
<message key="double1.error"/>
</field-validator>
<field-validator type="regex" short-circuit="true">
<param name="expression">^[+|-]*\s*\d+(,\d+)*$</param>
<param name="trim">true</param>
<message key="double1.error"/>
</field-validator>
</field>
<field name="double2" >
<field-validator type="required" short-circuit="true">
<message key="double2.error"/>
</field-validator>
<field-validator type="conversion" short-circuit="true">
<message key="double2.error"/>
</field-validator>
</field>
<field name="double3" >
<field-validator type="required" short-circuit="true">
<message key="double3.error"/>
</field-validator>
<field-validator type="conversion" short-circuit="true">
<message key="double3.error"/>
</field-validator>
<field-validator type="double" short-circuit="true">
<param name="minInclusive">2.64</param>
<message key="double3.error"/>
</field-validator>
</field>
<field name="double4" >
<field-validator type="required" short-circuit="true">
<message key="double4.error"/>
</field-validator>
<field-validator type="conversion" short-circuit="true">
<message key="double4.error"/>
</field-validator>
<field-validator type="double" short-circuit="true">
<param name="maxExclusive">8.32</param>
<message key="double4.error"/>
</field-validator>
</field>
<field name="double5" >
<field-validator type="required" short-circuit="true">
<message key="double5.error"/>
</field-validator>
<field-validator type="conversion" short-circuit="true">
<message key="double5.error"/>
</field-validator>
<field-validator type="double" short-circuit="true">
<param name="minInclusive">2.64</param>
<param name="maxExclusive">8.32</param>
<message key="double5.error"/>
</field-validator>
</field>
<field name="double6" >
<field-validator type="requiredstring" short-circuit="true">
<message key="double6.error"/>
</field-validator>
<field-validator type="regex" short-circuit="true">
<param name="expression">^[+|-]*\s*\d+(,\d+)*$</param>
<param name="trim">true</param>
<message key="double6.error"/>
</field-validator>
</field>
</validators>
- reguła w wierszach 12–21 sprawdza, czy pole wprowadzania danych double1 jest zgodne ze wzorcem wyrażenia regularnego reprezentującego liczbę rzeczywistą.
- Reguła z wierszy 23–30 sprawdza, czy pole wprowadzania danych double2 można przekonwertować na liczbę rzeczywistą typu double.
- Reguła w wierszach 32–43 sprawdza, czy pole wprowadzania danych double3 można przekonwertować na liczbę rzeczywistą typu double >=2,64. Należy zauważyć, że należy stosować anglosaską notację liczb rzeczywistych.
- Reguła w wierszach 45–56 sprawdza, czy pole wprowadzania danych double4 można przekonwertować na liczbę rzeczywistą typu double < 8,32.
- Reguła z wierszy 58–70 sprawdza, czy pole wprowadzania danych double5 można przekonwertować na liczbę rzeczywistą typu double z przedziału [2,64; 8,32[.
- Reguła w wierszach 72–80 sprawdza, czy pole double6 jest zgodne ze wzorcem wyrażenia regularnego reprezentującego liczbę rzeczywistą.
Po przetworzeniu pliku [FormDoubleModel-validation.xml] przez moduł przechwytujący walidacji, moduł ten uruchamia metodę validate akcji [FormDouble], o ile taka istnieje. Przedstawimy ją wraz z całą akcją.
13.8. Akcja [FormDouble]
Akcja [FormDouble] wygląda następująco:
package example;
import com.opensymphony.xwork2.ActionSupport;
import com.opensymphony.xwork2.ModelDriven;
import java.util.Map;
import org.apache.struts2.interceptor.SessionAware;
import org.apache.struts2.interceptor.validation.SkipValidation;
public class FormDouble extends ActionSupport implements ModelDriven, SessionAware {
// konstruktor bez parametrów
public FormDouble() {
}
// szablon akcji
public Object getModel() {
if (session.get("model") == null) {
session.put("model", new FormDoubleModel());
}
return session.get("model");
}
@SkipValidation
public String clearModel() {
// dane modelu
((FormDoubleModel) getModel()).clearModel();
// wynik
return INPUT;
}
public String cancel() {
// oczyszczamy model
((FormDoubleModel) getModel()).clearModel();
// wynik
return "cancel";
}
// SessionAware
Map<String, Object> session;
public void setSession(Map<String, Object> session) {
this.session = session;
}
// weryfikacja
@Override
public void validate() {
// czy podwójne wprowadzenie danych 6 jest prawidłowe?
if (getFieldErrors().get("double6") == null) {
// w ciągu „double6” zastępujemy przecinek kropką
String strDouble6 = (((FormDoubleModel) getModel()).getDouble6()).replace(',', '.');
// Ciąg znaków --> double
double double6 = Double.parseDouble(strDouble6);
// weryfikacja
if (double6 < 2.64 || double6 > 8.32) {
addFieldError("double6", getText("double6.error"));
}
}
}
}
Akcja [FormDouble] jest zbudowana na tym samym wzorze co akcja [FormInt]. Omówimy jedynie metodę validate. Przypominamy, że metoda validate jest wykonywana po przetworzeniu pliku walidacyjnego [FormDoubleModel-validation.xml] i przed wykonaniem metody execute.
- wiersz 48: jeśli w polu double6 występują już błędy, nie podejmuje się żadnych dalszych działań.
- wiersz 50: otrzymano ciąg znaków w postaci 45,67. Został on zapisany w polu double6 modelu. Zastępuje się przecinek kropką, uzyskując wartość 45.67.
- wiersz 52: ciąg znaków 45.67 jest przekształcany na liczbę rzeczywistą typu double. Powinno to zadziałać, ponieważ ciąg znaków double6 jest zgodny z formatem liczby rzeczywistej.
- wiersz 54: sprawdzamy, czy uzyskana liczba zmiennoprzecinkowa typu double mieści się w przedziale [2.64, 8.32].
- wiersz 55: jeśli tak nie jest, komunikat o błędzie o kluczu double6.error jest dołączany do pola double6. Komunikat ten będzie znajdował się w pliku [FormDoubleModel.properties]. Zostanie on wyświetlony, gdy błędny formularz zostanie ponownie wyświetlony.
13.9. Ostatnie szczegóły
A teraz wróćmy do złożoności pól wprowadzania danych w formularzu [FormDouble.JSP]. Przeanalizujemy pole double2. Rozważania te dotyczą również pól od double3 do double5, które mają szablon typu Double. W przypadku pól double1 i double6, które mają szablon typu String, nie ma żadnego problemu.
Pole wprowadzania danych double2 wygląda następująco:
<s:textfield name="double2" key="double2.prompt" value="%{#parameters['double2']!=null ? #parameters['double2'] : double2==null ? '' :getText('double.format',{double2})}"/>
Zacznijmy od najprostszego znacznika:
i zobaczmy, co się dzieje:
![]() |
- w [1] sprawdzamy poprawność liczby double2. Na zrzucie ekranu nie widać tego zbyt dobrze, ale wpisaliśmy liczbę z przecinkiem.
- W [2] – ekran potwierdzenia. Wprowadzona wartość double2 przeszła testy walidacji. Liczba jest wyświetlana z przecinkiem.
![]() |
- w [3] wracamy do formularza
- w [4], formularz. Zrzut ekranu nie oddaje tego dobrze, ale liczba double2, która początkowo wynosiła 4,32, zmieniła się na 4.32 z kropką dziesiętną.
![]() |
- w [5] ponownie zatwierdzamy formularz bez wprowadzania zmian
- w [6] pojawia się komunikat o błędzie w polu double2.
Problem polega na tym, że:
- początkowo w [1] ciąg znaków „4,32” został pomyślnie przekształcony w liczbę rzeczywistą 4,32. Oznacza to, że operacja String → Double zakończyła się powodzeniem i że w tym kontekście Struts uwzględnia ustawienia regionalne, w tym przypadku Francji.
- Nowy formularz [4] wyświetla w polu double2 wartość liczby rzeczywistej 4.32. Ponieważ nie zlokalizowano sposobu wyświetlania, domyślnie wyświetla się ono w formacie anglosaskim, c.a.d, z kropką jako separatorem dziesiętnym. Zatem w kierunku Double → String Struts nie uwzględnia już ustawień regionalnych, w przeciwnym razie wyświetliłby 4,32 z przecinkiem.
Jest to co najmniej niespójne. Niezależnie od tego, zlokalizujemy wyświetlanie liczby 4,32. Tag wprowadzania danych przyjmuje następującą postać:
<s:textfield name="double2" key="double2.prompt" value="%{getText('double.format',{double2})}"/>
Atrybut value określa wartość, która ma być wyświetlana w polu double2. Jest to wartość wyrażenia OGNL. Przypomnijmy definicję klucza double.format w pliku [FormDoubleModel.properties]:
double.format={0,number}
Metoda getText('klucz') pozwala uzyskać komunikat powiązany z danym kluczem. Komunikat ten jest wyszukiwany w pliku odpowiadającym bieżącej lokalizacji. Gdyby więc lokalizacją była „es” (Hiszpania), klucz double.format zostałby wyszukany w pliku [FormDoubleModel_es.properties].
Metoda getText('klucz', {param0, param1, ...}) pozwala na odzyskanie komunikatu z określonymi parametrami. Komunikat
jest komunikatem zdefiniowanym przez parametr 0. Jest to parametr pozycyjny. Metoda getText('double.format', {double2}) przypisze liczbę double2 do parametru 0. Ostatecznie żądamy wartości double2 w zlokalizowanym formacie liczbowym. We Francji liczba 4,56 zostanie zlokalizowana jako ciąg znaków „4,56”.
Po tej transformacji ponownie przeprowadzamy testy.
![]() |
Już przy pierwszym wyświetleniu formularza pojawia się błąd w polu [1]. Wróćmy do tagu:
<s:textfield name="double2" key="double2.prompt" value="%{getText('double.format',{double2})}"/>
Przy pierwszym wyświetleniu wartość szablonu double2 wynosi null, co jest wartością niecyfrową. Zmieniamy znacznik w następujący sposób:
<s:textfield name="double2" key="double2.prompt" value="%{double2==null ? '' : getText('double.format',{double2})}"/>
Tym razem sprawdzamy, czy wartość wynosi double2==null. Jeśli tak, wyświetlamy pusty ciąg znaków.
Po wprowadzeniu tej zmiany wznawiamy testy:
![]() |
![]() |
Ekrany od [1] do [4] pokazują, że problem, który staraliśmy się rozwiązać, został rozwiązany:
- w ekranie [1] wpisujemy 4,67
- w [2] liczba ta została zaakceptowana
- w [3] jest ona ponownie wyświetlana jako 4,67 z przecinkiem, co potwierdza [4].
Niestety, to nie koniec problemów. Przyjrzyjmy się poniższemu fragmentowi:
![]() |
- w [5] dodajemy jeden znak, aby unieważnić double2
- w [6] testy walidacyjne zadziałały i zgłoszono błąd. Jednak ciąg wyświetlany w [6] nie jest błędnym ciągiem, lecz aktualną wartością szablonu double2. Utracono wprowadzoną wartość.
Podczas przechodzenia z [5] do [6] zapytanie nie zostaje zrealizowane do końca. Zostaje ono zatrzymane przez interceptor walidacji. W [6] wyświetlono wartość modelu double2, który nie otrzymał nowej wartości z powodu tego zatrzymania. Wyświetlana jest zatem jego poprzednia wartość, podczas gdy należało wyświetlić wprowadzony ciąg znaków. Parametry zapytania są dostępne do wglądu za pomocą notacji #parameters['param']. Zmieniamy pole wprowadzania danych double2 w następujący sposób:
<s:textfield name="double2" key="double2.prompt" value="%{#parameters['double2']!=null ? #parameters['double2'] : double2==null ? '' : getText('double.format',{double2})}"/>
Wyświetlana wartość pola double2 jest obliczana w następujący sposób: jeśli istnieje parametr „double2”, to jest on wyświetlany, w przeciwnym razie wyświetlany jest szablon double2. Zachęcamy czytelnika do sprawdzenia, czy ta nowa wersja znacznika rozwiązuje napotkane problemy.
13.10. Conclusion
Zaskakujące jest, że wprowadzanie liczb rzeczywistych z kontrolą poprawności jest tak skomplikowane... Być może coś przeoczyłem w dokumentacji?









