Зміст
За допомогою простого прикладу навряд чи вдасться показати всю міць Spring Web Flow, проте складну програму не варто використовувати як навчальний приклад. Тому в основі прикладу буде щось середнє, а саме - програма для перевірки знань користувача за допомогою тестів. Тобто, користувачеві задаються питання та пропонується кілька варіантів відповіді на кожне з них. Щоб якомога краще показати переваги використання SWF, ми трохи ускладнили завдання. Отже:
Перейдемо до безпосереднього створення програми. Представимо класи моделі.
З попередньої частини нам знадобиться клас User, що містить всього одну властивість name:
package com.seostella.swftests.domain;
import java.io.Serializable;
/**
*
* @author seostella.com
*/
@SuppressWarnings("serial")
public class User implements Serializable {
private static final long serialVersionUID = 1L;
private String name;
public User() {
}
public User(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
Клас Test буде містити всю інформацію по одному тесту: питання, 3 варіанти відповіді, правильну відповідь і варіант відповіді користувача:
package com.seostella.swftests.domain;
import java.io.Serializable;
/**
*
* @author seostella.com
*/
@SuppressWarnings("serial")
public class Test implements Serializable {
private static final long serialVersionUID = 1L;
private String question;
private String answer1;
private String answer2;
private String answer3;
private int userAnswer;
private int correctAnswer;
public Test(
String question,
String answer1,
String answer2,
String answer3,
int correctAnswer) {
this.question = question;
this.answer1 = answer1;
this.answer2 = answer2;
this.answer3 = answer3;
this.correctAnswer = correctAnswer;
}
public String getAnswer1() {
return answer1;
}
public String getAnswer2() {
return answer2;
}
public String getAnswer3() {
return answer3;
}
public String getQuestion() {
return question;
}
public int getUserAnswer() {
return userAnswer;
}
public int getCorrectAnswer() {
return correctAnswer;
}
public void setAnswer1(String answer1) {
this.answer1 = answer1;
}
public void setAnswer2(String answer2) {
this.answer2 = answer2;
}
public void setAnswer3(String answer3) {
this.answer3 = answer3;
}
public void setQuestion(String question) {
this.question = question;
}
public void setCorrectAnswer(int correctAnswer) {
this.correctAnswer = correctAnswer;
}
public void setUserAnswer(int result) {
this.userAnswer = result;
}
public boolean isCorrectAnswer(){
if( this.userAnswer == this.correctAnswer ){
return true;
}
return false;
}
public String getUserAnswerAsText() {
switch( this.userAnswer ){
case 1:
return answer1;
case 2:
return answer2;
case 3:
return answer3;
}
return "n/a";
}
}
Клас TestList містить список тестів і кількість неправильних варіантів відповіді:
package com.seostella.swftests.domain;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
/**
*
* @author seostella.com
*/
@SuppressWarnings("serial")
public class TestList implements Serializable {
private static final long serialVersionUID = 1L;
private List<Test> testAnswers;
private int wrongAnswerCount = 0;
public TestList() {
testAnswers = new ArrayList<Test>();
}
public List<Test> getTests() {
return testAnswers;
}
public void setTests(List<Test> results) {
this.testAnswers = results;
}
public void addTest(Test test) {
testAnswers.add(test);
if( !test.isCorrectAnswer() ){
wrongAnswerCount++;
}
}
public int getWrongAnswerCount() {
return wrongAnswerCount;
}
}
Клас TestChecker містить екземпляри класів User і TestList:
package com.seostella.swftests.domain;
import com.seostella.swftests.domain.TestList;
import com.seostella.swftests.domain.User;
import java.io.Serializable;
/**
*
* @author seostella.com
*/
@SuppressWarnings("serial")
public class TestChecker implements Serializable {
private static final long serialVersionUID = 1L;
private User user;
private TestList testList;
public TestChecker() {
testList = new TestList();
}
public TestChecker(User user) {
this.user = user;
}
public TestChecker(User user, TestList testList) {
this.user = user;
this.testList = testList;
}
public User getUser() {
return user;
}
public TestList getTestList() {
return testList;
}
public void setUser(User user) {
this.user = user;
}
public void setTestList(TestList testList) {
this.testList = testList;
}
}
Далі представимо обробку 4-х дій (getUser, createUser, getNextTest і addTestResult), які будуть відбуватися під час виконання веб-програми і два допоміжних java-bean'а - QuestionService і UserService.
Частина логіки з обробки подій tests-flow знаходиться в класі TestFlowActions, друга частина - в класі QuestionService. Лістинг TestFlowActions представлений нижче:
package com.seostella.swftests.flow;
import com.seostella.swftests.domain.User;
import com.seostella.swftests.flow.exception.user.TooShortUsernameException;
import com.seostella.swftests.flow.exception.user.UserNotFoundException;
import com.seostella.swftests.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
/**
*
* @author seostella.com
*/
@Component
public class TestsFlowActions {
@Autowired
private UserService userService;
public TestsFlowActions() {
}
public User getUser(String name) throws TooShortUsernameException,
UserNotFoundException{
return userService.getUser(name);
}
public User createUser(String name) throws TooShortUsernameException{
return userService.createUser(name);
}
}
В цьому класі з'явилася нова сутність - це об'єкт userService. Він має анотацію @Autowired, яка означає, що значенням цього об'єкта буде java-bean UserService. Цей клас є сінглтоном, тому під час створення програми створюється тільки один екземпляр даного класу.
Метод getUser повертає користувача за вказаним іменем. Якщо користувача немає в списку наперед визначених користувачів (Sheldon, Leonard, Penny, Howard, Raj, Amy, Bernadette, Stuart, Priya або Leslie), то генерується виключення UserNotFoundException. Якщо користувач ввів менше 3-х символів, то генерується виключення TooShortUsernameException.
Метод createUser створює користувача з вказаним іменем. Якщо довжина імені менше 3-х символів, то повертається помилка TooShortUsernameException.
Клас UserService містить реалізацію описаних вище методів getUser і createUser. Зауважимо, що в конструкторі класу створюється кілька об'єктів класу User, які ініціалізувалися при створенні об'єкта UserService. Це користувачі з наступними іменами: Sheldon, Leonard, Penny, Howard, Raj, Amy, Bernadette, Stuart, Priya і Leslie.
package com.seostella.swftests.service;
import com.seostella.swftests.domain.User;
import com.seostella.swftests.flow.exception.user.TooShortUsernameException;
import com.seostella.swftests.flow.exception.user.UserNotFoundException;
import java.util.ArrayList;
import java.util.List;
import org.springframework.stereotype.Component;
/**
*
* @author seostella.com
*/
@Component
public class UserService {
private List<User> userList = null;
public UserService() {
userList = new ArrayList<User>();
userList.add( new User("Sheldon") );
userList.add( new User("Leonard") );
userList.add( new User("Penny") );
userList.add( new User("Howard") );
userList.add( new User("Raj") );
userList.add( new User("Amy") );
userList.add( new User("Bernadette") );
userList.add( new User("Stuart") );
userList.add( new User("Priya") );
userList.add( new User("Leslie") );
}
public User getUser(String name) throws TooShortUsernameException,
UserNotFoundException{
if (name != null && name.length() > 2) {
for ( User user : userList ){
if( user.getName().equals(name) ){
return user;
}
}
} else {
throw new TooShortUsernameException();
}
throw new UserNotFoundException();
}
public User createUser(String name) throws TooShortUsernameException{
if (name != null && name.length() > 2) {
User user = new User(name);
userList.add( user );
return user;
} else {
throw new TooShortUsernameException();
}
}
}
Клас QuestionService не містить анотації @Component, тому що нам необхідно щоб екземпляри цього класу створювалися при кожному старті flow, на відміну від UserService, який створюється один на всі запущені екземпляри tests-flow:
package com.seostella.swftests.service;
import com.seostella.swftests.domain.Test;
import com.seostella.swftests.domain.TestList;
import com.seostella.swftests.flow.exception.test.LastTestException;
import com.seostella.swftests.flow.exception.test.MaxWrongAnswersException;
import com.seostella.swftests.flow.exception.test.WrongAnswerException;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
/**
*
* @author seostella.com
*/
@SuppressWarnings("serial")
public class QuestionService implements Serializable{
private static final int MAX_WRONG_ANSWER = 3;
private List<Test> tests;
private int testIndex = 0;
public QuestionService() {
tests = new ArrayList<Test>();
tests.add(new Test(
"Сколько оборотов вокруг Солнца делает Земля за один год?",
"1", "4", "365",
1
));
tests.add(new Test(
"Сколько лет длилась Столетняя война?",
"86", "100", "116",
1
));
tests.add(new Test(
"Сколько игроков в футбольной комманде?",
"9", "10", "11",
3
));
tests.add(new Test(
"Какой океан омывает Индию",
"Индийский", "Тихий", "Атлантический",
1
));
tests.add(new Test(
"Столица Англии?",
"Париж", "Барселона", "Лондон",
3
));
}
public Test getNextTest(){
return tests.get(testIndex++);
}
public boolean isLastQuestion(){
if( testIndex >= tests.size() ){
return true;
}
return false;
}
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();
}
private void checkTestIsLast() throws LastTestException{
if ( isLastQuestion() ) {
testIndex = 0;
throw new LastTestException();
}
}
}
У конструкторі QuestionService створюється список з п'яти наперед визначених питань з варіантами відповідей і номером правильної відповіді. Також в класі присутня властивість testIndex, яка відповідає за контроль поточного номера питання.
Метод getNextTest повертає наступний тест в списку, а індекс-позицію поточного питання testIndex збільшує на одиницю.
Метод addAnswer додає в список відповідей користувача тест, отриманий в параметрі. Також в цьому методі перевіряється чи задані всі питання. Якщо всі питання вже були представлені користувачеві, то генерується виключення LastTestException. Якщо користувач здійснив першу або другу помилку, то генерується виключення WrongAnswerException. Якщо ж користувач здійснив третю помилку, то генерується виключення MaxWrongAnswersException.
В підпакетах test і user пакета com.seostella.swftests.flow.exception міститься 5 класів помилок: LastTestException, MaxWrongAnswersException, WrongAnswerException, TooShortUsernameException і UserNotFoundException. Лістинги всіх їх приводити не будемо, так як вони ідентичні, не рахуючи назви. Наведемо лістинг тільки одного класу UserNotFoundException:
package com.seostella.swftests.flow.exception.user;
/**
*
* @author seostella.com
*/
@SuppressWarnings("serial")
public class UserNotFoundException extends Exception {
public UserNotFoundException() {}
public UserNotFoundException(String message) {
super(message);
}
}
Переходимо до основної теми цієї статті - опис flow.
< | Spring Web Flow. Тести. Частина 1. Налаштування проекту | Spring Web Flow. Тести. Частина 3. Огляд flow | > |