13. Приклад 10 — Перетворення та перевірка дійсних чисел
Нова програма підтримує введення дійсних чисел:
![]() |
- у [1], форму введення
- у [2], відповідь, що повертається
Додаток працює аналогічно до функції введення цілих чисел, тому ми прокоментуємо лише ті моменти, які відрізняються.
13.1. Проєкт NetBeans
Проект NetBeans виглядає так:
![]() |
- у [1] — екрани додатка
- [Accueil.JSP]: головна сторінка
- [FormDouble.JSP]: форма введення даних
- [ConfirmationDouble.JSP]: сторінка підтвердження
- у [2] — файл повідомлень [messages.properties] та головний файл конфігурації Struts
- у [3]:
- [FormDouble.java]: дія, яка відображає та обробляє форму
- [FormDouble-validation.xml]: правила перевірки дії [FormDouble]. Цей файл делегує ці перевірки моделі відповідно до методу, який щойно розглядався.
- [FormDoubleModel]: модель дії [FormDouble]
- [FormDoubleModel-validation.xml]: правила перевірки моделі
- [FormDoubleModel.properties]: файл повідомлень моделі
- [example.xml]: додатковий файл конфігурації Struts
13.2. Конфігурація проєкту
Проєкт налаштовується переважно за допомогою такого файлу [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>
Він аналогічний тому, що розглядався для введення цілих чисел.
13.3. Файли повідомлень
Файл [messages.properties] має такий вигляд:
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}".
Файл [FormDoubleModel.properties] має такий вигляд:
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]
Рядок 1 відіграє важливу роль. Ми повернемося до нього пізніше.
13.4. Форма введення даних
Вигляд [FormDouble.JSP] такий:
<%@ 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>
Рядки з 13 по 18 — це шість полів для введення дійсних чисел. Поля від double2 до double5 мають складний атрибут value. Зазвичай шість полів для введення даних мають виглядати так:
<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"/>
Щоб вирішити деякі проблеми, що виникли під час тестування, довелося ускладнити структуру. Наразі читач може не зважати на цю складність. Ми пояснимо це пізніше.
13.5. Вікно підтвердження
Вікно підтвердження [ConfirmationFormDouble.JSP] виглядає так:
![]() |
Його код такий:
<%@ 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>
Вигляд [ConfirmationFormDouble.JSP] просто відображає шаблон [FormDoubleModel]. У рядках 47–49 показано відображення дійсного числа. Ми хочемо, щоб це відображення враховувало localisation додатка. Згідно з нею, дійсне число не відображатиметься однаково у Франції (10,7) та у Великій Британії (10.7). Для цього використовується тег <s:text>, який до цього застосовувався для інтернаціоналізації додатка. Отже, цей тег також використовується для локалізації.
Тут використовується ключ повідомлення double.format. Цей ключ міститься у файлі [FormDoubleModel.properties]:
double.format={0,number}
Значення, пов’язане з цим ключем, тут є не повідомленням, а форматом відображення:
- 0 — це параметр, що позначає значення, яке потрібно відобразити. У даному випадку це буде поле double5 шаблону.
- number визначає числовий формат. Він буде адаптований до кожної країни. Без цього формату число 10,7 завжди відображатиметься як 10,7 незалежно від країни.
Тег
<s:text name="double.format">
<s:param value="double5"/>
</s:text>
забезпечує відображення повідомлення {0, number}, де параметр double5 (рядок 2) замінить параметр 0 у форматі. Таким чином, шаблон double5 відображатиметься у локалізованому числовому форматі.
13.6. Шаблон [FormDoubleModel]
Поля від double1 до double6 з форми [FormDouble.JSP] вставляються в наступний шаблон [FormDoubleModel]:
package example;
public class FormDoubleModel {
// конструктор без параметрів
public FormDoubleModel() {
}
// поля
private String double1;
private Double double2;
private Double double3;
private Double double4;
private Double double5;
private String double6;
// порожній шаблон
public void clearModel() {
double1 = null;
double2 = null;
double3 = null;
double4 = null;
double5 = null;
double6 = null;
}
// гетери та сеттери
...
}
- поля double1 та double6 мають тип String
- інші поля мають тип Double
13.7. Перевірка моделі
Перевірка моделі контролюється двома файлами: [FormDouble-validation.xml] та [FormDoubleModel-validation.xml].
Файл [FormDouble-validation.xml] делегує перевірки файлу [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/приклад-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>
Ми вже зустрічали цей файл.
Файл [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/приклад-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>
- правило в рядках 12–21 перевіряє, чи поле введення double1 відповідає шаблону регулярного виразу, що представляє дійсне число.
- правило у рядках 23–30 перевіряє, чи поле введення double2 можна перетворити на число типу «double».
- правило в рядках 32–43 перевіряє, чи поле введення double3 можна перетворити на число типу «double» >=2,64. Слід зауважити, що для чисел потрібно використовувати англосаксонську нотацію.
- Правило в рядках 45–56 перевіряє, чи поле введення double4 можна перетворити на число типу «double» < 8,32.
- правило в рядках 58–70 перевіряє, чи поле введення double5 можна перетворити на число типу double в інтервалі [2,64; 8,32[.
- правило в рядках 72–80 перевіряє, чи поле double6 відповідає шаблону регулярного виразу, що представляє дійсне число.
Після обробки файлу [FormDoubleModel-validation.xml] перехоплювачем валідації він запускає метод validate дії [FormDouble], якщо такий існує. Ми розглянемо його разом з усією дією.
13.8. Дія [FormDouble]
Дія [FormDouble] виглядає наступним чином:
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 {
// конструктор без параметрів
public FormDouble() {
}
// модель дії
public Object getModel() {
if (session.get("model") == null) {
session.put("model", new FormDoubleModel());
}
return session.get("model");
}
@SkipValidation
public String clearModel() {
// очищення моделі
((FormDoubleModel) getModel()).clearModel();
// результат
return INPUT;
}
public String cancel() {
// очищення моделі
((FormDoubleModel) getModel()).clearModel();
// результат
return "cancel";
}
// SessionAware
Map<String, Object> session;
public void setSession(Map<String, Object> session) {
this.session = session;
}
// перевірка
@Override
public void validate() {
// чи є подвійний запис6 дійсним?
if (getFieldErrors().get("double6") == null) {
// у рядку double6 замінюємо кому на крапку
String strDouble6 = (((FormDoubleModel) getModel()).getDouble6()).replace(',', '.');
// рядок --> число типу double
double double6 = Double.parseDouble(strDouble6);
// перевірка
if (double6 < 2.64 || double6 > 8.32) {
addFieldError("double6", getText("double6.error"));
}
}
}
}
Акція [FormDouble] побудована за тим самим зразком, що й акція [FormInt]. Ми прокоментуємо лише метод validate. Нагадаємо, що метод validate виконується після обробки файлу перевірки [FormDoubleModel-validation.xml] і перед виконанням методу execute.
- рядок 48: якщо в полі double6 вже є помилки, ніяких подальших дій не виконується.
- рядок 50: отримано рядок у вигляді 45,67. Він було збережено у полі double6 моделі. Кома замінюється на крапку, щоб отримати 45.67.
- рядок 52: рядок 45.67 перетворюється на число типу double. Це має спрацювати, оскільки рядок double6 відповідає формату дійсного числа.
- рядок 54: перевіряємо, чи отримане число типу «double» знаходиться в інтервалі [2.64, 8.32].
- рядок 55: якщо це не так, повідомлення про помилку з кодом double6.error додається до поля double6. Це повідомлення буде знайдено у файлі [FormDoubleModel.properties]. Воно відобразиться, коли форма з помилкою буде показана знову.
13.9. Останні деталі
А тепер повернемося до складності полів для введення даних у формі [FormDouble.JSP]. Розглянемо поле double2. Цей аналіз поширюється на поля від double3 до double5, які мають шаблон типу Double. Для полів double1 та double6, які мають шаблон типу String, проблем немає.
Поле введення double2 виглядає наступним чином:
<s:textfield name="double2" key="double2.prompt" value="%{#parameters['double2']!=null ? #parameters['double2'] : double2==null ? '' :getText('double.format',{double2})}"/>
Почнемо з найпростішого тегу:
і подивимося, що відбувається:
![]() |
- у [1] перевіряється правильність числа double2. На скріншоті це не дуже добре видно, але число вводилося з комою.
- [2] — вікно підтвердження. Введене значення double2 пройшло перевірку. Число відображається з комою.
![]() |
- у [3] ми повертаємося до форми
- у [4] — сама форма. На знімку екрана погано видно, що число double2, яке спочатку було 4,32, стало 4.32 з крапкою.
![]() |
- у [5] ми знову підтверджуємо форму, не змінюючи нічого
- в [6] з’являється повідомлення про помилку в полі double2.
Проблема полягає в наступному:
- спочатку в [1] рядок «4,32» було успішно перетворено на дійсне число 4.32. Це означає, що операція String --> Double пройшла успішно і, отже, у цьому сенсі Struts враховує локаль, у даному випадку — Францію.
- Нова форма [4] відображає у полі double2 значення дійсного числа 4.32. Оскільки ми не локалізували відображення, воно за замовчуванням відображається в англосаксонському форматі — c.a.d — з крапкою в якості роздільника десяткових знаків. Отже, при перетворенні з типу Double у String Struts більше не враховує локаль, інакше він би відобразив 4,32 з комою.
Це, м’яко кажучи, нелогічно. Але нехай це не заважає — ми локалізуємо відображення числа 4,32. Тег введення даних виглядатиме так:
<s:textfield name="double2" key="double2.prompt" value="%{getText('double.format',{double2})}"/>
Атрибут value визначає значення, яке має відображатися у полі double2. Це значення є результатом виразу OGNL. Нагадаємо визначення ключа double.format у файлі [FormDoubleModel.properties]:
double.format={0,number}
Метод getText('ключ') дозволяє отримати повідомлення, пов'язане з ключем. Воно шукається у файлі, що відповідає поточній локалі. Отже, якщо б локаль була es (Іспанія), ключ double.format шукався б у файлі [FormDoubleModel_es.properties].
Метод getText('ключ', {param0, param1, ...}) дозволяє знайти повідомлення з заданими параметрами. Повідомлення
— це повідомлення, параметризоване параметром 0. Цей параметр є позиційним. Метод getText('double.format', {double2}) присвоїть число double2 параметру 0. У підсумку запитується значення double2 у локалізованому числовому форматі. У Франції число 4,56 буде локалізовано у вигляді рядка «4,56».
Після цього перетворення тести проводяться знову.
![]() |
Відразу після початкового відображення форми виявляється аномалія в [1]. Повернемося до тегу:
<s:textfield name="double2" key="double2.prompt" value="%{getText('double.format',{double2})}"/>
Під час початкового відображення значення шаблону double2 дорівнює null, що є нечисловим значенням. Змінюємо тег наступним чином:
<s:textfield name="double2" key="double2.prompt" value="%{double2==null ? '' : getText('double.format',{double2})}"/>
Цього разу перевіряємо, чи існує double2==null. Якщо так, виводимо порожній рядок.
Після внесення цієї зміни продовжуємо тестування:
![]() |
![]() |
Екрани від [1] до [4] показують, що проблема, яку ми намагалися вирішити, вирішена:
- у [1] вводимо 4,67
- у [2] це число було прийнято
- у [3] воно знову відображається як 4,67 з комою, що підтверджується [4].
На жаль, на цьому проблеми не закінчуються. Давайте розглянемо наступний фрагмент:
![]() |
- у [5] додаємо один символ, щоб зробити double2
- у [6] тести перевірки спрацювали, і помилка була виявлена. Однак рядок, що відображається як [6], — це не помилковий рядок, а поточне значення шаблону double2. Введене значення було втрачено.
Під час переходу від [5] до [6] запит не доходить до кінця. Він зупиняється перехоплювачем валідації. У [6] відображено значення шаблону double2, яке не отримало нового значення через це зупинення. Отже, відображається його попереднє значення, тоді як мало б відображатися введений рядок. Параметри запиту можна переглянути за допомогою нотації #parameters['param']. Поле введення double2 змінюємо наступним чином:
<s:textfield name="double2" key="double2.prompt" value="%{#parameters['double2']!=null ? #parameters['double2'] : double2==null ? '' : getText('double.format',{double2})}"/>
Відображуване значення поля double2 обчислюється так: якщо параметр «double2» існує, то його відображають, інакше відображають шаблон double2. Пропонуємо читачеві перевірити, чи ця нова версія тегу вирішує виявлені проблеми.
13.10. Conclusion
Дивно, що введення дійсних чисел із перевіркою правильності виявляється таким складним... Можливо, я щось пропустив у документації?









