一、关于 MVC:模型、视图、控制器
MVC 是一种软件设计模式。它描述了软件分成三个部分:
-
模型:管理一个应用的数据。这要从狭义上来理解。当然,不平凡的应用的任何部分都以这样或那样的方式处理应用的数据,但是来自 MVC 的模型对应于对用户可见的数据项,并且可能受到用户交互的改变。该模型与数据呈现给用户或任何应用工作流的方式无关,因此可以说该模型是 MVC 应用的核心部分。毫不奇怪,开发模型是任何 MVC 软件项目的第一步。
-
视图:描述数据和控制元素(输入、按钮、复选框、菜单等)向用户的呈现。视图可以提供不同的模式,如分页或非分页表、格式化列表或链接表等等。一个视图也可以使用不同的技术,比如安装在用户 PC 上的 GUI 组件、移动电话上的应用或者要在浏览器中查看的网页。
-
控制器:处理用户输入并准备视图部件工作所需的数据集。虽然视图显示了模型项,但是视图永远不需要知道数据是如何存储的,以及如何从一些持久存储(数据库)中检索数据。这是管制员的责任。因为用户输入决定了应用下一步要做什么,所以控制器也包含应用逻辑。任何计算和数据转换都发生在 MVC 的控制部分。
例如,考虑一个读书俱乐部应用。在这种情况下,模型由书籍(包括租赁状态)、书籍存储位置(建筑物、房间或书架)和成员等元素组成。对于搜索应用模块,通常将书籍、用户等列表定义为模型值。
图书俱乐部应用的视图部分将包含显示图书、显示成员、显示图书位置、允许成员租借图书、添加俱乐部成员、显示图书和成员列表以及各种搜索功能等页面。从技术上讲,这通常与模板引擎密切相关,模板引擎定义模型元素的占位符、循环的快捷方式(表格和列表)以及其他视图元素,如菜单和按钮。
控制器处理用户输入的数据。例如,如果视图当前显示书籍的搜索页面,并且用户输入书籍的名称并点击搜索按钮,则控制器被告知哪个按钮被点击。然后,控制器读取请求参数(在本例中是书的名称)和一些可能的模型值(例如,用户名和用户是否登录),查询数据库,构建结果列表,根据该列表创建模型,最后决定接下来显示哪个视图页面。
在实现细节上存在一些混乱。这来自于视图元素和模型元素之间的数据流的技术细节。MVC 没有假设何时更新视图元素和模型元素,以及选择哪个过程来保持它们同步。这就是为什么对于 MVC,你会在文献中发现许多不同的图。
对于 Java MVC,我们可以把我们对 MVC 的想法缩小到以下—一个模型(存储在内存中)定义了应用的状态;视图显示模型值并将用户交互发送到控制器;控制器准备模型数据,处理用户输入并相应地改变模型值,然后决定接下来显示哪个视图页面。这种 MVC 模型如图 1-1 所示。
图 1-1
Java MVC 设计模式
MVC 的历史
MVC 的出现可以追溯到 20 世纪 70 年代。它作为一个编程概念被引入计算机语言 Smalltalk。那时,它还没有名字。直到 20 世纪 80 年代后期,MVC 这个名字才被明确使用。它出现在期刊物体技术杂志的一篇文章中。
MVC 稳步地变得越来越广泛,它的思想被如此广泛地采用,以至于从 MVC 演化出了变体。我们不会在本书中讨论这些变体,但有一个简短的列表包括:
-
PAC(表现-抽象-控制) 和 HMVC(分层 MVC) **。**这是 MVC 的一个变种,其中子模块有它们自己的类似 MVC 的结构,只是后来才由它们构造了一个视图页面。
-
MVA(模型-视图-适配器) **。**在这种模式中,视图和模型是分离的,只有控制器(在这种情况下称为适配器)在模型和视图之间起中介作用。视图不能直接访问模型值。
-
MVP(模型-视图-演示者) **。**在 MVP 中,视图包含通知控制器(在这种情况下称为演示者)视图相关数据变化的逻辑。然后,演示者执行一些活动,并最终回调视图,以便通知用户有关数据更改的信息。
-
MVVM(模型-视图-视图-模型) **。**在 MVVM 中,引入了一些自动化,将模型值转换成视图元素,反之亦然。
随着互联网的兴起,MVC 的真正威力在 20 世纪 90 年代显露出来。尽管一些技术细节发生了变化,例如数据流的确切技术特征和数据穿越层边界的时间点,但思想仍然是相同的:模型保存应用状态,视图呈现浏览器页面,控制器处理浏览器和模型之间的交互,并决定显示哪个视图页面。
发明了各种 MVC web 框架; https://en.wikipedia.org/wiki/Comparison\_of\_web\_frameworks
向您展示了一个全面的列表(在页面上再往下,MVC 功能也被列出)。
Web 应用中的 MVC
如果我们试图让 Web 应用以 MVC 方式工作,它们会受到一些限制。最重要的区别来自 HTTP 协议的无状态特性,它用于视图(浏览器窗口)和控制器(HTTP 服务器)之间的通信。事实上,web 应用框架处理 HTTP 协议的方式导致了不同 MVC 实现之间的决定性差异。
更详细地说,关于 web 应用的 MVC 的重要问题如下:
-
**会话:**我们已经指出了 HTTP 的无状态本质。因此,如果浏览器发送一个请求,可能是因为用户在一个文本字段中输入了一些字符串,然后按了提交按钮,那么服务器如何知道是哪个用户在执行请求呢?这通常由会话来处理,会话由作为 cookie、请求或 POST 参数传输的会话 ID 来标识。会话由框架透明地处理,因此您不必从应用代码内部创建和维护会话。
-
**从视图中访问模型值:**对于 web 应用,某种模板引擎通常会处理视图生成。在那里,我们可以使用类似于
${user.firstName}
的表达式来读取模型条目的内容。 -
**传输数据范围:**如果从网页向服务器提交数据,我们基本上有两种选择。首先,可以传输完整的表格。第二,只有更改过的数据才能发送到服务器。后者减少了网络流量,但是需要一些脚本逻辑(JavaScript)来在网页上执行数据收集。
-
**更新视图:**对于 web 应用,更新视图的方式至关重要。要么在控制器发出请求后加载整个页面,要么只将网页中实际需要更新的部分从服务器传输到浏览器。同样,后一种方法减少了网络流量。
从这几点可以看出,为 web 应用编写一个 MVC 框架并不是一件非常简单的任务。这也是为什么有大量不同的 MVC 框架可以用于 web 应用。在本书的其余部分,我将向您展示,如果您的 Java 平台需要 MVC 软件,为什么选择 Java MVC 并不是最糟糕的事情。
面向 Java 的 MVC
在 Java 生态系统中,一个名为 Struts 的框架在 2000 年左右进入了软件世界。它是一个面向 web 应用的 MVC 框架,集成了 Java EE/Jakarta EE 和 Tomcat(一个归结为 web 功能的服务器产品)。尽管它不是 Java EE/Jakarta EE 规范的一部分,但它已经被用于许多软件项目,并且仍在被使用。相反,Java EE/Jakarta EE 将 JSF (Java Server Faces)命名为专用 web 框架。与 MVC 相反,JSF 使用面向组件的方法来创建 web 应用。
JSF 为任何 Java EE/Jakarta EE 8 或更高版本的产品开箱即用。直到版本 7,如果你想使用 MVC,Struts 是你可以使用的突出框架之一。然而,为了让 Struts 工作,必须向应用添加一个外部库,而且 Struts 总感觉像是一个扩展,而不太像是与 Java EE/Jakarta EE 无缝集成的东西。
在 Java EE 8/Jakarta EE 8 中,MVC 世界以 Java MVC 规范的形式重新进入了游戏。在 Java EE/Jakarta EE 世界中,它仍然是一个二等公民,但是有理由支持 MVC 而不是 JSF。在本章的最后,我们将讨论 MVC 相对于其他框架如 JSF 的优缺点。
最后,Java MVC (JSR-371)
最新的 Java EE/Jakarta EE MVC 实现在名称 Java MVC 下运行,并由 JSR-371 管理。它是第一个适用于 Java EE/Jakarta EE 服务器版本 8 或更高版本的 MVC 框架。事实上,JSR 描述了一个界面。为了让 Java MVC 真正工作,您需要添加一个实现库。
Note
我们使用 Eclipse Krazo 作为 Java MVC 实现库。 https://projects.eclipse.org/proposals/eclipse-krazo
见
或者
https://projects.eclipse.org/projects/ee4j.krazo
我们稍后将看到如何为您的 web 应用安装 Eclipse Krazo。
Java MVC 是包含在 Java EE/Jakarta EE 中的 REST 技术 JAX-RS 的一个精简而巧妙的扩展。这种关系赋予 Java MVC 一种现代感,并允许一种简洁和高度综合的编程风格。
我们已经知道 MVC 允许一些关于实现细节的混乱。图 1-1 描述了 Java MVC 是如何很好地工作的:对浏览器窗口中第一页的请求路由到控制器,控制器准备模型值(有或没有查询一些后端的附加数据)。控制器然后决定接下来显示哪个视图页面(浏览器页面)(可能是登录页面)。视图可以访问模型值。通过用户输入并提交给控制器的数据集,控制器获取请求参数(例如,登录名和密码),可能查询后端(用户数据库),更新模型,最后选择新的视图页面(例如,成功认证后的欢迎页面)。
但是有一个额外的特性可以与 Java MVC 无缝集成。您可以决定让 web 应用的一部分使用 AJAX 进行更细粒度的前端-后端通信,而不是总是在每次 HTTP 请求后加载一个完整的新页面。因为我们在 Java EE/Jakarta EE 8(或更高版本)环境中使用 Java MVC,所以我们可以使用 JAX-RS 来实现开箱即用。
为什么选择 MVC
有这么多的 web 前端技术,很难决定在您的项目中使用哪一种。新的 Java MVC 当然是一个选择,它可能非常适合您的需求。为了帮助你做决定,这里列出了 Java MVC 的利与弊。
缺点:
-
MVC 似乎是一种老式的设计模式。虽然这是真的,但它也被证明对许多项目都很有效,Java MVC 允许开发人员混合使用更现代的 web 开发技术。
-
MVC 迫使开发人员了解 HTTP 的内部机制。MVC 据说也是一种基于动作的设计模式。web 环境中的动作意味着 HTTP 请求和响应。MVC 不像其他框架那样真正隐藏 HTTP 通信的内部。
-
MVC 不像其他框架那样引入双向数据绑定。对于双向数据绑定,前端输入字段的变化会立即反映在模型值的变化中。相反,在 MVC 控制器中,您必须显式地实现模型值的更新。
优点:
-
因为与其他框架相比,它更接近 HTTP 通信的内部机制,尽管引入了一些复杂性,但这引入了更少的侵入性内存管理。看看 JSF,它为每个浏览器请求构建了一个完整的组件树(和组件数据树)。相比之下,MVC 应用可以用极小的内存占用来定制。
-
Java MVC 是 Java EE/Jakarta EE 8 规范的一部分。这有助于更可靠地处理维护。
-
如果你习惯了 Struts 或者类似的前端框架,那么切换到 Java MVC 感觉比切换到其他前端设计模式的其他产品更自然。
你好世界在哪里?
在许多与软件相关的开发指导书中,您会在第一章中找到一个非常简单的“Hello World”示例。对于 Jakarta EE,这意味着我们必须提供一种快捷方式来执行以下操作:
-
写一个简单的程序,比如输出字符串
"Hello World"
。 -
从字符串构建一个可部署的工件(例如一个
.war
文件)。 -
运行 Jakarta EE 服务器。
-
在服务器上部署应用(
.war
文件)。 -
将客户端(例如,浏览器)连接到服务器。
-
观察输出。
这是一大堆东西,所以与其构建一个快速而肮脏的设置来运行这样一个示例,我更愿意首先从总体上谈谈 Java/Jakarta Enterprise Edition(Java/Jakarta EE),然后讨论开发工作流,只有在这之后,才介绍一个简单的第一个项目。通过这种方式,我们可以确保您的第一个 Java MVC 应用被正确开发和运行。
如果您认为一个简单的 Hello World 示例会对您有所帮助,下面的段落将向您展示如何创建一个这样的示例。请注意,我们不会使用本书剩余部分中显示的开发过程——这只是一种简单、快速、可能不太干净的方法。您也可以安全地跳过这一部分,因为我们在第四章创建了一个合适的 Hello World 项目。
-
首先确保 OpenJDK 8 安装在你的电脑上。前往
https://jdk.java.net/java-se-ri/8-MR3
下载。在本节的其余部分,我们将 OpenJDK 8 文件夹称为OPENJDK8_DIR
。 -
从
https://projects.eclipse.org/projects/ee4j.glassfish/downloads
下载并安装 GlassFish 5.1(选择“全概要”变种)。在本节的其余部分,我们将 GlassFish 安装文件夹称为GLASSFISH_INST_DIR
。 -
在
GLASSFISH_INST_DIR/glassfish/config/asenv.conf
(Linux)或GLASSFISH_INST_DIR/glassfish/config/asenv.bat
(Windows)文件中,添加以下行:
REM Windows:
REM Note, if the OPENJDK8_DIR contains spaces, wrap it
REM inside "..."
set AS_JAVA=OPENJDK8_DIR
# Linux:
AS_JAVA="OPENJDK8_DIR"
必须用 OpenJDK 8 安装的安装文件夹替换OPENJDK8_DIR
。
- 启动 GlassFish 服务器:
REM Windows:
chdir GLASSFISH_INST_DIR
bin\asadmin start-domain
# Linux:
cd GLASSFISH_INST_DIR
bin/asadmin start-domain
您必须将GLASSFISH_INST_DIR
替换为 GlassFish 的安装文件夹。
-
在文件系统的任意位置创建一个名为
hello_world
的文件夹。其内容必须是(说明如下): -
从
https://mvnrepository.com
中获取lib
文件夹的罐子。在搜索字段中输入不带版本和.jar
扩展名的每个名称,选择版本,然后获取 JAR 文件。 -
Java 代码如下所示:
build
|- <empty>
src
|- java
| |- book
| |- javamvc
| |- helloworld
| |- App.java
| |- RootRedirector.java
| |- HelloWorldController.java
|- webapp
| |- META-INF
| | |- MANIFEST.MF
| |- WEB-INF
| |- lib
| | |- activation-1.1.jar
| | |- javaee-api-8.0.jar
| | |- javax.mail-1.6.0.jar
| | |- javax.mvc-api-1.0.0.jar
| | |- jstl-1.2.jar
| | |- krazo-core-1.1.0-M1.jar
| | |- krazo-jersey-1.1.0-M1.jar
| |- views
| | |- greeting.jsp
| | |- index.jsp
| |- beans.xml
| |- glassfish-web.xml
make.bat
make.sh
- 作为
MANIFEST.MF
,写下以下内容:
// App.java:
package book.javamvc.helloworld;
import javax.ws.rs.ApplicationPath;
import javax.ws.rs.core.Application;
@ApplicationPath("/mvc")
public class App extends Application {
}
// RootRedirector.java
package book.javamvc.helloworld;
import javax.servlet.FilterChain;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* Redirecting http://localhost:8080/HelloWorld/
* This way we don't need a <welcome-file-list> in web.xml
*/
@WebFilter(urlPatterns = "/")
public class RootRedirector extends HttpFilter {
@Override
protected void doFilter(HttpServletRequest req,
HttpServletResponse res,
FilterChain chain) throws IOException {
res.sendRedirect("mvc/hello");
}
}
// HelloWorldController.java
package book.javamvc.helloworld;
import javax.inject.Inject;
import javax.mvc.Controller;
import javax.mvc.Models;
import javax.mvc.binding.MvcBinding;
import javax.ws.rs.FormParam;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.core.Response;
@Path("/hello")
@Controller
public class HelloWorldController {
@Inject
private Models models;
@GET
public String showIndex() {
return "index.jsp";
}
@POST
@Path("/greet")
public Response greeting(@MvcBinding @FormParam("name")
String name) {
models.put("name", name);
return Response.ok("greeting.jsp").build();
}
}
- 视图文件如下所示:
Manifest-Version: 1.0
<%-- index.jsp --%>
<%@ page contentType="text/html;charset=UTF-8"
language="java" %>
<%@ taglib prefix="c"
uri="http://java.sun.com/jsp/jstl/core" %>
<html>
<head>
<meta charset="UTF-8">
<title>Hello World</title>
</head>
<body>
<form method="post"
action="${mvc.uriBuilder('HelloWorldController#
greeting').build()}">
Enter your name: <input type="text" name="name"/>
<input type="submit" value="Submit" />
</form>
</body>
</html>
<%-- greeting.jsp --%>
<%@ page contentType="text/html;charset=UTF-8"
language="java" %>
<%@ taglib prefix="c"
uri="http://java.sun.com/jsp/jstl/core" %>
<html>
<head>
<meta charset="UTF-8">
<title>Hello World</title>
</head>
<body>
Hello ${name}
</body>
</html>
(删除换行和HelloWorldController#
后的空格。)
-
作为
beans.xml
,创建一个空文件(尽管文件必须存在!). -
glassfish-web.xml
的内容如下: -
名为
make.sh
的 Linux 构建文件如下所示:
<?xml version="1.0" encoding="UTF-8"?>
<glassfish-web-app error-url="">
<class-loader delegate="true"/>
</glassfish-web-app>
#!/bin/bash
JAVA_HOME=/path/to/your/openjdk-8
rm -rf build/*
cp -a src/webapp/* build
mkdir build/WEB-INF/classes
$JAVA_HOME/bin/javac \
-cp src/webapp/WEB-INF/lib/javaee-api-8.0.jar:
src/webapp/WEB-INF/lib/javax.mvc-api-1.0.0.jar \
-d build/WEB-INF/classes \
src/java/book/javamvc/helloworld/*
cd build
$JAVA_HOME/bin/jar cf ../HelloWorld.war *
cd ..
(删除:
后的换行符和空格。)
- Windows 构建文件
make.bat
如下所示:
set JAVA_HOME=C:\dev\java-se-8u41-ri
mkdir build
CD build && RMDIR /S /Q .
CD ..
rmdir build
xcopy src\webapp build /s /e /i
mkdir build\WEB-INF\classes
%JAVA_HOME%\bin\javac ^
-cp src\webapp\WEB-INF\lib\javaee-api-8.0.jar;
src\webapp\WEB-INF\lib\javax.mvc-api-1.0.0.jar ^
-d build\WEB-INF\classes ^
src\java\book\javamvc\helloworld/*
cd build
%JAVA_HOME%\bin\jar cf ..\HelloWorld.war *
cd ..
(删除;
后的换行符和空格。)
要从控制台内部构建应用,进入hello_world
文件夹并启动脚本:
# Linux
cd hello_world
./make.sh
rem Windows
chdir hello_world
make
除了一些可以安全忽略的 Windows 构建脚本的错误消息之外,您最终会在主文件夹中找到HelloWorld.war
web 应用。在那里,您可以通过以下方式部署应用:
# Linux
GLASSFISH_INST_DIR/bin/asadmin deploy --force=true \
HelloWorld.war
rem Windows
GLASSFISH_INST_DIR\bin\asadmin deploy --force=true ^
HelloWorld.war
对于GLASSFISH_INST_DIR
,您必须替换 GlassFish 安装文件夹。
要查看它的运行情况,请在浏览器的地址栏中输入以下 URL:
http://localhost:8080/HelloWorld
参见图 1-2 和 1-3 。
图 1-3
Hello World 响应页面
图 1-2
Hello World 起始页
练习
-
练习 1: 确定 MVC 的三个组成元素。
-
练习 2: 对或错:模型的职责是与企业信息系统(例如,数据库)对话。
-
练习 3: 对或错:对于 MVC,将用户生成的数据传递给模型元素是自动完成的。
-
练习 4: 对或错:视图可以读取和访问模型值。
-
练习 5: 下列哪一项是正确的:(A)会话是模型对象,(B)会话是 HTTP 协议内部的属性,©您必须从应用代码内部创建和处理会话。
-
练习 6: 从版本 7 开始,Java MVC 成为 Java EE/Jakarta EE 规范的一部分。
摘要
MVC 代表模型-视图-控制器,是一种软件设计模式。模型管理应用的数据(限于向用户显示的内容,并受用户更改的影响);视图表示图形用户界面(GUI);控制器准备模型,处理用户输入,并决定在视图中显示什么(显示哪个视图页面)。
MVC 起源于 20 世纪 70/80 年代的桌面应用,后来被用来处理 web 应用。
Java 企业应用的 MVC(Java EE/Jakarta EE)被称为 Java MVC ,由 JSR-371 处理。从版本 8 开始,Java MVC 成为 Java EE/Jakarta EE 规范的一部分。
为了使用 Java MVC,需要在应用中添加一个实现。Eclipse Krazo 就是这样一个实现。
Java MVC 有助于节省内存,但是开发人员在某种程度上必须了解 HTTP 协议的特征。用户会话由 cookie、请求或 POST 参数处理。会话由框架透明地处理。
在下一章,我们将更详细地讨论 Java MVC 与 Java EE/Jakarta EE 的关系。
二、先决条件:Jakarta EE/Java EE
不能在独立模式下运行 Java MVC。相反,它必须伴随着 Java 企业版服务器(Java EE 或 Jakarta EE)提供的基础设施。我们在本章中讨论这意味着什么。
Java 对于企业应用的本质
在企业环境中,像 Java 这样的编程语言和软件平台必须满足对企业运营很重要的几个需求。它必须能够连接到一个或多个数据库,可靠地与同一公司中其他基于 It 的系统或相关业务建立通信,并且必须足够强大,能够可靠地处理输入,基于输入和数据库数据执行计算,并向客户提供适当的输出。作为一个交叉关注点,安全性也起着重要的作用。需要建立一个身份验证过程,强制用户识别自己的身份,并且需要获得授权来限制特定用户可以访问的资源量。此外,出于技术维护和审计的目的,需要记录活动,平台应该能够为技术健全性检查和与性能相关的调查提供监控数据。
为了让所有这些以期望的方式工作,语言和平台必须相对于未来的变化和增强保持稳定。这必须以一种新的语言和平台版本可以被 IT 人员适当处理的方式发生。Java EE/Jakarta EE 遵循这一思路,因此在企业环境中非常有用。
Jakarta EE 8 服务器完全运行在 Java 上并依赖于 Java。Java 是在 1991 年发明的,但 1996 年 Sun Microsystems 首次公开发布了 1.0 版本。从那以后,Java 作为一种语言和一个运行时环境或平台扮演了一个重要的角色。Java 如此成功有几个原因:
-
同一个 Java 程序可以在不同的操作系统上运行。
-
Java 运行在沙箱环境中。这提高了执行安全性。
-
Java 可以很容易地用自定义库进行扩展。
-
Java 语言的扩展非常缓慢。虽然缓慢的发展意味着最新版本中可能缺少新的有用的语言结构,但它有助于开发人员轻松跟踪新特性,并在长期运行的项目中彻底过渡到新的 Java 版本。此外,除了少数例外,Java 版本是向后兼容的。
-
Java 包含一个垃圾收集器,可以自动清理未使用的内存。
自 1998 年起,该平台被重新命名为 Java 2,并提供了不同的配置:
-
在桌面上运行的标准版 J2SE。它被进一步分为用于运行 Java 的 JRE (Java 运行时环境)和用于编译和运行 Java 的 JDK (Java 开发工具包)。
-
移动和嵌入式设备的微型版 J2ME。
-
企业版 J2EE 为 J2SE 增加了企业功能。每个 J2EE 配置包括一个完整的 J2SE 安装。
出于营销目的,“2”在 2006 年被删除,配置被命名为 JSE(或 JDK,这是 JSE 加开发工具),JME 和 JEE 分别。2018 年,JEE 被转移到 Eclipse 基金会,并更名为 Jakarta EE。Java 语言从 Java 7 到 Java 8 发生了实质性的变化。我们将在解释和代码示例中使用 Java 8 的所有现代特性。
Java 当然会继续发展。虽然在撰写本书时 Jakarta EE 的最新版本是 8,底层 Java 标准版也是 8,但您可以下载的最新 JavaSE (JSE)版本是 13。我们不会在本书中讨论 JavaSE 版本 9 或更高版本。
虽然 Java standard edition JSE 版本 8 的知识被认为是本书的先决条件,但对于只部分熟悉 Java 8 的读者来说,在阅读后续章节之前,以下新特性是值得研究的:
-
功能界面
-
λ演算(未命名函数)
-
用于处理集合和地图的流 API
-
新的日期和时间 API
我们将在本书的例子中适当的地方使用这些。
描述 Java EE/Jakarta EE 各部分的规范说明了每个部分能做什么以及如何做,并且它们跟踪新版本。Java EE/Jakarta EE 8 包含的子技术也用确切的版本号进行了详细描述。我们在这里列出了它们,并对每种技术的作用做了简短的描述。请注意,这个列表并不详尽——它没有包括一些更高级的 API,如果您查看官方文档,就可以了解这些 API。
-
Java MVC 1.0 - JSR-371: 这是我们在本书中主要关心的问题。
-
企业 Java bean s EJB**——3.2 版:**EJB 代表业务逻辑的入口点。每个 EJB 在整个 Jakarta EE 架构中扮演一个组件的角色,并负责一个专门的业务任务。EJB 允许开发人员添加安全性、事务性特性、与数据库通信的 JPA 特性以及 web 服务功能,它们也可以是消息传递的入口点。
-
Java Server Faces JSF**-2.3 版:** JSF 是基于组件的专用主 web 前端技术,用于浏览器访问。使用 Java MVC 在某种程度上是一种替代方法,没有人阻止你自由地混合它们。JSF 通常通过 EJB 与业务逻辑进行通信。
-
RESTful Web-Services JAX-RS**-2.1 版:** REST(表述性状态转移)是定义读写资源的原始 HTTP 协议。它最近在单页面 web 应用中获得了越来越多的关注,其中前端页面流完全由浏览器中运行的 JavaScript 处理。
-
**JSON 处理 JSON-P-Version 1.1:**JSON(JavaScript Object Notation)是一种精简的数据格式,如果浏览器中运行的 JavaScript 处理大量的表示逻辑,这种格式尤其有用。
-
JSON 绑定 JSON-B - Version 1.0: 这项技术简化了 JSON 数据和 Java 类之间的映射。
-
Web Sockets -版本 1.1: 提供 Web 客户端(浏览器)和 Jakarta EE 服务器之间的全双工通信。除了通过 HTTP 的“正常”访问,web 套接字还允许服务器向浏览器客户端发送消息!
-
**JPA-2.2 版:**Java 持久性 API 提供了对数据库的高级访问。
-
Java EE 安全 API**——1.0 版:**Jakarta EE 8 之前不存在的新安全 API。它包括 HTTP 身份验证机制、用于验证用户凭证和组成员身份的身份存储抽象,以及以编程方式处理安全性的安全上下文 API。
-
Java 消息服务 JMS**——2.0 版:**这是关于消息传递的,也就是说消息可以异步产生和消费。消息发送者产生并发布消息,并且可以立即继续其工作,即使消息稍后被消费。
-
Java 事务 API (JTA) -版本 1.2: JTA 确保将几个步骤组合成一个单元的流程可以作为一个整体提交或回滚。如果涉及分布式合作伙伴,这可能会变得棘手。JTA 在确保事务性方面帮助很大,即使对于更复杂的系统也是如此。
-
servlet**——4.0 版:**servlet 是服务器-浏览器通信的底层技术。通常在项目开始时只需配置一次。我们在需要运行其他技术的地方描述 servlets。
-
上下文和依赖注入 CDI-2.0 版: CDI 允许开发人员将上下文绑定到由专用生命周期管理的元素。此外,它将依赖关系注入到对象中,这简化了类的关联。我们将使用 CDI 将 JSF 元素连接到应用逻辑。
-
JavaMail-1.6 版:提供阅读和发送邮件的设施。这只是一个 API。对于实现,您可以使用 Oracle 的参考实现:
https://javaee.github.io/javamail/
-
Bean 验证**——2.0 版:**这允许开发人员限制方法调用参数,以符合某些值谓词。
-
拦截器**-1.2 版:**拦截器允许你将方法调用封装到拦截器类的调用中。虽然这也可以通过编程方法调用来完成,但是拦截器允许开发人员以声明的方式来完成。您通常将拦截器用于横切关注点,如日志记录、安全问题、监控等。
-
Java Server Pages JSP**-2.3 版:**JSP 可以用来建立服务器-浏览器通信中的页面流。JSP 是一项较老的技术,但是如果您愿意,仍然可以使用它。然而,你应该更喜欢 JSFs 而不是 JSP,在这本书里,我们不讨论 JSP。
-
JSP 标准标签库 JSTL -版本 1.2: 与 JSP 结合使用的用于页面元素的标签。
Java EE/Jakarta EE 运行在 Java Standard Edition (SE)之上,因此如果您为 Java EE/Jakarta EE 编程,您可以随时使用 Java SE 的任何类和接口。Java Standard Edition SE 中包含的一些技术在 Java Enterprise Edition 环境中扮演着重要的角色:
-
JDBC -版本 4.0: 一个用于数据库的访问 API。所有主要的数据库供应商都为他们的产品提供 JDBC 驱动程序。你可以使用它,但你不应该。请改用更高级的 JPA 技术。你偶尔会接触到,因为 JPA 在幕后使用 JDBC。
-
Java 命名和目录接口 JNDI: 在 Jakarta EE 8 环境中,对象会以一种相当松散的方式被其他对象访问。在现代企业版应用中,这通常通过 CDI,更准确地说,通过依赖注入来实现。然而,在幕后,一个由 JNDI 管理的查找服务发挥了作用。在过去,您必须直接使用 JNDI 接口以编程方式获取依赖对象。对于 Jakarta EE 8,您可以使用 JNDI,但通常您不必这样做。
-
Java API for XML Processing JAXP-版本 1.6: 一个通用的 XML 处理 API。可以通过 DOM(内存中的完整 XML 树)、SAX(基于事件的 XML 解析)或 StAX 来访问 XML 数据。这只是一个 API。通常您还必须添加一个实现,但是 Jakarta EE 服务器会自动为您添加。
-
用于 XML StAX 的流 API 版本 1.0: 用于对 XML 数据的流访问。这里的流意味着您可以根据显式需求串行访问 XML 元素(拉解析)。
-
Java XML 绑定 JAXB-2.2 版: JAXB 将 XML 元素连接到 Java 类。
-
XML Web Services JAX-WS-Version 2.2:Web Services 使用 XML 作为消息格式来远程连接组件。
-
JMX -版本 2.0: JMX 是一种通信技术,可以用来监控正在运行的 Jakarta EE 应用的组件。哪些信息可用于 JMX 监控取决于服务器实现,但是您可以向自己的组件添加监控功能。
这些规范是由一个社区进程来处理的,如果供应商想要说他们的服务器产品符合 Jakarta EE 的某个版本(或其前身之一,JEE 或 J2EE),他们必须通过测试。如果你感兴趣,相应的在线资源提供了相关信息。首先,在你喜欢的搜索引擎中输入“java community process jcp”或“java eclipse ee.next working group”。
Java 企业版最初是由太阳微系统公司开发的,名为 J2EE。在 2006 年,命名和版本化模式被更改为 JEE,在 J2EE 版本 1.4 之后是 JEE 版本 5。从那时起,主要的更新发生了,版本 JEE 6,JEE 7 和 JEE 8 被释放。2010 年,太阳微系统公司被甲骨文公司收购,在甲骨文公司旗下,发布了 JEE 7 和 JEE 8 版本。2017 年,甲骨文公司向 Eclipse 基金会提交了 Java EE,名称改为 Jakarta EE 8。
截至 2020 年初,从 JEE 8 到 Jakarta EE 8 的过渡仍在进行中。所以取决于你什么时候读这本书,它仍然可能是关于 Jakarta EE 8 的在线研究,你必须查阅关于 JEE 8 和 Jakarta EE 8 的页面。这是你应该记住的事情。为了在本书中保持简单,我们将只讨论 Jakarta EE。
写这本书的时候,发布的 Jakarta EE 8 服务器还不多。基本上有以下几种:
-
来自 Oracle Inc .的 GlassFish 服务器开源版。
-
WildFly 服务器,来自红帽
-
JBoss 企业应用平台,来自红帽
-
WebSphere Application Server Liberty,来自 IBM
-
开放自由,来自 IBM
这些服务器有不同的许可模式。GlassFish、WildFly 和 Open Liberty 是免费的。这意味着您可以出于开发和生产目的免费使用它们。要运行 JBoss 企业应用平台,您需要订阅,尽管源代码是开放的。WebSphere Application Server Liberty 是专有的。
在本书中,我们将讨论在 GlassFish 服务器开源版本 5.1 中运行 Java MVC。由于 Jakarta EE 8 的性质,转换到其他服务器总是可能的,尽管您将不得不花费相当多的时间来改变管理工作流。
GlassFish,一个免费的 Java 服务器
有几个免费的 Java EE/Jakarta EE 服务器可以用于评估和开发。GlassFish 服务器是一个特别好的选择,尤其是对于学习目的,因为它是开源的。
获取 GlassFish
在撰写本书时,最新版本是 5.1,您可以从以下网址下载:
https://projects.eclipse.org/
projects/ee4j.glassfish/downloads
选择“完整轮廓”变体。
Note
在这本书出版的时候,可能会有 GlassFish 的更高版本。您可以尝试 5.1 以上的版本,在本书中安装和使用它们可能不会有任何问题。但是为了避免任何问题,总是可以使用存档的 GlassFish 5.1 安装程序。
下载 ZIP 文件后,将其解压缩到文件系统中的任何位置。我们此后将安装文件夹称为GLASSFISH_INST_DIR
。在启动 GlassFish 之前,您必须确保您的系统上安装了 Java 8 JDK。
Note
JDK 8 是 GlassFish 5.1 的一个要求。您不能使用更高版本,也不应该使用更低版本。
从以下链接之一获得 JDK(对于 www.oracle.com
变体,您必须获得商业项目的付费订阅):
https://www.oracle.com/java/technologies/javase/
javase-jdk8-downloads.html
https://jdk.java.net/java-se-ri/8-MR3
jdk.java.net
变体指向 OpenJDK 发行版。对于 Linux,您的发行版的软件包提供商很可能已经为您提供了一个预构建的 Java 安装包。
如果 JDK 8 不是你的系统默认设置,你可以通过在控制台窗口输入java -version
来检查。您必须添加以下行
REM Windows:
REM Note, if the JDK_INST contains spaces, wrap it
REM inside "..."
set AS_JAVA=JDK_INST
# Linux:
AS_JAVA="JDK_INST"
在GLASSFISH_INST_DIR/glassfish/config/asenv.conf
(Linux)或者GLASSFISH_INST_DIR/glassfish/config/asenv.bat
(Windows)文件里面,在这里你必须用 JDK 8 安装的安装文件夹替换JDK_INST
。
现在,您可以在控制台窗口中检查安装。将用户目录(当前目录)更改为 GlassFish 安装文件夹,然后使用asadmin
启动服务器:
REM Windows:
chdir GLASSFISH_INST_DIR
bin\asadmin start-domain
# Linux:
cd GLASSFISH_INST_DIR
bin/asadmin start-domain
输出应该是这样的:
Waiting for domain1 to start .
Successfully started the domain : domain1
domain Location: [...]/glassfish/domains/domain1
Log File: [...]/glassfish/domains/domain1/logs/server.log
Admin Port: 4848
Command start-domain executed successfully.
您还可以检查指示的日志文件,以查看启动是否正常工作。您可以在http://localhost:4848
打开浏览器,查看网络管理员是否有空(应该有空)。
一旦您确认服务器正确启动,如果您愿意,您可以停止它。为此,请输入以下内容:
REM Windows:
bin\asadmin stop-domain
# Linux:
bin/asadmin stop-domain
Note
在本章的其余部分,我们假设您输入了cd GLASSFISH_INST_DIR
来切换到 GlassFish 安装目录。我也会停止区分 Windows 和 Linux,写bin/asadmin
,在 Windows 上应该是bin\asadmin.bat
。
GlassFish 服务器有三个管理前端:
-
外壳(或 windows 命令提示符)前端
-
用于浏览器访问的 GUI 前端
-
一个 REST HTTP 前端
GlassFish Shell 管理
shell 前端通过bin/asadmin
脚本工作,您可以从 shell(或 windows 命令提示符)调用该脚本。这个命令极其强大;它包含数百个选项和子命令。我们没有在这里全部列出,所以要获得完整的在线列表,请在您最喜欢的搜索引擎中输入“oracle glassfish server 管理指南”。
首先,asadmin
命令也提供了“帮助”功能。要查看它,请输入以下内容之一:
bin/asadmin help
bin/asadmin -?
其中第一个变体(help
)开启了一个更传呼。要列出所有子命令,请输入以下内容:
# Note: server must be running!
bin/asadmin list-commands
要查看特定子命令的帮助,您可以编写以下内容之一:
bin/asadmin help <SUB-COMMAND>
bin/asadmin -? <SUB-COMMAND>
在这里用子命令的名称代替<SUB-COMMAND>
。
Note
为了使许多子命令正常运行,服务器也必须运行。在下面的讨论中,我们假设在您发出任何子命令之前,服务器已经启动。
还有一个多模式会话,在那里打开一个特殊的子外壳。在这个子 shell 中,您可以直接输入子命令,而无需在前面加上bin/asadmin
。要启动多模式会话,请输入以下不带参数的内容:
bin/asadmin
您也可以使用multimode
子命令启动多模式会话:
bin/asadmin multimode
该子命令允许一个可选的--file <FILE_NAME>
参数,该参数使指定的文件作为子命令列表被读入,并按顺序执行:
bin/asadmin multimode --file commands_file.txt
文件路径相对于当前工作目录。在下面的段落中,我们展示了最有用的选项和子命令的列表。最有用的一般选项如表 2-1 所示。你在bin/asadmin --host 192.168.1.37 list-applications
中添加它们。
表 2-1
常规选项
|[计]选项
|
描述
|
| — | — |
| --host <HOST>
| 指定运行服务器的主机。如果不指定,将使用localhost
。 |
| --port <PORT>
| 管理端口。默认为4848
|
| --user``<USER_NAME>
| 使用指定的用户向服务器进行身份验证。如果您限制对asadmin
实用程序的访问,请使用此选项。默认为admin
用户。 |
| --passwordfile``<FILE_NAME>
| 如果您限制了对asadmin
实用程序的访问,并希望防止提示用户密码,您可以指定一个包含密码信息的文件。详见bin/asadmin -?
的输出。 |
关于可以添加到asadmin
命令的选项的完整列表,参见bin/asadmin -?
的输出。
表 2-2 给出了从服务器查询各类信息的子命令。您在bin/asadmin list-applications
中输入它们(显然,如果您还没有安装任何应用,列表将是空的)。
表 2-2
查询信息
|子命令
|
描述
|
| — | — |
| version
| 输出 GlassFish 服务器版本。 |
| list-applications
| 列出服务器上部署和运行的所有应用。 |
| list-containers
| 容器包含某种类型的组件(模块,如果你喜欢的话)。使用此子命令列出服务器中运行的所有容器。 |
| list-modules
| 列出服务器中运行的所有 OSGi 模块。我们不会在这本书里讨论 OSGi,但是如果你感兴趣的话,GlassFish 集成了一个 Apache Felix OSGi 模块管理系统。您还可以通过名为“Gogo”的 OSGi shell 来管理 GlassFish 组件,这需要更多的配置工作才能运行。 |
| list-commands
| 列出所有子命令。如果添加--localonly
,服务器不一定要运行,只会列出可以在服务器不运行时发出的子命令。 |
| list-timers
| 显示所有计时器。在这本书里我们不谈论计时器。 |
| list-domains
| 列出所有域。在本书中,我们将使用预先安装的默认域,名为domain1
,因此这将是这里显示的唯一条目。 |
在您执行 GlassFish 服务器的安装之后,将会有一个名为admin
的管理用户,没有密码。没有密码使管理任务变得容易,但也会使您的服务器不安全。要解决这个问题并给用户admin
一个密码,请输入以下内容:
bin/asadmin change-admin-password
然后你会被要求输入真正的密码,密码是空的,所以只需按回车键。然后输入新密码两次。
一旦admin
用户有了密码,您就必须输入大多数asadmin
子命令的密码。
启动域意味着启动 GlassFish 服务器。我们可以在一个 GlassFish 服务器中有几个域,但是多域设置留给了高级用户,所以我们将使用默认安装的单个domain1
域。
要启动、停止或重新启动 GlassFish 服务器,请输入以下命令之一:
bin/asadmin start-domain
bin/asadmin stop-domain
bin/asadmin restart-domain
所有三个子命令都将可选的域名作为参数(例如,domain1
或domain2
),但是因为我们只有一个默认域,所以这里可以省略它。
要查看服务器的正常运行时间,即自默认域启动以来经过的时间,请输入以下内容:
bin/asadmin uptime
Jakarta EE GlassFish 服务器带有内置数据库。这对于开发来说很方便,尽管您可能不会将该数据库用于生产设置。这个数据库是一个 Apache Derby 数据库。当 GlassFish 服务器启动时,它默认不运行。相反,要启动和停止数据库,请输入以下内容:
bin/asadmin start-database
bin/asadmin stop-database
其中数据库端口默认为1527
。
GlassFish GUI 管理
启动 GlassFish 服务器后,会提供一个 GUI 控制台,您应该使用它在浏览器中打开以下 URL:
http://localhost:4848
然后 GUI 将显示出来,如图 2-1 所示。
图 2-1
浏览器 GUI 管理
这里我们不讨论 GUI 管理的细节。然而,我们将在本书中偶尔使用和描述它,右上角的帮助按钮是您自己进行实验和研究的良好起点。
Note
您可以在终端中输入的许多操作在管理 GUI 中都有对应的内容。
GlassFish REST 接口管理
GlassFish Jakarta EE 8 服务器提供了一个 REST 接口,您可以使用它来调查和控制服务器。例如,您可以发出以下命令通过 REST 查看域日志:
curl -X GET -H "Accept: application/json" \
http://localhost:4848/monitoring/domain/view-log/details
Note
要实现这一点,必须在您的系统上安装curl
实用程序。或者,您可以使用任何其他 REST 客户端(Firefox REST-client 插件、Eclipse 的 REST 客户端等等)。
我们研究了几个例子。要找到关于这个接口的更深入的信息,请在您喜欢的搜索引擎中输入“rest interface administer glassfish”。此外,我们使用jq
工具提供生成的 JSON 数据的漂亮的格式输出。对于jq
,有 Linux 和 Windows 的安装程序。
管理 REST 界面分为两部分,分别用于配置和监控:
http://host:port/management/domain/[path]
http://host:port/monitoring/domain/[path]
对于普通的 GlassFish 安装,主机是localhost
,端口是4848
。对于[path]
,您必须替换一个资源标识符。例如,要查看日志条目,请输入以下内容:
curl -X GET -H "Accept: application/json" \
http://localhost:4848/management/domain/view-log
(如果在一行中输入,请删除反斜杠。)
REST 接口非常广泛。您可以使用 REST 的GET
动词查询许多属性,并且可以使用POST
或PUT
改变资源。首先,您可以研究 REST 功能的详细输出,一旦您输入以下内容,就会得到这些输出:
curl -X GET -H "Accept: application/json" \
http://localhost:4848/management/domain
例如,输出将包括以下内容:
"commands": [
...
{
"path": "list-commands",
"method": "GET",
"command": "list-commands"
},
{
"path": "restart-domain",
"method": "POST",
"command": "restart-domain"
},
{
"path": "uptime",
"method": "GET",
"command": "uptime"
},
{
"path": "version",
"method": "GET",
"command": "version"
}
...
]
还有很多其他的。要查看版本和正常运行时间,请输入以下内容:
curl -X GET -H "Accept: application/json" \
http://localhost:4848/management/domain/version | jq .
curl -X GET -H "Accept: application/json" \
http://localhost:4848/management/domain/uptime | jq .
如果您使用浏览器并在那里输入 REST URLs,您将获得更多关于 REST 资源的信息。如果您打开浏览器并输入http://localhost:4848/management/domain/version
,您将得到这个 CURL 输出的 HTML 变体。两者都告诉我们关于孩子的资源。
例如,这段代码向我们展示了与已安装的应用相关的命令:
curl -X GET -H "Accept: application/json" \
http://localhost:4848/management/domain/applications |
jq .
它告诉我们,对于实际的列表,我们必须输入以下内容:
curl -X GET -H "Accept: application/json" \
http://localhost:4848/management/domain/applications/ list-applications |
jq .
(在applications/
之后没有换行。)它告诉我们属性。为了获得更详细的输出,我们可以添加一个?long=true
,如:
curl -X GET -H "Accept: application/json" \
http://localhost:4848/management/domain/applications/
list-applications?long=true | jq .
使用预装的 Java 服务器
Java MVC 应用通常驻留在 WAR 文件(以.war
结尾的 ZIP 文件)中,因此它们可以安装在任何 Jakarta EE 兼容的服务器上。
因此,您不必使用 GlassFish。在本书中,我们将使用 GlassFish,但是如果您喜欢不同的 Jakarta EE 8 服务器,您可以使用它。当然,您必须通过查阅手册来学习如何管理该服务器。
Note
如果您的目标是专有服务器,通常不建议从不同供应商的不同产品开始开发。您至少应该尝试使用同一服务器的免费版本进行开发,或者尝试获得开发人员许可证。不过,要学习 Jakarta EE 8,首先使用 GlassFish,然后切换到不同的产品或供应商是一种合理的方法。
学习面向企业应用的 Java
为了学习 Java 语言(或标准版 API)或提高技能,您可以在大量的书籍和在线资源中进行选择。一个很好的起点是 Oracle 的官方 Java 教程,可以在
https://docs.oracle.com/javase/tutorial/
现实世界中的公司项目可能需要您查看 Java EE/Jakarta EE 技术栈中的其他技术。还有一个企业版 Java EE/Jakarta EE 的教程,您可以在以下网址找到:
https://javaee.github.io/tutorial/toc.html
你可能还想参考同一作者的书入门 Jakarta EE:企业版 Java:从新手到专业人士 (ISBN: 978-1484250785)。在这里,我们主要讨论 Java MVC,并且只在适当和需要的地方处理其他 Java EE/Jakarta EE 技术。
RESTful 服务
有一个很好的理由来简要地谈谈 JAX-RS,尽管它是本书范围局限于 Java MVC 的一个例外。JAX-RS 是 Java EE/Jakarta EE 处理 RESTful 服务的子技术。事实上,Java MVC 位于 JAX-RS 之上,这是框架程序员的聪明决定。它不仅允许开发人员让 Java MVC 非常干净地与 Java EE/Jakarta EE 框架的其余部分集成,还提供了一个简单的线索,说明如何使用 AJAX 和 JSON 数据片段混合 Java MVC 开发技术和更细粒度的客户端-服务器通信。
REST 是表述性状态转移的缩写。它是 web 相关操作的一种架构风格。客户端使用一组预定义的数据操作或 HTTP 方法— GET
、POST
、PUT
和DELETE
(以及更多)与服务器通信。由于不涉及任何状态,客户端使用动词GET
、DELETE
、POST
、PUT
等中的一个进行通信,并且在服务器执行完操作和/或返回数据后,服务器会立即忘记通信步骤。“表述性状态转移”这个名称源于这样一个事实:从客户端的角度来看,从服务器查询的数据的表示在通信步骤之间是变化的(或者可能会变化)。
自从 web 诞生以来,通信动词就一直是 HTTP 规范的一部分。更详细地说,我们有以下动词:
-
GET
:用于检索资源。资源是由 URIs 标识的,所以通信可以用类似于GET
http://some.server.com/myclub/member/37
的东西来描述。一个GET
操作不允许改变任何数据(除了访问统计之类的),而且必须是幂等的。这意味着第二个GET
使用相同的 URI,在这两个GET
之间没有中间操作,必须返回完全相同的数据。注意GET
操作被广泛滥用于任何类型的操作,包括更改数据。有了休息,我们回到了根本,数据不能改变。 -
DELETE
:用于删除信息。同样,所讨论的资源由一个 URI 寻址,所以你写DELETE
http://some.server.com/myclub/member/37
。ADELETE
必须是幂等的,这意味着使用相同的 URI 再次删除不能改变数据。在这种情况下,第二个DELETE
当然是多余的;删除已经删除的内容不应该做任何事情。作为 REST 关于第二个DELETE
的特性,服务器不能返回错误消息,而是忽略请求。 -
POST
:用于发布新信息。用户提交表单时通常会出现这种情况。POST
s 不是等幂的,所以使用相同数据的第二个 post 将导致服务器端的第二个数据集。一个帖子可以用POST
http://some.server.com/mycl
ub/member/37 [data]
来描述,其中[data]
代表传输的数据,通常以 XML 或 JSON 的形式在传输的消息体中传递。 -
PUT
:用于存储数据。如果数据描述的资源已经存在,资源将根据数据进行更改。如果它不存在,服务器可能会决定像指定了一个POST
一样工作。PUT
是等幂的,PUT
ting 再次使用相同的输入数据不会改变服务器上的数据。
其他动词在实际应用中不太常用。HEAD
用于检索关于资源的元数据(关于资源的信息,但不是资源本身)。使用一个TRACE
,您可以看到数据在到达服务器的途中发生了什么。这更多的是一种技术操作,并不特别关注数据负载。一个PATCH
就像一个PUT
有部分数据。拥有完整信息的PUT
比PATCH
更常用。OPTIONS
动词请求服务器拥有专用资源的能力(比如告诉服务器可以用资源做什么)。一个CONNECT
用于在服务器端建立透明隧道。同样,这更多的是一种技术设施,并不透露任何有关传输数据。
要定义 REST 端点,您需要编写一个 Java 类,并在类和/或方法级别添加注释javax.ws.rs.Path
。例如,考虑一个以 JSON 形式返回当前日期和时间的 REST 控制器:
package book.javavmc.restdate;
import java.time.ZonedDateTime;
import javax.ws.rs.*;
/**
* REST Web Service
*/
@Path("/d")
public class RestDate {
@GET
@Path("date")
@Produces("application/json")
public String stdDate() {
return "{\"date\":\"" + ZonedDateTime.now().toString() +
"\"}";
}
}
@Path
注释合并,所以最后,我们得到一个端点 URL,比如http://localhost:8080/theAppName/d/date
。
您将很快开始开发您的第一个 Java MVC 应用。这就是为什么我向您展示第一个代码片段,而没有解释如何构建和部署它。Java MVC 控制器看起来非常相似:
package book.javavmc.somecontroller;
import java.util.List;
import javax.inject.Inject;
import javax.mvc.Controller;
import javax.mvc.Models;
import javax.ws.rs.*;
@Path("/pets")
@Controller
public class PetshopController {
@Inject
private Models models;
@GET
public String showIndex() {
final List<Pet> pets = ...;
models.put("pets", pets);
return "index.jsp";
}
}
你可以看到我们再次使用javax.ws.rs.Path
来定义一个端点。我们稍后将看到 Java MVC 和 JAX-RS 之间的主要区别是@Controller
注释,以及 action 方法返回下一个视图页面的名称而不是数据。
Note
如果在您最喜欢的搜索引擎中输入“jax-rs ”,您将会找到更多关于 JAX-RS 的在线信息,包括官方规范。
练习
-
练习 1: 描述 JSE 和 Java EE/Jakarta EE 的关系。
-
练习 2: 对还是错?Java MVC 可以直接在 PC 或服务器的操作系统中运行。
-
练习 3: 对还是错?Java MVC 是一个 Jakarta EE 服务器。
-
练习 4: 对还是错?Jakarta EE 是 Java EE 的竞争对手。
-
练习 5: 对还是错?OpenJDK 8 和甲骨文的 JSE 8 没有区别。
-
练习 6: 对还是错?GlassFish 可用于商业产品,无需支付许可费用。
-
练习 7: 为什么我们在本书中使用 GlassFish?
-
练习 8: 对还是错?清除是一个 HTTP 动词。
-
练习 9: 描述 Java MVC 和 JAX-RS 的关系。
摘要
Java MVC 伴随着 Java 企业版服务器(Java EE 或 Jakarta EE)提供的基础设施。在企业环境中,像 Java 这样的编程语言和软件平台必须满足对企业运营很重要的几个需求。它必须能够连接到一个或多个数据库,可靠地与同一公司中其他基于 It 的系统或相关业务建立通信,并且必须足够强大,能够可靠地处理输入,基于输入和数据库数据执行计算,并向客户提供适当的输出。
Jakarta EE 8 服务器运行在 Java 上并依赖于 Java。Java 如此成功有几个原因:
-
同一个 Java 程序可以在不同的操作系统上运行。
-
Java 运行在沙箱环境中。这提高了执行安全性。
-
Java 可以很容易地用自定义库进行扩展。
-
Java 语言的扩展非常缓慢。虽然缓慢的发展意味着最新版本中可能缺少新的有用的语言结构,但它有助于开发人员轻松跟踪新特性,并在长期运行的项目中彻底过渡到新的 Java 版本。此外,除了少数例外,Java 版本是向后兼容的。
-
Java 包含一个垃圾收集器,可以自动清理未使用的内存。
Java 还在继续发展。虽然在写这本书时 Jakarta EE 的最新版本是 8,底层 Java 标准版也是 8,但您可以下载的最新 JavaSE (JSE)版本是 13。我们不会在本书中讨论 JavaSE 版本 9 或更高版本。
描述 Java EE/Jakarta EE 各部分的规范说明了每个部分能做什么以及如何做,并且它们跟踪新版本。Java EE/Jakarta EE 8 包括子技术,这些子技术也通过确切的版本号进行了详细描述。这些规范是由一个社区进程来处理的,如果供应商想要说他们的服务器产品符合 Jakarta EE 的某个版本(或其前身之一,JEE 或 J2EE),他们必须通过测试。
Java 企业版最初是由太阳微系统公司开发的,名为 J2EE。在 2006 年,命名和版本化模式被更改为 JEE,在 J2EE 版本 1.4 之后是 JEE 版本 5。从那以后,主要的更新发生了,版本 JEE 6,JEE 7 和 JEE 8 都发布了。2010 年,太阳微系统公司被甲骨文公司收购,在甲骨文公司旗下,发布了 JEE 7 和 JEE 8 版本。2017 年,甲骨文公司向 Eclipse 基金会提交了 Java EE,名称改为 Jakarta EE 8。
在本书中,我们将讨论在 GlassFish 服务器开源版本 5.1 中运行 Java MVC。由于 Jakarta EE 8 的性质,虽然您必须花费大量时间来更改管理工作流,但是转换到其他服务器总是可能的。GlassFish 提供了三个管理界面——用于 shell 或控制台的命令行工具、web 管理员 GUI 和管理 REST 界面。
Java MVC 位于 JAX-RS 之上,这是框架程序员的聪明决定。它不仅允许 Java MVC 非常干净地与 Java EE/Jakarta EE 框架的其余部分集成,还提供了一个简单的线索,说明如何使用 AJAX 和 JSON 数据片段混合 Java MVC 开发技术和更细粒度的客户端-服务器通信。REST 控制器和 Java MVC 控制器看起来非常相似。
在下一章,我们处理适合本书和其他 Java MVC 项目的开发工作流。
三、开发工作流程
在这一章中,我们将讨论开发技术、过程和工具,你可以在本书的例子和任何使用 Java MVC 的后续项目中使用它们。
使用 Gradle 作为构建框架
Gradle 是一个现代的构建框架/构建自动化工具。它提供了一种纯粹的声明式配置风格,但是如果需要的话,您也可以以 Groovy(或 Kotlin)脚本片段的形式添加命令式构建代码。
Note
最佳实践表明,对于构建脚本来说,声明性编程(它告诉构建脚本必须做什么,而不是它应该如何做)优于命令性编程(精确的逐步说明)。
在本书的其余部分,我们使用 Gradle 进行构建自动化,因为它具有非常简洁的构建配置,可以从控制台(Linux bash 和 Windows 控制台)和像 Eclipse 这样的 ide 内部使用。Gradle 构建脚本可以只有三行,但也可以包含任意长的代码。我们将使用 Gradle 作为工具,在本章的稍后部分将描述它的更多特性。
Caution
如果要使用 OpenJDK 8 构建和运行应用,必须添加一个有效的cacerts
文件。只需安装 OpenJDK 版本 10,然后复制OpenJDK10-INST-DIR/lib/security/cacerts to OpenJDK8-INST-DIR/lib/security/cacerts
文件即可。
使用 Eclipse 作为 IDE
Eclipse 是一个 IDE(集成开发环境),提供了大量有助于开发 Java 企业项目的功能。它是免费提供的,您可以免费将其用于商业和非商业项目。
Eclipse 可以通过插件进行扩展,很多插件都是社区开发的,可以免费使用。然而,插件也可能来自供应商,你可能需要购买许可证才能使用它们。在本书中,我们将只使用免费插件。如果你想尝试专有插件,在这种情况下,这可能会促进你的开发,请访问位于 https://marketplace.eclipse.org
的 Eclipse marketplace,并咨询每个 plugin . Eclipse . org development,它们都有使用许可
安装 Eclipse
Eclipse 有几种变体。要下载其中任何一款,请前往 https://www.eclipse.org/downloads/
或 https://www.eclipse.org/downloads/packages/
。在本书中,我们将使用面向企业 Java 开发人员的 Eclipse IDE 变体。
Note
如果您选择下载安装程序,您将被要求提供变体。要从一开始就选择企业版本,请单击下载包链接,并在下一页选择企业版本。
在本书中,我们将使用 Eclipse 版本 2020-03,但是您也可以使用更高的版本。请记住,如果你陷入困境而没有明显的解决方案,降级到 Eclipse 2020-03 是一个选择。
使用任何适合您需要的安装文件夹。插件安装和版本升级放在您选择的文件夹中,因此请确保适当的文件访问权限。在我的 Linux 机器上,我通常将 Eclipse 放在一个名为:
/opt/eclipse-2019-09
(或者你有的任何版本。)然后,我让它对我的 Linux 用户可写:
cd /opt
USER=... # enter user name here
GROUP=... # enter group name here
chown -R $USER.$GROUP eclipse-2019-09
这改变了 Eclipse 安装的所有文件的所有权,这对于单用户工作站是有意义的。相反,如果 Eclipse 有不同的用户,您可以创建一个名为eclipse
的新组,并授予该组写访问权限:
cd /opt
groupadd eclipse
chgrp -R eclipse eclipse-2019-09
chmod -R g+w eclipse-2019-09
USER=... # enter your username here
usermod -a -G eclipse $USER
chgrp ...
命令改变组所有权,而chmod ...
命令允许所有组成员进行写访问。usermod ...
命令将特定用户添加到新组中。
Note
你需要root
来执行这些命令。还要注意的是,usermod
命令不会影响 PC 上当前活动的窗口管理器会话。例如,您必须重新启动您的系统,或者根据您的发行版,注销并重新登录,该命令才能生效。
最后一步,您可以提供一个指向 Eclipse 安装文件夹的符号链接:
cd /opt
ln -s eclipse-2019-09 eclipse
这使得在您的系统上切换不同的 Eclipse 版本更加容易。
在 Windows 系统上,安装程序会为您设置访问权限,任何普通用户通常都可以安装插件。这取决于 Windows 版本和系统配置。企业环境通常有更细粒度的访问权限,普通用户不能安装插件和升级,超级用户用于管理目的。可以使用 Windows 访问权限管理来配置这些权限。
配置 Eclipse
启动时,Eclipse 使用系统上安装的默认 Java 版本。万一它找不到或者您安装了几个 Java 版本,您可以明确地告诉 Eclipse 选择哪个 Java。为此,请打开此文件
ECLIPSE-INST/eclipse.ini
并添加两行:
-vm
/path/to/your/jdk/bin/java
在-vmargs
线的正上方:
...
openFile
--launcher.appendVmargs
-vm
/path/to/your/jdk/bin/java
-vmargs
...
Note
eclipse.ini
文件的格式取决于 Eclipse 版本。检查 https://wiki.eclipse.org/Eclipse.ini
的正确语法。在该网站上,您还可以找到指定 Java 可执行文件路径的精确说明。这里显示的语法是针对 Eclipse 2020-03 的。
在 Windows PCs 上,您可以按如下方式指定路径:
...
-vm C:\path\to\your\jdk\bin\javaw
...
不要使用转义反斜杠,就像你对 Java 相关文件所期望的那样。
为了看 Java Eclipse 用哪个版本运行(不是构建项目!),启动 Eclipse,然后导航到 help➤about eclipse ide➤installation details➤configuration 选项卡。在窗格中,找到以java.runtime.version=....
开头的行
添加 Java 运行时
Eclipse 本身是一个 Java 应用,在上一节中,我们学习了如何告诉 Eclipse 根据自己的兴趣选择哪个 Java 版本。对于开发本身,您必须告诉 Eclipse 使用哪个 Java 版本来编译和运行它托管的应用。
为此,请注意您想要用于 Eclipse 开发的所有 JDK 安装的路径。然后,启动 Eclipse。
Note
当您启动 Eclipse 时,它会要求您提供一个工作空间。该文件夹可以包含几个不同或相关的项目。您可以选择一个现有的工作区,或者使用一个新的文件夹来创建一个空的工作区。
在月蚀里面,去 window➤preferences➤java➤installed jres。通常 Eclipse 足够聪明,可以自动提供它自己启动时使用的 JRE。如果这对你来说足够了,你在这里什么都不用做。否则,单击添加…按钮来注册更多的 JRE。在随后的对话框中,选择标准虚拟机作为 JRE 类型。
Note
对于 Java 8,除了顾名思义,您必须提供 JDK 安装的路径,而不是严格意义上的 JRE 安装。
选中复选框以标记您的主 JRE。不要忘记单击“应用”或“应用并关闭”按钮来注册您的更改。
添加插件
Eclipse 可以通过许多有用的插件进行扩展。有些是你开发所必需的,有些只是改进你的开发工作流程。在这本书里,我们不会使用太多多余的插件,我会在需要的时候提供插件安装说明。
作为例外,我们现在将安装一个 Gradle 插件。稍后我们将看到我们可以从控制台使用 Gradle,但是 Eclipse 中的 Gradle 插件允许我们直接从 ide 内部使用 Gradle。打开 Help➤Install 新软件…并在对话框中输入 Eclipse Buildship (Gradle)和 http://download.eclipse.org/buildship/updates/latest
。选择所有功能并完成向导。
Eclipse 日常使用
Eclipse 提供了许多函数,您可以通过打开内置的帮助来了解它们。为了给你一个起点,下面是帮助你最大限度地利用 Eclipse 的技巧:
-
您可以通过将光标放在标识符上并按 F3 键来获得标识符的定义。这适用于变量(导航到它们的声明)和类/接口(导航到它们的定义)。您甚至可以用这种方式检查被引用的和 Java 标准库类。Eclipse 将下载源代码并显示代码。这是通过查看代码来深入了解库的好方法。
-
若要快速查找资源,如文件、类或接口,请按 Ctrl+Shift+R。
-
开始输入代码并按 Ctrl+Space,Eclipse 将显示如何完成输入的建议。例如,键入
new SimpleDa
,然后按 Ctrl+Space。提供的列表将包含SimpleDateFormat
类的所有构造函数。更好的是,您可以通过键入new SiDF
并按 Ctrl+Space 来使其更短,因为 Eclipse 会猜测出缺少的小写字母。另外一个好处是,您不必为以这种方式引入的类和接口编写import
语句。Eclipse 将为您添加import
s。 -
让 Eclipse 通过按 Shift+Ctrl+O 为所有尚未解析的类添加
import
(将 O 视为“组织导入”)。 -
通过按 Ctrl+Alt+F 来格式化您的代码。这也适用于 XML 和其他文件类型。
-
让 Eclipse 通过在类型标志符上按 F4 向您显示超类型和子类型。
-
使用 F5 更新 Project Explorer 视图,以防在 Eclipse 外部添加或删除文件。
-
对于新的 Eclipse 安装,通过选择 Window➤Show View➤Other 打开 Problems 视图…➤General➤Problems.这将很容易为您指出 Eclipse 检测到的任何问题(编译器问题、配置问题等等)。
-
从 Window➤Show View➤Other 打开任务视图…➤General➤Tasks 获得您在代码注释中输入的所有“TODO”事件的列表。
-
如果“TODO”对您来说不够精细,您可以通过右键单击代码编辑器左侧任意位置的竖条来添加书签。书签随后会在书签视图中列出。
更多关于 Gradle 的信息
有了 Eclipse 和 Gradle 插件,我们可以提高对 Gradle 框架的了解。为了保持简单,我们从一个非常简单的非 Java MVC 项目开始。
Note
您可以在 https://docs.gradle.org/current/userguide/userguide.html
找到 Gradle 用户手册。
一个基本的梯度项目
为了更多地了解 Gradle,我们构建了一个简单的EchoLibrary
库,只有一个类和一个方法,将一个字符串打印到控制台。启动 Eclipse,会要求您提供一个工作区。选择您选择的任何文件夹。
Note
您可以将本书中的所有示例项目添加到一个名为JavaMVCBook
的工作空间中,以便将所有东西放在一起,但这取决于您。
去 File➤New➤Other…➤Gradle➤Gradle 项目。选择EchoLibrary
作为项目名称。您可以使用 Gradle 项目选项的默认设置。完成后,新建项目向导准备项目,并向保存 Gradle 配置的项目添加一些文件。
我们要做的下一件事是确保项目可以使用现有的 JSE 安装。Gradle 项目向导可能会尝试使用不存在的 JRE,并且会出现一个错误标记。见图 3-1 。
图 3-1
项目错误标记(红色感叹号)
要修复这种不匹配或检查是否使用了正确的 JRE,请右键单击该项目,然后选择“Properties➤Java”“构建 Path➤Libraries.”见图 3-2 。
图 3-2
JRE 不匹配
如果不匹配,请单击“类路径”,然后选择“添加库”,删除无效条目…➤JRE 系统图书馆。添加您向 Eclipse 注册的版本 8 JRE。然后单击应用并关闭按钮。
接下来,右键单击src/main/-java
➤New➤Package.,添加一个名为book.javamvc.echo
的包在包内,添加一个包含以下内容的Echo
类:
package book.javamvc.echo;
public class Echo {
public void echo(String msg) {
System.out.println(msg);
}
}
Gradle 主要概念
默认情况下,Gradle 在项目的根文件夹中使用一个名为build.gradle
的中心构建文件。在我们开始讨论这个文件之前,我们首先需要了解 Gradle 的主要概念:
-
Gradle 有一个核心,为构建相关的活动提供基础设施。活动本身存在于 Gradle 插件中,这些插件需要在构建文件中指定,并且运行在核心之上。对于每个项目,您可以指定哪些插件将用于 Gradle 构建。有编译 Java 类的插件;用于将工件打包成 ZIP、WAR 或 EAR 文件;用于运行应用;以及将应用发布到 Maven 存储库中。还有各种分析插件,IDE 集成插件,实用插件等等。你当然可以开发自己的插件。
-
插件执行任务。例如,Java 插件有一个用于编译 Java 类的
compileJava
任务和一个用于压缩和收集几个编译好的类的jar
任务。 -
每个 Gradle build 由一个初始化、一个配置和一个执行阶段组成。在初始化阶段,Gradle 确定子项目是否需要包含在构建中。(我们后面再讲子项目。)在配置阶段,Gradle 评估依赖关系并构建任务图,任务图包含构建需要执行的所有任务。所有对象上的配置总是在每个 Gradle 版本中运行。这是很重要的一点,也是刚开始使用 Gradle 的人容易犯的错误。这意味着对于一个任务执行,看似完全不相关的任务的配置也被调用。因此,出于性能原因,任何任务的配置都应该非常快。任务的配置不应该做任何依赖于任务是否实际执行的事情。在执行阶段,任务实际上完成了它们的工作(编译、移动、压缩等等)。
Note
许多 Gradle 手册和教程在开始时都围绕用户定义的任务,这实际上对 Gradle 的初学者有一点误导。在许多甚至更大的项目中,相应的build.gradle
文件指定和配置插件,但几乎从不直接处理任务。从技术的角度来看,任务是重要的,但是通过谈论不同的阶段和插件架构来开始 Gradle 的介绍会导致对 Gradle 功能的更彻底的理解。
标准梯度项目布局
所有 Gradle 插件默认期望的项目布局如下:
src
|- main
| |- java
| | |- <java source files>
| |- resources
| |- <resource files>
|
|- test
|- java
| |- <java source files>
|- resources
|- <resource files>
build
|- <any files built by Gradle>
build.gradle <Gradle build file>
settings.gradle <(Sub-)Project settings>
gradle.properties <optional project properties>
Note
如果你了解 Maven 构建框架,src
文件夹的布局对你来说会很熟悉。
我们将在后面的章节中学习如何改变项目结构。
中央 Gradle 构建文件
Eclipse 中的 Gradle 项目向导在项目的根文件夹中创建了一个示例文件build.gradle
。对于任何 Gradle 项目,包括不使用 Eclipse 的项目,这都是主要的构建文件。Eclipse 插件提供了一个带有一些示例条目的基本构建文件,但是您当然可以从头开始构建这个文件。
Caution
Eclipse Gradle 插件有时会有一个关于何时何地显示构建文件的有趣想法。如果在项目浏览器中找不到该文件,请打开 Gradle 任务视图并右键单击该项目,然后选择“打开 Gradle 构建脚本”选项。
构建文件通常从定义要使用的插件开始,然后配置插件。如果需要,带有操作说明的用户定义的任务也可以转到构建文件。还可以向现有任务中添加 Groovy 或 Kotlin 代码,这使您能够根据需要微调插件。
Note
在本书中,我们只展示 Groovy 代码用于 Gradle 构建。Groovy 是动态类型的,正因为如此,与静态类型的 Kotlin 相比,它可能更简洁一些。此外,Groovy 专门是一种脚本语言,因此它配备了许多用于脚本目的的实用程序,而 Kotlin 是一种大规模计算机语言,是 Java 的竞争对手。
插件通常对它们的默认值有一个非常精确和合理的想法,所以你的项目没有太多需要配置的。因此,构建文件可能相当小。这种“约定胜于配置”的风格并不是 Gradle 的发明,但是 Gradle——以优雅为设计目标——欣然接受了这一理念。
回到EchoLibrary
示例项目。我们关闭向导创建的示例文件build.gradle
,并用以下内容覆盖其内容:
// The EchoLibrary build file
plugins {
id 'java-library'
}
java {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
repositories {
jcenter()
}
dependencies {
testImplementation 'junit:junit:4.12'
}
前三行plugins { id 'java-library' }
指定我们想要使用java-library
插件。名字告诉所有人,我们事实上想要建立一个 Java 库,但是你可以在用户手册的插件部分了解细节。
java { sourceCompatibility = JavaVersion.VERSION_1_8; targetCompatibility = JavaVersion.VERSION_1_8 }
设置指定了我们库的 JRE 版本。可能的值可以在org.gradle.api.JavaVersion
类中查找,但是在那里你不会发现任何令人惊讶的东西(JDK 13 = JavaVersion.VERSION_1_13
等等)。
Note
Gradle 使用你的操作系统的默认 JDK 来编译类。你应该而不是使用你的 Gradle 项目配置来设置 JDK 路径,因为那样你会引入一些不必要的依赖。毕竟,JRE 13 可以很好地处理 JRE 8 文件,也许其他开发人员也想在他们自己的系统上使用相同的构建脚本。相反,您可以在 Gradle 调用之前,更改操作系统的JAVA_HOME
环境变量来指定一个 JDK 路径。
repositories { jcenter() }
行指出 Gradle 将在哪里加载你的项目所依赖的库。jcenter()
指向 Bintray 的 JCenter,但是您也可以将google()
用于 Android 项目,将mavenCentral()
用于 Maven Central。或者,您可以指定一个定制的 URL,如repositories { maven { url "
http://my.company.com/myRepo
" } }
,这对于私有或公司所有的存储库来说很方便。请参阅 Gradle 手册中名为“声明存储库”的部分
dependencies
部分指出了我们的项目需要哪些库。对于EchoLibrary
的例子,我们没有对外部库的依赖,但是对于单元测试,我们没有在这种情况下编写单元测试,但是对于有倾向的读者来说,这是一个很好的练习,我们添加了对 JUnit 测试库的依赖。
所有其他设置——如源文件的位置、生成的 JAR 文件的命名方式和写入位置、存储和缓存下载的依赖项的位置等——都由插件默认设置处理。
这个带有少量设置的构建文件现在可以用来执行各种构建任务。
运行梯度任务
Gradle 中与构建相关的和用户触发的活动被称为任务。从处理的角度来看,Gradle 的主要目标是调用任务。
Eclipse Gradle 插件有一个 Gradle 任务和一个 Gradle 执行视图。此外,诊断输出会显示在标准控制台视图中。安装 Gradle 插件后,默认情况下会打开两个与 Gradle 相关的视图。见图 3-3 。
图 3-3
gradle views(分级视图)
如果这不适合你,那就去 Window➤Show View➤Other 吧…➤Gradle 打开了一个格雷尔视图。控制台视图可从 Window➤Show View➤Console.获得
“分级任务”视图以树形视图列出所有可用的任务;见图 3-4 。可以使用“视图”菜单(菜单中的小向下三角形)过滤显示的任务范围。如果您引入任何自定义任务,这是启用“显示所有任务”项目的好时机。否则,自定义任务不会显示在列表中。见图 3-5 。
图 3-5
分级任务视图菜单
图 3-4
分级任务视图树
Caution
如果更改项目结构,例如添加、删除或重命名自定义任务,则必须单击菜单中的“刷新所有项目的任务”按钮(弯曲的双箭头)。否则,视图不会反映这些变化。
为了从 Gradle Tasks 视图中运行 Gradle task,首先必须在树中找到它。根据您在树中查找位置的精确程度,您还可以使用菜单过滤器来查找任务。找到后,双击它运行任务。诊断输出,包括任何错误消息,都显示在 Gradle 执行和控制台视图中。
任务可能有控制其功能的选项参数。例如,有一个tasks
任务,它只列出了所有任务的某个子集。更准确地说,任务有一个group
属性,其中一个组叫做other
。如果在没有参数的情况下运行tasks
任务,则属于other
组的任务不会包含在输出中。要使用该命令显示所有任务,您必须添加一个--all
参数。要从 Eclipse 中这样做,请转到 Run➤Run 配置,导航到 Gradle Task,并添加一个新条目,如图 3-6 所示(单击添加按钮两次以输入tasks
和--all
)。单击 Run 并切换到控制台视图查看输出。
图 3-6
自定义分级任务运行配置
对于EchoLibrary
的例子,构建一个库 JAR 很可能是主要任务。你可以在build
部分找到。一旦运行了它,最终的 JAR 就会出现在build/libs
文件夹中。
Caution
可以从 Eclipse 项目视图中过滤掉build
文件夹。在这种情况下,如果你想看到它,打开小三角形的项目视图菜单,转到过滤器和定制,并从 Gradle Build 文件夹条目中删除复选标记。
Gradle 任务说明
任务由插件定义,插件也可能修改或覆盖其他插件定义的任务,所以任务和插件之间没有一对一的关系。此外,Gradle 本身还定义了独立于插件的任务。表 3-1 定义了您通常在 Java 项目中使用的大多数任务。
表 3-1
梯度任务
|名字
|
组
|
描述
|
| — | — | — |
| help
| help
| 显示帮助消息。 |
| projects
| help
| 显示项目名称并列出所有子项目的名称(如果适用)。我们将在本章后面讨论子项目。 |
| tasks
| help
| 显示项目中可运行的任务。您必须添加--all
选项来包含来自other
组的任务。要查看属于某个组的任务,添加--group <groupName>
选项(对于groupname
,使用build
、build setup
、documentation
、help
、verification
或other
)。 |
| dependencies
| help
| 独立于插件。计算并显示项目的所有依赖项。您可以使用它来确定项目依赖于哪些库,包括可传递的依赖项(间接引入的依赖项,作为依赖项的依赖项)。 |
| init
| build setup
| 添加当前目录所需的文件,作为 Gradle 构建的根目录。通常只在新项目开始时这样做一次。使用 Eclipse Gradle 插件和新的 Gradle 项目向导,可以自动调用这个任务。这项任务不依赖于 Gradle 插件被激活。 |
| wrapper
| build setup
| 将 Gradle 包装添加到项目中。然后,可以在没有在操作系统级别安装 Gradle 的情况下执行 Gradle 构建(必须安装 Java)。使用 Eclipse Gradle 插件和新的 Gradle 项目向导,可以自动调用这个任务。这项任务不依赖于 Gradle 插件被激活。 |
| check
| verification
| 生命周期任务。抽象地定义在基础插件中,并由激活的插件具体化。取决于test
,但可能会运行额外的检查。 |
| test
| verification
| 运行所有单元测试。 |
| assemble
| build
| 生命周期任务。抽象地定义在基础插件中,并由激活的插件具体化。任何生成发行版或其他可消费工件的插件都应该使组装任务依赖于它。在自定义任务中,您可以编写类似于assemble.dependsOn( someTask )
的代码。调用此任务会绕过任何测试。 |
| build
| build
| 生命周期任务。抽象地定义在基础插件中,并由激活的插件具体化。依赖于check
和assemble
任务,并因此执行所有测试,然后根据激活的插件生成一个发行版或其他可消耗的工件。 |
| clean
| build
| 生命周期任务。删除build
目录。如果您希望确保后续的构建执行所有的构建步骤,甚至是那些看似可以从先前的构建操作中重用的步骤,那么您可以调用此任务。您通常不会在日常工作中调用这个任务,因为如果设置得当,Gradle 应该能够确定哪些准备任务需要执行,哪些不需要执行(因为之前的构建)。 |
| classes
| build
| 任何插件,在其构建过程中的某个地方,需要构建该任务中提供的 Java 类。它的职责是从源代码的main
部分(不是测试类)创建 Java 类。 |
| testClasses
| build
| 类似于classes
任务,但是处理来自源代码的test
部分。 |
| jar
| build
| 组装一个包含来自main
部分的类的 JAR 档案。 |
| ear
| build
| 只针对 EAR 插件。从子项目(web 应用和 EJB)组装 EAR 档案。 |
| javadoc
| documentation
| 从main
部分为源代码生成 JavaDoc API 文档。 |
| compileJava
| other
| 从main
部分编译 Java 源代码。 |
| compileTestJava
| other
| 从test
部分编译 Java 源代码。 |
每个插件的文档也可能描述该插件特别感兴趣的更多任务。
外挂程式等级
如果您正在为 Java MVC 和其他 Java 和 JEE/Jakarta EE 相关项目进行开发,下面的列表显示了您最常遇到的插件:
-
Base :提供大多数构建通用的基本任务和约定。
-
Java :任何类型的 Java 项目。
-
Java 库:扩展
Java
插件,向消费者提供关于 API 的知识。 -
Java 平台:不包含任何源代码,但描述了一组通常一起发布的相互关联的库。
-
应用:隐式应用
Java
插件,并允许声明一个main
类作为应用入口点。 -
WAR :扩展了
Java
插件,增加了以 WAR 文件形式构建 web 应用的能力。 -
EAR :允许创建一个 EAR 文件。
-
Maven Publish :增加了将工件发布到 Maven 仓库的功能。
-
Ivy Publish :添加将工件发布到 Ivy 存储库中的功能。
-
分发:增加了简化工件分发的功能。
-
Java 库分发:增加了简化工件分发的功能,特别关注 Java 库。
-
Checkstyle :增加 Checkstyle 检查。
-
PMD :增加 PMD 支票。
-
JaCoCo :添加 JaCoCo 检查。
-
CodeNarc :增加 CodeNarc 检查。
-
签名:增加签名功能。
-
项目报告插件:允许生成构建报告。
你可以通过查看 Gradle 用户手册,特别是标题为“Gradle 插件参考”的章节来了解更多关于每个插件的信息。
关于存储库的更多信息
如果 Gradle 确定项目引用了这样的库,它就会从存储库中加载库。您可以在build.gradle
中的repositories { }
部分指定存储库:
repositories {
repoSpec1 (repository specification, see below)
repoSpec2
...
}
您可以使用以下内容作为存储库规范:
mavenCentral()
硬编码指向位于 https://repo.maven.apache.org/maven2/
的公开可用的 Maven 资源库
jcenter()
硬编码指向位于 https://jcenter.bintray.com/
的公开可用的 Maven 资源库
google()
硬编码指向位于 https://maven.google.com/
的公开可用的 Android 专用 Maven 库
flatDir { ... }
指向包含库的文件夹。准确的语法是flatDir { dirs '/path1/to/folder', '/path2/to/folder', ... }.
它不支持元信息,所以如果一个依赖项可以在一个flatDir
存储库中和另一个具有元信息的存储库中查找(Maven、Ivy 等等),后者优先。
maven { ... }
给定一个显式 URL,指向一个 Maven 存储库。精确的语法是
maven { url "http://repo.mycompany.com/maven2" }
ivy { ... }
指向一个给定显式 URL 的 Ivy 存储库。精确的语法是
ivy { url "http://repo.mycompany.com/ivy" }
mavenLocal()
使用本地 Maven 缓存(通常在HOME-DIR/.m2
中)
对于您指定为存储库位置的 URL,Gradle 还支持https:
、file:
、sftp:
和s3:
(亚马逊 s3 服务)协议,或者gcs:
(谷歌云存储)。前三个,当然还有标准的http://
协议,使用标准的 URL 语法。如果需要,Gradle 手册会解释更多关于s3:
和gcs
的语法。
如果您需要提供连接到存储库的凭证,您可以在credentials { }
部分指定它们:
repositories {
maven {
url "http://repo.mycompany.com/maven2"
credentials {
username "user"
password "password"
}
}
}
这是用于基本认证的。有关更高级的身份验证方案,请参见 Gradle 手册中的“声明存储库”一节。
关于依赖性的更多信息
Gradle 中心对configurations
的依赖性。(依赖相关的)配置是一个依赖范围,这意味着它描述了一个使用场景。例如,假设您有一组仅对测试重要的依赖项,另一组是某个库的内部功能所需的依赖项,还有一组是内部功能所需的依赖项并被转发给客户端(因为它们出现在公共方法调用中)。所有这些都是不同的范围,或配置。
依赖相关的配置是由插件定义的,但是关于配置名有一个常识,内部配置也是互相继承的,这就导致了不同插件之间的配置名匹配。表 3-2 列出了你在 Java 相关项目中经常遇到的配置。
表 3-2
梯度构型
|名字
|
描述
|
| — | — |
| implementation
| 编译源代码的main
部分所需的任何依赖项都可以使用这种配置。依赖关系也将在运行时使用。 |
| compile
| 已弃用。替换为implementation
。您经常在博客和教程中发现这一点,所以添加这一点供您参考。用implementation
代替。 |
| compileOnly
| 依赖项只需要编译源代码的main
部分。在运行时,某种容器将提供依赖,因此项目不需要将这种依赖添加到可交付的工件中。 |
| runtimeOnly
| 编译源代码的main
部分不需要依赖关系,但是依赖关系会被添加到可交付的工件中。 |
| api
| 仅适用于 Java 库插件,标识了也必须传输到库客户端的依赖项,因为依赖项中的类型出现在公共方法调用中。 |
| providedCompile
| 只针对战争插件;与implementation
相同,但是依赖关系将而不是添加到 WAR 文件中。 |
| providedRuntime
| 只针对战争插件;与runtime
相同,但是依赖关系将而不是添加到 WAR 文件中。 |
| deploy
| 只针对 EAR 插件;将依赖项添加到 EAR 文件的根目录。 |
| earlib
| 只针对 EAR 插件;将依赖项添加到 EAR 文件的lib
文件夹中。 |
| testImplementation
| 编译源代码的test
部分所需的任何依赖项都可以使用这种配置。依赖关系也将在运行时使用。 |
| testCompile
| 已弃用。替换为testImplementation
。您经常在博客和教程中发现这一点,所以添加这一点供您参考。用testImplementation
代替。 |
| testCompileOnly
| 与compileOnly
相似,但用于信号源的test
部分。 |
| testRuntimeOnly
| 与runtimeOnly
相似,但用于信号源的test
部分。 |
一旦确定了所需的配置,就可以在build.gradle
文件的dependencies { }
部分指定一个列表:
dependencies {
implementation 'org.apache.commons:commons-math3:3.6.1'
// This is the same:
implementation group:'org.apache.commons',
name:'commons-math3',
version:'3.6.1'
// You can combine:
implementation 'org.apache.commons:commons-math3:3.6.1',
'org.apache.commons:commons-lang3:3.10'
// or like that:
implementation(
[ group:'org.apache.commons',
name:'commons-math3', version:'3.6.1' ],
[ group:'org.apache.commons',
name:'commons-lang3', version:'3.10' ]
)
// or like that:
implementation 'org.apache.commons:commons-math3:3.6.1'
implementation 'org.apache.commons:commons-lang3:3.10'
testImplementation 'junit:junit:4.12'
}
正常情况下,任何间接依赖关系都是自动解决的,它来自于依赖关系的依赖关系。这样的依赖被称为传递依赖。因此,如果您声明了对某个库 A 的依赖,而库 A 又依赖于库 B 和库 C,那么 Gradle 会负责在构建中包含 B 和库 C,而不需要在build.gradle
中显式声明对 B 和库 C 的依赖。如果您想防止 Gradle 包含可传递的依赖项,您可以使用transitive = false
来标记它们:
dependencies {
implementation (group: 'org.eclipse.jetty',
name: 'jetty-webapp',
version: '9.4.28.v20200408') {
transitive = false
}
}
如果您调用dependencies
任务,您可以研究这种可传递的依赖性。输出将是依赖关系和传递依赖关系的树状表示,例如,如下所示:
...
runtimeClasspath - Runtime classpath of source set 'main'.
\--- com.sparkjava:spark-core:2.8.0
+--- org.slf4j:slf4j-api:1.7.25
+--- org.eclipse.jetty:jetty-server:9.4.12
| +--- javax.servlet:javax.servlet-api:3.1.0
| +--- org.eclipse.jetty:jetty-http:9.4.12
| | +--- org.eclipse.jetty:jetty-util:9.4.12
| | \--- org.eclipse.jetty:jetty-io:9.4.12
| | \--- org.eclipse.jetty:jetty-util:9.4.12
...
(这里提到的依赖是implementation com.sparkjava:spark-core:- 2.8.0
。)
改变项目结构
我们了解到,通过坚持默认的项目结构,我们不必花费时间来配置项目,告诉它在哪里可以找到资源和资源。
如果出于某种原因,您需要一个定制的项目布局,将下面几行添加到您的build.gradle
文件中:
sourceSets {
main {
java {
srcDirs = ['src99/main/java']
}
resources {
srcDirs = ['src99/main/resources']
}
}
test {
java {
srcDirs = ['src99/test/java']
}
resources {
srcDirs = ['src99/test/resources']
}
}
}
因为所有的目录设置都被指定为列表(见[ ... ]
),所以你也可以在几个文件夹中分配资源和资源(使用逗号作为分隔符)。
为了更改 Gradle 存放临时和最终输出文件的构建文件夹,在您的build.gradle
文件中写入以下内容:
project.buildDir = 'gradle-build'
Gradle 构建文件是一个 Groovy 脚本
让我们修改EchoLibrary
示例build.gradle
文件:
// The EchoLibrary build file
plugins {
id 'java-library'
}
java {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
repositories {
jcenter()
}
dependencies {
testImplementation 'junit:junit:4.12'
}
除了jcenter()
中可疑的()
以及A B
和A = B
构造的奇怪混合,这个文件看起来可能像一个配置文件,其语法仅限于设置一些属性。然而,事实要悲观得多。事实上,build.gradle
文件是一个 Groovy 脚本,Groovy 是一种运行在 JVM 引擎之上的成熟的脚本语言。
虽然我们已经说过,对于构建定义文件,声明式编程风格比声明式编程风格更可取,但在某些情况下,添加编程语言结构(如条件语句、开关结构、循环和对 IO(文件和控制台)、数学、流、日期和时间以及您可能想到的任何其他库对象的调用)是可以接受的。此外,构建文件中的{ }
括号实际上并不表示块,而是闭包。所以dependencies { }
构造实际上是dependencies( { } )
的快捷方式,任何A B
构造实际上都是一个方法调用A( B )
。
例如,如果您想仅在定义了某个系统属性的情况下添加一个runtimeOnly
依赖项,并且还想输出相应的诊断消息,您可以编写以下代码:
...
dependencies {
if(System.getProperty("add.math") != null) {
println("MATH added")
runtimeOnly group: 'org.apache.commons',
name: 'commons-math3', version: '3.6.1'
}
...
testImplementation 'junit:junit:4.12'
}
...
现在,您可以调用添加了额外选项-Dadd.math
的任何任务来查看条件语句和控制台输出的工作情况。
脚本变量
为了增加可读性和维护优化,您可以将变量(属性)添加到构建文件中。为此,您可以使用一个ext { }
调用:
...
ext {
MATH_VERSION = '3.6.1'
JUNIT_VERSION = '4.12'
}
dependencies {
implementation group: 'org.apache.commons',
name: 'commons-math3', version: MATH_VERSION
testImplementation "junit:junit:${JUNIT_VERSION}"
}
...
为了让${}
替换生效,双引号是必需的——这是 Groovy 语言的一个特性(GString
对象)。否则在 Groovy 中你可以使用单引号和双引号来表示字符串。
如果变量范围被限制在当前闭包内(在一个{ }
内),你也可以使用标准的 Groovy 局部变量声明:
...
dependencies {
def MATH_VERSION = '3.6.1'
def JUNIT_VERSION = '4.12'
implementation group: 'org.apache.commons',
name: 'commons-math3', version: MATH_VERSION
testImplementation "junit:junit:${JUNIT_VERSION}"
}
...
自定义任务
我们可以在build.gradle
文件中定义自己的任务。因为我们可以在构建脚本中使用 Groovy 语言,所以这里的可能性是无限的。我们可以添加日志记录、在归档中包含非标准文件、执行加密、在服务器上部署工件、以非标准方式发布文件、执行计时、调用额外的准备和清理步骤等等。
要定义您自己的任务,您可以在您的build.gradle
脚本文件中的任意位置编写以下内容:
task hello {
group = 'build'
description = 'Hello World'
println 'Hello world! CONFIG'
doFirst {
println 'Hello world! FIRST'
}
doLast {
println 'Hello world! LAST'
}
}
group
和description
设置都是可选的;该组的缺省值是other
,如果省略描述,将会取一个空字符串。group
的可能值有build
、build setup
、documentation
、help
、verification
和other
。
要执行一个自定义任务,您可以像处理内置任务或插件定义的任务一样处理。然而,为了让 Eclipse Gradle 插件能够看到新任务,您首先必须右键单击项目,然后选择 Gradle➤Refresh Gradle 项目。然后,您将在 Gradle Tasks 视图的树形视图中看到新任务,并可以通过双击它来执行它。
主{ }
中的指令在配置阶段执行。要知道这样的指令对于所有声明的任务都是无条件执行的!对于任务执行问题,您可以将指令放入doFirst { }
或doLast { }
中。每个任务都有一个动作列表;如果您使用doFirst
,指令被添加到动作列表中,如果您使用doLast
,动作被添加到动作列表中。
稍后可以通过编写以下内容向任务的动作列表添加说明:
hello.doLast {
println 'Hello world! MORE LAST'
}
hello.doFirst {
println 'Hello world! MORE FIRST'
}
您可以将自定义任务添加到受抚养人的现有任务列表中,或将现有任务添加到受抚养人的新任务列表中。为此,请编写以下代码,例如:
build.dependsOn hello
hello.dependsOn build
这背后的神奇之处在于,在build.gradle
脚本中,任何任务都可以通过其名称直接获得。所以,如果你写build.dependsOn hello
,任何build
任务的执行都会首先导致hello
的执行。在hello.dependsOn build
中,hello
任务的执行首先产生build
执行。这样,就可以将任务相关性关系添加到现有的标准和非标准任务中。
Gradle 包装
如果您使用wrapper
任务或 Eclipse Gradle 插件来启动一个新项目,将会安装包装器脚本,这允许您在操作系统上没有任何 Gradle 安装的情况下运行 Gradle(尽管 Java 必须工作)。您可以从以下文件中看到这一点:
gradlew
gradlew.bat
gradle
|- wrapper
|- gradle-wrapper.jar
|- gradle-wrapper.properties
gradlew
和gradlew.bat
分别是 Linux 和 Windows 的梯度启动脚本。gradle
文件夹包含独立的 Gradle 安装。
Eclipse Gradle 插件不使用这些包装脚本。相反,在启动第一个 Gradle 任务时,会从USER_HOME/gradle
内部启动一个 Gradle 守护进程。这个守护进程在后台运行,任何从 Eclipse 触发的 Gradle 任务执行都会联系这个守护进程来进行实际的构建工作。这允许更快的任务执行。
如果从控制台调用 Gradle,将使用包装器,并且这样的守护进程也将启动。我们在“使用控制台开发”一节中讨论了面向控制台的开发方式。
多项目构建
Gradle 项目可以有子项目。除了收集展示某种相互关系的项目之外,由一个主项目和一个或多个子项目构建的层次结构对于 EAR 项目也很重要,在 EAR 项目中,我们通常有一个 web 应用,可能有一些 EJB,也可能有一些库。
要从 Eclipse 内部构建这样一个多项目,首先要像前面描述的那样创建一个普通的 Gradle 项目。然后,打开settings.gradle
文件并添加以下行:
include 'proj1', 'proj2'
当然,您可以为子项目选择不同的名称。接下来,在项目文件夹中创建两个文件夹,名称分别为proj1
和proj2
(或者您选择的任何名称)。在每个新文件夹中添加一个空的build.gradle
文件。您可以稍后在那里添加任何与子项目相关的构建指令。
右键单击该项目,然后选择“gradle➤refresh·格拉德项目”。Eclipse 将更新项目浏览器,并将主项目和两个子项目显示为不同的条目;参见图 3-7 。
图 3-7
Eclipse 中的 Gradle 多项目
由于 Gradle 插件中的一个错误,您必须修复所有三个条目的 JRE 库分配。在每一个上,右键单击,然后选择 Properties➤Libraries.删除错误的条目,然后单击 Add Library(添加到 classpath)➤JRE 系统 Library➤Workspace 默认的 JRE(或者任何适合您需要的东西))。错误标记现在应该消失了,如图 3-8 所示。
图 3-8
Eclipse 中的 Gradle 多项目,已修复
每个子项目可以使用自己的build.gradle
文件独立配置,但是也可以从根项目的build.gradle
文件中引用子项目:
// referring to a particular sub-project
project(':proj1') { proj ->
// adding a new task to proj1
task('hello').doLast { task ->
println "I'm $task.project.name" }
}
// we can directly address tasks
project(':proj1').hello {
doLast { println "I'm $project.name" }
}
// or, referring to all sub-projects
subprojects {
task hello {
doLast { task ->
println "I'm $task.project.name"
}
}
}
// or, referring
to the root project and all sub-projects
allprojects {
task hello {
doLast { task ->
println "I'm $task.project.name"
}
}
}
我们可以通过rootProject
变量从子项目的配置中处理根项目:
task action {
doLast {
println("Root project: " +
"${rootProject.name}")
}
}
您可以在 Gradle 用户手册的“配置多项目构建”和“创作多项目构建”章节中了解更多关于多项目构建的信息。我们将在第九章使用多项目。
添加部署任务
定制任务的一个很好的候选是部署过程。我们可以使用标准的build
任务来创建 WAR 或 EAR 文件,但是为了在本地开发服务器上部署它,一个定制的 Gradle 任务就派上了用场。在本书中,我们将使用以下任务在本地 GlassFish 服务器上进行部署和“取消部署”:
task localDeploy(dependsOn: build,
description:">>> Local deploy task") {
doLast {
def FS = File.separator
def glassfish = project.properties['glassfish.inst.dir']
def user = project.properties['glassfish.user']
def passwd = project.properties['glassfish.passwd']
File temp = File.createTempFile("asadmin-passwd",
".tmp")
temp << "AS_ADMIN_${user}=${passwd}\n"
def sout = new StringBuilder()
def serr = new StringBuilder()
def libsDir = "${project.projectDir}${FS}build" +
"${FS}libs"
def procStr = """${glassfish}${FS}bin${FS}asadmin
--user ${user} --passwordfile ${temp.absolutePath}
deploy --force=true
${libsDir}/${project.name}.war"""
// For Windows:
if(FS == "\\") procStr = "cmd /c " + procStr
def proc = procStr.execute()
proc.waitForProcessOutput(sout, serr)
println "out> ${sout}"
if(serr.toString()) System.err.println(serr)
temp.delete()
}
}
task localUndeploy(
description:">>> Local undeploy task") {
doLast {
def FS = File.separator
def glassfish = project.properties['glassfish.inst.dir']
def user = project.properties['glassfish.user']
def passwd = project.properties['glassfish.passwd']
File temp = File.createTempFile("asadmin-passwd",
".tmp")
temp << "AS_ADMIN_${user}=${passwd}\n"
def sout = new StringBuilder()
def serr = new StringBuilder()
def procStr = """${glassfish}${FS}bin${FS}asadmin
--user ${user} --passwordfile ${temp.absolutePath}
undeploy ${project.name}"""
// For Windows:
if(FS == "\\") procStr = "cmd /c " + procStr
def proc = procStr.execute()
proc.waitForProcessOutput(sout, serr)
println "out> ${sout}"
if(serr.toString()) System.err.println(serr)
temp.delete()
}
}
这些任务依赖于属性文件。Gradle 自动尝试读取一个名为gradle.properties
的属性文件,如果它存在,就从属性中创建一个映射,并将其放入project.properties
变量中。我们在项目文件夹中创建这样一个文件,如下所示:
glassfish.inst.dir = /path/to/glassfish/inst
glassfish.user = admin
glassfish.passwd =
这些任务创建一个临时密码文件;这只是 GlassFish 避免手动输入密码的方式。"...".execute()
创建一个在操作系统上运行的进程;对于 Windows 变体,我们必须在前面加上一个cmd /c
。
我们现在可以通过分别调用localDeploy
或localUndeploy
任务来执行部署或“取消部署”。由于我们添加了一个dependsOn: build
作为部署的任务依赖,所以没有必要构建一个可部署的工件;这是自动完成的。
使用控制台开发
因为 Eclipse Gradle 插件在项目文件夹中安装了包装脚本,所以可以从控制台(Linux 中的 bash 终端,Windows 中的命令解释器)而不是 Eclipse GUI 中完成所有与构建相关的工作。这是风格的问题;使用控制台,您可以避免切换 Eclipse 视图以及折叠和滚动树。此外,如果您必须添加任务选项或参数,与 GUI 相比,使用控制台要简单快捷得多。如果您没有 GUI,因为您想在服务器上进行构建,那么使用控制台是您唯一的选择。
本节介绍如何使用控制台进行 Gradle 构建。可以自由混合控制台和 GUI 触发的构建,因此您可以同时使用这两种方法。
如果您没有使用 Eclipse Gradle 插件来启动 Gradle 项目,那么您可以使用wrapper
任务来创建包装器。在这种情况下,Gradle 必须安装在您的操作系统上。Linux 脚本如下所示:
java -version
# observe output
# if you want to specify a different JDK:
export JAVA_HOME=/path/to/the/jdk
cd /here/goes/the/project
gradle init wrapper
对于 Windows,其内容如下:
java -version
# observe output
# if you want to specify a different JDK: set JAVA_HOME=C:\path\to\the\jdk
chdir \here\goes\the\project
gradle init wrapper
这里假设gradle
在PATH
中(在 Windows 中,gradle.bat
在你的PATH
中)。否则,您必须指定gradle
命令的完整路径。例如:C:\gradle\bin\gradle.bat.
要检查包装器的安装,可以通过以下方式列出项目目录中的可用任务:
./gradlew tasks
# Windows: gradlew tasks
输出应该是这样的:
> Task :tasks
--------------------------------------------------------
All tasks runnable from root project
--------------------------------------------------------
Build Setup tasks
-----------------
init - Initializes a new Gradle build.wrapper - Generates Gradle wrapper files.
[...]
如果输入以下内容,您可以看到gradlew
(对于 Windows 为gradlew.bat
)包装器命令的完整概要:
./gradlew -help
# Windows: gradlew -help
表 3-3 中显示了有趣且重要的选项参数的非详尽列表。指定要在选项列表后面执行的任何任务。
表 3-3
Gradle 命令选项
|[计]选项
|
描述
|
| — | — |
| -?, -h, –help
| 显示此帮助消息。 |
| -Dprop=val
| 设置 JVM 属性。可以在脚本内部使用System.getProperty("prop")
来读取。 |
| -Pprop=val
| 设置项目属性。可以在脚本内部使用prop
直接读取。 |
| -w, –warn
| 添加警告级别诊断输出。 |
| -i, –info
| 添加了一些信息级别的诊断输出。 |
| -d, –debug
| 出错时启用调试消息。 |
| -q, –quiet
| 仅显示错误级别消息(安静)。 |
| –offline
| 通常,Java 构建任务中引用的库被下载到缓存中。如果您想禁用网络访问,请使用此选项。 |
| –status
| 显示 Gradle 守护程序的状态。通常在第一次启动时,会启动一个后台进程(守护进程)来加速后续的 Gradle 调用。使用它来显示守护程序的状态。 |
| –stop
| 停止正在运行的守护进程。 |
| -v, –version
| 显示版本信息。 |
任务可以有选项和参数。为了使用tasks
任务(显示所有任务),例如,你可以添加--all
作为一个选项:
./gradlew tasks --all
# Windows: gradlew tasks --all
这显示了来自other
组的任务(通常被丢弃)。如果您运行./gradlew help --task <task>
,您可以查看任何特定任务的信息(选项)。
为了对构建脚本执行性能问题进行故障排除,还有另一个名为--profile
的选项,它将导致性能报告被添加到build/reports/profile
中。
对于我们的小示例项目,导航到项目文件夹,然后执行以下命令:
./gradlew build
# Windows: gradlew build
名为EchoLibrary.jar
的输出 JAR 在build/libs
文件夹中生成。
Note
为了简单起见,在本书的其余部分,我们将只显示控制台 Gradle 命令,并且只显示 Linux 版本。
安装 MVC
为了能够使用 Java MVC,从 Gradle 的角度来看,我们需要检查一些东西。首先,我们将 Java MVC 配置为一个 web 应用。出于这个原因,我们创建一个 web 项目并使用 WAR 插件。在build.gradle
中,添加以下内容:
plugins {
id 'war'
}
接下来,我们在build.gradle
的 dependencies 部分添加 Jakarta EE 8 API、Java MVC API 和一个 Java MVC 实现。这伴随着一个存储库规范、通常的 JUnit 测试库包含,以及我们想要使用 Java 1.8 的指示:
plugins {
id 'war'
}
java {
sourceCompatibility = 1.8
targetCompatibility = 1.8
}
repositories {
jcenter()
}
dependencies {
testImplementation 'junit:junit:4.12'
implementation 'javax:javaee-api:8.0'
implementation 'javax.mvc:javax.mvc-api:1.0.0'
implementation 'org.eclipse.krazo:krazo-jersey:1.1.0-M1'
// more dependencies...
}
// ... more tasks
就是这样;构建过程将确保下载所有的库,并在./gradlew
构建期间将 Java MVC 添加到 web 应用中。
练习
-
练习 1: 对还是错?使用命令式编程(逐步说明)是构建脚本的首选编程风格。
-
练习 2: 对还是错?对于命令式代码片段,您可以在 Gradle 构建脚本中使用 C++代码。
-
练习 3: 对还是错?Eclipse 为自己的功能和构建项目使用相同的 JRE。
-
练习 4: 确定梯度构建流程的三个阶段。
-
练习 5: 对还是错?使用标准的 Gradle Java 项目布局,Java 类进入
src/java/main
。 -
练习 6: 对还是错?要使用的 Gradle 插件在
settings.gradle
文件中指定。 -
练习 7: Gradle 根据需要下载项目依赖项。真的还是假的?从哪里下载在
build.gradle
中的downloads { }
部分指定。 -
练习 8: 用 Gradle 行话描述一下什么是配置。
-
练习 9: 使用 Eclipse Gradle 插件,创建一个包含两个类的
GraphicsPrimitives
Java 库:Circle
和Rectangle
。将其配置为使用 JRE 1.8。根据需要修改所有 Gradle build 配置文件。 -
练习 10: 如果您有两个自定义任务:
-
"Hi, I’m A"
在什么情况下打印到控制台? -
练习 11: 对还是错?Gradle 包装器只有在操作系统上安装了 Gradle 的情况下才能工作。
-
练习 12: 描述需要做什么才能让 Gradle 在
/opt/jdk8
(或者对于 Windows,在C:\jdk8
)使用 JDK。
task a {
println "Hi, I'm A"
}
task b {
println "Hi, I'm B"
}
摘要
在这一章中,我们讨论了开发技术、过程和工具,你可以在本书的例子和任何使用 Java MVC 的后续项目中使用它们。
Gradle 是一个现代的构建框架/构建自动化工具。您可以使用声明式配置风格,但也可以以 Groovy(或 Kotlin)脚本片段的形式添加命令式构建代码。最佳实践表明,对于构建脚本,声明式编程(它告诉构建脚本必须做什么,而不是它应该如何做)优于命令式编程(精确的逐步说明)。
Eclipse 是一个 IDE(集成开发环境),提供了大量有助于开发 Java 企业项目的功能。它可以通过插件进行扩展,从而增加额外的功能。对于本书,我们使用面向企业 Java 开发人员的 Eclipse IDE 变体。
对于这本书,我们需要 Eclipse Gradle 插件。Gradle 也可以从控制台使用,但是 Eclipse 中的 Gradle 插件允许我们直接从 ide 内部使用 Gradle。打开 Help➤Install 新软件,输入 Eclipse Buildship (Gradle)和 http://download.eclipse.org/buildship/updates/latest in the dialog
。选择所有功能并完成向导。
要在 Eclipse 中启动一个 Gradle 项目,请访问 File➤New➤Other…➤Gradle➤Gradle 项目。
主要的梯度概念如下。Gradle 有一个核心,为构建相关的活动提供基础设施。Gradle 插件是在主构建文件中指定的。它们运行在内核之上,并向内核添加功能。每个插件都以任务的形式展示与构建相关的活动。每个 Gradle build 由一个初始化、一个配置和一个执行阶段组成。在初始化阶段,Gradle 确定子项目是否需要包含在构建中。在配置阶段,Gradle 评估依赖项并构建一个任务图,其中包含构建所需执行的所有任务。所有对象上的配置总是在每个 Gradle 版本中运行。在执行阶段,任务完成它们的工作(编译、移动、压缩等等)。
所有 Gradle 插件的默认项目布局如下:
src
|- main
| |- java
| | |- <java source files>
| |- resources
| |- <resource files>
|
|- test
|- java
| |- <java source files>
|- resources
|- <resource files>
build
|- <any files built by Gradle>
build.gradle <Gradle build file>
settings.gradle <(Sub-)Project settings>
gradle.properties <optional project properties>
Eclipse 的 Gradle 项目向导在项目的根文件夹中创建了一个示例构建配置文件build.gradle
。对于任何 Gradle 项目,包括不使用 Eclipse 的项目,这都是主要的构建文件。Eclipse 插件提供了一个带有一些示例条目的基本构建文件。
构建文件通常从定义要使用的插件开始,然后配置插件。带有操作说明的用户定义的任务也可以转到构建文件。此外,可以向现有任务添加 Groovy 或 Kotlin 代码,这使您能够根据需要微调插件。
插件通常对它们的默认值有一个非常精确和合理的概念,所以你的项目可能没有太多需要配置的。因此,构建文件可能相当小。这种“约定胜于配置”的风格并不是 Gradle 的发明,但是 Gradle 欣然接受了这个想法。
Eclipse Gradle 插件有 Gradle 任务和 Gradle 执行视图。此外,诊断输出进入标准控制台视图。安装 Gradle 插件后,默认情况下会打开两个与 Gradle 相关的视图。
为了从 Gradle Tasks 视图中运行 Gradle 任务,首先必须在树中找到任务。根据您在树中查看的精确程度,您还可以使用菜单中的过滤器来查找任务。找到后,双击它运行任务。诊断输出,包括任何错误消息,显示在 Gradle 执行和控制台视图中。
如果 Gradle 确定项目引用了这样的库,它就会从存储库中加载库。您可以在build.gradle
中的repositories { }
部分指定存储库:
repositories {
repoSpec1 (repository specification, see below)
repoSpec2
...
}
您可以使用以下内容作为存储库规范:
-
mavenCentral()
硬编码指向位于
https://repo.maven.apache.org/maven2/
的公开可用的 Maven 资源库 -
jcenter()
硬编码指向位于
https://jcenter.bintray.com/
的公开可用的 Maven 资源库 -
google()
硬编码指向位于
https://maven.google.com/
的公开可用的 Android 专用 Maven 库 -
flatDir { ... }
指向包含库的文件夹。精确的语法是
flatDir { dirs '/path1/to/folder', '/path2/to/folder', ... }
不支持元信息,所以如果可以在一个
flatDir
存储库中和另一个具有元信息的存储库中查找依赖关系(Maven、Ivy 等等),后者优先。 -
maven { ... }
给定一个显式 URL,指向一个 Maven 存储库。精确的语法是
maven { url "
http://repo.mycompany.com/maven2"
-
—
ivy { ... }
指向一个给定显式 URL 的 Ivy 存储库。精确的语法是
ivy { url
"http://repo.mycompany.com/ivy
"
-
蓝色区域()
使用本地 Maven 缓存(通常在
HOME-DIR/.m2
)。
Gradle 中心对configurations
的依赖性。依赖相关的配置是一个依赖范围,这意味着它描述了一个使用场景,如测试、编译、供应等等。依赖相关的配置是由插件定义的,但是关于配置名有一个常识,内部配置也是互相继承的,这就导致了不同插件之间的配置名匹配。
一旦确定了所需的配置,就可以在build.gradle
文件的dependencies { }
部分指定一个列表:
dependencies {
implementation 'org.apache.commons:commons-math3:3.6.1'
// This is the same:
implementation group:'org.apache.commons',
name:'commons-math3',
version:'3.6.1'
// You can combine:
implementation 'org.apache.commons:commons-math3:3.6.1',
'org.apache.commons:commons-lang3:3.10'
// or like that:
implementation(
[ group:'org.apache.commons',
name:'commons-math3', version:'3.6.1' ],
[ group:'org.apache.commons',
name:'commons-lang3', version:'3.10' ]
)
// or like that:
implementation 'org.apache.commons:commons-math3:3.6.1'
implementation 'org.apache.commons:commons-lang3:3.10'
testImplementation 'junit:junit:4.12'
}
在build.gradle
中,可以为 IO(文件和控制台)、数学、流、日期和时间以及您可能想到的任何其他内容添加编程语言结构,如条件语句、开关结构、循环和对库对象的调用。此外,构建文件中的{ }
括号实际上并不表示块,而是闭包。因此,dependencies { }
构造实际上是dependencies( { } )
的快捷方式,任何A B
构造实际上都是一个方法调用A( B )
。
为了增加可读性和维护优化,您可以将变量(属性)添加到构建文件中。为此,使用一个ext { }
调用:
...
ext {
MATH_VERSION = '3.6.1'
JUNIT_VERSION = '4.12'
}
dependencies {
implementation group: 'org.apache.commons',
name: 'commons-math3', version: MATH_VERSION
testImplementation "junit:junit:${JUNIT_VERSION}"
}
...
为了让${}
替换生效,双引号是必需的。这是一个 Groovy 语言特性(GString
对象)。否则,在 Groovy 中,您可以使用单引号和双引号来表示字符串。
我们可以在build.gradle
文件中定义自己的任务。因为我们可以在构建脚本中使用 Groovy 语言,所以可能性是无限的。我们可以添加日志记录、在归档中包含非标准文件、执行加密、在服务器上部署工件、以非标准方式发布文件、执行计时、调用额外的准备和清理步骤等等。
要定义您自己的任务,您可以在您的build.gradle
脚本文件中的任意位置编写以下内容:
task hello {
group = 'build'
description = 'Hello World'
println 'Hello world! CONFIG'
doFirst {
println 'Hello world! FIRST'
}
doLast {
println 'Hello world! LAST'
}
}
group
和description
设置都是可选的;group
的缺省值是other
,如果您省略描述,将会取一个空字符串。group
的所有可能值是build
、build setup
、documentation
、help
、verification
和other
。
您可以将自定义任务添加到受抚养人的现有任务列表中,或将现有任务添加到受抚养人的新任务列表中。为此,请编写以下代码,例如:
build.dependsOn hello
hello.dependsOn build
这背后的神奇之处在于,在build.gradle
脚本中,任何任务都可以通过其名称直接获得。所以,如果你写build.dependsOn hello
,任何build
任务的执行都会首先导致hello
的执行。对于hello.dependsOn build
,任务hello
的执行首先产生一个build
的执行。这样,就可以将任务相关性关系添加到现有的标准和非标准任务中。
因为 Eclipse Gradle 插件在项目文件夹中安装了包装脚本,所以可以从控制台(Linux 中的 bash 终端,Windows 中的命令解释器)而不是 Eclipse GUI 中完成所有与构建相关的工作。这是风格的问题;使用控制台,您可以避免切换 Eclipse 视图以及折叠和滚动树。此外,如果您必须添加任务选项或参数,与 GUI 相比,使用控制台要简单快捷得多。如果您没有 GUI,因为您想在服务器上进行构建,那么使用控制台是您唯一的选择。
如果您没有使用 Eclipse Gradle 插件来启动 Gradle 项目,那么您可以使用wrapper
任务来创建包装器。在这种情况下,Gradle 必须安装在您的操作系统上。Linux 脚本如下所示:
java -version
# observe output
# if you want to specify a different JDK:
export JAVA_HOME=/path/to/the/jdk
cd /here/goes/the/project
gradle init wrapper
Windows 脚本如下所示:
java -version
# observe output
# if you want to specify a different JDK:
set JAVA_HOME=C:\path\to\the\jdk
chdir \here\goes\the\project
gradle init wrapper
这里假设gradle
在PATH
中(在 Windows 中,gradle.bat
在你的PATH
中)。否则,您必须指定gradle
命令的完整路径。比如:C:\gradle\bin\gradle.bat
。
如果输入以下内容,您可以看到gradlew
(对于 Windows 为gradlew.bat
)包装器命令的完整概要:
./gradlew -help
# Windows: gradlew -help
任务也可以有选项和参数。为了使用tasks
任务(显示所有任务),例如,你可以添加--all
作为一个选项:
./gradlew tasks --all
# Windows: gradlew tasks --all
这显示了来自other
组的任务(通常被丢弃)。如果您运行./gradlew help --task <task>
,您可以查看任何特定任务的信息(选项)。
为了能够使用 Java MVC,从一个渐变的角度来看,我们需要验证一些事情。首先,我们将 Java MVC 配置为一个 web 应用。出于这个原因,我们创建一个 web 项目并使用 WAR 插件。在build.gradle
中,添加以下内容:
plugins {
id 'war'
}
接下来,我们在build.gradle
的 dependencies 部分添加 Jakarta EE 8 API、Java MVC API 和一个 Java MVC 实现。这伴随着一个存储库规范、通常的 JUnit 测试库包含,以及我们想要使用 Java 1.8 的指示:
plugins {
id 'war'
}
java {
sourceCompatibility = 1.8
targetCompatibility = 1.8
}
repositories {
jcenter()
}
dependencies {
testImplementation 'junit:junit:4.12'
implementation 'javax:javaee-api:8.0'
implementation 'javax.mvc:javax.mvc-api:1.0.0'
implementation 'org.eclipse.krazo:krazo-jersey:1.1.0-M1'
// more dependencies...
}
// ... more tasks
在下一章,我们将讨论一个干净的"Hello World"
风格的项目,使用我们刚刚描述的开发工作流程。