a. 問題
前臺控制給出了一個基于MVC的,能有效管理用戶與J2EE應用之間進行的復雜交互。這個模式可以使處理頁面的現實順序和用戶的并發請求變得簡單。并且使增加和改變頁面現實變得更加容易。
另外一個常見的問題是,當EJB或者業務邏輯發生變化的時候,應用的客戶端也必須隨之改變。我們來看一下這個問題。
一般來說,為了表現一個賬戶中的用戶,我們使用一個業務邏輯來表示賬戶中的信息,象用戶名和口令,再用一個EJB來管理用戶的個人信息,象愛好,語言等。當要創建一個新的賬號或者修改一個已經存在的賬號時,必須訪問包含賬號信息的EJB,讀取個人信息,修改并且保存,這樣的一個流程。
當然,這只是一個非常簡單的例子,實際情況可能比這個復雜的多,象查看用戶定制了哪些服務,檢驗客戶信用卡的有效性,存放訂單等。在這個案例中,為了實現一個完整的流程,客戶端必須訪問賬戶EJB來完成一系列適當的工作。下面的例子顯示了一個Servlet客戶端如何來控制一個用戶訂單。
A servlet that does the workflow required for placing an order
// all required imports;
// exceptions to be caught appropriately wherever applicable;
// This servlet assumes that for placing an order the account and
// credit status of the customer has to be checked before getting the
// approval and committing the order. For simplicity, the EJBs that
// represent the business logic of account, credit status etc are
// not listed
public class OrderHandlingServlet extends HttpServlet {
// all required declarations, definitions
public void init() {
// all inits required done here
}
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws IOException, ServletException {
// other logic as required
// Get reference to the required EJBs
InitialContext ctxt = new InitialContext();
Object obj = ctxt.lookup("java:comp/env/ejb/UserAccount");
UserAccountHome acctHome = (UserAccountHome)
PortableRemoteObject.narrow(obj, UserAccountHome.class);
UserAccount acct = acctHome.create();
obj = ctxt.lookup("java:comp/env/ejb/CreditCheck");
CreditCheckHome creditCheckHome = (CreditCheckHome)
PortableRemoteObject.narrow(obj, CreditCheckHome.class);
CreditCheck credit = creditCheckHome.create();
obj = ctxt.lookup("java:comp/env/ejb/Approvals");
ApprovalsHome apprHome = (ApprovalsHome)
PortableRemoteObject.narrow(obj, ApprovalsHome.class);
Approvals appr = apprHome.create();
obj = ctxt.lookup("java:comp/env/ejb/CommitOrder");
CommitOrderHome orderHome = (CommitOrderHome)
PortableRemoteObject.narrow(obj, CommitOrderHome.class);
CommitOrder order = orderHome.create();
// Acquire the customer ID and order details;
// Now do the required workflow to place the order
int result = acct.checkStatus(customerId);
if(result != OK) {
// stop further steps
}
result = credit.checkCreditWorth(customerId, currentOrder);
if(result != OK) {
// stop further steps
}
result = appr.getApprovals(customerId, currentOrder);
if(result != OK) {
// stop further steps
}
// Everything OK; place the order
result = order.placeOrder(customerId, currentOrder);
// do further processing as required
}
}
以上的代碼顯示了一個單個的客戶端。如果這個應用支持多種客戶端的話,必須為每一個客戶端制定一種處理方法來完成工作流程。如果有一個EJB的實現流程需要改變的話,那么所有的參與這個流程的客戶端都需要改變。如果不同的EJB之間的交互需要改變的話,所有的客戶端都必須知道這一點,如果流程中需要增加一個新的步驟的話,所有的客戶端也必須隨之修改。
這樣一來,EJB和客戶端之間的改變變得非常困難。客戶端必須對每個EJB分開進行訪問,致使網絡速度變慢。同樣,應用越復雜,麻煩越大。
b. 建議的解決方法
解決這個問題的方法是,把客戶端和他們使用的EJB分割開。建議適用Session Fa?ade模式。這個模式通過一個Session Bean,為一系列的EJB提供統一的接口來實現流程。事實上,當客戶端只是使用這個接口來觸發流程。這樣,所有關于EJB實現流程所需要的改變,都和客戶端無關。
看下面這個例子。這段代碼用來控制與客戶相關的訂單的處理方法。
// All imports required
// Exception handling not shown in the sample code
public class OrderSessionFacade implements SessionBean {
// all EJB specific methods like ejbCreate defined here
// Here is the business method that does the workflow
// required when a customer places a new order
public int placeOrder(String customerId, Details orderDetails)
throws RemoteException {
// Get reference to the required EJBs
InitialContext ctxt = new InitialContext();
Object obj = ctxt.lookup("java:comp/env/ejb/UserAccount");
UserAccountHome acctHome = (UserAccountHome)
PortableRemoteObject.narrow(obj, UserAccountHome.class);
UserAccount acct = acctHome.create();
obj = ctxt.lookup("java:comp/env/ejb/CreditCheck");
CreditCheckHome creditCheckHome = (CreditCheckHome)
PortableRemoteObject.narrow(obj, CreditCheckHome.class);
CreditCheck credit = creditCheckHome.create();
obj = ctxt.lookup("java:comp/env/ejb/Approvals");
ApprovalsHome apprHome = (ApprovalsHome)
PortableRemoteObject.narrow(obj, ApprovalsHome.class);
Approvals appr = apprHome.create();
obj = ctxt.lookup("java:comp/env/ejb/CommitOrder");
CommitOrderHome orderHome = (CommitOrderHome)
PortableRemoteObject.narrow(obj, CommitOrderHome.class);
CommitOrder order = orderHome.create();
// Now do the required workflow to place the order
int result = acct.checkStatus(customerId);
if(result != OK) {
// stop further steps
}
result = credit.checkCreditWorth(customerId, currentOrder);
if(result != OK) {
// stop further steps
}
result = appr.getApprovals(customerId, currentOrder);
if(result != OK) {
// stop further steps
}
// Everything OK; place the order
int orderId = order.placeOrder(customerId, currentOrder);
// Do other processing required
return(orderId);
}
// Implement other workflows for other order related functionalities (like
// updating an existing order, canceling an existing order etc.) in a
// similar way
}
在模式允許的情況下,Servlet代碼將很容易實現。
// all required imports
// exceptions to be caught appropriately wherever applicable
public class OrderHandlingServlet extends HttpServlet {
// all required declarations, definitions
public void init() {
// all inits required done here
}
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws IOException, ServletException {
// other logic as required
// Get reference to the session facade
InitialContext ctxt = new InitialContext();
Object obj = ctxt.lookup("java:comp/env/ejb/OrderSessionFacade");
OrderSessionFacadeHome facadeHome = (OrderSessionFacadeHome)
PortableRemoteObject.narrow(obj, OrderSessionFacadeHome.class);
OrderSessionFacade facade = facadeHome.create();
// trigger the order workflow
int orderId = facade.placeOrder(customerId, currentOrder);
// do further processing as required
}
}
就象上面顯示的,客戶端的邏輯變得非常簡單。流程中的任何改變只要修改模式中的一處地方就可以了。客戶端可以仍舊使用原來的接口,而不必做任何修改。同樣,這個模式可以用來響應其他處理器的流程處理。這讓你能用同樣的模式來處理不同客戶端的不同流程。在這個例子中,模式提供了很好的伸縮性和可維護性。
c. 要點
§ 既然這種模式不涉及到數據訪問,就應該用Session Bean來實現。
§ 對于用簡單接口來實現復雜EJB的子系統來說,是一個理想的選擇。
§ 這個模式不適用于無流程處理的應用。
§ 這個模式可以減少客戶端于EJB之間的通信和依賴。
§ 所有和EJB有關的交互,都有同一個Session Bean來控制,可以減少客戶端對EJB的誤用。
§ 這個模式可以使支持多類型客戶端變得更容易。
§ 可以減少網絡數據傳遞。
§ 所有的服務器端的實現細節都對客戶端隱藏,在改變發生后,客戶端不用重新發布。
§ 這個模式可以同樣看成一個集中處理器來處理所有的安全或日志紀錄。
4. Data Access Object
a. 問題
目前為止,你看到的模型都是用來構建可伸縮的,易于維護的J2EE應用。這些模式盡可能的把應用在多個層上來實現。但是,還有一點必須強調:EJB的數據表現。它們包括象EJB這樣的數據庫語言。如果數據庫有改變的話,相應的SQL也必須改變,而EJB也必須隨之更新。
這些常見問題就是:訪問數據源的代碼與EJB結合在一起,這樣致使代碼很難維護。看以下的代碼。
An EJB that has SQL code embedded in it
// all imports required
// exceptions not handled in the sample code
public class UserAccountEJB implements EntityBean {
// All EJB methods like ejbCreate, ejbRemove go here
// Business methods start here
public UserDetails getUserDetails(String userId) {
// A simple query for this example
String query = "SELECT id, name, phone FROM userdetails WHERE name = " + userId;
InitialContext ic = new InitialContext();
datasource = (DataSource)ic.lookup("java:comp/env/jdbc/DataSource");
Connection dbConnection = datasource.getConnection();
Statement stmt = dbConnection.createStatement();
ResultSet result = stmt.executeQuery(queryStr);
// other processing like creation of UserDetails object
result.close();
stmt.close();
dbConnection.close();
return(details);
}
}
b. 建議的解決方法
為了解決這個問題,從而讓你能很方便的修改你的數據訪問。建議使用DAO模式。這個模式把數據訪問邏輯從EJB中拿出來放入獨立的接口中。結果是EJB保留自己的業務邏輯方法,在需要數據的時候,通過DAO來訪問數據庫。這樣的模式,在要求修改數據訪問的時候,只要更新DAO的對象就可以了。看以下的代碼。
A Data Access Object that encapsulates all data resource access code
// All required imports
// Exception handling code not listed below for simplicity
public class UserAccountDAO {
private transient Connection dbConnection = null;
public UserAccountDAO() {}
public UserDetails getUserDetails(String userId) {
// A simple query for this example
String query = "SELECT id, name, phone FROM userdetails WHERE name = " + userId;
InitialContext ic = new InitialContext();
datasource = (DataSource)ic.lookup("java:comp/env/jdbc/DataSource");
Connection dbConnection = datasource.getConnection();
Statement stmt = dbConnection.createStatement();
ResultSet result = stmt.executeQuery(queryStr);
// other processing like creation of UserDetails object
result.close();
stmt.close();
dbConnection.close();
return(details);
}
// Other data access / modification methods pertaining to the UserAccountEJB
}
現在你有了一個DAO對象,利用這個對象你可以訪問數據。再看以下的代碼。
An EJB that uses a DAO
// all imports required
// exceptions not handled in the sample code
public class UserAccountEJB implements EntityBean {
// All EJB methods like ejbCreate, ejbRemove go here
// Business methods start here
public UserDetails getUserDetails(String userId) {
// other processing as required
UserAccountDAO dao = new UserAccountDAO();
UserDetails details = dao.getUserDetails(userId);
// other processing as required
return(details);
}
}
任何數據源的修改只要更新DAO就可以解決了。另外,為了支持應用能夠支持多個不同的數據源類型,你可以開發多個DAO來實現,并在EJB的發布環境中指定這些數據源類型。在一般情況下,EJB可以通過一個Factory對象來得到DAO。用這種方法實現的應用,可以很容易的改變它的數據源類型。
c. 要點
§ 這個模式分離了業務邏輯和數據訪問邏輯。
§ 這種模式特別適用于BMP。過一段時間,這種方式同樣可以移植到CMP中。
§ DAOs可以在發布的時候選擇數據源類型。
§ DAOs增強了應用的可伸縮性,因為數據源改變變得很容易。
§ DAOs對數據訪問沒有任何限制,甚至可以訪問XML數據。
§ 使用這個模式將導致增加一些額外的對象,并在一定程度上增加應用的復雜性。