详解EJB 3会话Bean

 
详解EJB 3 会话Bean
 
 
         在本文中,将讨论EJB 3会话Beans——EJB客户端应用程序所用到的核心业务服务对象,有助于理解简单新颖的EJB 3会话Bean模型,主要涉及以下主题:
 
Ø 会话Bean类型,包括有状态及无状态,及何时该用何种。
Ø Bean类、业务接口、业务方法。
Ø 通过会话Bean注释符实现的依赖性注入(即资源捆绑)。
Ø 回调方法。
Ø 拦截器方法(Interceptors)。
Ø 异常处理。
Ø 客户端视图。(Client View)
 
 
 
会话Bean 简介
         会话Bean是Java组件,其既可运行于独立的EJB容器中,也可运行于作为标准Java平台、Java EE应用程序服务器一部分EJB容器中。这些Java组件通常用于建模某一特定用户任务,如输入顾客信息或实现某一维持客户端会话状态的过程。另外,会话Bean还能在多种类型应用程序中实现所需的业务逻辑,如人力资源、订单录入及费用报表等等。
 
 
         会话Bean 类型
         会话Bean有以下两种类型:
 
Ø 无状态:这种类型的Bean不能为客户端程序维持任何会话状态。
Ø 有状态:这种类型的Bean维持状态,且对每一客户请求,都对应一个特例Bean。有状态Bean可看作是运行在服务器上的客户端程序扩展。
 
 
何时该用会话Bean
         会话Bean通常用于编写业务逻辑,维持客户端的会话状态,且对那些执行一个或多个业务操作的后台处理或用户任务进行建模。一般可用在以下方面:
 
Ø 人力资源程序中的会话Bean,其创建一个新的雇员,并分配其到某一部门。
Ø 费用报表程序中的会话Bean,其创建一张新的费用报表。
Ø 订单录入程序中的会话Bean,其为特定顾客创建一个新的订单。
Ø 在电子商务程序中的会话Bean,其管理着“购物车”中的具体商品。
Ø 在EJB 3容器中的会话Bean,其用于交易服务负载平衡(无须开发者编写交易支持系统)。
Ø 当客户端程序没有在同一服务器上时,会话Bean用于提出部署需求。
Ø 会话Bean还可用于在组件或方法层上,平衡由容器提供的安全支持。
 
 
会话Bean既能用于传统两层或三层架构的“胖”客户端程序,也能用于三层基于Web的应用程序,这些应用程序能被部署在不同的逻辑或物理层,或它们的组合中,下面,将讨论某些最有可能的组合。
 
 
         “胖”客户端三层架构
         图1展示了一种三层会话Bean的典型架构,其“胖”客户端前台程序,带有一些数据输入窗口,主要用于公司客户服务代表、银行出纳员等等;而这些客户端程序则可使用Java Swing技术,以Java Platform、Java SE,或运行于命令行的纯旧式Java对象(POJO)来开发。通常,最终用户都是从桌面运行客户端程序,输入某些数据,并按下某个界面元素,如点击“提交”按钮来引发一个事件。下面是一般性的工作流程:
 
1、 用户的某个动作将建立到会话Bean的一个连接,而这个Bean使用远程方法调用(RMI)运行于EJB容器中。
2、 客户端程序调用会话Bean中一个或多个业务方法。
3、 会话Bean处理请求并验证数据——通过与数据库、企业应用程序、老式系统上的某些程序等进行交互,以执行某一特定业务操作或任务。
4、 会话Bean最终发回一个响应到客户端程序,这可通过数据聚集或包含确认信息的简单对象来实现。
 
 
图1:三层架构中的会话Bean
 
 
         Web 应用程序三层架构
         这种架构,如图2所示,是一种典型的前台应用,通常为运行于台式机或笔记本电脑浏览器中的Web应用程序。目前,各种类型的客户端设备,如PDA、手机、使用Telnet协议的设备,都开始用于运行这类程序。运行于浏览器或移动设备中的Web程序,提供了用户界面(数据输入窗口、提交按钮等等),使用了JSP、JSF、或Java Servlets等Web技术,而典型的用户动作,如输入搜索条件、在“购物车”中添加特定商品,都会通过这些Web技术,调用运行于某个EJB容器中的会话Bean。一旦会话Bean被调用,它会处理请求,并发回响应到Web程序中,而Web程序进行相应处理后,再发回到客户设备上(浏览器、PDA、Telnet)。
 
 
图2:三层架构Web应用程序中的会话Bean
 
 
         在前面所讨论的三层架构中,客户端程序(Web程序)及会话Bean既能运行于某个应用程序服务器的同一实例中,也能运行于同一电脑上的不同实例中,还可以运行于不同电脑上应用程序服务器的不同实例中。
 
 
         无状态会话Bean
         无状态会话Bean由以下方面组成:
 
Ø 一个Bean类,其包含了相关业务方法的实现。
Ø 业务接口,其包含了相关业务方法的声明,以便对客户应用程序可见。
 
 
Bean
         一个无状态会话Bean类是一种标准的Java类,并在类级别的声明上带有@Stateless注释符;如果部署描述符取代了注释符,那么Bean类应指明作为一个无状态会话Bean。如果同时使用了注释符及部署描述符两者(混合模式中),且在Bean类中写明了类级别或成员级别的注释符,那么也必须要写明@Stateless注释符;在同时使用了两者情况下的部署过程中,描述符中的设置或值将会覆盖类中的注释符。
         为演示无状态会话Bean的用法,我们创建了一个SearchFacade会话Bean,其为客户端程序提供了多种搜索功能,工作流程如下:
 
 
1、 程序使用者会输入或选择一个或多个搜索条件,这在之后会提交给SearchFacade会话Bean。
2、 SearchFacade会话Bean会访问后台数据库,以返回所需信息。在此为简化代码,只返回了Bean类中硬编码的值。
3、 会话Bean把满足搜索条件的信息返回给客户端应用程序。
 
 
例1为SearchFacade Bean的定义,SearchFacade Bean为一个在类级别上带有@Stateless注释符的标准Java类。
 
 
例1:SearchFacadeBean.java
 
package com.apress.ejb3.chapter02;
 
import javax.ejb.Stateless;
 
@Stateless(name="SearchFacade")
 
public class SearchFacadeBean implements SearchFacade,
   SearchFacadeLocal {
   public SearchFacadeBean() {
   }
}
 
 
         业务接口
         一个无状态会话业务接口是一种标准的Java接口,其并未对任何特定EJB接口进行扩展。这种接口含有一张业务方法定义的列表,可由客户端程序访问。每个会话Bean都必须有一个能通过Bean类实现的业务接口,其既可在设计时由相应工具生成,如Oracle JDeveloper、NetBeans、Eclipse,也可在部署时由EJB容器生成。
 
         业务接口也能使用注释符,如下:
 
Ø @Remote注释符用于指明远程业务接口。
Ø @Local注释符用于指明本地业务接口。(如果在接口中未标明注释符,默认为local本地接口)
 
 
如果架构中存在一种需求,客户端程序(Web程序或“胖”客户端)必须运行在一个与往日运行EJB容器中会话Bean不同的Java虚拟机上,那么就需要使用远程接口了;这个Java虚拟机可在同一台电脑上,也可在不同的电脑上,但如果应用程序架构倾向于对客户端程序及会话Bean使用同一Java虚拟机,使用本地接口仍是最好的选择。
但有时候,应用程序架构会同时需要远程及本地接口,例如,一个企业可能有一个订单录入程序,其使用会话Bean开发,而这些会话Bean有用于提交新订单的业务方法,同时还有一些日常行政方面的任务,如输入产品数据,潜在地,就可能有两个不同的客户端程序访问后台订单录入程序,如下:
 
Ø 一个Web客户端程序(如图3)能与会话Bean运行在同一Java虚拟机中,并用于提交新订单。
Ø 一个“胖”客户端程序(如图4)运行在终端用户的桌面电脑上,用于日常数据输入。
 
 
图3:一个使用会话Bean本地接口的Web客户端程序
 
 
图4:一个使用会话Bean远程接口的“胖”客户端程序
 
 
         而SearchFacade会话Bean有远程及本地两种接口,如图5:
 
 
图5:SearchFacade会话Bean的业务接口
 
 
         例2是SearchFacade远程业务接口的代码段,有一个@Remote注释符与一个wineSearch()方法声明,wineSearch()方法接受一个代表酒类型的参数,并返回所有条件匹配的酒名列表。
 
 
例2:SearchFacade.java
 
package com.apress.ejb3.chapter02;
 
import java.util.List;
 
import javax.ejb.Remote;
 
@Remote
public interface SearchFacade {
   List wineSearch(String wineType);
}
 
 
         例3是SearchFacade本地业务接口的代码段,有一个@Local注释符与一个wineSearch()方法声明。
 
 
例3:SearchFacadeLocal.java
 
package com.apress.ejb3.chapter02;
 
import java.util.List;
 
import javax.ejb.Local;
 
@Local
public interface SearchFacadeLocal {
   List wineSearch(String wineType);
}
 
 
         业务方法
         在会话Bean内实现的方法必须对应于声明在远程或本地接口中的业务方法,它们具有相同的名字及方法签名,基于约定进行匹配。而Bean类中的其他方法,如果无法在业务接口中找到对应的声明,那将只作为Bean类的私有(Private)方法。
         Bean SearchFacade实现了一个方法——wineSearch(),其同时声明在远程及本地业务接口中,wineSearch()方法根据酒的类型,返回一个静态酒名列表,例4是wineSearch()的实现部分。
 
 
例4:SearchFacadeBean.java
 
package com.apress.ejb3.chapter02;
 
import java.util.ArrayList;
import java.util.List;
 
import javax.ejb.Stateless;
 
@Stateless(name="SearchFacade")
 
public class SearchFacadeBean implements SearchFacade,
   SearchFacadeLocal {
   public SearchFacadeBean() {
   }
 
   public List wineSearch(String wineType) {
      List wineList = new ArrayList();
      if (wineType.equals("Red"))    //此处搜索红葡萄酒
      {
         wineList.add("Bordeaux——波尔多葡萄酒");
         wineList.add("Merlot——墨尔乐红葡萄酒");
         wineList.add("Pinot Noir——黑比诺葡萄酒");
      }
 
      else if (wineType.equals("White"))    //此处搜索白葡萄酒
      {
         wineList.add("Chardonnay——夏敦埃酒");
      }
 
      return wineList;
   }
}
 
 
         依赖性注入
         以下将主要讨论无状态会话Bean中的依赖性注入——即资源捆绑。
         EJB 3容器提供了一种便利,可以把多种类型的资源注入到无状态会话Bean中。举例来说,为了执行用户任务或处理发自客户端程序的请求,会话Bean中的业务方法需要一种或多种类型的“资源”,这些资源可以是其他会话Bean、数据源、或消息队列。
         通过使用注释符或部署描述符,无状态会话Bean所用的资源就可被注入了,之后可由实例变量或set方法的注释来获取。例5中的代码使用了set方法及实例变量,myDb代表数据源。
 
 
例5:数据源注入
 
@Resource
DataSource myDb;
 
or
 
@Resource
public void setMyDb(DataSource myDb) {
   this.myDb = myDb;
}
 
 
         而set方法一般用作预先设定或初始化注入资源的属性。
 
 
         回调方法
         在应用程序使用会话Bean时,对某些特定实例及用例,可能需要精确控制如对象创建、对象销毁等等事件,比如说,SearchFacade会话Bean在创建时,可能需要执行一些数据库初始化操作;而在销毁时,需要关闭一些数据库连接,在此,通过回调方法,程序就能精确控制Bean生命周期的每个阶段。回调方法可以是会话Bean中的带有callback注释的任意方法,而EJB容器将在Bean生命周期的适当阶段对其进行调用。
 
         以下是无状态会话Bean的两种回调方法:
 
Ø PostConstruct:以@PostContruct注释符表示。在Bean类中的任何方法都能标以此注释符。
Ø PreDestroy:以@PreDestroy注释符表示。与上面一样,Bean类中的任何方法都能标以此注释符。
 
 
PostContruct回调发生在一个Bean实例在EJB容器中实例化之后,如果Bean使用了依赖性注入以获取引用资源或环境中的其他对象,PostContruct将会发生在注入完成之后及Bean类中的第一个业务方法被调用之后。
在SearchFacade会话Bean这个例子中,我们可以有一个业务方法wineSearchByCountry(),其返回特定国家的酒名列表,且有一个PostConstruct回调方法,initializeCountryWineList()将会在Bean实例化时初始化国家的酒名列表。在实际应用中,一般会从后台数据库加载这个列表,但在本文中,仅以硬编码的方式将它们压入一个HashMap中,如例6所示:
 
 
例6:PostConstruct方法
 
@PostConstruct
 
public void initializeCountryWineList()
{
   //countryMap为HashMap
   countryMap.put("Australia", "Sauvignon Blanc");//澳大利亚——勃朗克葡萄酒
   countryMap.put("Australia", "Grenache");
   countryMap.put("France","Gewurztraminer");
   countryMap.put("France","Bordeaux");//法国——波尔多葡萄酒
}
 
 
         而PreDestroy回调则发生在容器销毁对象池中一个不再使用或到期的Bean实例之前,这个回调方法能用于关闭为依赖性注入创建的任意连接池,且也能用于释放其他资源。
       在SearchFacade会话Bean这个例子中,可以在SearchFacade Bean中添加一个PreDestroyt回调方法(如destroyWineList()),用于在Bean销毁时清除酒名列表。在实际应用时,可在PreDestroy中关闭为依赖性注入创建的任意资源,但在本文中,只是清除有国家名及酒名列表的HashMap。例7是destroyWineList()的代码:
 
 
例7:PreDestroy方法
 
@PreDestroy
public void destroyWineList()
   {
   countryMap.clear();
   }
 
 
         定义在Bean类上的回调方法必须有修饰符public void <METHOD>();回调方法也能定义在Bean监听(listener)类上,这些方法必须有public void <METHOD>(Object)修饰符,而Object可以声明为实际Bean类型,其通常为运行时传递给回调方法的参数。
 
 
         拦截器方法(Interceptors
         在EJB 3规范中提供一种名为拦截器的注释符,其可拦截一个业务方法的调用,会话Bean及消息驱动Bean(MDBs)都可被定义一个拦截器方法。
         在一个典型的应用程序中,多处都可用到拦截器方法,我们可能需要在业务方法被调用之前或之后执行一个特定的任务,举例来说,可能会做以下这些事情:
 
Ø 在关键业务方法转账超过十万元时执行额外的安全检查。
Ø 做一些性能分析,以计算执行特定任务的时间。
Ø 在方法调用之前或之后作一些额外的日志记录。
 
 
即可为特定方法添加一个@AroundInvoke注释符,也可定义一个拦截类——其方法在Bean类中业务方法调用之前被调用;而一个拦截类通常在与之相联的Bean类上标以@Interceptor,如果有多个拦截类,就要使用@Interceptors了。被标以@AroundInvoke注释符的方法应有以下修饰符:public Object <METHOD>(InvocationContext) throws Exception,下面是InvocationContext的定义:
 
 
package javax.ejb;
 
   public interface InvocationContext {
   public Object getBean();
   public java.lang.reflect.Method getMethod();
   public Object[] getParameters();
   public void setParameters(Object[] params);
   public EJBContext getEJBContext();
   public java.util.Map getContextData();
   public Object proceed() throws Exception;
}
 
 
         以下是代码中方法的详细说明:
 
Ø getBean()返回方法调用时这个Bean的实例。
Ø getMethod()返回被调用的Bean实例中的方法。
Ø getParameters()返回方法调用的参数。
Ø setParameters()修改用于方法调用的参数。
Ø getEJBContext()使拦截方法可以访问Bean的EJBContext。
Ø getContextData()允许数值在同一InvocationContext实例中的拦截器方法间传递。
Ø proceed()调用下一个拦截器方法,如果只有一个拦截器方法,将调用目标Bean方法。
 
 
在SearchFacade会话Bean中,可添加一个拦截器方法,用于记录由客户端程序调用的每个业务方法执行的时间。例8为一个时间记录方法,将输出业务方法执行所需的时间;而InvocationContext用于获取Bean类名及调用方法名。在调用业务方法之前,将记录当前系统时间,以便与业务方法执行之后的系统时间相减。最后,使用System.out.println在控制台窗口中输出详细信息。
 
 
例8:拦截器方法
 
@AroundInvoke
 
public Object TimerLog (InvocationContext ctx) throws Exception
   {
   String beanClassName = ctx.getClass().getName();
   String businessMethodName = ctx.getMethod().getName();
   String target = beanClassName + "." + businessMethodName ;
   long startTime = System.currentTimeMillis();
   System.out.println ("正在调用:" + target);
      try {
         return ctx.proceed();
         }
      finally {
         System.out.println("正在退出:" + target);
         long totalTime = System.currentTimeMillis() - startTime;
         System.out.println("Business method " + businessMethodName +
         "in " + beanClassName + "takes " + totalTime + "ms to execute");
         System.out.println(“Bean类:”+beanClassName +”中的业务方法:”+ businessMethodName +”执行用了”+ totalTime + "毫秒");
 
      }
 
}
 
 
         有状态会话Bean
         与无状态会话Bean类似,有状态会话Bean也包含Bean类及业务接口。
 
 
         Bean
         一个有状态会话Bean类是带有@Stateful注释符的标准Java类,如果使用了部署描述符而不是注释符,那么Bean类应被标为一个有状态会话Bean。在混合模式的情况中,同时有注释符及部署描述符,且在类中使用了类级别或成员级别的注释符,那么也必须指定@Stateful注释符。
         为演示有状态会话Bean,我们将创建一个ShoppingCart会话Bean,其保持追踪添加到用户“购物车”中的商品及各自的数量,在本文中,将使用硬编码演示客户端与有状态会话Bean之间的状态及会话维护。例9中为一个ShoppingCart会话Bean的定义。
 
 
例9:ShoppingCartBean.java
 
package com.apress.ejb3.chapter02;
 
import javax.ejb.Stateful;
 
@Stateful(name="ShoppingCart")
 
public class ShoppingCartBean implements ShoppingCart,
   ShoppingCartLocal {
   public ShoppingCartBean() {
   }
}
 
 
         在这个例子中,交易发生之前及之后,应用程序想要通过EJB容器获取相应通知,并使用这些通知来管理相关数据。有状态会话Bean在实现了javax.ejb.SessionSynchronization接口后,可通过EJB容器接收这种通知,但这是一个可选项。有状态会话Bean可从EJB容器中接收以下三种类型的交易通知:
 
Ø afterBegin:表明一个新的交易开始。
Ø beforeCompletion:表明交易即将提交。
Ø afterCompletion:表明交易已经完成。
 
 
例如,ShoppingCart会话Bean将实现javax.ejb.SessionSynchronization接口以得到一个afterCompletion通知,所以,它可以清空“购物车”缓存。
 
 
         业务接口
         有状态会话Bean的业务接口与无状态会话Bean中的类似,且使用相同的注释符:@Local和 @Remote。ShoppingCart会话Bean有远程及本地两种接口,如图6所示:
 
 
图6:ShoppingCart中的业务接口
 
 
         在我们的Web程序中,将主要用到本地接口,而远程接口在此主要是便于Bean的单元测试。
 
         例10与例11 为ShoppingCart的远程及本地业务接口,它们各自带有@Remote与@Local注释符。
 
 
例10:ShoppingCart.java
 
package com.apress.ejb3.chapter02;
 
import javax.ejb.Remote;
 
@Remote
public interface ShoppingCart {
}
 
 
例11:ShoppingCartLocal.java
 
package com.apress.ejb3.chapter02;
 
import javax.ejb.Local;
 
@Local
public interface ShoppingCartLocal {
}
 
 
         在此,大家也可按例12中的编码风格,在通过业务接口名指定@Stateful或@Stateless之前,先指定@Local与@Remote注释符。
 
 
例12:ShoppingCartBean.java
 
package com.apress.ejb3.chapter02;
 
import javax.ejb.Local;
import javax.ejb.Remote;
import javax.ejb.Stateful;
 
@Local({ShoppingCartLocal.class})
@Remote({ShoppingCart.class})
@Stateful(name="ShoppingCart")
 
public class ShoppingCartBean implements ShoppingCart,
   ShoppingCartLocal {
   public ShoppingCartBean() {
   }
}
 
 
         业务方法
         有状态会话Bean中的业务方法也与无状态会话Bean中的类似;在我们的ShoppingCart例子中,下面这些业务方法会从“购物车”中添加或去除酒类,并返回一个“购物车”中的物品清单。
         例13是实现了addWineItem()、removeWineItem()、getCartItems()的ShoppingCart。
 
 
例13:ShoppingCartBean.java
 
package com.apress.ejb3.chapter02;
import java.util.ArrayList;
 
import javax.ejb.Stateful;
 
@Stateful(name="ShoppingCart")
 
public class ShoppingCartBean implements ShoppingCart,
   ShoppingCartLocal {
   public ShoppingCartBean() {
   }
   public ArrayList cartItems;
 
   public void addWineItem(String wine) {
      cartItems.add(wine);
   }
 
   public void removeWineItem(String wine) {
      cartItems.remove(wine);
   }
 
   public void setCartItems(ArrayList cartItems) {
      this.cartItems = cartItems;
   }
 
   public ArrayList getCartItems() {
      return cartItems;
   }
}
 
 
         回调方法
         有状态会话Bean支持构造、析构、激活、钝化的回调事件,以下是各自对应的项目:
 
Ø PostConstruct:以@PostConstruct注释符表示。Bean类中的任意方法都可标以此注释符。
Ø PreDestroy:以@PreDestroy注释符表示。
Ø ProActivate:以@PreActivate注释符表示。
Ø PrePassivate:以@PrePassivate注释符表示。
 
 
PostContruct回调函数发生在一个Bean实例在EJB容器中初始化之后,如果Bean在此环境中使用了依赖性注入机制来获取资源或其他对象的引用,则PostConstruct事件发生在注入执行之后,及Bean类中第一个业务方法调用之前。
         在ShoppingCart会话Bean的例子中,可有一个名为initialize()的业务方法来初始化商品清单,如例14:
 
 
例14:PostConstruct方法
 
@PostConstruct
public void initialize()
   {
   cartItems = new ArrayList();
   }
 
 
         PreDestroy回调函数发生在任意带有@Remove注释符的方法执行完毕之后。在ShoppingCart会话Bean例子中,可有一个名为exit()的业务方法把商品清单写入数据库。在本文中,只是在命令行窗口中输出一条信息表示回调,例15为exit()方法的代码,其带有@PreDestroy。
 
 
例15:PreDestroy方法
 
@PreDestroy
public void exit()
   {
   //此处模拟将商品清单写入数据库
   System.out.println("已将清单信息保存入数据库。");
   }
 
 
         对有状态会话Bean来说,@Remove注释符是一个非常有用的生命周期方法,当带有@Remove注释符的方法被调用时,容器会在方法执行之后,从对象池中移除Bean实例。例16是stopSession()代码,其标以@Remove。
 
 
例16:Remove方法
 
@Remove
public void stopSession()
   {
   //在此,方法体可为空白。
   System.out.println("此消息出自带有@Remove注释的stopSession方法。");
   }
 
 
         PrePassivate回调函数发生在一个有状态会话Bean实例空闲太久时,在这个事件中,容器可能“钝化”并把它的状态存储在缓存中。标以@PrePassivate的方法会在容器钝化Bean实例之前被调用。
         而PostActivate事件会在客户程序再次使用一个钝化的有状态会话Bean时发生,此时会创建一个带有之前存储状态的新实例。标以@PostActivate的方法在当Bean实例准备好时被调用。
 
 
         拦截器方法
         无状态与有状态会话Bean的拦截器方法,也存在稍许差异,比如说,可在有状态会话Bean中使用AroundInvoke方法,对有状态会话Bean来说,其实现了SessionSynchronization(会话同步),而afterBegin则执行在任意AroundInvoke方法调用之前,且也在beforeCompletion()回调函数调用之前。
 
 
         异常处理
         EJB 3规范中概述了两种类型的异常:
 
Ø 应用程序异常
Ø 系统异常
 
 
应用程序异常是与业务逻辑执行相关的、客户端应该处理的异常。举例来说吧,如果客户端程序传递了一个无效的参数,如错误的信用卡号,就可以抛出一个应用程序异常。
另一方面,系统异常则是由系统级的错误产生,如JNDI(Java Naming and Directory Interface)错误、或无法获取数据库连接。并且,系统异常必须为java.rmi.RemoteException的一个子类,或非应用程序异常的java.lang.RuntimeException的一个子类,
从EJB程序的角度来看,我们可编写继承自java.lang.Exception的相应程序异常类,来处理应用程序异常。
而对于系统异常,则应用程序必须捕捉到特定的异常——如由JNDI失败产生的NamingException,再抛出一个EJBException。
 
 
         客户端视图(Client View
         一个会话Bean可看作是客户端应用程序的一个逻辑扩展,它为程序处理了大量的逻辑与数据。一般来说,一个客户端应用程序可通过会话Bean的客户端视图接口,来访问会话对象,而客户端视图接口就是我们前面讨论过的业务接口。
 
         客户端程序可通过以下三种方式访问会话Bean:
 
Ø Remote:远程客户程序运行在不同于要访问的会话Bean的另外一个JVM中,如图4所示,其通过Bean的远程业务接口来访问一个会话Bean。一个远程客户程序可以是其他另外的EJB、Java客户端程序、或Java Servlet。同时,远程客户程序具有位置独立性,意味着当它们运行于同一JVM中时,能使用相同的API。
Ø Local:本地客户程序运行于同一JVM中,如图3所示,其通过本地业务接口访问会话Bean。一个本地客户程序可以是其他另外的EJB、使用Java Servlets的Web应用程序、JavaServer Pages(JSP)、JavaServer Faces(JSF)。但本地客户程序具有位置依赖性,表1是远程及本地客户程序的对比。
Ø Web Service:可将无状态会话Bean发布为Web Service,其可被Web Service客户端调用。
 
 
Remote(远程)
Local(本地)
Bean与客户端之间松耦合
对组件的轻量级访问
位置独立性
位置依赖性
开销巨大的远程调用
必须与Bean在同一JVM中
对象必须被串行化
无要求
对象通过传值传递
对象通过引用传递
表1:
 
 
         在某些情况中,会话Bean同时需要本地及远程业务接口来支持多种不同类型的客户端程序。而客户端程序可通过依赖性注入或JDNI查询来获取一个会话Bean的业务接口,在调用会话Bean中的方法之前,客户端程序需要通过JNDI来得到Bean的一个桩对象。一旦客户端程序得到桩对象的句柄,它就能调用会话Bean中的所有业务方法。在无状态会话Bean的情况中,每一次调用都可获得一个新的“桩”;而在有状态会话Bean中,“桩”需要在客户端捕捉,这样,容器就知道在随后的调用中,该返回哪一个Bean的实例。如果使用了依赖性注入,则下面这行代码就能得到SearchFacade会话Bean的业务接口:
 
@EJB SearchFacade searchFacade;
 
 
         如果客户端程序以远程的方式来访问会话Bean,在通过相关属性获取了上下文接口之后,就可以使用JDNI查询了;本地客户端程序也能使用JNDI查询,但需要编写一点简单的依赖性注入代码。例17是SearchFacadeTest客户端程序的JNDI代码,其查询SearchFacade Bean,调用了wineSearch()业务方法,并输出结果。
       要注意的是,如果远程客户端是一个Java应用程序或命令行程序,则可以使用一个应用程序客户端容器来调用会话Bean,它也支持远程客户端的依赖性注入。
 
 
例17:SearchFacadeClient.java
 
package com.apress.ejb3.chapter02.client;
 
import com.apress.ejb3.chapter02.SearchFacade;
import java.util.List;
import javax.naming.InitialContext;
import javax.naming.NamingException;
 
public class SearchFacadeTest {
   public SearchFacadeTest() {
   }
 
   public static void main(String[] args) {
      SearchFacadeTest searchFacadeTest = new SearchFacadeTest();
      searchFacadeTest.doTest();
   }
 
   @EJB
   static SearchFacade searchFacade;
 
   void doTest(){
      InitialContext ic;
 
      try {
         ic = new InitialContext();
         System.out.println("SearchFacade查找");
         System.out.println("搜索酒类");
         List winesList = searchFacade.wineSearch("Red");
         System.out.println("正在输出酒类清单");
         for (String wine:(List<String>)winesList ){
            System.out.println(wine);
         }
      } catch (NamingException e) {
         e.printStackTrace();
      }
   }
}
 
 
         例18是ShoppingCartTest客户端程序,其查询有状态ShoppingCart会话Bean,调用addWineItem()业务方法来向“购物车”中添加一瓶酒,调用getCartItems()业务方法获取购物清单,最终输出购物车中的酒类清单。
 
 
例18:ShoppingCartTest.java
 
package com.apress.ejb3.chapter02.client;
 
import com.apress.ejb3.chapter02.ShoppingCart;
 
import java.util.ArrayList;
 
import java.util.List;
 
import javax.naming.InitialContext;
import javax.naming.NamingException;
 
public class ShoppingCartTest {
   public ShoppingCartTest() {
   }
 
   public static void main(String[] args) {
      ShoppingCartTest shoppingCartTest = new ShoppingCartTest();
      shoppingCartTest.doTest();
   }
 
@EJB
static ShoppingCart shoppingCart;
 
   void doTest(){
      InitialContext ic;
 
      try {
         ic = new InitialContext();
         System.out.println("查找购物车");
         System.out.println("正在添加酒类");
         shoppingCart.addWineItem("Zinfandel");
         System.out.println("正在输出购物车中商品");
         ArrayList cartItems = shoppingCart.getCartItems();
         for (String wine:(List<String>)cartItems ){
            System.out.println(wine);
         }
      } catch (NamingException e) {
         e.printStackTrace();
      }
   }
}
 
 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
是的,您说得对。在Java EE服务器中,EJB需要进行钝化(Passivation)和激活(Activation)的操作,以避免占用过多内存资源。而在Spring框架中,Bean对象不需要进行钝化和激活的操作。 具体来说,EJB容器会在EJB组件处于空闲状态时,将其序列化并保存到磁盘中,以释放内存资源。当客户端再次请求访问EJB组件时,EJB容器会自动将其反序列化并恢复状态,然后调用@PostActivate注解指定的激活方法。这个过程称为钝化和激活。 而在Spring框架中,Bean对象的生命周期是由Spring容器来管理的。Spring容器会在启动时创建和初始化所有的Bean对象,然后将它们存储在内存中,供客户端请求使用。当客户端不再使用某个Bean对象时,Spring容器会将其标记为垃圾对象,并等待Java虚拟机的垃圾回收器将其回收。 由于Spring Bean没有状态,因此不需要进行钝化和激活的操作。即使Spring Bean被标记为垃圾对象,也不会影响应用程序的正常运行。Spring容器会自动管理Bean对象的生命周期和运行环境,以确保应用程序的正确运行。 需要注意的是,如果Spring Bean中包含了一些需要释放的资源,如数据库连接、文件句柄等,那么需要在Bean销毁时手动释放这些资源,以避免资源泄露和内存泄露的问题。可以使用@PreDestroy注解指定Bean销毁时需要执行的清理方法,或者实现DisposableBean接口中的destroy()方法来完成资源释放的操作。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值