Зміст
Наведемо частину коду для полегшення розуміння наступного опису (нижченаведений код є частиною tests-flow.xml):
...
<subflow-state id="identifyUser" subflow="tests/user">
<output name="user" value="flowScope.testChecker.user"/>
<transition on="userReady" to="startTest" />
</subflow-state>
<subflow-state id="startTest" subflow="tests/test">
<output name="testList" value="flowScope.testChecker.testList"/>
<transition on="testsFinished" to="successResult" />
<transition on="testsFail" to="failResult" />
</subflow-state>
...
При отриманні події userReady відбувається передача управління стану startTest. Результат виконання цього flow буде записаний в змінну flowScope.testChecker.testList, про що говорить наступний код:
<output name="testList" value="flowScope.testChecker.testList"/>
При успішному закінченні test-flow управління передається стану successResult, при неуспішному - стану failResult:
<transition on="testsFinished" to="successResult" />
<transition on="testsFail" to="failResult" />
Оригінальний текст test-flow представлений нижче:
<?xml version="1.0" encoding="UTF-8"?>
<flow xmlns="http://www.springframework.org/schema/webflow"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/webflow
http://www.springframework.org/schema/webflow/spring-webflow-2.0.xsd">
<var name="testList" class="com.seostella.swftests.domain.TestList"/>
<on-start>
<evaluate expression="new com.seostella.swftests.service.QuestionService()"
result="flowScope.questionService" />
</on-start>
<view-state id="singleTest" model="flowScope.test">
<on-entry>
<set name="flowScope.test"
value="flowScope.questionService.getNextTest()" />
</on-entry>
<transition on="checkTest" to="singleTest">
<evaluate expression="flowScope.questionService.addAnswer(testList,flowScope.test)" />
</transition>
<transition to="testsFinished" on-exception=
"com.seostella.swftests.flow.exception.test.LastTestException" />
<transition to="wrongAnswer" on-exception=
"com.seostella.swftests.flow.exception.test.WrongAnswerException" />
<transition to="testsFail" on-exception=
"com.seostella.swftests.flow.exception.test.MaxWrongAnswersException" />
</view-state>
<view-state id="wrongAnswer">
<transition to="singleTest" />
</view-state>
<end-state id="testsFinished">
<output name="testList" />
</end-state>
<end-state id="testsFail">
<output name="testList" />
</end-state>
</flow>
Стан singleTest по суті є гібридом стану-відображення і стану-дії і міг б бути розділений на два стани:
<view-state id="singleTest" model="flowScope.test">
<on-entry>
<set name="flowScope.test"
value="flowScope.questionService.getNextTest()" />
</on-entry>
<transition on="checkTest" to="singleTest">
<evaluate expression="flowScope.questionService.addAnswer(testList,flowScope.test)" />
</transition>
<transition to="testsFinished" on-exception=
"com.seostella.swftests.flow.exception.test.LastTestException" />
<transition to="wrongAnswer" on-exception=
"com.seostella.swftests.flow.exception.test.WrongAnswerException" />
<transition to="testsFail" on-exception=
"com.seostella.swftests.flow.exception.test.MaxWrongAnswersException" />
</view-state>
Вираз flowScope.questionService може бути спрощено до questionService, так як questionService є змінною, що потрапляє в облать видимості даного flow.
Розберемося, що робить даний стан:
<on-entry>
<set name="flowScope.test"
value="flowScope.questionService.getNextTest()" />
</on-entry>
- на початку виконання створюється змінна test в межах flowScope. Значенням цієї змінної буде результат виконання flowScope.questionService.getNextTest (). Тобто, результат виконання методу getNextTest() класу QuestionService. Цей метод наведено нижче:
public Test getNextTest(){
return tests.get(testIndex++);
}
- тут все просто: зі списку test повертається елемент з індексом testIndex, а сам індекс testIndex збільшується на одиницю.
Так як singleTest є станом-відображенням, наведемо лістинг однойменного jspx-файлу (singleTest.jspx), відповідального за відображення:
<html xmlns:jsp="http://java.sun.com/JSP/Page"
xmlns:form="http://www.springframework.org/tags/form">
<jsp:output omit-xml-declaration="yes"/>
<jsp:directive.page contentType="text/html;charset=UTF-8" />
<head>
<title>Tests - Spring Web Flow 2.x Tutorial | seostella.com</title>
</head>
<body>
<h1>Пожалуйста ответьте на вопрос</h1>
<p><strong>${test.question}</strong></p>
<form:form commandName="test">
<input type="hidden" name="_flowExecutionKey"
value="${flowExecutionKey}"/>
<b>Ответы: </b><br/>
<form:radiobutton path="userAnswer"
label="${test.answer1}" value="1"/><br/>
<form:radiobutton path="userAnswer"
label="${test.answer2}" value="2"/><br/>
<form:radiobutton path="userAnswer"
label="${test.answer3}" value="3"/><br/>
<br/>
<input type="submit" class="button"
name="_eventId_checkTest" value="Далее"/>
</form:form>
</body>
</html>
Як видно з коду, наведеного вище, при натисканні на кнопку "Далі" генерується подія checkTest. Ця подія обробляється станом singleTest наступним чином:
<transition on="checkTest" to="singleTest">
<evaluate expression="flowScope.questionService.addAnswer(testList,flowScope.test)" />
</transition>
- знову бере участь об'єкт класу QuestionService. На цей раз виконується метод addAnswer, якому в якості параметрів передаються змінні testList (змінна, в якій зберігаються результати тестів) і flowScope.test (заповнена даними з форми змінна).
Розглянемо як відбувається заповнення змінної test даними з форми. Форма прив'язується до змінної наступним рядком:
<form:form commandName="test">
Властивості ж змінної test прив'язуються до відповідних полів форми таким чином:
<form:radiobutton path="userAnswer"
label="${test.answer1}" value="1"/>
Тобто, до даного radiobutton прив'язується властивість userAnswer об'єкта test. В результаті, після натискання на кнопку "Далі", властивість userAnswer заповнюється одним із значень: 1, 2 або 3, в залежності від того, який варіант відповіді вибрав користувач.
Повернемося до транзакції checkTest, яка виконується після натискання на кнопку "Далі":
<transition on="checkTest" to="singleTest">
<evaluate expression="flowScope.questionService.addAnswer(testList,flowScope.test)" />
</transition>
Метод addAnswer класу QuestionService наведено нижче:
public void addAnswer( TestList testList, Test test )
throws LastTestException, WrongAnswerException, MaxWrongAnswersException{
testList.addAnswer(test);
if ( !test.isCorrectAnswer() ) {
if (testList.getWrongAnswerCount() >= MAX_WRONG_ANSWER) {
throw new MaxWrongAnswersException();
}
checkTestIsLast();
throw new WrongAnswerException();
}
checkTestIsLast();
}
- як бачимо, спочатку зберігається об'єкт test (містить питання і варіант відповіді користувача) у списку testList за допомогою виразу testList.addAnswer (test). Лістинг методу addAnswer показаний нижче:
public void addAnswer(Test test) {
testAnswers.add(test);
if( !test.isCorrectAnswer() ){
wrongAnswerCount++;
}
}
- в список testAnswers додається об'єкт test. Також перевіряється правильність відповіді користувача (test.isCorrectAnswer()) і якщо відповідь невірна, то лічильник невірних відповідей wrongAnswerCount збільшується на одиницю.
Повернемося до методу addAnswer класу QuestionService. Після додавання відповіді в список testList відбувається перевірка на правильність відповіді вже в цьому методі. Якщо відповідь невірна, перевіряється кількість неправильних результатів виразом:
if ( !test.isCorrectAnswer() ) {
if (testList.getWrongAnswerCount() >= MAX_WRONG_ANSWER) {
throw new MaxWrongAnswersException();
}
checkTestIsLast();
throw new WrongAnswerException();
}
Константа MAX_WRONG_ANSWER дорівнює 3. Якщо неправильних відповідей 3 і більше, то генерується виключення MaxWrongAnswersException. Обробка цього виключення буде розглянута пізніше.
Якщо неправильних відповідей менше 3-х, то виконується перевірка чи питання є останнім за допомогою методу checkTestIsLast:
private void checkTestIsLast() throws LastTestException{
if ( isLastQuestion() ) {
testIndex = 0;
throw new LastTestException();
}
}
public boolean isLastQuestion(){
if( testIndex >= tests.size() ){
return true;
}
return false;
}
- методом checkTestIsLast генерується виключення LastTestException якщо питання останнє у списку.
Наведемо ще раз розглянутий метод addAnswer класу QuestionService:
public void addAnswer( TestList testList, Test test )
throws LastTestException, WrongAnswerException, MaxWrongAnswersException{
testList.addAnswer(test);
if ( !test.isCorrectAnswer() ) {
if (testList.getWrongAnswerCount() >= MAX_WRONG_ANSWER) {
throw new MaxWrongAnswersException();
}
checkTestIsLast();
throw new WrongAnswerException();
}
checkTestIsLast();
}
І, нарешті, у випадку якщо користувач дав невірну відповідь, але кількість неправильних відповідей менше 3-х і питання не є останнім, то генерується виключення WrongAnswerException.
Останнім рядком методу checkTestIsLast перевіряється чи поточне питання є останнім у разі якщо користувач дав правильну відповідь. Якщо питання останнє, то генерується виключення LastTestException.
Повернемося до стану singleTest і оцінимо як обробляються 3 включення (LastTestException, WrongAnswerException, MaxWrongAnswersException):
<view-state id="singleTest" model="flowScope.test">
<on-entry>
<set name="flowScope.test"
value="questionService.getNextTest()" />
</on-entry>
<transition on="checkTest" to="singleTest">
<evaluate expression="questionService.addAnswer(testList,flowScope.test)" />
</transition>
<transition to="testsFinished" on-exception=
"com.seostella.swftests.flow.exception.test.LastTestException" />
<transition to="wrongAnswer" on-exception=
"com.seostella.swftests.flow.exception.test.WrongAnswerException" />
<transition to="testsFail" on-exception=
"com.seostella.swftests.flow.exception.test.MaxWrongAnswersException" />
</view-state>
- при виникненні виключення LastTestException виконується перехід на стан testsFinished. При виникненні виключення WrongAnswerException - на стан wrongAnswer, а при виключенні MaxWrongAnswersException - на стан testsFail. Зазначимо, що стану testsFinished і testsFail є кінцевими станами та управління у них передається батьківському flow (разом зі змінною testList):
<end-state id="testsFinished">
<output name="testList" />
</end-state>
<end-state id="testsFail">
<output name="testList" />
</end-state>
На завершення розгляду стану singleTest, наведемо сторінку, яка відображається користувачеві в даному стані:
Рис 7. Сторінка singleTest.jspx
Не розглянутим залишився єдиний стан-відображення wrongAnswer:
<view-state id="wrongAnswer">
<transition to="singleTest" />
</view-state>
- після цього стану виконується безумовний перехід до стану singleTest.
Однойменна сторінка wrongAnswer.jspx відповідає за відображення:
<html
xmlns:c="http://java.sun.com/jsp/jstl/core"
xmlns:jsp="http://java.sun.com/JSP/Page"
xmlns:form="http://www.springframework.org/tags/form">
<jsp:output omit-xml-declaration="yes"/>
<jsp:directive.page contentType="text/html;charset=UTF-8" />
<head>
<title>Tests - Spring Web Flow 2.x Tutorial | seostella.com</title>
</head>
<body>
<h1>Ошибка</h1>
<p>
Вы дали неверный ответ! Если Вы допустите 3 ошибки, то провалите тест.
На данный момент Вы совершили ${testList.wrongAnswerCount}
<c:choose>
<c:when test="${testList.wrongAnswerCount == 1}">
ошибку.
</c:when>
<c:otherwise>
ошибки.
</c:otherwise>
</c:choose>
</p>
<a href="${flowExecutionUrl}&_eventId=singleTest" title="Продолжить">Продолжить</a>
</body>
</html>
- цей документ нічим не примітний, окрім заміни кнопки на посилання наступним кодом:
<a href="${flowExecutionUrl}&_eventId=singleTest" title="Продолжить">Продолжить</a>
А так виглядає сторінка в браузері:
Рис 8. Сторінка wrongAnswer.jspx
Код веб-програми доступний за наступною адресою - Завантажити код Spring Web Flow Tests.
Більш докладно Spring Web Flow описано в документі Spring Web Flow Reference Guide
< | Spring Web Flow. Тести. Частина 4. Flow Авторизації |
2 листопада 2015 р. 17:37
|
Спасибо за цикл статей, но в этой вы не добавили схему переходов, вместо нее продублировали скриншот страницы wrongAnswer.jspx.
Поправьте пожалуйста. |