本教程介绍了开发 Java EE 6 企业应用程序的基础知识,并说明了作为 Java EE 6 规范一部分引入的某些 EJB 3.1 技术功能。在本教程中,将创建一个企业应用程序,用户可通过该应用程序向数据库发布消息以及从中检索消息。
该应用程序包含一个 EJB 模块和 Web 模块。EJB 模块包含一个实体类、实体类的会话 Facade 以及消息驱动 Bean。Web 模块包含用于显示和发布消息的 Servlet 以及计算会话中的用户数的单个会话 Bean。
在开始本教程之前,可能需要熟悉以下文档。
注意:本文档使用的是 NetBeans IDE 6.8 发行版。
教程练习
要学习本教程,您需要具备以下软件和资源。
软件或资源 | 要求的版本 |
---|---|
NetBeans IDE | 6.8,Java 版本 |
Java Development Kit (JDK) | 版本 6 |
GlassFish Server Open Source Edition | 3.0.1 |
注意:
先决条件
本文档假定您具备以下技术的一些基本知识或编程经验:
您可以下载已完成项目的 zip 归档文件。
在本教程中,将创建一个名为 NewsApp 的简单 Java EE 6 多层企业应用程序示例。NewsApp 应用程序使用 Java EE 6 规范中引入的一些功能。
NewsApp 应用程序结构通常与以下层相对应。
在 IDE 中生成企业应用程序时,EJB 和 Web 应用程序模块将打包到 EAR 归档中,然后将归档部署到服务器上。以后,通常从客户端层访问应用程序。客户端层是运行客户端的环境,通常是用户本地系统上的 Web 浏览器。
注意:在本教程的示例中,将使用一台计算机托管 Java EE 服务器和数据库以及查看 Web 页。在大型企业应用程序中,不同的层通常分布在多台计算机上。Web 层和业务层应用程序通常部署到在不同计算机中托管的 Java EE 服务器上。
有关 Java EE 企业应用程序结构的更多详细信息,请参见 Java EE 6 教程第 I 部分中的分布式多层应用程序一章。
本练习旨在创建 NewsApp 企业应用程序项目。将使用“新建项目”向导创建一个企业应用程序,其中包含 EJB 模块和 Web 模块。
在单击“完成”时,IDE 将创建三个项目:NewsApp、NewsApp-ejb 和 NewsApp-war。如果在“项目”窗口中展开 "NewsApp" 节点,则会发现企业应用程序项目不包含任何源代码。所有源代码将包含在向导创建的两个模块中,这些模块将在“Java EE 模块”节点下面列出。
企业应用程序项目仅在部署描述符文件 sun-application.xml 中包含有关应用程序的配置详细信息。在创建将部署到 GlassFish v3 的 Java EE 企业应用程序时,不需要使用部署描述符文件。
在本练习中,将在 EJB 模块中创建一个实体类、消息驱动 Bean 以及会话 Facade。此外,将创建一个持久性单元以便为容器提供有关数据源和如何管理实体的信息,还会创建消息驱动 Bean 使用的 Java 消息服务 (Java Message Service, JMS) 资源。
在本练习中,您将创建 NewsEntity 实体类。实体类是一个简单的 Java 类,通常表示数据库中的一个表。在创建实体类时,IDE 会添加 @Entity 标注以将该类定义为实体类。创建实体类后,将在其中创建一些字段,以表示要包含在表中的数据。
每个实体类都必须具有一个主键。在创建实体类时,IDE 会添加 @Id 标注以声明要用作主键的字段。IDE 还会添加 @GeneratedValue 标注并指定主 Id 的键生成策略。
要创建 NewsEntity 类,请执行以下步骤:
在单击“完成”时,IDE 将创建 persistence.xml 和 NewsEntity.java 实体类。IDE 将在源代码编辑器中打开 NewsEntity.java。
在源代码编辑器中,执行以下步骤。
private String title; private String body;
在单击“生成”时,IDE 将为字段添加 getter 和 setter 方法。
您可以将 NewsEntity.java 关闭。
有关实体类的更多详细信息,请参见 Java EE 6 教程第 I 部分中的 Java 持久性 API 简介一章。
在本练习中,将使用向导在 EJB 模块中创建 NewMessage 消息驱动 Bean。该向导还会帮助您创建所需的 JMS 资源。消息驱动 Bean 接收和处理 Web 模块中的 Servlet 发送到队列的消息。
要创建消息驱动 Bean,请执行以下步骤:
在单击“完成”时,将在源代码编辑器中打开 Bean 类 NewMessage.java。您可以看到 IDE 在该类中添加了 @MessageDriven 标注和配置属性。
@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”向导还会在 sun-resources.xml 中添加 JMS 资源的信息。您无需配置部署描述符即可指定 JMS 资源。如果在 IDE 中使用“运行”操作将应用程序部署到 GlassFish,则会在部署时在服务器上创建 JMS 资源。
EJB 规范允许您使用标注将资源直接引入类中。现在,您要使用标注将 MessageDrivenContext 资源引入类中,然后注入 PersistenceContext 资源,EntityManager API 将使用该资源管理持久性实体实例。将在源代码编辑器中将标注添加到类中。
public class NewMessageBean implements MessageListener { @Resource private MessageDrivenContext mdc;
@PersistenceContext(unitName = "NewsApp-ejbPU") private EntityManager em;IDE 还会生成以下 persist 方法。
public void persist(Object object) { em.persist(object); }
public void save(Object object) { em.persist(object); }
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(); } }
注意:在生成 import 语句时,您需要确保导入 jms 和 javax.annotation.Resource 库。
有关消息驱动 Bean 的更多详细信息,请参见 Java EE 6 教程第 I 部分中的什么是消息驱动 Bean?一章。
在本练习中,将为 NewsEntity 实体类创建一个会话 Facade。EJB 3.0 规范通过减少所需的代码数量并允许使用标注将类声明为会话 Bean,简化了会话 Bean 的创建过程。通过将业务接口指定为可选,EJB 3.1 规范进一步简化了会话 Bean 的要求。本地客户端可以通过本地接口或无接口视图访问会话 Bean。在本教程中,不会为该 Bean 创建接口。Web 应用程序中的 Servlet 将通过无接口视图访问该 Bean。
要创建会话 Facade,请执行以下步骤:
在单击“完成”时,将创建会话 Facade "NewsEntityFacade.java" 并在源代码编辑器中打开该 Facade。IDE 将添加 @Stateless 标注以声明该类是无状态会话 Bean。IDE 还会添加 PersistenceContext 标注,以便将资源直接注入会话 Bean 组件中。
注意:如果远程客户端将访问该 Bean,则仍然需要使用远程接口。
有关会话 Bean 的更多信息,请参见 Java EE 6 教程第 I 部分中的什么是会话 Bean?一章。
在本节中,将在 Web 模块中创建两个 Servlet。ListNews Servlet 通过 EJB 模块中的实体 Facade 从数据库中检索消息。PostMessage Servlet 用于发送 JMS 消息。
在本节中,还会在 Web 模块中创建单个会话 Bean 以计算会话中的当前用户数。通过使用 EJB 3.1 规范,您可以在 Web 应用程序中创建企业 Bean。在 EJB 3.1 之前,所有企业 Bean 必须位于 EJB 模块中。
EJB 3.1 规范引入了 @Singleton 标注,可通过该标注轻松创建单个会话 Bean。EJB 3.1 还定义了其他标注以配置单个会话 Bean 的属性,例如,在实例化该 Bean 时。
在实例化单个会话 Bean 后,它将在应用程序生命周期内存在。顾名思义,单个会话 Bean 在应用程序中只能有一个实例。与无状态会话 Bean 类似,单个会话 Bean 可以具有多个客户端。
要创建单个会话 Bean,请执行以下步骤:
在单击“完成”时,IDE 将创建单个会话 Bean 类并在编辑器中打开该类。您可能会发现,IDE 在该类中添加了 @Singleton 标注以声明单个会话 Bean。该向导还使用@LocalBean 标注该类。
@Singleton @LocalBean public class SessionManagerBean { }
@Singleton @LocalBean @WebListener public class SessionManagerBean implements HttpSessionListener{ }
@WebListener 标注是 Servlet 3.0 API 的一部分,可用于直接在代码中实现侦听程序。
在实现 HttpSessionListener 时,IDE 将在旁注中显示一条警告。
IDE 将添加 sessionCreated 和 sessionDestroyed 方法。
@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; }
将从 Servlet 中调用此方法以显示当前的用户/打开会话数。
现在,会话 Bean 代码应如下所示。
@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 的更多详细信息,请参见 Java EE 6 教程第 I 部分中的什么是会话 Bean?一章。
在本练习中,将创建一个简单的 Servlet 以显示存储的消息。将使用标注从该 Servlet 中调用企业 Bean NewsEntityFacade。
单击“完成”后,将在源代码编辑器中打开类 ListNews.java。在源代码编辑器中,执行以下步骤。
IDE 将添加 @EJB 标注以注入企业 Bean。
将会在代码中看到以下标注,它们用于注入两个企业 Bean。
@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。
protected void processRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { request.getSession(true); response.setContentType("text/html;charset=UTF-8");
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>");
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>");
在本练习中,您将创建用于传递消息的 PostMessage Servlet。您将使用标注向该 Servlet 中直接注入创建的 JMS 资源,从而指定变量名称及其映射到的名称。然后,您将添加用于发送 JMS 消息的代码,以及用于在 HTML 表单中添加消息的代码。
在单击“完成”时,将在源代码编辑器中打开 PostMessage.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;
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) && (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();
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>");
现在,您可以运行项目。运行项目时,您希望浏览器打开包含 ListNews Servlet 的页面。可以通过在企业应用程序的“属性”对话框中指定该页的 URL 来实现这一目的。该 URL 是相对于应用程序上下文路径的 URL。输入该相对 URL 后,您便可以从“项目”窗口中生成、部署和运行应用程序。
要设置相对 URL 并运行应用程序,请执行以下操作:
运行项目时,将在浏览器中打开 ListNews Servlet,其中显示了数据库中消息的列表。首次运行项目时,数据库为空,但您可以单击添加消息按钮来添加消息。
使用 PostMessage Servlet 添加消息时,会将该消息发送到消息驱动 Bean 以写入持久性存储,并且将调用 ListNews Servlet 以显示数据库中的消息。ListNews 检索到的数据库中消息的列表通常不包含新消息,因为消息服务是异步的。
您可以采用下列方法下载本教程的解决方案(作为一个项目)。
单击“从 Kenai 中获取”后,IDE 会将本地文件夹初始化为 Subversion 资源库,并签出项目源代码。
注意:
下面是您创建项目时可能会遇到的一些问题。
使用向导创建 JMS 资源时,输出窗口中可能会显示以下服务器错误消息:
[com.sun.enterprise.connectors.ConnectorRuntimeException: JMS resource not created : jms/Queue]
此消息可能表明未创建 JMS 资源,或者未在应用服务器中注册该资源。您可以使用应用服务器的管理控制台来检查、创建和编辑 JMS 资源。
要打开管理控制台,请执行以下操作:
您需要确保将 PostMessage Servlet 中的 JMS 连接工厂资源映射到已在 Sun Java System Application Server 中注册的 JMS 连接工厂资源的正确 JNDI 名称上。
应在 Sun Java System Application Server 中注册以下资源: