案例研究介绍
本节介绍一个简单的应用程序来说明 Gradle 的使用:一个 To Do 应用程序。 在整本书中,我们将应用内容来演示 Gradle 在构建管道的每个阶段的功能。 该用例最初是一个没有 GUI 的普通 Java 应用程序,只需通过控制台输入进行控制。 在本章中,您将通过添加组件来扩展此应用程序以学习更高级的概念。
To Do 应用程序将充当帮助您广泛了解 Gradle 功能的工具。 您将学习如何应用 Gradle 的标准插件来引导、配置和运行您的应用程序。 在本章结束时,您将对 Gradle 的工作原理有一个基本的了解,您可以将其应用于使用 Gradle 构建您自己的基于 Web 的 Java 项目。
待办事项应用程序
当今世界很忙碌。 我们中的许多人在职业和私人生活中同时管理多个项目。 通常,您可能会发现自己处于不知所措和失控的境地。 保持井井有条并专注于优先事项的关键是维护良好的待办事项列表。 当然,您总是可以在一张纸上写下您的任务,但是无论您走到哪里都能够访问您的操作项目不是很方便吗? 无论是通过手机还是公共接入点,互联网的访问几乎无处不在。 您将构建自己的基于 Web 且具有视觉吸引力的应用程序,如图 3.1 所示。
任务管理用例
现在您知道了最终目标,让我们确定应用程序需要实现的用例。 每个任务管理系统都由操作项或任务的有序列表组成。 任务有一个标题来表示完成它所需的操作。 可以将任务添加到列表中和从列表中删除,并标记为活动或已完成以指示其状态。 如果您想让描述更准确,该列表还应该允许修改任务的标题。 对任务的更改应自动保存到数据存储中。

为了使任务列表有序,您将包含按任务状态过滤任务的选项:活动或已完成。 现在,我们将坚持使用这组最小的功能。 图 3.2 显示了浏览器中呈现的用户界面的屏幕截图。

让我们从用户界面方面退后一步,从头开始构建应用程序。 在其第一个版本中,您将通过实现通过命令行控制的基本功能来奠定其基础。 在下一节中,我们将重点关注应用程序的组件以及它们之间的交互。
检查组件交互
我们发现待办事项应用程序实现了典型的创建、读取、更新和删除 (CRUD) 功能。 为了持久保存数据,您需要用模型来表示它。 您将创建一个名为 ToDoItem 的新 Java 类,它是一个充当模型的普通旧 Java 对象 (POJO)。 为了使解决方案的第一次迭代尽可能简单,我们不会引入像数据库这样的传统数据存储来存储模型数据。 相反,您会将其保存在内存中,这很容易实现。 实现持久性契约的类称为 InMemoryToDoRepository。 缺点是关闭应用程序后无法保留数据。 在本书的后面,我们将采用这个想法并展示如何为其编写更好的实现。
每个独立的 Java 程序都需要实现一个主类,即应用程序的入口点。 您的主类将被称为 ToDoApp 并将运行直到用户决定退出程序。 您将向用户提供一个命令菜单,他们可以通过输入触发特定操作的字母来管理其待办事项列表。 每个操作命令都映射到一个名为 CommandLineInput 的枚举。 CommandLineInputHandler 类代表用户交互和命令执行之间的粘合剂。 图 3.3 说明了列出所有可用任务的用例中按时间顺序排列的对象交互。

现在您已准备好实现应用程序的功能。 在下一节中,我们将直接深入代码。
构建应用程序的功能
在上一节中,我们确定了类、它们的函数以及它们之间的交互。 现在是时候让它们充满生机了。 首先,让我们看一下待办事项的模型。
待办事项模型类
ToDoItem 类的每个实例代表待办事项列表中的一个操作项。 属性 id 定义了项目的唯一标识,使您能够将其存储在内存数据结构中,并在您想要在用户界面中显示它时再次读取它。 此外,模型类公开字段名称和已完成。 为简洁起见,代码片段中排除了 getter 和 setter 方法以及 compareTo 方法:
package com.manning.gia.todo.model;
public class ToDoItem implements Comparable<ToDoItem> {
private Long id;
private String name;
private boolean completed;
(...)
}
现在让我们看看用于读取和写入模型的存储库实现。
模型在内存中的持久性
将数据存储在内存中很方便并且简化了实现。 在本书的后面,您可能想要提供更复杂的实现,例如数据库或文件持久性。 为了能够交换实现,您将创建一个接口 ToDoRepository,如以下清单所示。
package com.manning.gia.todo.repository;
import com.manning.gia.todo.model.ToDoItem;
import java.util.Collection;
public interface ToDoRepository {
List<ToDoItem> findAll();
ToDoItem findById(Long id);
Long insert(ToDoItem toDoItem);
void update(ToDoItem toDoItem);
void delete(ToDoItem toDoItem);
}
该接口声明了您期望的所有 CRUD 操作。 您可以查找所有现有的待办事项、通过 ID 查找特定的待办事项、插入新的操作项以及更新或删除它们。 接下来,您将创建此接口的可扩展且线程安全的实现。 下一个清单显示了 InMemoryToDoRepository 类,它将待办事项存储在 ConcurrentHashMap 的实例中。
package com.manning.gia.todo.repository;
import com.manning.gia.todo.model.ToDoItem;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.atomic.AtomicLong;
public class InMemoryToDoRepository implements ToDoRepository {
private AtomicLong currentId = new AtomicLong();
private ConcurrentMap<Long, ToDoItem> toDos = new ConcurrentHashMap<Long, ToDoItem>();
@Override
public List<ToDoItem> findAll() {
List<ToDoItem> toDoItems = new ArrayList<ToDoItem>(toDos.values());
Collections.sort(toDoItems);
return toDoItems;
}
@Override
public ToDoItem findById(Long id) {
return toDos.get(id);
}
@Override
public Long insert(ToDoItem toDoItem) {
Long id = currentId.incrementAndGet();
toDoItem.setId(id);
toDos.putIfAbsent(id, toDoItem);
return id;
}
@Override
public void update(ToDoItem toDoItem) {
toDos.replace(toDoItem.getId(), toDoItem);
}
@Override
public void delete(ToDoItem toDoItem) {
toDos.remove(toDoItem.getId());
}
}
到目前为止,您已经了解了待办事项的数据结构以及用于存储和检索数据的内存中实现。 为了能够引导 Java 程序,您需要创建一个主类。
应用程序的入口点
ToDoApp 类在控制台上打印应用程序的选项,从提示中读取用户的输入,将单个字母输入转换为命令对象,并进行相应的处理,如下面的清单所示。
package com.manning.gia.todo;
import com.manning.gia.todo.utils.CommandLineInput;
import com.manning.gia.todo.utils.CommandLineInputHandler;
public class ToDoApp {
public static final char DEFAULT_INPUT = '\u0000';
public static void main(String args[]) {
CommandLineInputHandler commandLineInputHandler = new CommandLineInputHandler();
char command = DEFAULT_INPUT;
while (CommandLineInput.EXIT.getShortCmd() != command) {
commandLineInputHandler.printOptions();
String input = commandLineInputHandler.readInput();
char[] inputChars = input.length() == 1 ? input.toCharArray() : new char[]{DEFAULT_INPUT};
command = inputChars[0];
CommandLineInput commandLineInput = CommandLineInput.getCommandLineInputForInput(command);
commandLineInputHandler.processInput(commandLineInput);
}
}
}
到目前为止,我们已经在特定用例的上下文中讨论了应用程序的组件及其交互:查找用户的所有待办事项。 清单 3.3 应该能让您大致了解组件的职责以及它们的内部工作方式。 如果您不理解此处提供的类定义的每个小实现细节,请不要担心。 更重要的是项目的自动化。 我们将在本章的其余部分讨论具体问题,例如使用 Gradle 设置项目、编译源代码、组装 JAR 文件以及运行应用程序。 现在是 Gradle 登上舞台的时候了。