// 
//     Licensed to the Apache Software Foundation (ASF) under one
//     or more contributor license agreements.  See the NOTICE file
//     distributed with this work for additional information
//     regarding copyright ownership.  The ASF licenses this file
//     to you under the Apache License, Version 2.0 (the
//     "License"); you may not use this file except in compliance
//     with the License.  You may obtain a copy of the License at
// 
//       http://www.apache.org/licenses/LICENSE-2.0
// 
//     Unless required by applicable law or agreed to in writing,
//     software distributed under the License is distributed on an
//     "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
//     KIND, either express or implied.  See the License for the
//     specific language governing permissions and limitations
//     under the License.
//

= 使用 EJB 3.1 创建企业应用程序
:jbake-type: tutorial
:jbake-tags: tutorials 
:jbake-status: published
:icons: font
:syntax: true
:source-highlighter: pygments
:toc: left
:toc-title:
:description: 使用 EJB 3.1 创建企业应用程序 - Apache NetBeans
:keywords: Apache NetBeans, Tutorials, 使用 EJB 3.1 创建企业应用程序

本教程介绍了开发 Java EE 6 企业应用程序的基础知识，并说明了作为 Java EE 6 规范一部分引入的某些 EJB 3.1 技术功能。在本教程中，将创建一个企业应用程序，用户可通过该应用程序向数据库发布消息以及从中检索消息。

该应用程序包含一个 EJB 模块和 Web 模块。EJB 模块包含一个实体类、实体类的会话 Facade 以及消息驱动 Bean。Web 模块包含用于显示和发布消息的 Servlet 以及计算会话中的用户数的单个会话 Bean。

在开始本教程之前，可能需要熟悉以下文档。

* link:javaee-gettingstarted.html[+Java EE 应用程序入门指南+]

*教程练习*

* <<intro,关于 NewsApp 企业应用程序>>
* <<Exercise_1,创建企业应用程序项目>>
* <<Exercise_2,对 EJB 模块进行编码>>
* <<Exercise_2a,创建实体类>>
* <<Exercise_2b,创建消息驱动 Bean>>
* <<Exercise_2c,创建会话 Facade>>
* <<Exercise_3,对 Web 模块进行编码>>
* <<Exercise_3a,创建单个会话 Bean>>
* <<Exercise_3b,创建 ListNews Servlet>>
* <<Exercise_3c,创建 PostMessage Servlet>>
* <<Exercise_4,运行项目>>
* <<Exercise_5,下载解决方案项目>>
* <<Exercise_6,疑难解答>>

*要学习本教程，您需要具备以下软件和资源。*

|===
|软件或资源 |要求的版本 

|link:https://netbeans.org/downloads/index.html[+NetBeans IDE+] |7.2、7.3、7.4、8.0、Java EE 版本 

|link:http://www.oracle.com/technetwork/java/javase/downloads/index.html[+Java 开发工具包 (JDK)+] |版本 7 或 8 

|GlassFish Server Open Source Edition |3.x、4.x 
|===

*先决条件*

本文档假定您具备以下技术的一些基本知识或编程经验：

* Java 编程
* NetBeans IDE

您可以下载link:https://netbeans.org/projects/samples/downloads/download/Samples/JavaEE/NewsAppEE6.zip[+已完成项目的 zip 档案文件+]。


== 关于 NewsApp 企业应用程序

在本教程中，将创建一个名为 NewsApp 的简单 Java EE 6 多层企业应用程序示例。NewsApp 应用程序使用 Java EE 6 规范中引入的一些功能。

NewsApp 应用程序结构通常与以下层相对应。

* *Web 层*。Web 层包含应用程序的表示逻辑，它在 Java EE 服务器上运行。在 NewsApp 应用程序中，Web 层由 Web 模块表示，其中包含访问 EJB 模块中的业务逻辑的 Servlet。
* *业务层*。业务层应用程序也在 Java EE 服务器上运行，其中包含应用程序的业务逻辑。在 NewsApp 应用程序中，业务层由 EJB 模块表示。EJB 模块包含处理来自 Web 层客户端的请求的代码，并管理事务以及如何将对象永久保存到数据库中。
* *EIS 层*。EIS 层是应用程序的持久性存储层。在 NewsApp 应用程序中，该层由存储消息的数据库表示。

在 IDE 中构建企业应用程序时，EJB 和 Web 应用程序模块将打包到 EAR 档案中，然后将档案部署到服务器上。随后通常从*客户端层*访问该应用程序。客户端层是运行客户端的环境，它通常是用户本地系统上的 Web 浏览器。

NOTE: 在本教程的示例中，您将使用单台计算机承载 Java EE 服务器、数据库并查看 Web 页。在大型企业应用程序中，不同的层通常分布在多台计算机上。Web 层和业务层应用程序通常部署到在不同计算机中托管的 Java EE 服务器上。

有关 Java EE 企业应用程序结构的更多详细信息，请参见 link:http://download.oracle.com/javaee/6/tutorial/doc/[+Java EE 6 教程第 I 部分+]中的link:http://download.oracle.com/javaee/6/tutorial/doc/bnaay.html[+分布式多层应用程序+]一章。


== 创建企业应用程序项目

本练习旨在创建 NewsApp 企业应用程序项目。将使用新建项目向导创建一个企业应用程序，其中包含 EJB 模块和 Web 模块。

1. 从主菜单中选择 "File"（文件）> "New Project"（新建项目）（Ctrl-Shift-N 组合键；在 Mac 上为 ⌘-Shift-N 组合键）。
2. 从 "Java EE" 类别中选择 "Enterprise Application"（企业应用程序），然后单击“下一步”。
3. 将项目命名为 *NewsApp* 并设置项目位置。
4. 取消选中 "Use Dedicated Folder"（使用专用文件夹）选项（如果该选项处于选中状态）。
（在本教程中，我们将项目库复制到一个专门的文件夹中，因为需要与其他用户或项目共享库。）
单击 "Next"（下一步）。

[start=5]
. 将服务器设置为 "GlassFish Server"，并将 Java EE 版本设置为 "Java EE 6" 或 "Java EE 7"。

[start=6]
. 选中 "Create EJB Module"（创建 EJB 模块）和 "Create Web Application Module"（创建 Web 应用程序模块）。单击 "Finish"（完成）。

image::images/new-entapp-wizard.png[title="新建项目向导"]

单击 "Finish"（完成），此时 IDE 将创建三个项目：NewsApp、NewsApp-ejb 和 NewsApp-war。如果在 "Projects"（项目）窗口中展开 "NewsApp" 节点，则会发现企业应用程序项目不包含任何源代码。所有源代码将包含在向导创建的两个模块中，这些模块将在 "Java EE Modules"（Java EE 模块）节点下面列出。

企业应用程序项目中只包含应用程序的相关配置和打包详细信息。构建并运行企业应用程序时，IDE 将创建一个 EAR 档案并将 EAR 部署到服务器。在某些情况下，企业应用程序项目将包含部署描述符文件以及其他信息，但创建部署到 GlassFish Server 的 Java EE 企业应用程序时，不需要部署描述符文件。

image::images/ejb-projectswindow.png[title="显示应用程序结构的 "Projects"（项目）窗口"] 


== 对 EJB 模块进行编码

在本练习中，将在 EJB 模块中创建一个实体类、消息驱动 Bean 以及会话 Facade。此外，将创建一个持久性单元以便为容器提供有关数据源和如何管理实体的信息，还会创建消息驱动 Bean 使用的 Java 消息服务 (JMS) 资源。


=== 创建实体类

在本练习中，您将创建  ``NewsEntity``  实体类。实体类是一个简单的 Java 类，通常表示数据库中的一个表。在创建实体类时，IDE 会添加  ``@Entity``  标注以将该类定义为实体类。创建实体类后，将在其中创建一些字段，以表示要包含在表中的数据。

每个实体类都必须具有一个主键。在创建实体类时，IDE 会添加  ``@Id``  标注以声明要用作主键的字段。IDE 还会添加  ``@GeneratedValue``  标注并指定主 Id 的键生成策略。

要创建  ``NewsEntity``  类，请执行以下步骤。

1. 右键单击 "Projects"（项目）窗口中的 EJB 模块，然后选择 "New"（新建）＞ "Other"（其他）打开新建文件向导。
2. 从 "Persistence"（持久性）类别中，选择 "Entity Class"（实体类），然后单击 "Next"（下一步）。
3. 键入 *NewsEntity* 作为类名。
4. 键入 *ejb* 作为包名。
5. 在新建实体类向导中，将 "Primary Key Type"（主键类型）保留为  ``Long`` 。
6. 选择 "Create Persistence Unit"（创建持久性单元）。单击 "Next"（下一步）。
7. 保留默认的持久性单元名称。
8. 选择  ``EclipseLink (JPA2.0)(default)`` （EclipseLink (JPA2.0) (默认)）作为持久性提供器。
9. 为 "Data Source"（数据源）选择一个数据源（例如，如果您希望使用 JavaDB，则选择  ``jdbc/sample`` ）。
10. 确保持久性单元将使用 Java 事务 API，并且已将 "Table Generation Strategy"（表生成策略）设置为 "Create"（创建），以便在部署应用程序时将创建基于实体类的表。

image::images/new-pu-wizard.png[title=""Provider and Database"（提供器和数据库）面板"]


[start=11]
. 单击 "Finish"（完成）。

单击 "Finish"（完成），此时 IDE 将创建  ``persistence.xml``  和  ``NewsEntity.java``  实体类。IDE 将在源代码编辑器中打开  ``NewsEntity.java`` 。

在源代码编辑器中，执行以下步骤。


[start=1]
. 将以下字段声明添加到类中：

[source,java]
----

private String title;
private String body;
----

[start=2]
. 在源代码编辑器中右键单击，选择 "Insert Code"（插入代码）（Alt-Insert 组合键；在 Mac 上为 Ctrl-I 组合键），然后选择 "Getter and Setter"（Getter 和 Setter），以打开 "Generate Getters and Setters"（生成 Getter 和 Setter）对话框。

[start=3]
. 在对话框中选择  ``body``  和  ``title``  字段。单击 "Generate"（生成）。

image::images/ejb-gettersetter.png[title=""Generate Getters and Setters"（生成 getter 和 setter）对话框"]

在单击 "Generate"（生成）时，IDE 将为字段添加 getter 和 setter 方法。


[start=4]
. 保存对  ``NewsEntity.java``  的更改。

您可以将  ``NewsEntity.java``  关闭。

有关实体类的更多详细信息，请参见 link:http://download.oracle.com/javaee/6/tutorial/doc/[+Java EE 6 教程第 I 部分+]中的 link:http://java.sun.com/javaee/6/docs/tutorial/doc/bnbpz.html[+Java 持久性 API 简介+]一章。


=== 创建消息驱动 Bean

在本练习中，将使用向导在 EJB 模块中创建 NewMessage 消息驱动 Bean。该向导还会帮助您创建所需的 JMS 资源。消息驱动 Bean 接收和处理 Web 模块中的 Servlet 发送到队列的消息。

要创建消息驱动 Bean，请执行以下步骤：

1. 右键单击 "Projects"（项目）窗口中的 EJB 模块，然后选择 "New"（新建）＞ "Other"（其他）打开新建文件向导。
2. 从 "Enterprise JavaBeans" 类别中，选择 "Message-Driven Bean"（消息驱动 Bean）文件类型。单击 "Next"（下一步）。
3. 键入 *NewMessage* 作为 EJB 名称。
4. 从 "Package"（包）下拉列表中选择  ``ejb`` 。
5. 单击 "Project Destination"（项目目标）字段旁边的 "Add"（添加）按钮，以打开 "Add Message Destination"（添加消息目标）对话框。
6. 在 "Add Message Destination"（添加消息目标）对话框中，键入 *jms/NewMessage* 并选择 "Queue"（队列）作为目标类型。单击 "OK"（确定）。
7. 确认项目目标正确无误。单击 "Finish"（完成）。

image::images/ejb-newmessage.png[title="新建消息驱动 Bean 向导"]

单击 "Finish"（完成），此时将在源代码编辑器中打开 Bean 类  ``NewMessage.java`` 。您可以看到 IDE 在该类中添加了  ``@MessageDriven``  标注和配置属性。


[source,java]
----

       
@MessageDriven(mappedName = "jms/NewMessage", activationConfig =  {
        @ActivationConfigProperty(propertyName = "acknowledgeMode", propertyValue = "Auto-acknowledge"),
        @ActivationConfigProperty(propertyName = "destinationType", propertyValue = "javax.jms.Queue")
    })
public class NewMessage implements MessageListener {
----

 ``@MessageDriven``  标注向容器说明该组件是一个消息驱动 Bean，并指定了该 Bean 使用的 JMS 资源。当 IDE 生成类时，将从类名 ( ``NewMessage.java`` ) 派生资源的映射名称 ( ``jms/NewMessage`` )。JMS 资源被映射到 Bean 从中接收消息的目标的 JNDI 名称。新建消息驱动 Bean 向导还会在  ``glassfish-resources.xml``  中添加 JMS 资源的信息。您无需配置部署描述符即可指定 JMS 资源。如果在 IDE 中使用 "Run"（运行）操作将应用程序部署到 GlassFish，则会在部署时在服务器上创建 JMS 资源。

EJB 规范允许您使用标注将资源直接引入类中。现在，您要使用标注将  ``MessageDrivenContext``  资源引入类中，然后注入  ``PersistenceContext``  资源，EntityManager API 将使用该资源管理持久性实体实例。将在源代码编辑器中将标注添加到类中。

1. 通过在类中添加以下带标注的字段（以粗体显示），将  ``MessageDrivenContext``  资源注入类中：

[source,java]
----

public class NewMessage implements MessageListener {

*@Resource
private MessageDrivenContext mdc;*
----

[start=2]
. 在代码中右键单击，从弹出式菜单中选择 "Insert Code"（插入代码）（Alt-Insert 组合键；在 Mac 上为 Ctrl-I 组合键），然后选择 "Use Entity Manager"（使用实体管理器），以便将实体管理器引入类中。IDE 在源代码中添加以下  ``@PersistenceContext``  标注。

[source,java]
----

@PersistenceContext(unitName = "NewsApp-ejbPU")
private EntityManager em;
----
IDE 还会生成以下  ``persist``  方法。

[source,java]
----

public void persist(Object object) {
    em.persist(object);
}
----

[start=3]
. 修改  ``persist``  方法，将名称更改为  ``save`` 。该方法应如下所示：

[source,java]
----

public void *save*(Object object) {     
    em.persist(object);
}
----

[start=4]
. 在  ``onMessage``  方法主体中添加以下代码（以粗体显示）以修改该方法。

[source,java]
----

public void onMessage(Message message) {
    *ObjectMessage msg = null;
    try {
        if (message instanceof ObjectMessage) {
            msg = (ObjectMessage) message;
            NewsEntity e = (NewsEntity) msg.getObject();
            save(e);            
        }
    } catch (JMSException e) {
        e.printStackTrace();
        mdc.setRollbackOnly();
    } catch (Throwable te) {
        te.printStackTrace();
    }*
}
----

[start=5]
. 在编辑器中右键单击，然后选择 "Fix Imports"（修复导入）（Alt-Shift-I 组合键；在 Mac 上为 ⌘-Shift-I 组合键），以生成所有必要的 import 语句。保存所做的更改。

NOTE: 在生成 import 语句时，您需要*确保导入  ``javax.jms``  和  ``javax.annotation.Resource``  库*。

有关消息驱动 Bean 的更多详细信息，请参见 link:http://download.oracle.com/javaee/6/tutorial/doc/[+Java EE 6 教程第 I 部分+]中的link:http://java.sun.com/javaee/6/docs/tutorial/doc/gipko.html[+什么是消息驱动 Bean？+]一章。


=== 创建会话 Facade

在本练习中，将为 NewsEntity 实体类创建一个会话 Facade。EJB 3.0 规范通过减少所需的代码数量并允许使用标注将类声明为会话 Bean，简化了会话 Bean 的创建过程。通过将业务接口指定为可选，EJB 3.1 规范进一步简化了会话 Bean 的要求。本地客户端可以通过本地接口或无接口视图访问会话 Bean。在本教程中，不会为该 Bean 创建接口。Web 应用程序中的 Servlet 将通过无接口视图访问该 Bean。

要创建会话 Facade，请执行以下步骤：

1. 右键单击 EJB 模块，然后选择 "New"（新建）＞ "Other"（其他）。
2. 从 "Persistence"（持久性）类别中，选择 "Session Beans for Entity Classes"（实体类的会话 Bean）。单击 "Next"（下一步）。
3. 从可用实体类的列表中选择  ``ejb.NewsEntity`` ，然后单击 "Add"（添加）以将该类移动到 "Selected Entity Classes"（选定的实体类）窗格中。单击 "Next"（下一步）。
4. 检查是否将包设置为  ``ejb`` 。单击 "Finish"（完成）。

image::images/ejb-sessionforentity.png[title="新建消息驱动 Bean 向导"]

单击 "Finish"（完成），此时 IDE 将生成会话 Facade 类  ``NewsEntityFacade.java``  和  ``AbstractFacade.java`` ，并在编辑器中打开文件。正如您在生成的代码中所能看到的一样， ``@Stateless``  标注用于将  ``NewsEntityFacade.java``  声明为无状态会话 Bean 组件。IDE 还会添加  ``PersistenceContext``  标注，以便将资源直接注入会话 Bean 组件中。 ``NewsEntityFacade.java``  用于扩展  ``AbstractFacade.java`` ，后者包含业务逻辑并可用于管理事务。

NOTE: 如果 Bean 将由远程客户端访问，则还需要远程接口。

有关会话 Bean 的更多信息，请参见 link:http://download.oracle.com/javaee/6/tutorial/doc/[+Java EE 6 教程第 I 部分+]中的link:http://java.sun.com/javaee/6/docs/tutorial/doc/gipjg.html[+什么是会话 Bean？+]一章。


== 对 Web 模块进行编码

在本节中，将在 Web 模块中创建两个 Servlet。ListNews Servlet 通过 EJB 模块中的实体 Facade 从数据库中检索消息。PostMessage Servlet 用于发送 JMS 消息。

在本节中，还会在 Web 模块中创建单个会话 Bean 以计算会话中的当前用户数。通过使用 EJB 3.1 规范，您可以在 Web 应用程序中创建企业 Bean。在 EJB 3.1 之前，所有企业 Bean 必须位于 EJB 模块中。


=== 创建单个会话 Bean

EJB 3.1 规范引入了  ``@Singleton``  标注，可通过该标注轻松创建单个会话 Bean。EJB 3.1 还定义了其他标注以配置单个会话 Bean 的属性，例如，在实例化该 Bean 时。

在实例化单个会话 Bean 后，它将在应用程序生命周期内存在。顾名思义，单个会话 Bean 在应用程序中只能有一个实例。与无状态会话 Bean 类似，单个会话 Bean 可以具有多个客户端。

要创建单个会话 Bean，请执行以下步骤。

1. 右键单击 Web 模块，然后选择 "New"（新建）＞ "Other"（其他）打开新建文件向导。
2. 选择 "Enterprise JavaBeans" 类别中的 "Session Bean"（会话 Bean）。单击 "Next"（下一步）。
3. 键入 *SessionManagerBean* 作为 EJB 名称。
4. 键入 *ejb* 作为包名。
5. 选择 "Singleton"（单个）。单击 "Finish"（完成）。

image::images/ejb-newsingleton.png[title="在新建会话 Bean 向导中创建单个会话 Bean"]

单击 "Finish"（完成），此时 IDE 将创建单个会话 Bean 类并在编辑器中打开该类。您可能会发现，IDE 在该类中添加了  ``@Singleton``  标注以声明单个会话 Bean。该向导还使用  ``@LocalBean``  标注该类。


[source,java]
----

@Singleton
@LocalBean
public class SessionManagerBean {

}
----

1. 使用  ``@WebListener``  标注该类并实现  ``HttpSessionListener`` 。

[source,java]
----

@Singleton
@LocalBean
*@WebListener*
public class SessionManagerBean *implements HttpSessionListener*{

}
----

 ``@WebListener``  标注是 Servlet 3.0 API 的一部分，可用于直接在代码中实现监听程序。

在实现  ``HttpSessionListener``  时，IDE 将在旁注中显示一条警告。


[start=2]
. 单击左旁注中的警告标记，然后选择 "Implement all abstract methods"（实现所有抽象方法）。

image::images/ejb-implementabstract.png[title="用于实现抽象方法的编辑器提示"]

IDE 将添加  ``sessionCreated``  和  ``sessionDestroyed``  方法。


[start=3]
. 添加  ``counter``  静态字段，并将初始值设置为  ``0`` 。

[source,java]
----

@LocalBean
@WebListener
public class SessionManagerBean implements HttpSessionListener{
    *private static int counter = 0;*
----

[start=4]
. 修改生成的  ``sessionCreated``  和  ``sessionDestroyed``  方法主体，以便在新会话启动时增加字段值，并在会话完成时减少字段值。该值将存储在  ``counter``  字段中。

[source,java]
----

public void sessionCreated(HttpSessionEvent se) {
    *counter++;*
}

public void sessionDestroyed(HttpSessionEvent se) {
    *counter--;*
}
----

[start=5]
. 添加以下方法以返回当前的  ``counter``  值。

[source,java]
----

public int getActiveSessionsCount() {
        return counter;
    }
----

将从 Servlet 中调用此方法以显示当前的用户/打开会话数。


[start=6]
. 保存所做的更改。

现在，会话 Bean 代码应如下所示。


[source,java]
----

@Singleton
@LocalBean
@WebListener
public class SessionManagerBean implements HttpSessionListener {
    private static int counter = 0;

    public void sessionCreated(HttpSessionEvent se) {
        counter++;
    }

    public void sessionDestroyed(HttpSessionEvent se) {
        counter--;
    }

    public int getActiveSessionsCount() {
        return counter;
    }

}
----

有关单个会话 Bean 的更多详细信息，请参见 link:http://download.oracle.com/javaee/6/tutorial/doc/[+Java EE 6 教程第 I 部分+]中的link:http://java.sun.com/javaee/6/docs/tutorial/doc/gipjg.html[+什么是会话 Bean？+]一章。


=== 创建  ``ListNews``  Servlet

在本练习中，将创建一个简单的 Servlet 以显示存储的消息。将使用标注从该 Servlet 中调用企业 Bean NewsEntityFacade。

1. 右键单击 Web 模块项目，然后选择 "New"（新建）> "Servlet"。
2. 键入 *ListNews* 作为类名。
3. 输入 *web* 作为包名。单击 "Finish"（完成）。

单击 "Finish"（完成），此时将在源代码编辑器中打开类  ``ListNews.java`` 。在源代码编辑器中，执行以下步骤。

1. 在源代码编辑器中右键单击，选择 "Insert Code"（插入代码）（Alt-Insert 组合键；在 Mac 上为 Ctrl-I 组合键），然后选择 "Call Enterprise Bean"（调用企业 Bean）。
2. 在 "Call Enterprise Bean"（调用企业 Bean）对话框中，展开 "NewsApp-ejb" 节点，然后选择 "NewsEntityFacade"。单击 "OK"（确定）。

IDE 将添加  ``@EJB``  标注以注入企业 Bean。


[start=3]
. 可以再次使用 "Call Enterprise Bean"（调用企业 Bean）对话框在 NewsApp-war 节点下面注入 SessionManagerBean。

将会在代码中看到以下标注，它们用于注入两个企业 Bean。


[source,java]
----

@WebServlet(name = "ListNews", urlPatterns = {"/ListNews"})
public class ListNews extends HttpServlet {

    @EJB
    private SessionManagerBean sessionManagerBean;
    @EJB
    private NewsEntityFacade newsEntityFacade;
                
----

您还会看到使用了  ``@WebServlet``  标注将类声明为 Servlet 并指定了 Servlet 名称。 ``@WebServlet``  标注是 Java EE 6 规范中引入的 Servlet 3.0 API 的一部分。可以使用标注标识 Servlet，而不是使用  ``web.xml``  部署描述符。NewsApp 应用程序不包含  ``web.xml`` 。


[start=4]
. 在  ``processRequest``  方法中，添加以下代码（以粗体显示）以返回当前会话或创建新会话。

[source,java]
----

protected void processRequest(HttpServletRequest request, HttpServletResponse response)
        throws ServletException, IOException {
        *request.getSession(true);*
        response.setContentType("text/html;charset=UTF-8");
----

[start=5]
. 将以下代码（以粗体显示）添加到  ``processRequest``  方法中，以输出消息并添加指向 PostMessage Servlet 的链接。（根据需要取消注释方法中的代码。）

[source,xml]
----

out.println("<h1>Servlet ListNews at " + request.getContextPath () + "</h1>");

*List news = newsEntityFacade.findAll();
for (Iterator it = news.iterator(); it.hasNext();) {
    NewsEntity elem = (NewsEntity) it.next();
    out.println(" <b>"+elem.getTitle()+" </b><br />");
    out.println(elem.getBody()+"<br /> ");
}
out.println("<a href='PostMessage'>Add new message</a>");*

out.println("</body>");
                    
----

[start=6]
. 添加以下代码（以粗体显示）以检索并输出用户/打开会话数。

[source,xml]
----

out.println("<a href='PostMessage'>Add new message</a>");

*out.println("<br><br>");
out.println(sessionManagerBean.getActiveSessionsCount() + " user(s) reading the news.");*

out.println("</body>");
                    
----

[start=7]
. 按 Ctrl-Shift-I 组合键以为该类生成所有必要的 import 语句。在生成 import 语句时，您希望*导入  ``java.util``  库*。

[start=8]
. 保存对该文件所做的更改。


=== 创建  ``PostMessage``  Servlet

在本练习中，您将创建用于传递消息的  ``PostMessage``  Servlet。您将使用标注向该 Servlet 中直接注入创建的 JMS 资源，从而指定变量名称及其映射到的名称。然后，您将添加用于发送 JMS 消息的代码，以及用于在 HTML 窗体中添加消息的代码。

1. 右键单击 Web 模块项目，然后选择 "New"（新建）> "Servlet"。
2. 键入  ``PostMessage``  作为类名。
3. 输入  ``web``  作为包名，然后单击 "Finish"（完成）。

单击 "Finish"（完成），此时将在源代码编辑器中打开类  ``PostMessage.java`` 。在源代码编辑器中，执行以下步骤。

1. 通过添加以下字段声明（以粗体显示），使用标注来注入  ``ConnectionFactory``  和  ``Queue``  资源：

[source,java]
----

@WebServlet(name="PostMessage", urlPatterns={"/PostMessage"})
public class PostMessage extends HttpServlet {                
    *@Resource(mappedName="jms/NewMessageFactory")
    private  ConnectionFactory connectionFactory;

    @Resource(mappedName="jms/NewMessage")
    private  Queue queue;*
----

[start=2]
. 现在，通过将下面以粗体显示的代码添加到  ``processRequest``  方法中，添加用于发送 JMS 消息的代码：

[source,java]
----

response.setContentType("text/html;charset=UTF-8");

// Add the following code to send the JMS message
*String title=request.getParameter("title");
String body=request.getParameter("body");
if ((title!=null) &amp;&amp; (body!=null)) {
    try {
        Connection connection = connectionFactory.createConnection();
        Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
        MessageProducer messageProducer = session.createProducer(queue);

        ObjectMessage message = session.createObjectMessage();
        // here we create NewsEntity, that will be sent in JMS message
        NewsEntity e = new NewsEntity();
        e.setTitle(title);
        e.setBody(body);

        message.setObject(e);                
        messageProducer.send(message);
        messageProducer.close();
        connection.close();
        response.sendRedirect("ListNews");

    } catch (JMSException ex) {
        ex.printStackTrace();
    }
}*
                        
PrintWriter out = response.getWriter();
                    
----

[start=3]
. 将以下行（以粗体显示）添加到  ``processRequest``  方法中，以添加用于添加消息的 Web 窗体。（根据需要取消注释用于输出 HTML 的代码。）

[source,xml]
----

out.println("Servlet PostMessage at " + request.getContextPath() + "</h1>");

// The following code adds the form to the web page
*out.println("<form>");
out.println("Title: <input type='text' name='title'><br/>");
out.println("Message: <textarea name='body'></textarea><br/>");
out.println("<input type='submit'><br/>");
out.println("</form>");*

out.println("</body>");
                
----

[start=4]
. 按 Ctrl-Shift-I 组合键以为该类生成所有必要的 import 语句。

NOTE: 在为  ``Connection`` 、 ``ConnectionFactory`` 、 ``Session``  和  ``Queue``  选择要导入的库时，*请确保导入  ``javax.jms``  库*。

image::images/import-jms.png[title="在 "Fix All Imports"（修复所有导入）对话框中选择 JMS 库"]


[start=5]
. 保存对该文件所做的更改。


== 运行项目

现在，您可以运行项目。运行项目时，您希望浏览器打开包含  ``ListNews``  Servlet 的页面。可以通过在企业应用程序的 "Properties"（属性）对话框中指定该页的 URL 来实现这一目的。该 URL 是相对于应用程序上下文路径的 URL。输入该相对 URL 后，您便可以从 "Projects"（项目）窗口中构建、部署和运行应用程序。

要设置相对 URL 并运行应用程序，请执行以下操作：

1. 在 "Projects"（项目）窗口中，右键单击 "NewsApp" 企业应用程序节点，然后在弹出式菜单中选择 "Properties"（属性）。
2. 在 "Categories"（类别）窗格中选择 "Run"（运行）。
3. 在 "Relative URL"（相对 URL）文本字段中，键入 */ListNews*。
4. 单击 "OK"（确定）。
5. 在 "Projects"（项目）窗口中，右键单击 "NewsApp" 企业应用程序节点，然后选择 "Run"（运行）。

运行项目时，将在浏览器中打开  ``ListNews``  Servlet，其中显示了数据库中消息的列表。首次运行项目时，数据库为空，但您可以单击添加消息按钮来添加消息。

image::images/ejb-browser1.png[title=""ListNews Servlet" 页"]

使用  ``PostMessage``  Servlet 添加消息时，会将该消息发送到消息驱动 Bean 以写入持久性存储，并且将调用  ``ListNews``  Servlet 以显示数据库中的消息。 ``ListNews``  检索到的数据库中消息的列表通常不包含新消息，因为消息服务是异步的。


== 下载解决方案项目

您可以采用下列方法下载本教程的解决方案（作为一个项目）。

* 下载link:https://netbeans.org/projects/samples/downloads/download/Samples%252FJavaEE%252FNewsAppEE6.zip[+已完成项目的 zip 档案文件+]。
* 通过执行以下步骤从 NetBeans 样例检出项目源代码：
1. 从主菜单中选择 "Team"（团队开发）> "Subversion" > "Checkout"（检出）。
2. 在 "Checkout"（检出）对话框中，输入以下资源库 URL：
 ``https://svn.netbeans.org/svn/samples~samples-source-code`` 
单击 "Next"（下一步）。

[start=3]
. 单击 "Browse"（浏览）以打开 "Browse Repository Folders"（浏览资源库文件夹）对话框。

[start=4]
. 展开根节点并选择 *samples/javaee/NewsAppEE6*。单击 "OK"（确定）。

[start=5]
. 指定用于存储源代码的本地文件夹（本地文件夹必须为空）。

[start=6]
. 单击 "Finish"（完成）。

单击 "Finish"（完成），此时 IDE 会将本地文件夹初始化为 Subversion 资源库，并检出项目源代码。


[start=7]
. 在完成检出操作后将会显示一个对话框，在该对话框中单击 "Open Project"（打开项目）。

NOTE: 需要 Subversion 客户端检出源代码。有关安装 Subversion 的更多信息，请参见 link:../ide/subversion.html[+NetBeans IDE 中的 Subversion 指南+]中有关link:../ide/subversion.html#settingUp[+设置 Subversion+] 的部分。


== 疑难解答

下面是您创建项目时可能会遇到的一些问题。


=== JMS 资源问题

使用向导创建 JMS 资源时，输出窗口中可能会显示以下服务器错误消息：


[source,java]
----

[com.sun.enterprise.connectors.ConnectorRuntimeException:
                    JMS resource not created : jms/Queue]
                
----

此消息可能表明未创建 JMS 资源，或者未在应用服务器中注册该资源。您可以使用应用服务器的管理控制台来检查、创建和编辑 JMS 资源。

要打开管理控制台，请执行以下操作：

1. 在 "Services"（服务）窗口中展开 "Servers"（服务器）节点，确认应用服务器处于运行状态。应用服务器节点旁边的绿色小箭头指示服务器正在运行。
2. 右键单击应用服务器节点，然后选择 "View Admin Console"（查看管理控制台）以在浏览器中打开登录窗口。
3. 登录到服务器。默认的用户名和口令分别为  ``admin``  和  ``adminadmin`` 。
4. 在浏览器中，展开管理控制台左框中的 "Resources"（资源）节点及其下面的 "JMS Resources"（JMS 资源）节点。
5. 单击左框中的 "Connection Factories"（连接工厂）和 "Destination Resources"（目标资源）链接，以检查是否在服务器中注册了这些资源，并在必要时修改这些资源。如果这些资源不存在，您可以在管理控制台中创建这些资源。

您需要确保将 PostMessage Servlet 中的 JMS 连接工厂资源映射到已在 Sun Java System Application Server 中注册的 JMS 连接工厂资源的正确 JNDI 名称上。

应在 Sun Java System Application Server 中注册以下资源：

* JNDI 名称为  ``jms/NewMessage``  且类型为  ``javax.jms.Queue``  的目标资源
* JNDI 名称为  ``jms/NewMessageFactory``  且类型为  ``javax.jms.QueueConnectionFactory``  的连接工厂资源


link:/about/contact_form.html?to=3&subject=Feedback:%20Creating%20an%20Enterprise%20Application%20with%20EJB%203.1[+发送有关此教程的反馈意见+]



== 另请参见

有关使用 NetBeans IDE 开发 Java EE 应用程序的更多信息，请参见以下资源：

* link:javaee-intro.html[+Java EE 技术简介+]
* link:javaee-gettingstarted.html[+Java EE 应用程序入门指南+]
* link:../web/quickstart-webapps.html[+Web 应用程序开发简介+]
* link:../../trails/java-ee.html[+Java EE 和 Java Web 学习资源+]

您可以在 link:http://docs.oracle.com/javaee/7/tutorial/doc/ejb-intro.htm[+Java EE 7 教程+]中找到有关使用企业 Bean 的详细信息。

要发送意见和建议、获得支持以及随时了解 NetBeans IDE Java EE 开发功能的最新开发情况，请link:../../../community/lists/top.html[+加入 nbj2ee 邮件列表+]。

