Содержание
Приведем часть кода для облегчения понимания следующего описания (нижеприведенный код является частью 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.
В завершении приведем схему переходов между состояниями для облегчения понимания принципов (изображение кликабельное):
Рис 8. Страница wrongAnswer.jspx
Более подробно Spring Web Flow описан в документе Spring Web Flow Reference Guide
< | Spring Web Flow. Тесты. Часть 4. Flow Авторизации |
2 ноября 2015 г. 17:37
|
Спасибо за цикл статей, но в этой вы не добавили схему переходов, вместо нее продублировали скриншот страницы wrongAnswer.jspx.
Поправьте пожалуйста. |