Session Bean 是实现业务逻辑的地方。简单地说,像我们要实现两数相加或是从数据库中读取数据,都是通过Session Bean 来实现。根据是否可以维护会话状态,Session Bean 分为有状态 bean 和无状态 bean。有状态 bean 可以维护会话状态,无状态 bean 不维护会话状态。要维护会话状态,意味着 EJB 容器要为每个用户创建一个 bean实例,并通过该实例保存着与用户的会话状态。不维护会话状态,意味着一个 bean 实例不需要保存与某个用户的会话状态,这时一个 bean 实例可以为多个用户服务。
要开发一个 Session Bean,我们需要定义接口和 Bean class。其中接口分为远程(remote)和本地(local)接口。在 EJB3.0中,不要求你同时实现 remote 和 local 接口,但实现两者是比较好的做法。
远程接口(remote interface):定义了 session bean 的业务方法,这些方法可以被来自 EJB 容器之外的应用访问到。 本地接口(local interface):同样定义了 session bean 的业务方法,这些方法可以被同处于 EJB 容器内的其它应用使用。因为 local 接口允许 bean 之间直接通过内存交互,没有分布式对象协议的开销,从而改善了性能。 Bean 类(bean class):bean class 包含了业务逻辑,它必须具备一个远程或本地接口。在 Bean 类,我们应该实现接口的业务方法,尽管这并不是必须的,但我们没理由不这样做。由于无状态会话 Bean 不维护会话状态,意味着一个 bean 实例可以为多个用户服务。因此 EJB 容器使用实例池化技术管理无状态会话 Bean。简单的说就是:当无状态会话 Bean 部署到应用服务器时,EJB 容器会为它预先创建一些 bean 实例放在对象池。当有用户访问 EJB 方法时,EJB 容器会从对象池中取出一个实例为之服务,服务完了就回到对象池。当下一个用户再访问 EJB 方法时,EJB 容器有可能再次把该实例取出来为之服务。正因如此,无状态会话 Bean 只需要少量的实例就可以为成百上千的用户服务,大大提高了系统性能。
由于无状态会话 Bean 能够支持多个用户,并且通常在 EJB 容器中共享,可以为需要大量客户的应用提供更好的扩充能力。无状态会话 Bean 比有状态会话 Bean 更具性能优势,在条件允许的情况下开发人员应该首先考虑使用无状态会话 Bean。
开发步骤:
1. 定义一个包含业务方法的接口。这个接口不需要包含任何注释,它是一个普通的 java 接口。调用 EJB的客户端使用这个接口引用从 EJB 容器返回的存根(stub)。代码如下:
public interface HelloWorld { public String SayHello(String name); }
2. 编写 Bean class。HelloWorldBean.java 。Bean 类推荐的命名方式是:接口+Bean,如:HelloWorldBean,代码如下:
@Stateless @Remote ({HelloWorld.class}) public class HelloWorldBean implements HelloWorld { public String SayHello(String name) { return name +"说:你好!世界,这是我的第一个EJB3哦."; } }
在 Bean 类上面有两个注释@Stateless 和@Remote。
@Stateless 注释指明这是一个无状态会话 Bean。@Stateless 注释的定义如下:
@Target(TYPE) @Retention(RUNTIME) public @interface Stateless { String name( ) default ""; String mappedName() default ""; }
name()属性用于指定 session bean 的 EJB 名称。该名称在 EJB Jar 包中必须是全局唯一的,而在 EAR 中却可以重复(因为 EAR 可以包含多个 EJB Jar,而每个 jar 可以存在一个同名的 EJB,在 EAR 中要定位某个 EJB,可以这样使用:xxx.jar#HelloWorldBean)。如果不指定该属性,默认就是 bean class 的非限定名称。对本例而言,EJB 名称默认为 HelloWorldBean。mappedName()属性指定 Bean 的全局 JNDI 名称,这个属性在 weblogic,Sun 应用服务器和 glassfish 起作用。@Remote 注释指定这个无状态 Bean 的 remote 接口。Bean 类可以具有多个 remote 接口,每个接口之间用逗号分隔,
如:@Remote ({HelloWorld.class,Hello.class,World.class})。如果你只有一个接口,你可以省略大括号,对于本例而言,可以写成这样:@Remote (HelloWorld.class)。
经过上面两步,一个 HelloWorld EJB 就开发完了。现在我们把它发布到 Jboss 中。在发布前我们需要把它打成 Jar包。打 JAR 包的方法有很多,如使用 jar 命令、集成开发工具或者 Ant。
开发只有 Local 接口的无状态 Session Bean 的步骤和上节开发只有 Remote 接口的无状态会话 Bean 的步骤相同,两者唯一不同之处是,前者使用@Remote 注释声明接口是远程接口,后者使用@Local 注释声明接口是本地接口。当@Local 和@Remote 注释都不存在时,容器会将 Bean class 实现的接口默认为 Local 接口。如果 EJB 与客户端部署在同一个应用服务器,采用 Local 接口访问 EJB 优于 Remote 接口。因为通过 Remote 接口访问 EJB 需要在tcp/ip 协议基础上转换和解释 Corba IIOP 协议消息,在调用 EJB 的这一过程中存在对象序列化,协议解释、tcp/ip通信等开销。而通过 Local 接口访问 EJB 是在内存中与 bean 彼此交互的,没有了分布式对象协议的开销,大大改善了性能。下面是只有 Local 接口的无状态会话 Bean。开发步骤:
1. 业务接口:LocalHelloWorld.java
public interface LocalHelloWorld { public String SayHello(String name); }
2. Bean 类:LocalHelloWorldBean.java
@Stateless @Local ({LocalHelloWorld.class}) public class LocalHelloWorldBean implements LocalHelloWorld { public String SayHello(String name) { return name +"说:你好!这是一个只具有Local接口的无状态Bean"; } }
3. 编写客户端
InitialContext ctx = new InitialContext(); LocalHelloWorld helloworld = (LocalHelloWorld) ctx.lookup("LocalHelloWorldBean/local"); out.println(helloworld.SayHello("hello"));
调用 Local 接口的客户端与 EJB 容器必须在同一个 JVM。为了确保客户端与应用服务器处于同一个 JVM,请把客户端应用部署在应用服务器下。
在实际应用中,同时实现 Remote 与 Local 接口是一种比较好的做法。这样你既可以在远程访问 EJB,也可以在本地访问 EJB。代码示例:
@Stateless @Remote ({HelloWorld.class}) @Local ({HelloWorld.class}) public class HelloWorldBean implements HelloWorld { public String SayHello(String name) { return name +"说:如果是本地调用,则是本地bean,否则是远程bean "; } }
当EJB实现了多个接口的时候(不包括:java.io.Serializable接口,java.ejb.*接口),该如何在Remote或Local注解中定义其类型?有两种方式:
第一种方法:
/** * 定义方式1: */ @Stateless @Remote(value={Interface01.class, Interface02.class }) @Local(Interface03.class) public class EjbBean implements Interface01,Interface02, Interface03 { }
第二种方法:将@Remote或@Local注解直接定义到接口上
前面我们已经了解到,stateless session bean 创建在对象池中,提供给众多用户使用,如果Bean class有自己的成员属性(变量),那么这些变量就会受到所有调用它的用户影响。在一些应用场合中,有时我们需要每个用户都有自己的一个实例,这个实例不受其他用户影响。就好比购物车对象,每个用户都应有自己的购物车,有状态 Bean 正好满足你的这种需求。每个有状态 Bean 在 bean 实例的生命周期内都只服务于一个用户。
对于 Stateful Session Bean,用户每调用一次 lookup()都将创建一个新的 bean 实例,如果你希望一直使用某个 Bean实例,你必须在客户端缓存存根。如:你使用 Stateful Session Bean 做购物车,在 A 页面中通过 lookup()得到购物车存根,添加一个商品到购物车,接着浏览商品。在 B 页面发现了一个最牛 B 的商品,你想把它加入购物车,如果你企图通过 lookup()方法获取刚才的购物车,这时你将会得到一个没有任何商品的新购物车。要想获取到刚才的购物车,你需要在第一次 lookup()购物车时,也就是在 A 页面,使用 session(或别的缓存方案)缓存购物车存根,这样在后续的页面(如 B 页面)就可以通过 session 里的存根对象获取到原先的购物车。
Stateful Session Bean 的开发步骤与 Stateless Session Bean 的开发步骤相同。注:在 EJB3.0 最终规范中,stateful session bean 不要求必须实现 Serializable 接口。尽量实现。代码示例:
1. EJB服务端代码:
业务接口
public interface Cart extends Serializable { public void AddBuyItem(String productName); public List<String> getBuyItem(); }
Bean 类:CartBean.java
@Stateful @Remote(Cart.class) public class CartBean implements Cart{ private List<String> buyitem = new ArrayList<String>(); public void AddBuyItem(String productName) { buyitem.add(productName); } public List<String> getBuyItem() { return buyitem; } }
2. 客户端代码:
InitialContext ctx = new InitialContext(); Cart cat = (Cart)session.getAttribute("cat"); if(cat==null){//创建一个购物车 cat = (Cart) ctx.lookup("CartBean/remote"); session.setAttribute("cat", cat); } cat.AddBuyItem("EJB3"); List<String> buyitem = cat.getBuyItem(); out.println("购物车的商品列表:<br>"); for(String name : buyitem){ out.println(" "+ name+ "<br>"); }