使用 JavaServer Faces 构建 Java EE 应用程序的表示层
1. 构建表示层概述
在开发应用程序时,数据库、持久化和业务逻辑层固然重要,但如果没有实现表示层(也称为应用程序的前端层),这些层的功能就无法为用户所用。表示层实现了应用程序的展示逻辑,使用户能够利用应用程序其他层实现的所有功能。本文将介绍如何使用 JavaServer Faces (JSF) 技术为示例应用程序构建表示层。
构建表示层的主要步骤包括:
- 构建 JSF 豆(beans),通过它们访问已有的会话豆。
- 在 JSF 豆的基础上开发 JSF 页面。
- 保障 Web 应用程序的安全。
- 配置 Web 应用程序。
- 将 Web 应用程序部署到应用服务器。
2. 从表示层访问 Java EE 功能
在某些情况下,不一定需要构建 Web 组件来访问 EJB 和 JTA 组件,例如可以创建一个应用程序客户端,使用
appclient
命令行工具来访问企业豆。但在大多数情况下,构建 Java EE 应用程序的表示层意味着构建一个利用 Java EE 组件提供业务逻辑的 Web 应用程序。
3. 选择 Web 层技术
在实现 Java EE Web 应用程序时,有多种技术可供选择,如 JSP 技术。但 JavaServer Faces 技术相比 JSP 具有许多优势,其中最重要的是业务逻辑和表示的清晰分离。
由于已经构建了企业豆,因此可以开发利用这些企业豆功能的 JSF 豆,而不是从头开始实现应用程序的业务逻辑。企业豆作为构建在持久化层之上的外观(Facade),将客户端与实体模型解耦,这种解决方案被称为 Facade 模式。
4. 规划表示层
为了说明如何在 JSF 管理豆中利用 EJB 组件,需要构建一个简单的 JSF 应用程序。该应用程序至少需要以下两个页面:
-
index.jsp
:显示所有可供购买的书籍,并允许用户将感兴趣的书籍添加到购物车。
-
showcart.jsp
:显示购物车中的物品,允许用户删除不必要的物品并最终下单。
还需要创建以下 JSF 管理豆:
-
OrderJSFBean
:用于与
CartBean
和
OrderBean
企业豆进行交互。
-
BookJSFBean
:用于访问书籍表中的数据。
此外,为了保证应用程序的安全性,还需要创建两个页面:
-
login.jsp
:用于基于表单的身份验证。
-
login_error.jsp
:如果身份验证失败,用户将被重定向到该页面。
5. 使用 JAAS 保障 Java EE 应用程序的安全
由于示例应用程序需要处理敏感信息,因此需要采用一种能够实现用户身份验证和授权的安全机制。Java Authentication and Authorization Service (JAAS) 提供了一种标准的方法来实现这些目标。
在 GlassFish 中使用 JAAS,首先需要设置一个安全领域(security realm),将用户组织到安全组中。然后,需要在应用程序中添加一个登录页面,用户可以在该页面输入凭据。经过身份验证的用户将只能访问其所属组允许的资源。
6. 在 GlassFish 中创建 JDBC 领域
GlassFish 自带了三个预定义的安全领域:
admin-realm
、
file realm
和
certificate realm
。除了这些预配置的领域,还可以创建以下领域:
- LDAP 领域:将身份验证信息存储在 LDAP 数据库中。
- JDBC 领域:将身份验证信息存储在关系数据库中。
- Solaris 领域:仅可在 Solaris 操作系统上使用。
以下是创建 JDBC 领域的具体步骤:
6.1 创建存储账户信息的数据库表
在创建 JDBC 领域之前,需要创建数据库表来存储用户的账户信息。具体需要设置以下三个表:
- 用户凭据表:可以使用现有的
customers
表,但需要修改其结构,添加一个
password
列来存储用户的密码。
- 用户组信息表。
- 两个表之间的多对多关系连接表。
创建这些表后,可以创建一个视图,以便 JDBC 领域只使用一个数据库接触点。以下是在 MySQL 服务器上执行的 SQL 命令:
use dbsample;
ALTER TABLE customers ADD COLUMN password CHAR(32);
CREATE TABLE groups(
group_id VARCHAR(25) PRIMARY KEY,
group_desc VARCHAR(100)
);
CREATE TABLE customergroups(
cust_id INTEGER,
group_id VARCHAR(25),
PRIMARY KEY(cust_id, group_id),
FOREIGN KEY(cust_id) REFERENCES customers(cust_id),
FOREIGN KEY(group_id) REFERENCES groups(group_id)
);
CREATE VIEW login_v
AS SELECT c.cust_id, c.password, g.group_id FROM
customers c, groups g, customergroups r
WHERE c.cust_id = r.cust_id AND g.group_id = r.group_id;
注意,
customers
表中添加的
password
列定义为
CHAR(32)
,因为 GlassFish 中默认的加密算法是 MD5,MD5 哈希值是一个 32 字符的字符串。
接下来,需要向这些表中填充数据,以下是插入两个客户记录身份验证信息的 SQL 语句:
INSERT INTO groups VALUES('testRole', 'Security group for users of the sample app');
INSERT INTO customergroups VALUES(2, 'testRole');
UPDATE customers
SET password = '42766beab1dc267fbf26df32e1addfff'
WHERE cust_id = 1;
UPDATE customers
SET password = '0f8031d929f89ecb1d251f0f8bc9d9f9'
WHERE cust_id = 2;
6.2 创建 JDBC 领域
完成数据库设置后,可以在应用服务器中创建 JDBC 领域。具体步骤如下:
1. 以
admin
身份连接到管理控制台(Admin Console)。
2. 导航到
Configuration/Security/Realm
页面。
3. 点击
New
按钮,在
Name
字段中输入名称,例如
myjdbc
,在
Class Name
框中选择
com.sun.enterprise.security.auth.realm.jdbc.JDBCRealm
。
4. 在对话框的
Properties Specific to This Class
部分,按照以下表格填写字段:
| 属性名称 | 属性值 | 描述 |
|---|---|---|
| JAAS context | jdbcRealm | 要使用的登录模块类型,默认为 jdbcRealm。 |
| JNDI | jdbc/mysqlpool | 可通过其访问安全表的数据源的 JNDI 名称。 |
| User Table | login_v |
包含要使用的用户信息的表,这里使用从
customers
和
groups
表派生的视图。
|
| User Name Column | cust_id | 用户表中的用户名列。 |
| Password Column | password | 用户表中的密码列。 |
| Group Table | login_v |
包含安全组信息的表,这里使用从
customers
和
groups
表派生的视图。
|
| Group Name Column | group_id | 组表中的用户名列。 |
-
完成属性设置后,点击
OK按钮,将创建一个新的 JDBC 领域。
7. 使用 JSF 构建示例应用程序的表示层
7.1 绘制项目结构图
在开始构建之前,需要创建一个表示项目目录结构的图表,包括要创建的文件。以下是项目结构的示意图:
graph LR
A[sampleapp] --> B[target]
B --> C[WEB-INF]
C --> D[classes]
D --> E[ejbjpa]
E --> F[jsfbeans]
C --> G[lib]
A --> H[src]
H --> I[ejbjpa]
I --> J[jsfbeans]
A --> K[WebContent]
K --> L[stylesheet.css]
K --> M[index.jsp]
K --> N[showcart.jsp]
K --> O[login.jsp]
K --> P[login_error.jsp]
根据这个结构图,创建相应的目录和文件。
7.2 开发 JSF 管理豆
在 JSF 应用程序中,管理豆封装了应用程序的业务逻辑,以便 JSF 页面可以引用豆的属性。以下是
OrderJSFBean
管理豆的源代码:
package ejbjpa.jsfbeans;
import javax.ejb.EJB;
import javax.naming.InitialContext;
import javax.faces.context.FacesContext;
import javax.servlet.http.HttpSession;
import java.util.List;
import java.util.Map;
import ejbjpa.ejb.*;
import ejbjpa.entities.*;
@EJB(name="ejb/CartBean", beanInterface=Cart.class)
public class OrderJSFBean {
private Cart cart;
@EJB
private OrderSample order;
private List<ShoppingCart> cartItems;
private Integer custId;
public OrderJSFBean() {
custId = Integer.parseInt(
FacesContext.getCurrentInstance().getExternalContext().
getUserPrincipal().getName());
try{
if (cart == null) {
cart = (Cart) (new InitialContext()).lookup("java:comp/env/ejb/CartBean");
}
cart.initialize(custId);
} catch (Exception e) {
e.printStackTrace();
}
}
public Integer getCustId() {
return custId;
}
public List<ShoppingCart> getCartItems() {
cartItems = null;
try {
cartItems = cart.getItems();
} catch (Exception e) {
e.printStackTrace();
}
return cartItems;
}
public void addToCart() {
try {
FacesContext cxt = FacesContext.getCurrentInstance();
Map params = cxt.getExternalContext().getRequestParameterMap();
String isbn = (String)params.get("isbn");
String price_str = (String)params.get("price");
Double price =new Double(price_str);
cart.addItem(isbn, 1, price);
} catch (Exception e) {
e.printStackTrace();
}
}
public void removeFromCart() {
try {
FacesContext cxt = FacesContext.getCurrentInstance();
Map params = cxt.getExternalContext().getRequestParameterMap();
String itemId = (String)params.get("itemId");
cart.removeItem(itemId);
} catch (Exception e) {
e.printStackTrace();
}
}
public String ProceedToCheckout() {
try {
order.placeOrder(custId, 1);
} catch (Exception e) {
e.printStackTrace();
}
return "continue";
}
}
BookJSFBean
管理豆与
OrderJSFBean
不同,它不引用企业豆,而是直接通过 JDBC 连接到基础数据库,并从
books
表中检索所有记录。以下是
BookJSFBean
管理豆的源代码:
package ejbjpa.jsfbeans;
import javax.ejb.EJB;
import javax.naming.InitialContext;
import javax.faces.context.FacesContext;
import javax.servlet.http.HttpSession;
import java.util.List;
import ejbjpa.ejb.*;
import ejbjpa.entities.*;
import java.sql.*;
import javax.sql.DataSource;
public class BookJSFBean {
private Connection connDb;
public void openConnection() throws Exception {
if(connDb != null)
return;
DataSource dataSource = (DataSource) (new
InitialContext()).lookup("java:comp/env/jdbc/mysqlpool");
connDb = dataSource.getConnection();
}
public ResultSet getAllBooks() throws Exception {
ResultSet rslt = null;
this.openConnection();
Statement stmt = connDb.createStatement();
rslt = stmt.executeQuery("SELECT * FROM books");
return rslt;
}
}
创建管理豆的源文件后,可以编译它们。在
sampleapp
目录下执行以下命令:
javac -cp target/WEB-INF/lib/appejb.jar;yourglassfishdir/lib/javaee.jar -d target/WEB-INF/classes src/ejbjpa/jsfbeans/*.java
编译完成后,
ejbjpa/jsfbeans
目录将出现在
sampleapp/target/WEB-INF/classes
目录中,并且会生成
BookJSFBean.class
和
OrderJSFBean.class
文件。
使用 JavaServer Faces 构建 Java EE 应用程序的表示层
7.3 开发 JSF 页面
构建应用程序的下一步是创建 JSF 页面。以下是
index.jsp
页面的源代码,该页面引用了
BookJSFBean
管理豆的
allBooks
属性:
<%@ taglib uri="http://java.sun.com/jsf/html" prefix="h" %>
<%@ taglib uri="http://java.sun.com/jsf/core" prefix="f" %>
<f:view>
<head>
<link href="stylesheet.css" rel="stylesheet" type="text/css"/>
</head>
<h:form>
<h2>List of books</h2>
<br/>
<h:dataTable value="#{book.allBooks}" var ="book"
headerClass= "header"
columnClasses="evenCol, oddCol">
<h:column>
<f:facet name="header">
<h:outputText value="ISBN"/>
</f:facet>
<h:outputText value="#{book.isbn}"/>
</h:column>
<h:column>
<f:facet name="header">
<h:outputText value="Title"/>
</f:facet>
<h:outputText value="#{book.title}"/>
</h:column>
<h:column>
<f:facet name="header">
<h:outputText value="Author"/>
</f:facet>
<h:outputText value="#{book.author}"/>
</h:column>
<h:column>
<f:facet name="header">
<h:outputText value="Price"/>
</f:facet>
<h:outputText value="#{book.price}"/>
</h:column>
<h:column>
<f:facet name="header">
<h:outputText value="Copies left"/>
</f:facet>
<h:outputText value="#{book.quantity}"/>
</h:column>
<h:column>
<h:commandLink action="#{OrderJSFBean.addToCart}" value="Add to cart">
<f:param name = "isbn" value = "#{book.isbn}"/>
<f:param name = "price" value = "#{book.price}"/>
</h:commandLink>
</h:column>
</h:dataTable>
<p/>
<h:commandButton action="showcart" value="Move to cart"/>
</h:form>
</f:view>
从代码中可以看出,
index.jsp
页面将显示所有书籍记录,允许用户将感兴趣的书籍添加到购物车。
以下是
showcart.jsp
页面的源代码,该页面可以从
index.jsp
页面中启动:
<%@ taglib uri="http://java.sun.com/jsf/html" prefix="h" %>
<%@ taglib uri="http://java.sun.com/jsf/core" prefix="f" %>
<f:view>
<head>
<link href="stylesheet.css" rel="stylesheet" type="text/css"/>
</head>
<h:form>
<h2>Your shopping cart items to buy now</h2>
<br/>
<h:dataTable value="#{OrderJSFBean.cartItems}" var ="shoppingCart"
headerClass= "header"
columnClasses="evenCol, oddCol">
<h:column>
<f:facet name="header">
<h:outputText value="Book isbn"/>
</f:facet>
<h:outputText value="#{shoppingCart.book_id}"/>
</h:column>
<h:column>
<f:facet name="header">
<h:outputText value="Quantity"/>
</f:facet>
<h:outputText value="#{shoppingCart.units}"/>
</h:column>
<h:column>
<f:facet name="header">
<h:outputText value="Unit price"/>
</f:facet>
<h:outputText value="#{shoppingCart.unit_price}"/>
</h:column>
<h:column>
<h:commandLink action="#{OrderJSFBean.removeFromCart}" value="Delete">
<f:param name = "itemId" value = "#{shoppingCart.book_id}"/>
</h:commandLink>
</h:column>
</h:dataTable>
<p/>
<h:commandButton action="#{OrderJSFBean.ProceedToCheckout}" value="Proceed to checkout"/>
<h:commandButton action="continue" value="Continue shopping"/>
</h:form>
</f:view>
showcart.jsp
页面允许用户查看购物车内容,必要时删除物品,然后可以下单,下单后购物车将自动清空。
7.4 创建安全页面
接下来创建基于表单身份验证所需的页面。以下是
login.jsp
页面的源代码:
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/functions" prefix="fn" %>
<html>
<head><title>Login Page</title></head>
<h2>Please login:</h2>
<form method="POST" action="j_security_check">
<p>Enter Customer ID: <input type="text" name="j_username" size="25"></p>
<p>Enter Password:<input type="password" size="15" name="j_password"></p>
<input type="submit" value="Submit">
<input type="reset" value="Reset">
</form>
</html>
如果身份验证失败,将显示
login_error.jsp
页面,其源代码如下:
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<html>
<head><title>Login error page</title></head>
<body>
<c:url var="url" value="index.faces"/>
<h2>User name or password is wrong.</h2>
<p>Please enter a valid user name and password.
Click here to <a href="${url}">try again.</a></p>
</body>
</html>
login_error.jsp
页面会告知用户身份验证失败,并提供再次尝试的链接。
8. 配置应用程序
在将应用程序打包成部署存档之前,需要创建部署描述符
sun-web.xml
和
web.xml
,以及配置资源文件
faces-config.xml
。
以下是
faces-config.xml
配置文件的源代码:
<?xml version="1.0"?>
<faces-config xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-facesconfig_1_2.xsd"
version="1.2">
<managed-bean>
<managed-bean-name>OrderJSFBean</managed-bean-name>
<managed-bean-class>ejbjpa.jsfbeans.OrderJSFBean</managed-bean-class>
<managed-bean-scope>session</managed-bean-scope>
</managed-bean>
<managed-bean>
<managed-bean-name>book</managed-bean-name>
<managed-bean-class>ejbjpa.jsfbeans.BookJSFBean</managed-bean-class>
<managed-bean-scope>session</managed-bean-scope>
</managed-bean>
<navigation-rule>
<navigation-case>
<description>
By clicking the "Move to cart" button on the index.jsp page
you move to showcart.jsp
</description>
<from-outcome>showcart</from-outcome>
<to-view-id>/showcart.jsp</to-view-id>
</navigation-case>
<navigation-case>
<description>
Clicking the "Continue shopping" turns you back to index.jsp
showing the list of books available
</description>
<from-outcome>continue</from-outcome>
<to-view-id>/index.jsp</to-view-id>
</navigation-case>
</navigation-rule>
</faces-config>
以下是
web.xml
配置文件的源代码:
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
version="2.5">
<security-constraint>
<web-resource-collection>
<web-resource-name>testing web app</web-resource-name>
<url-pattern>/*</url-pattern>
<http-method>POST</http-method>
<http-method>GET</http-method>
</web-resource-collection>
<auth-constraint>
<role-name>testRole</role-name>
</auth-constraint>
<user-data-constraint>
<transport-guarantee>NONE</transport-guarantee>
</user-data-constraint>
</security-constraint>
<login-config>
<auth-method>FORM</auth-method>
<realm-name>myjdbc</realm-name>
<form-login-config>
<form-login-page>/login.jsp</form-login-page>
<form-error-page>/login_error.jsp</form-error-page>
</form-login-config>
</login-config>
<security-role>
<role-name>testRole</role-name>
</security-role>
<servlet>
<display-name>FacesServlet</display-name>
<servlet-name>FacesServlet</servlet-name>
<servlet-class>javax.faces.webapp.FacesServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>FacesServlet</servlet-name>
<url-pattern>*.faces</url-pattern>
</servlet-mapping>
<welcome-file-list>
<welcome-file>index.faces</welcome-file>
</welcome-file-list>
<ejb-ref>
<ejb-ref-name>ejb/CartBean</ejb-ref-name>
<ejb-ref-type>Session</ejb-ref-type>
<remote>ejbjpa.ejb.Cart</remote>
</ejb-ref>
<resource-ref>
<res-ref-name>jdbc/mysqlpool</res-ref-name>
<res-type>javax.sql.DataSource</res-type>
<res-auth>Container</res-auth>
<res-sharing-scope>Shareable</res-sharing-scope>
</resource-ref>
</web-app>
以下是
sun-web.xml
运行时部署描述符的源代码:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE sun-web-app PUBLIC "-//Sun Microsystems, Inc.//
DTD Application Server 8.0 Servlet 2.4//EN"
"http://www.sun.com/software/appserver/dtds/sun-web-app_2_4-0.dtd">
<sun-web-app>
<context-root>/sampleapp</context-root>
<security-role-mapping>
<role-name>testRole</role-name>
<group-name>testRole</group-name>
</security-role-mapping>
</sun-web-app>
完成所有文件的创建后,将它们打包成部署包。在
sampleapp/target
目录下执行以下命令:
jar cvf jsfapp.war .
然后使用以下命令部署存档:
asadmin deploy jsfapp.war
9. 总结
通过上述步骤,完成了一个示例应用程序的开发。展示了如何使用 JavaServer Faces 技术实现 Java EE 应用程序的表示层,并利用封装了应用程序业务逻辑的企业豆。后续可以对该示例应用程序进行测试,找出可能需要改进的地方以提高效率。
用 JavaServer Faces 构建 Java EE 应用表示层
超级会员免费看
89

被折叠的 条评论
为什么被折叠?



