EJB3.0笔记-11Session beans (转载)
转载from: http://blog.sina.com.cn/s/blog_5016113a0100at06.html 风在哪里吹 sina blog.
Session bean包含业务逻辑而entity class则模塑持久数据,Session bean还可以表示任务流(taskflow),所谓任务流是指,为完成一项特定任务所需的所有步骤。Session bean与entity class的关系就像剧本和演员的关系,剧本指导演员工作。
Session bean可以分为两种基本类型:stateless和stateful。stateless不维护任何介于两次方法调用间的状态。stateful session bean是客户端应用程序在服务器端的延续。调用stateful session bean的方法,可以从会话状态中读取数据,也可以向其中写入数据,这些数据在所有方法调用之间都是共享的。stateful session bean也许有一个超时期,如果客户端在超时前没有对其进行任何调用,该实例就会被容器自动销毁,除了超时,客户端也可以通过bean的remove方法明确将其移除。stateless session bean也可能存在一个超时值,同样也可以被客户端强制移除,但是效果和stateful session bean不一样,它只是简单地让该客户端指向EJB Object的引用失效,而bean实例并不被销毁,它可以继续为其他客户端请求服务。
Stateless Session Bean
stateless session bean具有很好的性能,它不保存任何会话状态,因此也就不需要钝化或激活,这进一步降低了切换的开销。可以使用实例变量(没有static修饰)来维护实例的内部状态,但是不能保证该实例一直为同一个客户端服务,也就无法为客户维护状态。是否可以使用类变量(有static修饰)来维护bean的内部状态?如果stateless 实现了实例池,stateless ejb的一个变量被一个用户改变了,其它用户使用到这个stateless ejb时,会使用到已经变化的变量(ejb其他书籍)
以一个实例来说明session bean
ProcessPayment EJB
是一个stateless bean,TravelAgent EJB通过它向乘客收取航程的费用。
业务接口:ProcessPayment
public interface ProcessPayment{
public boolean byCheck(Customer customer,CheckDO check,double amount)throws PaymentException;//通过支票支付
public boolean byCredit(Customer customer,CreditCardDO card,double amount)throws PaymentException;//通过信用卡支付
……
}
一个stateless session bean可以拥有一个或多个业务接口(也就是方法),业务接口可以是远程接口,也可以是本地接口,但是不能二者兼备。如果远程接口和本地接口拥有相同的方法,EJB规范允许为它们定义公共的基类。可以使用@javax.ejb.Remote和@javax.ejb.Local声明远程接口和本地接口。
本地和远程接口
@Local
public interface ProcessPaymentLocal extends ProcessPayment{…}
@Remote
public interface ProcessPaymentRemote extends ProcessPayment{…}
将entity bean作为参数
在ProcessPayment EJB的业务接口中,每一个方法接受一个Customer entity bean(顾客)作为参数,它实现了java.io.Serializable或Externalizable,以便序列化并通过网络进行传输。
领域对象:CreditCardDO和CheckDO
是简单的可序列化Java对象。CreditCardDO将信用卡相关的数据搜集在一起,以便使它更易于在网格传输。
应用程序异常PaymentException
由其他java子系统抛出的异常与EJB所要刻画的业务流程毫无关系,所以要把它们包装成系统特定业务异常。EJB容器将任何不从RuntimeException继承的异常都当作应用程序异常,由于应用程序异常会被传播到作为调用方的客户端,因而此类异常中的任何实例变量都必须能够被序列化。
Bean class:ProcessPaymentBean
ProcessPaymentEJB模塑了一个专门的业务流程。
@Stateless
public class ProcessPaymentBean implements ProcessPaymentRemote,ProcessPaymentLocal{….}
还可以使用@Local和@Remote注解对bean直接进行标注:
@Stateless
@Local(ProcessPaymentLocal.class)
@Remote(ProcessPaymentRemote.class)
public class ProcessPaymentBean {….}
但这种方式类的结构不清晰,不推荐使用。
访问环境型成员属性(注入)
每个EJB容器都有一个自己的内部注册表(registry),用于保存配置数据和指向外部资源或服务的引用。该注册表被称为Enterprise Naming Context(ENC).在部署EJB容器时,系统会将内嵌于注解(如@Resource)中的元数据,以及保存于EJB XML部署描述文件中的信息填入ENC中。ProcessPaymentBean中有两个要注入的变量:
@Resource(mappedName=”titanDB”)DataSource dataSource;//数据源
@Resource(name=”min”) int minCheckNumber;//常量
XML部署描述文件
EJB可以选择在JAR文件的META-INF/ejb-jar.xml中定义XML部署描述信息
<?xml version="1.0"?>
<ejb-jar………..>
<enterprise-beans>
<session> 是一个session bean
<ejb-name>ProcessPaymentBean</ejb-name> ejb名
<remote>com.titan.processpayment.ProcessPaymentRemote</remote> 实现的业务接口
<local>com.titan.processpayment.ProcessPaymentLocal</local>
<ejb-class>com.titan.processpayment.ProcessPaymentBean</ejb-class> 类名
<session-type>Stateless</session-type> 什么类型的session bean
<env-entry> 注入常量值
<env-entry-name>min</env-entry-name>
<env-entry-type>java.lang.Integer</env-entry-type>
<env-entry-value>10</env-entry-value>
<injection-target>
<injection-target-class>
com.titan.processpayment.ProcessPaymentBean
</injection-target-class>
<injection-target-name>minCheckNumber</injection-target-name>
</injection-target>
</env-entry>
<resource-ref> 注入数据源
<res-ref-name>theDatasource</res-ref-name>
<res-type>javax.sql.DataSource</res-type>
<res-auth>Container</res-auth>
<mapped-name>java:/DefaultDS</mapped-name>
<injection-target>
<injection-target-class>
com.titan.processpayment.ProcessPaymentBean
</injection-target-class>
<injection-target-name>dataSource</injection-target-name>
</injection-target>
</resource-ref>
</session>
</enterprise-beans>
</ejb-jar>
SessionContext
SessionContext对象被作为bean实例与EJB容器交互的接口。Session bean可以通过@Resource SessionContext ctx;注解获得该对象的引用。SessionContext.getBusinessObject(Class class)方法返回一个指向当前EJB的引用。
EJBContext
SessionContext继承自javax.ejb.EJBContext类。EJBContext.lookup()可以在EJB ENC中查找数据。EJBContext.getCallerPrincipal()用于获取java.security.Principal对象,该对象代表了当前访问bean的客户端。EJBContext.isCallerInRole(String role)可以辨别当前访问bean的客户端是否具有指定的角色。也是通过@Resource SessionContext ctx;注解获得这个对象。
Stateless session bean的生命周期
Stateless session bean的生命周期相当简单,只有两个状态:Does Not Exist和Method-Ready Pool。Method-Ready Pool是一个实例池,由未被使用的stateless session bean对象组成。stateless bean在其生命周期中定义了实例池,而stateful bean则没有。
Does Not Exist状态
表示stateless bean还没有被实例化
Method-Ready Pool状态
当EJB服务器首次启动时,它可能会创建一些stateless bean的实例(一些厂商可能没有对stateless实 例做池化处理),并使它们进入Method-Ready Pool状态。如果stateless实例的数量不足以为客户端请 求提供服务时,服务器就会创建更多的实例,并将它们添加到实例池中。
进入Method-Ready Pool状态
当实例从Does Not Exist状态迁移到Method-Ready Pool时,会执行三步操作。1,容器调用stateless的 class.newInstance()方法构造一个实例。2.容器根据注解或XML部署描述文件,把所需资源注入进来。 3,容器产生一个post-consruction事件,调用stateless中标注了@PostConstruct的回调函数。回调函 数必须返回void,不带参数,且不能抛出checked exception,如:
@PostConstruct
public void init(){…}
等价XML
<session>
<ejb-name>myBean</ejb-name>
<post-contruct>
<lifecycle-callback-method>init</lifecycle-callback-method>
</post-contruct>
</session>
处于Method-Ready Pool状态
一旦bean实例处于Method-Ready Pool状态,它已经为响应客户端请求做好了准备;一旦bean实例完成 对客户端请求服务,它就会解除与EJB Object的关联,并重新回到Method-Ready Pool状态
离开Method-Ready Pool状态
当EJB服务器不再需要bean实例时,它会移除内存对象来减少处于Method-Ready Pool状态的实例总数, stateless就变回Does Not Exist状态,这时会产生bean的PreDestroy事件。bean 可以为某个方法标注 @javax.annotation.PreDestroy注解,以注册响应该事件的回调方法,如:
@PreDestroy
public void cleanup(){……}
等价XML
<session>
<ejb-name>myBean</ejb-name>
<pre-destroy>
<lifecycle-callback-method>cleanup</lifecycle-callback-method>
</ pre-destroy>
</session>
Stateful Session Bean
每个stateful session bean在生命周期内只服务于一个客户端,它维护着会话状态,它可以在不同的方法调用间维护特定于某个客户端的数据。
建立TravelAgentEJB
TravelAgentEJB是一个stateful session bean,它封装了航程预订的业务逻辑。为了完成这项任务,bean需要知道是哪位乘客希望预订哪次航程的船舱。
远程接口:TravelAgent
在编写远程接口时,我们只关注bean的业务定义。这个接口在客户端和服务器端都使用,所以要定义得简单点
@Remote
public interface TravelAgentRemove{
public Customer findOrCreateCustomer(String first,String last);//查找或创建用户
public void updateAddress(Address addr);//更新用户地址
public void setCrruiseID(int cruise);//航程,注意这里使用ID,是为了方便客户端使用。
public void setCabinID(int cabin);//舱位,保持舱位信息
public TicketDO bookPassage(CreditCardDO card,double price);
//预订航程,客户、船舱和航程被stateful session bean保持,所以只需要信息卡和票价就可以交易
}
领域对象:TicketDO
它是Reservation entity bean的简化版,用于预订成功后把信息返回给客户端,由于返回Reservation要序列化大量对象,效率很低,所以使用TicketDO
public class TicketDDO implements java.io.Serializable{
public int customerID;
public int cruiseID;
public int cabinID;
public double price;
public String description;//打印时使用
….
}
Bean的客户端代码
即客户端怎么调用服务器端session bean.假设有一个带GUI输入域的java应用程序在使用TravelAgentEJB
Context jndi=getInitialContext();
Object ref=jndi.lookup(“TravelAgentBean/remote”);
TravelAgentRemote agent=(TravelAgentRemote)PortableRemoteObject.narrow(ref, TravelAgentRemote.class);
Customer cust=agent.findOrCreateCustomer(textField_firstName.getText(),textField_lastName.getText());//得到或创建乘客
Address updatedAddress=new Address(一系列用户输入的信息);
agent.updateAddress(updatedAddress);//更新乘客地址为最新的地址
agent.setCruiseID(用户输入的信息);
agent.setCabinID(用户输入的信息);
CreditCardDO card=new CreditCardDO(一系列用户输入的信息);
double price=double.valueOf(用户输入的信息);
TicketDO ticket=agent.bookPassage(card,price);// 行程预订
PrintingService.print(ticket);//打印出一张预订票
TravelAgentBean服务器端代码
这是一个服务器端EJB。在设计的角度上看,将任务流封装到了此stateful session bean中,同时为客户端提供一个简单的接口TravelAgentRemove,为实现策略变更时提供更好的灵活性
@Stateful //是一个stateful session bean
public class TravelAgentBean implements TravelAgentRemote{
@PersistenceContext(unitName=”titan”);
private EntityManager entityManager;//注入持久化管理者,用于数据库操作
@EJB private ProcessPaymentLocal processPayment;
//注入EJB,这个EJB用于以信用卡方式向乘客收费。
private Customer customer;//用于保存乘客的信息,以便在多个方法调用间重复使用
private Cruise cruise;//保存航程信息,以便在多个方法调用间重复使用
private Cabin cabin;
//查询用户,如果没有找到,就创建一个新的并保存到数据库
public Customer findOrCreateCustomer(String first,String last){….}
//让用户地址为最新的.
public void updateAddress(Address addr){
this.customer.setAddress(addr);
this.customer=entityManager.merge(customer);
}
public void setCruiseID(int cruise){
this.cabin=entityManger.find(Cabin.class,cabinID);//查找航程
}
public void setCabinID(int cabin){….}//查找船舱,实现方式同上
@Remove //执行这个方法后,容器可以把这个session bean移除
public TicketDO bookPassage(CreditCardDO card,double price){
try{
//预订
Reservation reservaton=new Reservation(customer,cruise,cabin,price,new Date());
entityManager.persist(reservation);//保存到数据库
//扣钱。customer能在各种方法间使用,是因为这个是stateful session
processPayment.byCredit(customer,card,price);
//返回购票信息到客户端,它只保存一些ID,序列化效率效高
TicketDO ticket=new TicketDO(customer,cruise,cabin,price);
return ticket;
}catch(Exception e){
throw new EJBException(e);
}
}
}
XML部署文件
<?xml version="1.0"?>
<ejb-jar………..>
<enterprise-beans>
<session> //是一个session bean
<ejb-name>TravelAgentBean</ejb-name> //ejb名
<remote>com.titan.travelagent.TravelAgentRemote</remote>//实现的业务接口
<ejb-class>com.titan.travelagent.TravelAgentBean</ejb-class>//类名
<session-type>Stateful</session-type>//什么类型的session bean
<ejb-local-ref> //注入本地EJB
<ejb-ref-name>ejb/PaymentProcessor</ejb-ref-name>
<ejb-ref-type>Session</ejb-ref-type> //类型
<local>com.titan.processpayment.ProcessPaymentLocal</local> //本地接口
<injection-target>
<injection-target-class>
com.titan.travelagent.TravelAgentBean
</injection-target-class>
<injection-target-name>processPayment</injection-target-name>
</injection-target>
</env-entry>
<persistence-context-ref>
<persistence-context-ref-name>persistence/titan</persistence-context-ref-name>
<persistence-unit-name>titan</persistence-unit-name>
<injection-target>
<injection-target-class>
com.titan.travelagent.TravelAgentBean
</injection-target-class>
<injection-target-name>entityManager</injection-target-name>
</injection-target>
</persistence-context-ref>
</session>
</enterprise-beans>
</ejb-jar>
Stateful Session Bean的生命周期
Stateful Session Bean和其他类型的bean最大的不同是它不使用实例池。它在整个生命周期中只服务于一个客户端。它的生命周期包含三种状态:Does Not Exist,Method-Ready和Passivated
Dost Not Exist状态
表示stateful bean实例还未被实例化。
Method-Ready状态
表示可以为来自客户端的请求提供服务。客户端第一次调用stateful session bean的方法时,bean的 生命周期就开始了,首先容器调用bean class的newInstance()方法新建一个实例,接着容器将所有依 赖都注入到bean实例中,如果bean class定义了@PostConstruct回调方法,容器将调用此方法。bean实 例离开Method-Ready状态之后,不是进入Passivated状态就是进入Does Not Exist状态。客户端应用程 序可以调用业务接口中标注了@Remove的方法,将bean移除。如果bean在Method-Ready状态下超时,容 器可能会调用@PreDestory回调方法。
Passivated状态
为了节省服务器资源,容器可能将bean实例钝化:将bean的会话状态保存起来,并将其从内存中移除。 如果客户端请求调用被钝化的bean,容器会将其重新激活,如果该bean定义了@PostActivate回调方 法,则该方法将被调用。当bean激活时,对象被反序列化,非持久化数据成员会被设置为对应类型的默 认值,所以应用使用@PostActivate回调方法来重置这些非持久型数据成员的值。
系统异常
只要bean方法抛出系统异常,容器就将相关的EJB object设置为无效,并销毁这个实例,此时bean直接变为Does Not Exist状态,容器并不会调用bean的@preDestory回调方法
Stateful session bean和Extend Persistence Context
通过@PresistenceContext注解注入TravelAgentBean中的EntityManager默认为一个transaction-scoped persistence context,也就是说TravelAgentBean中的每一个方法,在其作业域内都有一个事务开始和结束,这意味着任何要保存或获取的entity bean实例都将在方法调用结束时变成游离对象。可以使用@PresistenceContext(unitName=”titan”,type=EXTENDED)让注入的EntityManager是一个Extend Persistence Context,让查询所得的entity一直保持托管状态。Stateful session bean是唯一允许注入Extend Persistence Context的EJB组件。
嵌套的Stateful session bean
使用@EJB注入另一个Stateful session bean。我们不用管理内嵌的stateful session bean的生命周期,它随着容纳它的bean的创建而创建,当容纳它的Bean被移除时,它也被移除。
浙公网安备 33010602011771号