第 3 章 用 JBoss EAP 开发应用程序

3.1. 概述

本指南提供了使用 Red Hat JBoss Developer Studio 和 JBoss EAP 7 quickstart 例程开发应用程序的信息。

Red Hat JBoss Developer Studio 是一个基于 Eclipse 的集成开发环境(IDE),它集成了 JBoss 应用程序开发插件。JBoss Developer Studio 可以使用 JBoss 专有的向导并帮助您部署应用程序至 JBoss EAP 服务器。JBoss EAP 7 提供了许多 Quickstarts 例程来帮助用户使用不同的 Java EE 7 技术编写应用程序。

3.2. 设置开发环境

我们推荐您使用 JBoss Developer Studio 11.0 或采用 JBoss EAP 7.1 的更新版本。

  1. 下载和安装 JBoss Developer Studio。

    相关的说明,请参考《JBoss Developer Studio 安装指南》中的使用安装程序安装 JBoss Developer Studio Stand-alone

  2. 在 JBoss Developer Studio 中设置 JBoss EAP 服务器。

    相关的说明,请参考《JBoss Developer Studio 工具入门指南》中的使用运行时检测从 IDE 内部设置 JBoss EAP

3.3. 使用 Quickstart 例程

JBoss EAP 里包含的 Quickstart 例程是 Maven 项目。

3.3.1. 关于 Maven

Maven 是一个在 Java 开发中创建、构建和管理软件项目的分布式自动构建工具。Maven 使用标准的配置文件(Project Object Model 或称为 POM)来定义项目和管理构建过程。POM 使用 XML 文件描述了模块和组件的依赖关系、构建顺序和结果项目软件包的目标及输出。这确保了项目以正确和统一的方式进行构建。

Maven 是通过资料库实现这一点的。Maven 资料库保存 Java 库、插件和其他的构建构件。默认的公共资料库是 Maven 2 Central Repository,但资料库可以是机构私有和内部的,在开发团队间共享公用的构件。资料库也可来自第三方。更多信息请参考 Apache Maven 项目和 Introduction to Repositories

JBoss EAP 附带的 Maven 资料库包含了 Java EE 开发人员构建 JBoss EAP 应用程序所常用的内容。

关于在 JBoss EAP 里使用 Maven 的更多信息,请参考《JBoss EAP 开发指南》中的在 JBoss EAP 里使用 Maven

3.3.2. 在 Quickstarts 里使用 Maven

构建和部署应用程序到 JBoss EAP 7 的构件和依赖关系存放在公用资料库。从 JBoss EAP 7 开始,构建 quickstarts 不再需要配置 settings.xml 来使用这些资料库。现在是通过 Quickstart 项目的 POM 文件来配置 Maven 资料库。这种方式让 Quickstart 更易于使用,然而,我们通常不建议将其用于产品项目,因为这会拖慢构建过程。

Red Hat JBoss Developer Studio 包含了 Maven,所以不需要单独下载和安装。我们推荐您使用 JBoss Developer Studio version 11.0 或更新的版本。

如果您计划用 Maven 命令行来构建和部署应用程序,您必须先从 Apache Maven 项目下载 Maven 并根据 Maven 文档里的说明安装它。

3.3.3. 下载和运行 Quickstarts

3.3.3.1. 下载 Quickstarts

JBoss EAP 带有详尽的 quickstart 示例代码以帮助用户开始用不同的 Java EE 7 技术编写应用程序。您可以从 Red Hat 客户门户网站下载 Quickstarts 例程。

  1. 登录 Red Hat 客户门户
  2. 点击 Downloads
  3. Product Downloads 列表里,点击 Red Hat JBoss Enterprise Application Platform
  4. Version 下拉菜单里选择 7.1
  5. 在表里找到 Red Hat JBoss Enterprise Application Platform 7.1.0 Quickstarts 条目并点击 Download
  6. 将 ZIP 保存到想要的位置。
  7. 解压 ZIP 归档文件。

3.3.3.2. 在 Red Hat JBoss Developer Studio 里运行 Quickstarts

在下载了 Quickstarts 例程后,它们可以导入到 JBoss Developer Studio 并部署至 JBoss EAP。

将 Quickstarts 导入至 JBoss Developer Studio

每个 Quickstart 都附带一个 POM(Project Object Model)文件,它包含这个 Quickstart 的项目和配置信息。用这个 POM 文件,您可以轻松地将 Quickstart 导入到 Red Hat JBoss Developer Studio 里。

重要

在导入到 Red Hat JBoss Developer Studio 时,如果您的 Quickstart 项目文件夹位于 IDE 工作区里,IDE 会生成一个无效的项目名及 WAR 归档名。在开始导入之前,请确定您的 Quickstart 项目文件夹在 IDE 工作区之外。

  1. 启动 JBoss Developer Studio。
  2. 选择 FileImport
  3. 选择 MavenExisting Maven Projects,然后点击 Next

    图 3.1. 导入现有的 Maven 项目

    The *Import* window.
  4. 浏览要操作的 Quickstart 目录(例如 helloworld quickstart),然后点击 OKProjects 列表框将用所选的 Quickstart 项目的 pom.xml 文件填充。

    图 3.2. 选择 Maven 项目

    The *Maven Projects* selection window.
  5. 点击 Finish

运行 helloworld Quickstart

运行 helloworld quickstart 是检验 JBoss EAP 服务器是否配置和运行正常的简易方法。

  1. 如果您还没有定义服务器,请添加 JBoss EAP 服务器至 JBoss Developer Studio。请参考《JBoss Developer Studio 工具入门指南》中的使用运行时检测从 IDE 内部设置 JBoss EAP
  2. 右击 Project Explorer 标签页里的 helloworld 项目并选择 Run AsRun on Server

    图 3.3. Run As - Run on Server

    The *Run As* -> *Run on Server* screen capture.
  3. 从服务器列表选择 JBoss EAP 7.1 服务器并点击 Next

    图 3.4. 在服务器上运行

    The *Run on Server* window.
  4. helloworld quickstart 已经列出在服务器进行配置。点击 Finish 部署这个 quickstart。

    图 3.5. 修改服务器上配置的资源

    The *Add and Remove Resources* window.
  5. 检验结果。

    • Server 标签页里,JBoss EAP 7.1 服务器的状态已改为 Started
    • Console 标签页显示了详述 JBoss EAP 服务器启动和 helloworld quickstart 部署的信息。

      WFLYUT0021: Registered web context: /helloworld
      WFLYSRV0010: Deployed "helloworld.war" (runtime-name : "helloworld.war")
    • helloworld 应用程序可通过 http://localhost:8080/helloworld 访问,它会显示文本 Hello World!

有关 helloworld quickstart 的更多信息,请参考探讨 helloworld Quickstart 例程

运行 bean-validation Quickstart

某些 Quickstarts 例程,如 bean-validation,不会提供用户界面层而是用 Arquillian 测试来演示功能。

  1. 导入 bean-validation quickstart 至 JBoss Developer Studio。
  2. Servers 标签页,右击服务器并选择 Start 以启动 JBoss EAP 服务器。如果您没有看到 Servers 标签页或还没有定义服务器,请添加 JBoss EAP 服务器至 JBoss Developer Studio。请参考《JBoss Developer Studio 工具入门指南》中的使用运行时检测从 IDE 内部设置 JBoss EAP
  3. 右击 Project Explorer 标签页里的 bean-validation 项目并选择 Run AsMaven Build
  4. Goals 字段输入下列内容并点击 Run

    clean verify -Parq-remote

    图 3.6. 编辑配置

    The *Edit Configuration* window.
  5. 检验结果。

    Console 标签页显示了 bean-validation Arquillian 测试的结果:

    -------------------------------------------------------
     T E S T S
    -------------------------------------------------------
    Running org.jboss.as.quickstarts.bean_validation.test.MemberValidationTest
    Tests run: 5, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 2.189 sec
    
    Results :
    
    Tests run: 5, Failures: 0, Errors: 0, Skipped: 0
    
    [INFO] ------------------------------------------------------------------------
    [INFO] BUILD SUCCESS
    [INFO] ------------------------------------------------------------------------

3.3.3.3. 用命令行运行 Quickstarts

您可以轻易地从命令行用 Maven 构建和部署 Quickstarts。如果您还没有安装 Maven,请参考 Apache Maven 项目来下载并安装它。

Quickstarts 的根目录提供了一个 README.md 文件,它包含关于系统要求、配置 Maven、添加用户和运行 Quickstarts 的普通信息。

每个 Quickstart 也包含自己的 README.md 文件,它提供运行这个 Quickstart 的专门的说明及 Maven 命令。

从命令行运行 helloworld Quickstart

  1. 复查 helloworld quickstart 根目录里的 README.md 文件。
  2. 启动 JBoss EAP 服务器。

    $ EAP_HOME/bin/standalone.sh
  3. 进入 helloworld quickstart 目录。
  4. README.md 文件里提供的 Maven 命令构建和部署 Quickstart。

    $ mvn clean install wildfly:deploy
  5. helloworld 应用程序可通过 http://localhost:8080/helloworld 访问并显示文本 Hello World!

3.4. 检查 Quickstart 例程

3.4.1. 探讨 helloworld Quickstart 例程

helloworld quickstart 显示了如何将简单的 servlet 部署至 JBoss EAP。业务逻辑被涵盖在服务之中,作为 Contexts and Dependency Injection (CDI) bean 提供,并被注入 Servlet。此 quickstart 是确保正确配置和启动服务器的起点。

有关使用命令行来构建和部署此 quickstart 的详细说明,请参考 helloworld quickstart 根目录中的 README.html 文件。本主题介绍如何使用 Red Hat JBoss Developer Studio 来运行 quickstart,并假定您已安装 Red Hat JBoss Developer Studio、配置 Maven、导入并成功运行 helloworld quickstart。

前提条件
检查目录结构

helloworld quickstart 的代码可在 QUICKSTART_HOME/helloworld/ 目录中找到。helloworld quickstart 由一个 Servlet 和一个 CDI bean 构成。它还包括一个 beans.xml 文件(位于应用程序的 WEB-INF/ 目录中),版本号为 1.1,以及一个 bean-discovery-mode(模式为 all)。这个标记文件将 WAR 识别为 bean 归档,指示 JBoss EAP 在本应用程序中查找 bean 和激活 CDI。

src/main/webapp/ 目录中包含 quickstart 文件。本示例的所有配置文件都位于 src/main/webapp/WEB-INF/ 目录中,其中包括 beans.xml 文件。src/main/webapp/ 目录中还包含 index.html 文件,它使用简单的 meta 刷新将用户的浏览器重定向到 Servlet,位于 http://localhost:8080/helloworld/HelloWorld。quickstart 不需要 web.xml 文件。

检查代码

这些列表里已经排除了软件包声明和导入。Quickstart 源代码里有完整的列表。

  1. 检查 HelloWorldServlet 代码。

    HelloWorldServlet.java 文件位于 src/main/java/org/jboss/as/quickstarts/helloworld/ 目录。这个 servlet 发送信息至浏览器。

    示例:HelloWorldServlet 类别代码

    42 @SuppressWarnings("serial")
    43 @WebServlet("/HelloWorld")
    44 public class HelloWorldServlet extends HttpServlet {
    45
    46     static String PAGE_HEADER = "<html><head><title>helloworld</title></head><body>";
    47
    48     static String PAGE_FOOTER = "</body></html>";
    49
    50     @Inject
    51	   HelloService helloService;
    52
    53     @Override
    54     protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    55         resp.setContentType("text/html");
    56         PrintWriter writer = resp.getWriter();
    57         writer.println(PAGE_HEADER);
    58         writer.println("<h1>" + helloService.createHelloMessage("World") + "</h1>");
    59         writer.println(PAGE_FOOTER);
    60         writer.close();
    61     }
    62
    63 }

    表 3.1. HelloWorldServlet 的细节

    行数备注

    43

    您只需要添加 @WebServlet 注解,并提供一个映射到用于访问 servlet 的 URL。

    46-48

    每个网页都需要正确的 HTML 格式。这个 Quickstart 使用了静态字符串编写最小的页头和页尾输出。

    50-51

    这些行会注入生成实际消息的 HelloService CDI bean。只要我们不更改 HelloService 的 API,此方法可让我们日后在不更改视图层的情况下更改 HelloService 实现。

    58

    这一行调用服务以生成 "Hello World" 信息,然后写入 HTTP 请求。

  2. 检查 HelloService 代码。

    HelloService.java 文件位于 src/main/java/org/jboss/as/quickstarts/helloworld/ 目录中。此服务只是返回消息。无需 XML 或注解注册。

    示例:HelloService 类别代码

    公共类 HelloService {
    
        String createHelloMessage(String name) {
            return "Hello " + name + "!";
        }
    }

3.4.2. 探讨 numberguess Quickstart 例程

numberguess quickstart 显示了如何创建简单的非持久性应用程序并将其部署到 JBoss EAP。信息通过 JSF 视图显示,业务逻辑被涵盖在两个 CDI bean 中。在 numberguess quickstart 中,您拥有 10 次机会,可以猜选 1 到 100 之间的数字。每次猜选之后,系统会告知您数字是过高还是过低。

numberguess quickstart 的代码位于 QUICKSTART_HOME/numberguess/ 目录中,QUICKSTART_HOME 目录是您下载和解压 JBoss EAP quickstart 的位置。numberguess quickstart 由多个 bean、配置文件和 Facelets (JSF) 视图组成,封装成为 WAR 模块。

有关使用命令行来构建和部署此 quickstart 的详细说明,请参考 numberguess quickstart 根目录中的 README.html 文件。以下示例使用 Red Hat JBoss Developer Studio 来运行 quickstart。

前提条件
检查配置文件

本示例的所有配置文件都位于 quickstart 的 QUICKSTART_HOME/numberguess/src/main/webapp/WEB-INF/ 目录中。

  1. 检查 faces-config.xml 文件。

    这个 quickstart 使用 JSF 2.2 版本的 faces-config.xml 文件名。标准版 Facelets 是 JSF 2.2 中默认的视图处理程序,因此无需配置。本文件只包含根元素,仅作为标记文件,用于指示应当在应用程序中启用 JSF。

    <faces-config version="2.2"
       xmlns="http://xmlns.jcp.org/xml/ns/javaee"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="
          http://xmlns.jcp.org/xml/ns/javaee
          http://xmlns.jcp.org/xml/ns/javaee/web-facesconfig_2_2.xsd">
    
    </faces-config>
  2. 检查 beans.xml 文件。

    beans.xml 文件包含 1.1 版本号和 bean-discovery-mode(模式为 all)。这个文件是标记文件,将 WAR 识别为 bean 归档,指示 JBoss EAP 在本应用程序中查找 bean 和激活 CDI。

    <beans xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="
          http://xmlns.jcp.org/xml/ns/javaee
          http://xmlns.jcp.org/xml/ns/javaee/beans_1_1.xsd"
        bean-discovery-mode="all">
    </beans>
注意

这个 quickstart 不需要 web.xml 文件。

3.4.2.1. 检查 JSF 代码

JSF 将 .xhtml 文件扩展应用于源文件,但使用 .jsf 扩展来提供渲染视图。home.xhtml 文件位于 src/main/webapp/ 目录中。

示例:JSF 源代码

19<html xmlns="http://www.w3.org/1999/xhtml"
20	xmlns:ui="http://java.sun.com/jsf/facelets"
21	xmlns:h="http://java.sun.com/jsf/html"
22	xmlns:f="http://java.sun.com/jsf/core">
23
24	<head>
25	<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
26	<title>Numberguess</title>
27	</head>
28
29	<body>
30	<div id="content">
31		<h1>Guess a number...</h1>
32		<h:form id="numberGuess">
33
34		<!-- Feedback for the user on their guess -->
35	<div style="color: red">
36		<h:messages id="messages" globalOnly="false" />
37		<h:outputText id="Higher" value="Higher!"
38 		  rendered="#{game.number gt game.guess and game.guess ne 0}" />
39		<h:outputText id="Lower" value="Lower!"
40		   rendered="#{game.number lt game.guess and game.guess ne 0}" />
41	</div>
42
43	<!-- Instructions for the user -->
44	<div>
45	I'm thinking of a number between <span
46	id="numberGuess:smallest">#{game.smallest}</span> and <span
47	id="numberGuess:biggest">#{game.biggest}</span>. You have
48	#{game.remainingGuesses} guesses remaining.
49	</div>
50
51	<!-- Input box for the users guess, plus a button to submit, and reset -->
52	<!-- These are bound using EL to our CDI beans -->
53	<div>
54	Your guess:
55	<h:inputText id="inputGuess" value="#{game.guess}"
56		required="true" size="3"
57		disabled="#{game.number eq game.guess}"
58		validator="#{game.validateNumberRange}" />
59		<h:commandButton id="guessButton" value="Guess"
60			action="#{game.check}"
61			disabled="#{game.number eq game.guess}" />
62	</div>
63	<div>
64	<h:commandButton id="restartButton" value="Reset"
65	action="#{game.reset}" immediate="true" />
66	</div>
67	</h:form>
68
69	</div>
70
71	<br style="clear: both" />
72
73	</body>
74</html>

下列行编号与在 JBoss Developer Studio 中查看文件时显示的编号对应。

表 3.2. JSF 详情

行数备注

36-40

这是发送给用户的信息:"Higher!" 和 "Lower!"

45-48

随着用户不断猜选,他们能够猜选的数字范围会逐渐缩小。本句更改以确保他们知道有效的数字猜选范围。

55-58

这个输入字段绑定一个使用值表达式的 Bean 属性。

58

使用验证程序绑定,确保用户不会意外输入猜选范围以外的数字。如果未采用验证程序,用户可能使用猜选范围外的数值。

59-61

必须为用户提供将猜选值发送至服务器的方法。此处我们绑定 bean 上的操作方法。

3.4.2.2. 检查类文件

所有的 numberguess quickstart 源文件都可在 QUICKSTART_HOME/numberguess/src/main/java/org/jboss/as/quickstarts/numberguess/ 目录中找到。这些列表中已经排除了软件包声明和导入。quickstart 源代码中有完整的列表。

  1. 检查 Random.java 限定词码

    限定词用于消除两个 bean 之间的歧义,这两个 bean 都能基于其类型进行注入。有关限定词的详细信息,请参考《JBoss EAP 开发指南》中的使用限定词解析模糊注入@Random 限定词用于注入随机数。

    @Target({ TYPE, METHOD, PARAMETER, FIELD })
    @Retention(RUNTIME)
    @Documented
    @Qualifier
    public @interface Random {
    
    }
  2. 检查 MaxNumber.java 限定词码

    @MaxNumber 限定词用于注入允许的最大数。

    @Target({ TYPE, METHOD, PARAMETER, FIELD })
    @Retention(RUNTIME)
    @Documented
    @Qualifier
    public @interface MaxNumber {
    }
  3. 检查 Generator.java 代码

    Generator 类别通过生产方法创建随机数,以同样的方式给出可能的最大数值。此类别属于应用程序范围,所以您不会每次得到不同的随机数。

    @SuppressWarnings("serial")
    @ApplicationScoped
    public class Generator implements Serializable {
    
        private java.util.Random random = new java.util.Random(System.currentTimeMillis());
    
        private int maxNumber = 100;
    
        java.util.Random getRandom() {
            return random;
        }
    
        @Produces
        @Random
        int next() {
            // a number between 1 and 100
            return getRandom().nextInt(maxNumber - 1) + 1;
        }
    
        @Produces
        @MaxNumber
        int getMaxNumber() {
            return maxNumber;
        }
    }
  4. 检查 Game.java 代码

    属于会话范围的 Game 类别是该应用程序的主要入口点。它负责设置或重新设置游戏、获取和验证用户的猜选,并利用 FacesMessage 向用户提供反馈。它使用构建后生命周期方法初始化游戏,从 @Random Instance<Integer> bean 检索随机数。

    注意此类别中的 @Named 注解。只有在您想要使用表达式语言 (EL)(本例中为 #{game})使 bean 可由 JSF 视图访问时,才会需要此注解。

    @SuppressWarnings("serial")
    @Named
    @SessionScoped
    public class Game implements Serializable {
    
        /**
         * The number that the user needs to guess
         */
        private int number;
    
        /**
         * The users latest guess
         */
        private int guess;
    
        /**
         * The smallest number guessed so far (so we can track the valid guess range).
         */
        private int smallest;
    
        /**
         * The largest number guessed so far
         */
        private int biggest;
    
        /**
         * The number of guesses remaining
         */
        private int remainingGuesses;
    
        /**
         * The maximum number we should ask them to guess
         */
        @Inject
        @MaxNumber
        private int maxNumber;
    
        /**
         * The random number to guess
         */
        @Inject
        @Random
        Instance<Integer> randomNumber;
    
        public Game() {
        }
    
        public int getNumber() {
            return number;
        }
    
        public int getGuess() {
            return guess;
        }
    
        public void setGuess(int guess) {
            this.guess = guess;
        }
    
        public int getSmallest() {
            return smallest;
        }
    
        public int getBiggest() {
            return biggest;
        }
    
        public int getRemainingGuesses() {
            return remainingGuesses;
        }
    
        /**
         * Check whether the current guess is correct, and update the biggest/smallest guesses as needed. Give feedback to the user
         * if they are correct.
         */
        public void check() {
            if (guess > number) {
                biggest = guess - 1;
            } else if (guess < number) {
                smallest = guess + 1;
            } else if (guess == number) {
                FacesContext.getCurrentInstance().addMessage(null, new FacesMessage("Correct!"));
            }
            remainingGuesses--;
        }
    
        /**
         * Reset the game, by putting all values back to their defaults, and getting a new random number. We also call this method
         * when the user starts playing for the first time using {@linkplain PostConstruct @PostConstruct} to set the initial
         * values.
         */
        @PostConstruct
        public void reset() {
            this.smallest = 0;
            this.guess = 0;
            this.remainingGuesses = 10;
            this.biggest = maxNumber;
            this.number = randomNumber.get();
        }
    
        /**
         * A JSF validation method which checks whether the guess is valid. It might not be valid because there are no guesses left,
         * or because the guess is not in range.
         *
         */
        public void validateNumberRange(FacesContext context, UIComponent toValidate, Object value) {
            if (remainingGuesses <= 0) {
                FacesMessage message = new FacesMessage("No guesses left!");
                context.addMessage(toValidate.getClientId(context), message);
                ((UIInput) toValidate).setValid(false);
                return;
            }
            int input = (Integer) value;
    
            if (input < smallest || input > biggest) {
                ((UIInput) toValidate).setValid(false);
    
                FacesMessage message = new FacesMessage("Invalid guess");
                context.addMessage(toValidate.getClientId(context), message);
            }
        }
    }