Spring: добавление одной записи в список внезапно создает дубликат - PullRequest
0 голосов
/ 20 ноября 2018

Чтобы подобрать Spring, я играю с очень простыми страницами, чтобы понять аннотации и поведение Spring в целом.Если быть точным, я создал страницу для отображения списка Task и другую страницу для добавления нового Task.Я использовал @SessionAttibutes, чтобы сохранить список задач в сеансе, вместо вызова службы для получения полного списка Task каждый раз, когда я получаю доступ к странице списка задач.

Это мой TaskService

@Service
public class TaskService {
  private List<Task> taskCache = new ArrayList<Task>();

  public List<Task> retrieveTasks() {
    System.out.println("GETTING TASK LIST FOR THE FIRST TIME");
    return taskCache;
  }

  public Task addTask(String title, String desc, Date targetDate) {
    Task newTask = new Task(title, desc, targetDate);
    taskCache.add(newTask);

    return newTask;
  }
}

Это мой TaskController

@Controller
@SessionAttributes("taskList")
public class TaskController {
  @Autowired
  private TaskService taskService;

  @GetMapping("/task-view")
  public String showTaskList() {
    return "task/view";
  }

  @GetMapping("/task-add")
  public String showAddTaskForm(@ModelAttribute("task") Task newTask) {
    return "task/add";
  }

  @PostMapping("/task-add")
  public String addTask(ModelMap model, @ModelAttribute("task") Task newTask, @ModelAttribute("taskList") List<Task> taskList) {
    System.out.println("ADDING NEW TASK: " + taskList.size());
    taskList.add(taskService.addTask(newTask.getTitle(), newTask.getDesc(), new Date()));
    System.out.println("DONE ADDING NEW TASK: " + taskList.size());
    return "redirect:/task-view";
  }

  @ModelAttribute("taskList")
  public List<Task> taskList() {
    System.out.println("CALLING TASK SERVICE TO GET LIST OF TASKS");
    return taskService.retrieveTasks();
  }
}

Это мой view.jsp

<html>
  <head>
    <title>Task Page</title>
  </head>
  <body>
    This is your task list:
    ${taskList}

    <div>
      <a href="task-add">Add a task</a>
    </div>
  </body>
</html>

Это мой add.jsp

<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form"%>
<html>
  <head>
    <title>Task Page</title>
  </head>
  <body>
    <form:form method="POST" action="/task-add" modelAttribute="task">
      <div>
        <form:label path="title">Title</form:label>
        <form:input path="title" />
      </div>

      <div>
        <form:label path="desc">Description</form:label>
        <form:input path="desc" />
      </div>

      <div>
        <input type="submit" value="Add">
      </div>
    </form:form>
  </body>
</html>

С точки зрения атрибута сессий, он работает, как я ожидал.Функция TaskService.retrieveTasks() вызывается только при первом доступе к view.jsp.Впоследствии тот же список задач извлекается из сеанса.

Проблема заключается в том, что когда я пытаюсь добавить два новых Task на страницу add.jsp, я вижу следующие строки в консоли.

CALLING TASK SERVICE TO GET LIST OF TASKS
GETTING TASK LIST FOR THE FIRST TIME
ADDING NEW TASK: 0
DONE ADDING NEW TASK: 2
ADDING NEW TASK: 2
DONE ADDING NEW TASK: 4

Первые две строки были напечатаны, когда я в первый раз получил доступ к view.jsp, что верно.Однако я понятия не имею, что происходит между ADDING NEW TASK и DONE ADDING NEW TASK линиями.Я был бы очень признателен, если бы кто-нибудь мог объяснить мне, почему строка ниже добавляет дубликат в моем списке Task

taskList.add(taskService.addTask(newTask.getTitle(), newTask.getDesc(), new Date()));

Я также попытался удалить часть taskList.add() и только сделалсервисный звонок на taskService.addTask().В этом случае дубликатов нет, и мой taskList обновляется одной новой записью.Однако я не понимаю, как taskService мог косвенно изменить мой taskList, когда в классе TaskService нет инъекций.

Я что-то пропустил?

Ответы [ 2 ]

0 голосов
/ 20 ноября 2018

Проблема в методе retrieveTasks:

public List<Task> retrieveTasks() {
    System.out.println("GETTING TASK LIST FOR THE FIRST TIME");
    return taskCache;
}

Этот метод возвращает ссылку на объект taskCache.Каждый, кто имеет доступ к этой ссылке, может изменить этот изменяемый список.Например, в PHP, если вы вернули массив, чем по умолчанию, вы получите копию массива.Не в Java.

Вы должны изменить метод следующим образом:

public List<Task> retrieveTasks() {
        System.out.println("GETTING TASK LIST FOR THE FIRST TIME");
        return new ArrayList<>(taskCache); // create copy of taskCache
}

Это на самом деле то, что будет делать реальный сервис.Данные будут храниться в базе данных, но вы не будете возвращать прямую ссылку на эти данные.Вы всегда извлекаете текущие данные из БД и материализуете их в совершенно новом Списке.

0 голосов
/ 20 ноября 2018
  @ModelAttribute("taskList")
  public List<Task> taskList() {
    System.out.println("CALLING TASK SERVICE TO GET LIST OF TASKS");
    return taskService.retrieveTasks();
  }

Я думаю, это то, что вам не хватает.Метод taskList () возвращает список taskCache, и это список, который изменяет taskService.Так что taksList и taskCache - это один и тот же объект.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...