Võ Văn Hải's blog

Chỉ có một điều tuyệt đối đó là mọi thứ đều tương đối…

Stateful Session Bean – EJB2 – ShoppingCart


Stateful Session Bean

I. Khái niệm

1. Stateful Session Bean

– Một phiên giao dịch giữa client và bean còn gọi là một conversation. Một Stateful Session Bean có khả năng lưu lại trạng thái của conversation trong các biến thực thể của lớp bean xuyên qua các request của conversation. Một ví dụ quen thuộc là shopping cart dùng để lưu trữ các món hàng đã chọn xuyên qua hàng loạt request chọn hàng (hoặc loại bỏ hàng đã chọn) của một session mua hàng. Như vậy, Stateful Session Bean chứa trong nó business logic và trạng thái conversation của client.

– Stateful Session Bean được thiết kế để phục vụ cho một[1] client trong đời sống của nó thông qua một EJB Object.

– Stateful Session Bean không có khả năng tự lưu giữ (persistent) giống như entity bean. Để lưu giữ được trạng thái của conversation như trên, Stateful Session Bean có 3 trạng thái:

  • Does Not Exits: không tồn tại trong bộ nhớ.
  • Method-Ready: sẵn sàng phục vụ các yêu cầu từ client được ủy nhiệm (delegate) bởi EJB Object.
  • Passivated: thụ động, lưu trữ trạng thái của conversation, sẽ được kích hoạt (activate) khi cần.

– Chọn cách cài đặt Stateful Session Bean khi:

  • Cần khởi gán khi tạo bean.
  • Client triệu gọi phương thức nhiều lần.
  • Cần lưu trữ thông tin client xuyên qua nhiều lần gọi.

– Trạng thái của conversation, lưu trong biến thành viên của lớp EJB, có thể là các đối tượng: kiểu cơ bản, kiểu serializable (java.rmi.Serializable, tuần tự hóa được), javax.ejb.EJBHome (kiểu home interface), javax.ejb.EJBObject (kiểu remote interface), javax.ejb.SessionContext, … Không được khai báo các biến này là transient.

2. Vòng đời của Stateful Session Bean

– Stateful Session Bean không dùng instance pooling. Ở trạng thái không hoạt động, thực thể bean bị đẩy khỏi bộ nhớ một cách đơn giản trong lúc EJB Object vẫn còn giữ liên hệ với client.

– Khi phương thức create() của EJB Home được gọi, container triệu gọi phương thức newInstance() của lớp bean để tạo một thực thể bean mới. Sau đó container triệu gọi phương thức setSessionContext() để liên kết bean với một SessionContext dùng suốt vòng đời của nó, lúc này bean sẽ được gán cho một EJB Object. Cuối cùng, container triệu gọi phương thức ejbCreate() tương ứng với create() của EJB Home. Thực thể bean bây giờ ở trạng thái Method-Ready.

– Để tiết kiệm tài nguyên, khi không phục vụ client, bean có thể chuyển sang trạng thái Passivated bằng phương thức ejbPassivate(). Bean sẽ trả lại tất cả các tài nguyên đang mở và đặt các field thành null. Trạng thái của conversation sẽ được lưu, cách lưu trữ tùy đối tượng.

– Khi có yêu cầu của client đối với một bean đang ở trạng thái Passivated, container sẽ kích hoạt thực thể bean, khôi phục bean và tham chiếu SessionContext, trạng thái conversation lưu trữ, … rồi gọi ejbActivate().

– Khi phương thức của bean nhận một exception hệ thống, container không dùng EJB Object và loại bỏ thực thể bean. Bean sẽ chuyển trực tiếp sang trạng thái Does Not Exits, không gọi phương thức ejbRemove().

3. Các phương thức callback

– Phương thức ejbCreate() khởi gán dữ liệu thành viên, cũng là nơi lưu trữ trạng thái conversation, cho thực thể bean. Sau khi được tạo, bean có thể ở:

  • Trạng thái Method-Ready, thực hiện các business method phục vụ cho client.
  • Không cần thiết, sẽ được container chuyển thành trạng thái Passivated.
  • Chuyển sang trạng thái Does Not Exist do lỗi server hoặc do người dùng chấm dứt session.

Phương thức ejbCreate() được nạp chồng cho phép khởi tạo thực thể bean bằng nhiều cách.

– Phương thức ejbPassivate() sẽ được container gọi trước khi chuyển bean vào trạng thái Passivated, lưu trữ trạng thái của conversation. Phương thức này gọi thực thể bean để giải phóng các tài nguyên liên kết với nó. Container chuyển trạng thái của bean thành Passivated theo nguyên tắc LRU (Least Recently Used – bean tồn tại lâu nhất sẽ được chọn để thụ động hóa). Bean ở trạng thái Passivated có thời gian timeout, quá thời gian này hoặc do lỗi server, bean sẽ bị loại.

– Phương thức ejbActivate() chuyển bean từ trạng thái Passivated ngược trở lại trạng thái Method-Ready. Trạng thái của conversation đang lưu trữ sẽ được đọc lại và bean được khôi phục trước khi container gọi ejbActivate(). Khi kích hoạt bean, container dùng nguyên tắc JIT (Just-In-Time – tức thời).

– Phương thức ejbRemove() được container gọi khi client triệu gọi phương thức remove() trên home interface hoặc remote interface. Phương thức này giải phóng tài nguyên, loại bỏ thực thể bean.

– Phương thức setSessionContext() được container gọi để liên kết một session context với một bean chỉ định ngay từ đầu vòng đời của bean đó.

II. Thiết kế

A. Tạo Stateful Session Bean

– Cần đặt CLASSPATH đến các file jar trong thư mục client của JBOSS_HOME.

1. Tạo remote interface

package cart;

import helper.BookException;

import java.util.Vector;

import javax.ejb.EJBObject;

import java.rmi.RemoteException;

public interface Cart extends EJBObject {

public void addBook( String title ) throws RemoteException;

public void removeBook( String title ) throws BookException, RemoteException;

public Vector getContents() throws RemoteException;

}

2. Tạo home interface

package cart;

import java.rmi.RemoteException;

import javax.ejb.CreateException;

import javax.ejb.EJBHome;

public interface CartHome extends EJBHome {

Cart create( String person ) throws RemoteException, CreateException;

Cart create( String person, String id ) throws RemoteException, CreateException;

}

3. Tạo lớp EJB

package cart;

import helper.BookException;

import helper.IdVerifier;

import java.util.Vector;

import javax.ejb.SessionBean;

import javax.ejb.SessionContext;

import javax.ejb.CreateException;

public class CartEJB implements SessionBean {

String customerName;

String customerId;

Vector contents;

public void addBook( String title ) {

contents.addElement( title );

}

public void removeBook( String title ) throws BookException {

boolean result = contents.removeElement( title );

if ( result == false ) {

throw new BookException( title + ” not in cart.” );

}

}

public Vector getContents() {

return contents;

}

public void ejbCreate( String person ) throws CreateException {

if ( person == null ) {

throw new CreateException( “Null person not allowed.” );

} else {

customerName = person;

}

customerId = “0”;

contents = new Vector();

}

public void ejbCreate( String person, String id ) throws CreateException {

if ( person == null ) {

throw new CreateException( “Null person not allowed.” );

} else {

customerName = person;

}

IdVerifier idChecker = new IdVerifier();

if ( idChecker.validate( id ) ) {

customerId = id;

} else {

throw new CreateException( “Invalid id: ” + id );

}

contents = new Vector();

}

public void ejbRemove() { }

public void ejbActivate() { }

public void ejbPassivate() { }

public void setSessionContext( SessionContext sc ) { }

}

4. Tạo các lớp hỗ trợ (helper)

– Các lớp trên có phương thức ném ra các exception do người dùng viết. Cần bổ sung các lớp exception này vào gói EJB.

package helper;

public class BookException extends Exception {

public BookException() { }

public BookException( String msg ) {

super( msg );

}

}

package helper;

public class IdVerifier {

public IdVerifier() { }

public boolean validate( String id ) {

boolean result = true;

for ( int i = 0; i < id.length(); ++i ) {

if ( Character.isDigit( id.charAt( i ) ) == false )

result = false;

}

return result;

}

}

B. Tạo client truy xuất Staful Session Bean

1. Định vị home interface

System.setProperty(“java.naming.factory.initial“, “org.jnp.interfaces.NamingContextFactory“);

System.setProperty( “java.naming.provider.url”, “192.168.0.3:1099” );

System.setProperty( “java.naming.factory.url.pkgs”, “org.jboss.naming” );

+ Tạo JNDI naming context như một giao diện giữa client và JNDI.

Context initial = new InitialContext();

2. Tìm đối tượng thông qua JNDI

Object objref = initial.lookup( “cart/CartJNDI” );

3. Thu hẹp (narrow) tham chiếu

CartHome home = ( CartHome )PortableRemoteObject.narrow( objref, CartHome.class );

4. Sinh ra một thực thể EJB

shoppingCart = home.create( “Bill Gates”, “123” );

5. Triệu gọi các business method (phương thức nghiệp vụ)

6. Code hoàn chỉnh

import cart.Cart;

import cart.CartHome;

import helper.BookException;

import java.awt.*;

import java.awt.event.*;

import javax.swing.*;

import java.util.Vector;

import java.util.Enumeration;

import javax.naming.Context;

import javax.naming.InitialContext;

import javax.rmi.PortableRemoteObject;

public class CartClient extends JFrame {

CartHome home;

static Cart shoppingCart;

Container c;

String[] items = { “JSP in 21 Days”, “Mastering EJB”, “J2EE Tutorial” };

JLabel book = new JLabel( “Aptech J2EE Book’s list” );

JLabel cart = new JLabel( “Your shopping cart” );

JButton bAdd = new JButton( “Add” );

JButton bRemove = new JButton( “Remove” );

JComboBox bookCB = new JComboBox( items );

static JComboBox cartCB = new JComboBox();

public CartClient( String s ) {

super( s );

c = getContentPane();

c.setLayout( new GridLayout( 2, 3, 2, 2 ) );

c.add( book );

c.add( bookCB );

c.add( bAdd );

bAdd.addActionListener( new ButtonListener() );

c.add( cart );

c.add( cartCB );

c.add( bRemove );

bRemove.addActionListener( new ButtonListener() );

try {

System.setProperty(“java.naming.factory.initial”,

“org.jnp.interfaces.NamingContextFactory”);

System.setProperty( “java.naming.provider.url”, “localhost:1099” );

System.setProperty( “java.naming.factory.url.pkgs”, “org.jboss.naming” );

Context initial = new InitialContext();

Object objref = initial.lookup( “cart/CartJNDI” );

CartHome home = ( CartHome )PortableRemoteObject.narrow( objref, CartHome.class );

shoppingCart = home.create( “Bill Gates”, “123” );

} catch ( Exception ex ) {

ex.printStackTrace();

}

setSize( 500, 95 );

setVisible( true );

setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );

}

static void refreshCart() {

cartCB.removeAllItems();

try {

Vector bookList = new Vector();

bookList = shoppingCart.getContents();

Enumeration enumer = bookList.elements();

while ( enumer.hasMoreElements() )

cartCB.addItem( ( String ) enumer.nextElement() );

} catch ( Exception ex ) { }

}

class ButtonListener implements ActionListener {

public void actionPerformed( ActionEvent ev ) {

if ( ev.getSource() == bAdd ) {

try {

shoppingCart.addBook( ( String )bookCB.getSelectedItem() );

CartClient.refreshCart();

} catch ( Exception ex ) { }

}

if ( ev.getSource() == bRemove && cartCB.getItemCount() > 0 ) {

try {

shoppingCart.removeBook( ( String )cartCB.getSelectedItem() );

CartClient.refreshCart();

} catch ( Exception ex ) { }

}

}

}

public static void main( String[] args ) {

new CartClient( “Aptech Book Store” );

}

}

– Chú ý ta chỉ dùng các business method của EJB, không sử dụng các phương thức thêm và xóa item trực tiếp của JComboBox. Mỗi lần gọi phương thức là một request trong toàn bộ session chọn mua hàng cho shopping cart.

III. Triển khai

1. Chuẩn bị các deployment descriptor

– Chuẩn bị ejb-jar.xml trong thư mục META-INF. Chú ý <session-type>Stateful.

<?xml version =”1.0″ encoding=”UTF-8″?>

<ejb-jar version=”2.1″ xmlns=”http://java.sun.com/xml/ns/j2ee&#8221; xmlns:xsi=”http://www.w3.org/2001/XMLSchema-instance&#8221; xsi:schemaLocation=”http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/ejb-jar_2_1.xsd”&gt;

<enterprise-beans>

<session>

<ejb-name>Cart</ejb-name>

<home>cart.CartHome</home>

<remote>cart.Cart</remote>

<ejb-class>cart.CartEJB</ejb-class>

<session-type>Stateful</session-type>

<transaction-type>Container</transaction-type>

</session>

</enterprise-beans>

</ejb-jar>

– Chuẩn bị jboss.xml, trong thư mục META-INF.

<?xml version =”1.0″ encoding=”UTF-8″?>

<jboss>

<enterprise-beans>

<session>

<ejb-name>Cart</ejb-name>

<jndi-name>cart/CartJNDI</jndi-name>

</session>

</enterprise-beans>

</jboss>

2. Đóng gói EJB

– Dùng công cụ jar của JDK để đóng gói EJB

C:\EJB\StatefulSession>jar cvf cart.jar cart/*.class helper/*.class META-INF/*.xml

added manifest

adding: cart/Cart.class(in = 330) (out= 234)(deflated 29%)

adding: cart/CartEJB.class(in = 1705) (out= 867)(deflated 49%)

adding: cart/CartHome.class(in = 336) (out= 206)(deflated 38%)

adding: helper/BookException.class(in = 292) (out= 209)(deflated 28%)

adding: helper/IdVerifier.class(in = 452) (out= 334)(deflated 26%)

adding: META-INF/ejb-jar.xml(in = 359) (out= 191)(deflated 46%)

adding: META-INF/jboss.xml(in = 203) (out= 133)(deflated 34%)

– Kết quả gói cart.jar có nội dung như sau:

cart

Cart.class

CartEJB.class

CartHome.class

helper

BookException.class

IdVerifier.class

META-INF

ejb-jar.xml

jboss.xml

MANIFEST.MF

3. Chạy JBoss server

– Chạy %JBOSS_HOME%\bin\run.bat trong một console.

[INFO,MailService] Mail Service ‘Mail’ bound to java:/Mail

[INFO,MailService] Started

[INFO,ServiceControl] Started 47 services

[INFO,STDOUT] JBoss-2.4.10 Started in 0m:2s.734

3. Triển khai gói cart.jar

– Sao chép gói cart.jar vào thư mục %JBOSS_HOME%\deploy\ , quan sát chi tiết triển khai gói này trong console chạy JBoss server.

[INFO,AutoDeployer] Auto deploy of file:/C:/JBoss/deploy/cart.jar

[INFO,J2eeDeployer] Deploy J2EE application: file:/C:/JBoss/deploy/cart.jar

[INFO,J2eeDeployer] Create application cart.jar

[INFO,J2eeDeployer] install EJB module cart.jar

[INFO,ContainerFactory] Deploying:file:/C:/JBoss/tmp/deploy/Default/cart.jar

[INFO,ContainerFactory] Deploying Cart

[INFO,ContainerFactory] Deployed application: file:/C:/JBoss/tmp/deploy/Default/cart.jar

[INFO,J2eeDeployer] J2EE application: file:/C:/JBoss/deploy/cart.jar is deployed.

4. Sử dụng client để truy xuất bean

– Chạy client từ dòng lệnhtrong một console khác:

C:\EJB\StatefulSession>java -classpath %CLASSPATH%;C:\JBoss\client\jboss-client.jar;. CartClient


[1] Đây là mô hình khái niệm, một số container cung cấp khả năng instance pooling và instance swapping

5 Responses to “Stateful Session Bean – EJB2 – ShoppingCart”

  1. CMP said

    Thay oi cho em hoi CMP:
    trong file “ejb:jar”
    lam sao su dung cac toan tu so sanh trong ejb-jar.xml nhu trong sql thay. nho thay chi giup em
    vidu: 1000 < price < 2000
    va : name dieu kien %n%

    em cam on thay

  2. Được chứ. Trong câu sql em có thể làm bình thường mà!
    Em xem thêm trong bài CMP này. Ở file ejb-jar.xml chỗ element ta có thể dùng các toán tử so sánh, like,…

  3. nguyen ngoc truong said

    me muon xin video tao Session Bean shopcart duoc khong a!rat chan thanh cam on a!

  4. VCH said

    Cho hỏi là cái này gọi từ client là desktop application, nhưng giả sử là web application thì sao. Mỗi lần có request thì mỗi lần nó create() 1 lần, vậy làm sao mỗi lần request đó nó biết gọi lại carts cũ

  5. Quyet said

    Hay thế!!!

Leave a comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.