Conversational versus Nonconversational Session Beans
如何写会话Bean 写会话bean的类,必须实现javax.ejb.SessionBean接口 public interface javax.ejb.SessionBean extends javax.ejb.EnterpriseBean { public abstract void setSessionContext(SessionContext ctx) throws java.rmi.RemoteException;
public abstract void ejbPassivate() throws java.rmi.RemoteException;
public abstract void ejbActivate() throws java.rmi.RemoteException;
public abstract void ejbRemove() throws java.rmi.RemoteException; } 会话bean和实体bean都继承了javax.ejb.EnterpriseBean接口 让我们详细看看接口中的各种方法: setSessionContext(SessionContext ctx) 容器调用这个方法来通过会话上下文与bean连接。Bean可以通过会话上下文向容器查询当前事物的状态和当前的安全状态等。 import javax.ejb.*; public class MyBean implements SessionBean { private SessionContext ctx; public void setSessionContext(SessionContext ctx) { this.ctx = ctx; } ... }
ejbCreate(…) 用来初始化你的会话bean,可以定义多个不同参数的ejbCreate()方法来用不同的方法初始化bean。 import javax.ejb.*; public class MyBean implements SessionBean { private int memberVariable; public void ejbCreate(int initialValue) { this.memberVariable = initialValue; } ... } ejbCreate()方法是容器可以调用的callback方法,客户端代码不能调用它,因为客户端不能直接处理beans??他们必须通过容器,但是客户端必须采用某种方法向ejbCreate方法传递参数,客户端提供初始化参数。Home接口是客户端用来初始化调用的接口工厂。你必须在home接口中复制每一个ejbCreate()方法,例如:如果在bean类中你有下面的ejbCreate方法 public void ejbCreate(int i) throws ... 那么你必须在你的home接口里有下面的create()方法 public void create(int i) throws ... 客户端调用home接口中的create()方法,将参数传递给ejbCreate()。 EjbPassivate() 如果出现太多的实例bean,EJB容器可以将它们中的一些钝化,将它们写到临时的存出空间例如数据库或文件系统。容器释放它们所申请的空间。 import javax.ejb.*; public class MyBean implements SessionBean { public void ejbPassivate() { <close socket connections, etc...> } ... } ejbActivate() 当客户需要使用被钝化的bean时,容器将被钝化的bean重新导入内存,激活它们。 Bean又被导致内存,这时需要重新得到bean所需要的资源。 import javax.ejb.*; public class MyBean implements SessionBean { public void ejbActivate() { <open socket connections, etc...> } ... } ejbRemove() 当容器将会话bean实例remove掉时,调用此方法。所有的bean都有这种方法,它没有参数,它将释放所有以分配的资源。 import javax.ejb.*; public class MyBean implements SessionBean { public void ejbRemove() { <prepare for destruction> } ... } 容器可以在任何时候调用ejbRemove()方法,但如果遇到异常,则有可能禁止容器调用此方法。
业务方法 应该定义一些解决业务问题的方法:例如: import javax.ejb.*; public class MyBean implements SessionBean { public int add(int i, int j) { return (i + j); } ... } 因为客户端要调用这些方法,因此,你必须在bean的远程接口中列出这些方法。
得到Home对象的参考 /* * Get System properties for JNDI initialization */ Properties props = System.getProperties(); /* * Form an initial context */ Context ctx = new InitialContext(props); /* * Get a reference to the home object - the * factory for EJB objects */ MyHome home = (MyHome) ctx.lookup("MyHome"); 建立EJB对象 得到Home对象以后,可以将Home对象作为建立EJB对象的工厂。调用create()方法建立EJB对象。 MyRemoteInterface ejbObject = home.create(); 无参数是因为无状态beans不需要初始参数。 调用方法 客户端有了EJB对象以后就可以通过它来调用方法。当客户端在EJB对象上调用方法时,EJB对象必须选择一个bean实例来应答。EJB对象可以建立一个新的实例或是重用已经存在的实例。 ejbObject.add(); 破坏EJB对象 调用EJB或Home对象上的remove()方法来破坏EJB对象。 ejbObject.remove();
无状态会话bean基础 无状态会话bean是可以模仿业务过程的组件,它可以在单独的方法调用中被执行。Stateless Session Bean不能够维持一个调用客户的状态,在一个方法调用中,Stateless Session Bean 可以维持调用客户的状态,当方法执行完,状态不会被保持。在调用完成后,Stateless Session Bean被立即释放到缓冲池中,所以Stateless Session Bean具有很好的伸缩性,可以支持大量用户的调用。 无状态会话beans的特点 没有对话状态 无状态会话bean可以拥有内部状态,它们的状态不能为特殊的客户端定制。这意味着所有的无状态bean对于客户端是无差别的,客户端也不能分离它们。客户端必须将所有的必需的客户端数据作为业务逻辑方法的参数传给无状态bean,无状态bean可以从外部资源(例如数据库)获得所需的数据。 初始化无状态bean只有一种方法 我们知道会话bean的初始化调用ejbCreate()方法,因为无状态会话bean不能够在方法调用之间保留状态,因此它也不能在客户端给ejbCreate()调用传递数据以后保留状态。调用不带参数的ejbCreate()或create()。 容器可以聚集和重用无状态会话Bean 构建“Hello,World!”远程接口 package com.wiley.compBooks.roman.session.helloworld; import javax.ejb.*; import java.rmi.RemoteException; import java.rmi.Remote; /** * This is the HelloBean remote interface. * * This interface is what clients operate on when * they interact with EJB objects. The container * vendor will implement this interface; the * implemented object is the EJB object, which * delegates invocations to the actual bean. */ public interface Hello extends EJBObject { /** * The one method - hello - returns a greeting to the client. */ public String hello() throws java.rmi.RemoteException; } Source 4.1 Hello.java. Hello接口继承了EJBObject接口,EJBObject继承Remote接口,因此hello可以抛出rmi异常。 下面建立bean,实现业务方法:hello()。 他实现了javax.ejb.SessionBean接口 package com.wiley.compBooks.roman.session.helloworld; import javax.ejb.*; /** * Demonstration stateless session bean. */ public class HelloBean implements SessionBean { // // EJB-required methods // public void ejbCreate() { System.out.println("ejbCreate()"); } public void ejbRemove() { System.out.println("ejbRemove()"); } public void ejbActivate() { System.out.println("ejbActivate()"); } public void ejbPassivate() { System.out.println("ejbPassivate()"); } public void setSessionContext(SessionContext ctx) { System.out.println("setSessionContext()"); } // // Business methods // public String hello() { System.out.println("hello()"); return "Hello, World!"; } } Source 4.2 HelloBean.java 注意:不需要实现自己的远程接口,初始化方法不带参数。破坏bean时,使用比较简单的ejbRemove()方法。ejbActivate() 和ejbPassivate()方法不需应用在无状态会话bean,因此,这两个方法为空。 建立“Hello,World!”Home接口 Home接口继承了javax.ejb.EJBHome。Home接口为EJB对象扩展了一个不带参数的方法??create()方法。 package com.wiley.compBooks.roman.session.helloworld; import javax.ejb.*; import java.rmi.RemoteException; /** * This is the home interface for HelloBean. This interface * is implemented by the EJB Server´s glue-code tools - the * implemented object is called the Home Object and serves * as a factory for EJB Objects. * * One create() method is in this Home Interface, which * corresponds to the ejbCreate() method in HelloBean. */ public interface HelloHome extends EJBHome { /* * This method creates the EJB Object. * * @return The newly created EJB Object. */ Hello create() throws RemoteException, CreateException; } creat方法抛出了a java.rmi.RemoteException和aavax.ejb.CreateException.异常。 写配置描述符 在EJB1.0中,配置描述符是作为文件存储在磁盘上的java对象。在EJB1.1种,配置描述符是一个XML文档。EJB容器或IDE环境应该提供生成配置描述符的工具。 配置描述符的设置 bean home的名字 企业级bean类名 home接口类名 远程接口类名 Re-entrant 状态或无状态 会话时间
HelloBean的配置描述符 环境属性 bean通过使用此信息来适应不同的特殊环境。 Ejb-jar文件 我们需要将我们所需要的文件打包成Ejb-jar文件。 企业级的bean 远程接口 home接口 配置描述符,包括属性 以上这些必须被包含进Ejb-jar文件。在EJB1.0中,jar文件理有一个文本文件的列表。它表示jar的详细信息。它用来鉴别哪个企业bean在Ejb-jar文件。在EJB1.1中,XML文件包含了所有的必要信息。 生成Ejb-jar文件 jar cmf ../manifest HelloWorld.jar * 配置bean 最后,我们还需要在Ejb容器中配置bean。常常执行一下步骤: Ejb-jar文件的检验 容器工具来产生EJB对象和home对象 容器工具来生成RMI所需的stubs和skeletons 写无状态bean的客户代码 package com.wiley.compBooks.roman.session.helloworld; import javax.ejb.*; import javax.naming.*; import java.rmi.*; import java.util.Properties; /** * This class is an example of client code that invokes * methods on a simple stateless session bean. */ public class HelloClient { public static void main(String[] args) { try { /* * Get System properties for JNDI initialization */ Properties props = System.getProperties(); /* * Form an initial context */ Context ctx = new InitialContext(props); /* * Get a reference to the home object * (the factory for EJB objects) */ HelloHome home = (HelloHome) ctx.lookup("HelloHome"); /* * Use the factory to create the EJB Object */ Hello hello = home.create(); /* * Call the hello() method, and print it */ System.out.println(hello.hello()); /* * Done with EJB Object, so remove it */ hello.remove(); } catch (Exception e) { e.printStackTrace(); } } } 客户端代码执行了一下任务: 定位home接口 使用home接口建立EJB对象 调用EJB对象上的hello() 移走EJB对象 运行 首先运行应用服务器。对于BEA的WebLogic,执行 t3server 客户端执行: java -Djava.naming.factory.initial= weblogic.jndi.TengahInitialContextFactory -Djava.naming.provider.url= t3://localhost:7001 com.wiley.compBooks.roman.session.helloworld.HelloClient 服务端输出: setSessionContext() ejbCreate() hello() ejbRemove() 客户端输出: Hello, World!
激活状态bean package com.wiley.compBooks.roman.session.count; import javax.ejb.*; import java.rmi.RemoteException; /** * These are CountBean´s business logic methods. * * This interface is what clients operate on when they * interact with EJB objects. The container vendor will implement this interface; the implemented object is * the EJB Object, which delegates invocations to the * actual bean. */ public interface Count extends EJBObject { /** * Increments the int stored as conversational state */ public int count() throws RemoteException; } Source Count.java
package com.wiley.compBooks.roman.session.count; import javax.ejb.*; /** * Demonstration Stateful Session Bean. This bean is * initialized to some integer value and has a business * method that increments the value. * * This example shows the basics of how to write a stateful * session bean and how passivation/activation works. */ public class CountBean implements SessionBean { private SessionContext ctx; // The current counter is our conversational state. public int val; // // Business methods // /** * Counts up */ public int count() { System.out.println("count()"); return ++val; } // // EJB-required methods // public void ejbCreate(int val) throws CreateException { this.val = val; System.out.println("ejbCreate()"); } public void ejbRemove() { System.out.println("ejbRemove()"); } public void ejbActivate() { System.out.println("ejbActivate()"); } public void ejbPassivate() { System.out.println("ejbPassivate()"); } public void setSessionContext(SessionContext ctx) { } } Source CountBean.java Bean实现了javax.ejb.SessionBean。所以,它必须定义所有SessionBean定义的方法。 OjbCreate()初始化带了val的参数。它将作为counter的初始状态。在钝化和激活bean的过程中,val变量被保护。
计数bean的home接口 package com.wiley.compBooks.roman.session.count; import javax.ejb.*; import java.rmi.RemoteException; /** * This is the home interface for CountBean. This interface * is implemented by the EJB Server´s glue-code tools - the * implemented object is called the Home Object and serves * as a factory for EJB Objects. * * One create() method is in this Home Interface, which * corresponds to the ejbCreate() method in the CountBean file. */ public interface CountHome extends EJBHome { /* * This method creates the EJB Object. * * @param val Value to initialize counter to * * @return The newly created EJB Object. */ Count create(int val) throws RemoteException, CreateException; } Source CountHome.java. 计数bean的配置描述符
计数bean的配置描述符 计数bean的环境属性 生成计数bean的Ejb-jar文件 计数bean的客户端代码 package com.wiley.compBooks.roman.session.count; import javax.ejb.*; import javax.naming.*; import java.util.Properties; /** * This class is a simple example of client code that invokes * methods on a simple Stateless Enterprise Bean. * * We create 3 EJB Objects in this example, but we allow * the container to have only 2 in memory. This illustrates how * beans are passivated to storage. */ public class CountClient { public static void main(String[] args) { try { /* * Get System properties for JNDI initialization */ Properties props = System.getProperties(); /* * Get a reference to the Home Object - the * factory for EJB Objects */ Source CountClient.java 1、需要JNDL初始化上下文 2、使用JNDL定位home接口 3、使用home对象建立3个不同的计数EJB对象,因此也就和三个不同的客户端建立了会话 4、配置描述符限制了同时只能有两个bean工作,因此3个bean中一定有钝化的。在调用ejbPassivate()时,打印一条信息。 5、在每个EJB对象上调用count()方法,调用ejbActivate()方法激活bean,该方法打印一条信息。 6、最后所有的EJB对象被删除。 package com.wiley.compBooks.roman.session.count; import javax.ejb.*; import javax.naming.*; import java.util.Properties; /** * This class is a simple example of client code that invokes * methods on a simple Stateless Enterprise Bean. * * We create 3 EJB Objects in this example, but we allow * the container to have only 2 in memory. This illustrates how * beans are passivated to storage. */ public class CountClient { public static void main(String[] args) { try { /* * Get System properties for JNDI initialization */ Properties props = System.getProperties(); /* * Get a reference to the Home Object - the * factory for EJB Objects */ Context ctx = new InitialContext(props); CountHome home = (CountHome) ctx.lookup("CountHome"); /* * An array to hold 3 Count EJB Objects */ Count count[] = new Count[3]; int countVal = 0; /* * Create and count() on each member of array */ System.out.println("Instantiating beans..."); for (int i=0; i < 3; i++) { /* * Create an EJB Object and initialize * it to the current count value. */ count[i] = home.create(countVal); /* * Add 1 and print */ countVal = count[i].count(); System.out.println(countVal); /* * Sleep for 1/2 second */ Thread.sleep(500); } /* * Let´s call count() on each EJB Object to * make sure the beans were passivated and * activated properly. */ System.out.println("Calling count() on beans..."); for (int i=0; i < 3; i++) { /* * Add 1 and print */ countVal = count[i].count(); System.out.println(countVal); /* * Sleep for 1/2 second */ Thread.sleep(500); } /* * Done with EJB Objects, so remove them */ for (int i=0; i < 3; i++) { count[i].remove(); } } catch (Exception e) { e.printStackTrace(); } } } Source CountClient.java 运行客户端: 对于BEA的WebLogic,执行: java -Djava.naming.factory.initial= weblogic.jndi.TengahInitialContextFactory -Djava.naming.provider.url= t3://localhost:7001 com.wiley.compBooks.roman.session.count.CountClient 客户端输出: Instantiating beans... 1 2 3 Calling count() on beans... 2 3 4 服务端输出: ejbCreate() count() ejbCreate() count() ejbCreate() ejbPassivate() count() ejbPassivate() ejbActivate() count() ejbPassivate() ejbActivate() count() ejbPassivate() ejbActivate() count() ejbPassivate() ejbActivate() ejbRemove() ejbActivate() ejbRemove() ejbRemove()
为Beans增加功能
EJB 上下文:通往容器的门户 存在如下信息: 1、关于bean的home对象和EJB对象的信息 2、bean的当前事务信息。 3、 对于客户授权的安全信息。Bean可以通过查询环境决定客户执行操作所需要的安全层次。 4、 bean的环境属性。 容器将所有这些信息保存在一个称为EJB context object的对象里。EJB上下文作为容器的物理部分,可以被bean访问。这些访问可以让bean得到当前的状态和改变当前的状态。 上下文可以在bean的生命期中被更改。 EJB1.0 javax.ejb.EJBContext接口: public interface javax.ejb.EJBContext { public javax.ejb.EJBHome getEJBHome(); public java.util.Properties getEnvironment(); public java.security.Identity getCallerIdentity(); public boolean isCallerInRole(java.security.Identity); public javax.jts.UserTransaction getUserTransaction(); public void setRollbackOnly(); public boolean getRollbackOnly(); } 会话bean的上下文 上下文根据bean的不同分为:会话上下文和实体上下文。它们分别被用于会话bean和实体bean
javax.ejb.EJBContext public interface javax.ejb.SessionContext extends javax.ejb.EJBContext { public javax.ejb.EJBObject getEJBObject(); } 注意:SessionContext接口继承了EJBContext接口,在EJBContext中定义的方法提供了对会话bean的存取路径。 对于会话bean,调用setSessionContext,这个方法在javax.ejb.SessionBean接口中被定义。对于实体bean,调用setEntityContext。 SessionContext.getEJBObject() 在EJB中,beans可以作为其他bean的客户端。如果一个bean需要调用另外的bean,getEJBObject()方法则是必需的。在java中,对象可以使用this关键字保存自身的参考。在EJB中,bean不能使用this关键字给其他bean传递对象,这是因为所有的客户调用bean上的方法都是间接调用bean的EJB对象。Bean可以使用this关键字将自己传给EJB对象。 了解EJB的安全性 首先,客户端必须是可被鉴别的。 其次,客户端必须是已经授权的。 第一步:鉴别 不同的EJB容器拥有不同的鉴别客户端的方法。例如:BEA的WebLogic中,当不同客户端代码使用JNDL定位Home对象时,提供不同的用户名和密码。 Properties props = System.getProperties(); props.put(Context.SECURITY_PRINCIPAL, "EmployeeA"); props.put(Context.SECURITY_CREDENTIALS, "myPassword1"); Context ctx = new InitialContext(props); // Use the initial context to lookup home objects... EJB没有制定如何鉴别的规范,因此这样就影响了可移植性。要了解这方面,查看各类容器的文档。 当运行这段代码时,应用服务器将验证你的用户名和密码,这是应用服务器规范。许多应用服务器允许在属性文件里设置用户名和密码。这个文件将在运行时由应用服务器读。 高级点的服务器支持已经存在的验证系统的整合。例如将用户名和密码列表存储在LDAP服务器中。 第二步:授权 只有经过授权的客户端才可以调用bean中的方法。EJB中有两种验证授权的方法:declaratively和programmatically。即:由容器执行所有的授权检验、在程序中进行授权检查。 Declarative授权检查时,要在配置描述符中声明bean的授权需要。例如使用BEA的WebLogic服务器的配置描述符的例子: (accessControlEntries submitPurchaseOrder [employees] approvePurchaseOrder [managers] DEFAULT [administrators] ); end accessControlEntries 容器将在运行时自动的执行安全检查。抛会出java.lang.SecurityException异常。 Programmatic授权检查,必须查询EJB上下文得到当前客户端的授权信息。由两种方法调用CallerInRole(Identity role)和getCallerIdentity()。 isCallerInRole() import java.security.Identity; ... public class MyBean implements SessionBean { private SessionContext ctx; ... public void foo() { Identity id = new MyIdentity("administrators"); if (ctx.isCallerInRole(id)) { System.out.println("An admin called me"); return; } System.out.println("A non-admin called me"); } }
import java.security.Identity; public class MyIdentity extends Identity { public MyIdentity(String id) { super(id); } } getCallerIdentity() import java.security.Identity; ... public class MyBean implements SessionBean { private SessionContext ctx; ... public void bar() { Identity id = ctx.getCallerIdentity(); String name = id.getName(); System.out.println("The caller´s name is " + name); } } 了解EJB对象的操作 许多EJB应用程序需要客户端有与bean断开的能力,还要有与bean重建连接的能力。EJB提供了EJB object handles。EJB对象操作对于EJB对象是一个长生命期的代理。可以用它来重建与EJB对象的连接,并保证会话状态不被丢失。下面是EJB对象操作的代码 // First, get the EJB object handle from the EJB object. javax.ejb.Handle myHandle = myEJBObject.getHandle(); // Next, serialize myHandle, and then save it in // permanent storage. ObjectOutputStream stream = ...; stream.writeObject(myHandle); // time passes... // When we want to use the EJB object again, // deserialize the EJB object handle ObjectInputStream stream = ...; Handle myHandle = (Handle) stream.readObject(); // Convert the EJB object handle back into an EJB object MyRemoteInterface myEJBObject = (MyRemoteInterface) myHandle.getEJBObject(); // Resume calling methods again myEJBObject.callMethod(); 例子:The Puzzle Game “Fazuul”(参考原文)