EJB整理2-会话Bean

    技术2022-06-12  87

    1 会话Bean

    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 类,我们应该实现接口的业务方法,尽管这并不是必须的,但我们没理由不这样做。

    1.1 无状态 bean开发

    由于无状态会话 Bean 不维护会话状态,意味着一个 bean 实例可以为多个用户服务。因此 EJB 容器使用实例池化技术管理无状态会话 Bean。简单的说就是:当无状态会话 Bean 部署到应用服务器时,EJB 容器会为它预先创建一些 bean 实例放在对象池。当有用户访问 EJB 方法时,EJB 容器会从对象池中取出一个实例为之服务,服务完了就回到对象池。当下一个用户再访问 EJB 方法时,EJB 容器有可能再次把该实例取出来为之服务。正因如此,无状态会话 Bean 只需要少量的实例就可以为成百上千的用户服务,大大提高了系统性能。

    由于无状态会话 Bean 能够支持多个用户,并且通常在 EJB 容器中共享,可以为需要大量客户的应用提供更好的扩充能力。无状态会话 Bean 比有状态会话 Bean 更具性能优势,在条件允许的情况下开发人员应该首先考虑使用无状态会话 Bean。

    1.1.1 开发只实现 Remote 接口的无状态 Session 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。

    1.1.2 开发只实现 Local 接口的无状态 Session Bean

    开发只有 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,请把客户端应用部署在应用服务器下。

    1.1.3 开发实现了Remote与Local接口的无状态Session Bean

    在实际应用中,同时实现 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注解直接定义到接口上

    1.2 有状态 bean开发

    前面我们已经了解到,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>"); }  


    最新回复(0)