EJB实例教程笔记(二)
电子书EJB3实例教程byLiHuoming.pdf笔记
2.4 Stateful Session Bean(有状态Bean)开发
需要每个用户都有自己的一个实例,这个实例不受其他用户影响。每个有状态Bean在Bean实例的生命周期内只服务于一个用户,Stateful Session Bean保持了用户的信息。
对于stateful Session Bean,用户每调用一次lookup()都将创建一个新的Bean实例,如果希望使用某个Bean实例,必须在客户端缓存存根。
定义接口Cart.java如下:
package com.sillycat.ejb;
import java.io.Serializable;
import java.util.List;
public interface Cart extends Serializable{
public void AddBuyItem(String productName);
public List<String> getBuyItem();
}
定义Bean文件CartBean.java如下:
package com.sillycat.ejb.impl;
import java.util.ArrayList;
import java.util.List;
import javax.ejb.Remote;
import javax.ejb.Stateful;
import com.sillycat.ejb.Cart;
@Stateful
@Remote(Cart.class)
public class CartBean implements Cart{
private static final long serialVersionUID = 4371026853912745479L;
private List<String> buyitems = new ArrayList<String>();
public void AddBuyItem(String productName) {
buyitems.add(productName);
}
public List<String> getBuyItem() {
return buyitems;
}
}
单元测试CartTest.java
package com.sillycat.ejb;
import static org.junit.Assert.assertEquals;
import java.util.List;
import javax.naming.InitialContext;
import org.junit.BeforeClass;
import org.junit.Test;
public class CartTest {
protected static Cart cart;
@BeforeClass
public static void setUpBeforeClass() throws Exception {
InitialContext ctx = new InitialContext();
cart = (Cart) ctx.lookup("CartBean/remote");
}
@Test
public void testBuyItems() {
cart.AddBuyItem("无聊啊");
cart.AddBuyItem("其实我喜欢写程序");
List<String> items = cart.getBuyItem();
assertEquals(2,items.size());
}
}
原本书中的例子,是要求,如果要使用同一个实例,存根,也就是lookup得到的cart要存放在session中,呵呵,到时候我想其他办法吧,放一个cart进session不是我想看到的。
2.5 激活机制(Activation mechanism)
每个有状态Bean在其生命周期内只服务于一个用户,对有状态Bean使用实例池化机制是毫无意义的。
2.6 Stateful Session Bean的生命周期
stateful Session Bean的生命周期包含三种状态:does not exist,method-ready和passivated。method-ready状态和method-ready pool之间有显著的不同。
2.7 EJB调用机制
调用远程或本地接口的方法时,接口使用的是存根(stub)对象,该存根实现了session bean的远程或本地接口。它负责将调用经过网络发送到远程EJB容器,或将请求路由到位于本地JVM内的EJB容器。存根是在部署期间使用JDK的java.lang.reflect.Proxy动态生成的。
2.8 如何改变Session Bean的JNDI名称
JBOSS提供@LocalBinding和@RemoteBinding,示例如下OperationBean.java:
package com.sillycat.ejb.impl;
import javax.ejb.Local;
import javax.ejb.Remote;
import javax.ejb.Stateless;
import org.jboss.ejb3.annotation.LocalBinding;
import org.jboss.ejb3.annotation.RemoteBinding;
import com.sillycat.ejb.Operation;
import com.sillycat.ejb.OperationLocal;
@Stateless
@Remote(Operation.class)
@RemoteBinding(jndiBinding="easyejb/OperationRemote")
@Local(OperationLocal.class)
@LocalBinding(jndiBinding="easyejb/OperationLocal")
public class OperationBean implements Operation, OperationLocal {
private int total = 0;
public int Addup() {
total++;
return total;
}
}
如果使用weblogic/Sun Application Server/Glassfish等,用的是@Stateless.mappedName()。
由于JNDI名称与厂商有关,使用注释定义JNDI名称会有移植问题,所以建议使用ejb-jar.xml部署描述文件来定义。文件放置在jar的META-INF目录中。
2.9 Session Bean的生命周期事件
@PostConstruct
当Bean对象完成实例化后,标注这个注释的方法会被立即调用。(有状态和无状态)
@PreDestroy
容器销毁一个无用或者过期的bean实例之前调用。(有状态和无状态)
@PrePassivate
有状态的Bean实例空闲事件过长,就会发生钝化(passivate)。标注了这个注释的方法会在钝化之前被调用。Bean实例被钝化后,在一段时间内,如果没有用户对bean实例进行操作,容器会从硬盘中删除它。以后,针对该bean方法的调用,容器都会抛出例外。(有状态)
@PostActivate
当客户端再次使用已经钝化的有状态bean时,EJB容器会重新实例化一个Bean实例,并从硬盘中将之前的状态恢复,标注了这个注释的方法会在激活完成时被调用。(有状态)
@Init
它区别于@PostConstruct在于多个@Init注释方法可以同时存在于有状态session bean中,但每个bean实例只会有一个@Init注释的方法会被调用。@PostConstruct在@Init之后被调用。(有状态)
@Remote
当客户端调用了标注了@Remote注释的方法时,容器将在方法执行结束后,把bean实例删除。
注释的回调方法必须返回void,不带参数,且不能抛出任何checked exception。
示例接口文件LifeCycle.java:
package com.sillycat.ejb;
public interface LifeCycle {
public String say();
public void stopSession();
}
EJB类LifeCycleBean.java如下:
package com.sillycat.ejb.impl;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import javax.ejb.Init;
import javax.ejb.PostActivate;
import javax.ejb.PrePassivate;
import javax.ejb.Remote;
import javax.ejb.Remove;
import javax.ejb.Stateful;
import com.sillycat.ejb.LifeCycle;
@Stateful
@Remote(LifeCycle.class)
public class LifeCycleBean implements LifeCycle{
public String say(){
try{
Thread.sleep(1000*10);
}catch(InterruptedException e){
e.printStackTrace();
}
return "有状态会话Bean生命周期";
}
@Init
public void initialize(){
System.out.println("@Init事件");
}
@PostConstruct
public void construct(){
System.out.println("@PostConstruct事件");
}
@PreDestroy
public void exit(){
System.out.println("@PreDestroy事件");
}
@PrePassivate
public void serialize(){
System.out.println("@PrePassivate事件");
}
@PostActivate
public void activate(){
System.out.println("@PostActivate事件");
}
@Remove
public void stopSession(){
System.out.println("@Remove事件");
}
}
测试页面ejbTest2.jsp如下:
<%@ page contentType="text/html; charset=UTF-8"%>
<%@ page import="com.sillycat.ejb.*, javax.naming.*"%>
<%
try {
LifeCycle lifecycle = (LifeCycle) session.getAttribute("lifecycle");
if (lifecycle == null) {
InitialContext ctx = new InitialContext();
lifecycle = (LifeCycle) ctx.lookup("LifeCycleBean/remote");
session.setAttribute ("lifecycle", lifecycle);
}
out.println(lifecycle.say());
out.println("<BR>请注意观察Jboss控制台输出.等待10分钟,容器将会钝化此会话Bean,@PrePassivate注释的方法将会执行<BR>");
out.println("<font color=red>你可以调用stopSession方法把会话Bean实例删除。在删除会话Bean时,将触发@PreDestroy事件<BR></font>");
/*
lifecycle.stopSession();
*/
} catch (Exception e) {
out.println(e.getMessage());
}
%>
日志情况:
第一次点击进入:
13:47:35,933 INFO [STDOUT] @PostConstruct事件
13:47:35,935 INFO [STDOUT] @Init事件
13:57:02,775 INFO [STDOUT] @PrePassivate事件
刷新页面:
14:10:56,584 INFO [STDOUT] @PostActivate事件
这里比较奇怪的是@PostConstruct事件竟然在@Init前面了。呵呵。
2.10 拦截器(Interceptor)
拦截器可以拦截Session Bean和Message-driven bean的方法调用或生命周期事件。拦截器用于封装应用的公用行为,使这些行为与业务逻辑分离。拦截器可以是同一bean类中的方法或是一个外部类。
接口示例HelloChina.java:
package com.sillycat.ejb;
public interface HelloChina {
public String sayHello(String name);
public String myname();
}
EJB的Bean如下HelloChinaBean.java:
package com.sillycat.ejb.impl;
import javax.ejb.Local;
import javax.ejb.Remote;
import javax.ejb.Stateless;
import javax.interceptor.Interceptors;
import com.sillycat.ejb.HelloChina;
import com.sillycat.ejb.HelloChinaLocal;
@Stateless
@Remote(HelloChina.class)
@Local(HelloChinaLocal.class)
@Interceptors(HelloInterceptor.class)
//@Interceptors({A.class,B.class})
public class HelloChinaBean implements HelloChina, HelloChinaLocal {
public String myname() {
return "sillycat";
}
//@Interceptors(HelloInterceptor.class) 拦截某一方法
public String sayHello(String name) {
return name + ",hello ejb.";
}
}
这里@Interceptors({A.class,B.class})是多个拦截类的写法,另外也可以把这个标签写在特定的方法上,表示只拦截这个方法。
拦截器类HelloInterceptor.java如下:
package com.sillycat.ejb.impl;
import javax.interceptor.AroundInvoke;
import javax.interceptor.InvocationContext;
public class HelloInterceptor {
@AroundInvoke
public Object log(InvocationContext ctx) throws Exception {
System.out.println("*** HelloInterceptor intercepting");
long start = System.currentTimeMillis();
try {
if (ctx.getMethod().getName().equals("sayHello")) {
System.out.println("*** sayHello 已经被调用! *** ");
String name = (String)ctx.getParameters()[0];
//get the first parameters
if("boss".equals(name)){
throw new Exception("the method will not go any further.");
}
}
if (ctx.getMethod().getName().equals("myname")) {
System.out.println("*** myname 已经被调用! *** ");
}
return ctx.proceed();
} catch (Exception e) {
throw e;
} finally {
long time = System.currentTimeMillis() - start;
System.out.println("用时:" + time + "ms");
}
}
}
中途如果抛出错误,没有调用ctx.proceed(),那么将不会调用最终的业务方法。
if("boss".equals(name)){
throw new Exception("the method will not go any further.");
}
javax.interceptor.InvocationContext封装了客户端所调用业务方法的信息。
getTarget() 指向背调用的bean实例
getMethod()指向被拦截的业务方法
getParameters()获取被拦截业务方法的参数
setParameters()设置被拦截业务方法的参数
getContextData()方法返回一个Map对象,它在整个方法调用期间都可以访问。位于同一个方法调用内的不同拦截器之间,可以利用它来传递上下文相关的数据。
我们的测试类HelloChinaTest.java如下:
package com.sillycat.ejb;
import javax.naming.InitialContext;
import org.junit.BeforeClass;
import org.junit.Test;
public class HelloChinaTest {
protected static HelloChina helloChina;
@BeforeClass
public static void setUpBeforeClass() throws Exception {
InitialContext ctx = new InitialContext();
helloChina = (HelloChina) ctx.lookup("HelloChinaBean/remote");
}
@Test
public void testSayHello() {
System.out.println(helloChina.sayHello("sillycat"));
System.out.println(helloChina.myname());
System.out.println(helloChina.sayHello("boss"));
}
}
也可以将拦截方法直接放到业务方法类里面,直接将拦截类的方法拷贝过去就行了,加上@AroundInvoke标签。
默认拦截器
在一个EJB模块中有很多的EJB,如果对所有EJB方法进行拦截,就要再ejb-jar.xml中配置了,这里支持通配符。
禁用拦截器
如果配置ejb-jar.xml给很多EJB都加上了拦截,但是某些EJB我们又不想拦截,那么就要使用@ExcludeDefaultInterceptors注释。
@Stateless
@Remote(HelloChina.class)
@Local(HelloChinaLocal.class)
@ExcludeDefaultInterceptors
这里仅仅是不执行默认拦截器,如果自身定义了拦截器,还是要执行的。
拦截生命周期事件
电子书EJB3实例教程byLiHuoming.pdf笔记
2.4 Stateful Session Bean(有状态Bean)开发
需要每个用户都有自己的一个实例,这个实例不受其他用户影响。每个有状态Bean在Bean实例的生命周期内只服务于一个用户,Stateful Session Bean保持了用户的信息。
对于stateful Session Bean,用户每调用一次lookup()都将创建一个新的Bean实例,如果希望使用某个Bean实例,必须在客户端缓存存根。
定义接口Cart.java如下:
package com.sillycat.ejb;
import java.io.Serializable;
import java.util.List;
public interface Cart extends Serializable{
public void AddBuyItem(String productName);
public List<String> getBuyItem();
}
定义Bean文件CartBean.java如下:
package com.sillycat.ejb.impl;
import java.util.ArrayList;
import java.util.List;
import javax.ejb.Remote;
import javax.ejb.Stateful;
import com.sillycat.ejb.Cart;
@Stateful
@Remote(Cart.class)
public class CartBean implements Cart{
private static final long serialVersionUID = 4371026853912745479L;
private List<String> buyitems = new ArrayList<String>();
public void AddBuyItem(String productName) {
buyitems.add(productName);
}
public List<String> getBuyItem() {
return buyitems;
}
}
单元测试CartTest.java
package com.sillycat.ejb;
import static org.junit.Assert.assertEquals;
import java.util.List;
import javax.naming.InitialContext;
import org.junit.BeforeClass;
import org.junit.Test;
public class CartTest {
protected static Cart cart;
@BeforeClass
public static void setUpBeforeClass() throws Exception {
InitialContext ctx = new InitialContext();
cart = (Cart) ctx.lookup("CartBean/remote");
}
@Test
public void testBuyItems() {
cart.AddBuyItem("无聊啊");
cart.AddBuyItem("其实我喜欢写程序");
List<String> items = cart.getBuyItem();
assertEquals(2,items.size());
}
}
原本书中的例子,是要求,如果要使用同一个实例,存根,也就是lookup得到的cart要存放在session中,呵呵,到时候我想其他办法吧,放一个cart进session不是我想看到的。
2.5 激活机制(Activation mechanism)
每个有状态Bean在其生命周期内只服务于一个用户,对有状态Bean使用实例池化机制是毫无意义的。
2.6 Stateful Session Bean的生命周期
stateful Session Bean的生命周期包含三种状态:does not exist,method-ready和passivated。method-ready状态和method-ready pool之间有显著的不同。
2.7 EJB调用机制
调用远程或本地接口的方法时,接口使用的是存根(stub)对象,该存根实现了session bean的远程或本地接口。它负责将调用经过网络发送到远程EJB容器,或将请求路由到位于本地JVM内的EJB容器。存根是在部署期间使用JDK的java.lang.reflect.Proxy动态生成的。
2.8 如何改变Session Bean的JNDI名称
JBOSS提供@LocalBinding和@RemoteBinding,示例如下OperationBean.java:
package com.sillycat.ejb.impl;
import javax.ejb.Local;
import javax.ejb.Remote;
import javax.ejb.Stateless;
import org.jboss.ejb3.annotation.LocalBinding;
import org.jboss.ejb3.annotation.RemoteBinding;
import com.sillycat.ejb.Operation;
import com.sillycat.ejb.OperationLocal;
@Stateless
@Remote(Operation.class)
@RemoteBinding(jndiBinding="easyejb/OperationRemote")
@Local(OperationLocal.class)
@LocalBinding(jndiBinding="easyejb/OperationLocal")
public class OperationBean implements Operation, OperationLocal {
private int total = 0;
public int Addup() {
total++;
return total;
}
}
如果使用weblogic/Sun Application Server/Glassfish等,用的是@Stateless.mappedName()。
由于JNDI名称与厂商有关,使用注释定义JNDI名称会有移植问题,所以建议使用ejb-jar.xml部署描述文件来定义。文件放置在jar的META-INF目录中。
2.9 Session Bean的生命周期事件
@PostConstruct
当Bean对象完成实例化后,标注这个注释的方法会被立即调用。(有状态和无状态)
@PreDestroy
容器销毁一个无用或者过期的bean实例之前调用。(有状态和无状态)
@PrePassivate
有状态的Bean实例空闲事件过长,就会发生钝化(passivate)。标注了这个注释的方法会在钝化之前被调用。Bean实例被钝化后,在一段时间内,如果没有用户对bean实例进行操作,容器会从硬盘中删除它。以后,针对该bean方法的调用,容器都会抛出例外。(有状态)
@PostActivate
当客户端再次使用已经钝化的有状态bean时,EJB容器会重新实例化一个Bean实例,并从硬盘中将之前的状态恢复,标注了这个注释的方法会在激活完成时被调用。(有状态)
@Init
它区别于@PostConstruct在于多个@Init注释方法可以同时存在于有状态session bean中,但每个bean实例只会有一个@Init注释的方法会被调用。@PostConstruct在@Init之后被调用。(有状态)
@Remote
当客户端调用了标注了@Remote注释的方法时,容器将在方法执行结束后,把bean实例删除。
注释的回调方法必须返回void,不带参数,且不能抛出任何checked exception。
示例接口文件LifeCycle.java:
package com.sillycat.ejb;
public interface LifeCycle {
public String say();
public void stopSession();
}
EJB类LifeCycleBean.java如下:
package com.sillycat.ejb.impl;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import javax.ejb.Init;
import javax.ejb.PostActivate;
import javax.ejb.PrePassivate;
import javax.ejb.Remote;
import javax.ejb.Remove;
import javax.ejb.Stateful;
import com.sillycat.ejb.LifeCycle;
@Stateful
@Remote(LifeCycle.class)
public class LifeCycleBean implements LifeCycle{
public String say(){
try{
Thread.sleep(1000*10);
}catch(InterruptedException e){
e.printStackTrace();
}
return "有状态会话Bean生命周期";
}
@Init
public void initialize(){
System.out.println("@Init事件");
}
@PostConstruct
public void construct(){
System.out.println("@PostConstruct事件");
}
@PreDestroy
public void exit(){
System.out.println("@PreDestroy事件");
}
@PrePassivate
public void serialize(){
System.out.println("@PrePassivate事件");
}
@PostActivate
public void activate(){
System.out.println("@PostActivate事件");
}
@Remove
public void stopSession(){
System.out.println("@Remove事件");
}
}
测试页面ejbTest2.jsp如下:
<%@ page contentType="text/html; charset=UTF-8"%>
<%@ page import="com.sillycat.ejb.*, javax.naming.*"%>
<%
try {
LifeCycle lifecycle = (LifeCycle) session.getAttribute("lifecycle");
if (lifecycle == null) {
InitialContext ctx = new InitialContext();
lifecycle = (LifeCycle) ctx.lookup("LifeCycleBean/remote");
session.setAttribute ("lifecycle", lifecycle);
}
out.println(lifecycle.say());
out.println("<BR>请注意观察Jboss控制台输出.等待10分钟,容器将会钝化此会话Bean,@PrePassivate注释的方法将会执行<BR>");
out.println("<font color=red>你可以调用stopSession方法把会话Bean实例删除。在删除会话Bean时,将触发@PreDestroy事件<BR></font>");
/*
lifecycle.stopSession();
*/
} catch (Exception e) {
out.println(e.getMessage());
}
%>
日志情况:
第一次点击进入:
13:47:35,933 INFO [STDOUT] @PostConstruct事件
13:47:35,935 INFO [STDOUT] @Init事件
13:57:02,775 INFO [STDOUT] @PrePassivate事件
刷新页面:
14:10:56,584 INFO [STDOUT] @PostActivate事件
这里比较奇怪的是@PostConstruct事件竟然在@Init前面了。呵呵。
2.10 拦截器(Interceptor)
拦截器可以拦截Session Bean和Message-driven bean的方法调用或生命周期事件。拦截器用于封装应用的公用行为,使这些行为与业务逻辑分离。拦截器可以是同一bean类中的方法或是一个外部类。
接口示例HelloChina.java:
package com.sillycat.ejb;
public interface HelloChina {
public String sayHello(String name);
public String myname();
}
EJB的Bean如下HelloChinaBean.java:
package com.sillycat.ejb.impl;
import javax.ejb.Local;
import javax.ejb.Remote;
import javax.ejb.Stateless;
import javax.interceptor.Interceptors;
import com.sillycat.ejb.HelloChina;
import com.sillycat.ejb.HelloChinaLocal;
@Stateless
@Remote(HelloChina.class)
@Local(HelloChinaLocal.class)
@Interceptors(HelloInterceptor.class)
//@Interceptors({A.class,B.class})
public class HelloChinaBean implements HelloChina, HelloChinaLocal {
public String myname() {
return "sillycat";
}
//@Interceptors(HelloInterceptor.class) 拦截某一方法
public String sayHello(String name) {
return name + ",hello ejb.";
}
}
这里@Interceptors({A.class,B.class})是多个拦截类的写法,另外也可以把这个标签写在特定的方法上,表示只拦截这个方法。
拦截器类HelloInterceptor.java如下:
package com.sillycat.ejb.impl;
import javax.interceptor.AroundInvoke;
import javax.interceptor.InvocationContext;
public class HelloInterceptor {
@AroundInvoke
public Object log(InvocationContext ctx) throws Exception {
System.out.println("*** HelloInterceptor intercepting");
long start = System.currentTimeMillis();
try {
if (ctx.getMethod().getName().equals("sayHello")) {
System.out.println("*** sayHello 已经被调用! *** ");
String name = (String)ctx.getParameters()[0];
//get the first parameters
if("boss".equals(name)){
throw new Exception("the method will not go any further.");
}
}
if (ctx.getMethod().getName().equals("myname")) {
System.out.println("*** myname 已经被调用! *** ");
}
return ctx.proceed();
} catch (Exception e) {
throw e;
} finally {
long time = System.currentTimeMillis() - start;
System.out.println("用时:" + time + "ms");
}
}
}
中途如果抛出错误,没有调用ctx.proceed(),那么将不会调用最终的业务方法。
if("boss".equals(name)){
throw new Exception("the method will not go any further.");
}
javax.interceptor.InvocationContext封装了客户端所调用业务方法的信息。
getTarget() 指向背调用的bean实例
getMethod()指向被拦截的业务方法
getParameters()获取被拦截业务方法的参数
setParameters()设置被拦截业务方法的参数
getContextData()方法返回一个Map对象,它在整个方法调用期间都可以访问。位于同一个方法调用内的不同拦截器之间,可以利用它来传递上下文相关的数据。
我们的测试类HelloChinaTest.java如下:
package com.sillycat.ejb;
import javax.naming.InitialContext;
import org.junit.BeforeClass;
import org.junit.Test;
public class HelloChinaTest {
protected static HelloChina helloChina;
@BeforeClass
public static void setUpBeforeClass() throws Exception {
InitialContext ctx = new InitialContext();
helloChina = (HelloChina) ctx.lookup("HelloChinaBean/remote");
}
@Test
public void testSayHello() {
System.out.println(helloChina.sayHello("sillycat"));
System.out.println(helloChina.myname());
System.out.println(helloChina.sayHello("boss"));
}
}
也可以将拦截方法直接放到业务方法类里面,直接将拦截类的方法拷贝过去就行了,加上@AroundInvoke标签。
默认拦截器
在一个EJB模块中有很多的EJB,如果对所有EJB方法进行拦截,就要再ejb-jar.xml中配置了,这里支持通配符。
禁用拦截器
如果配置ejb-jar.xml给很多EJB都加上了拦截,但是某些EJB我们又不想拦截,那么就要使用@ExcludeDefaultInterceptors注释。
@Stateless
@Remote(HelloChina.class)
@Local(HelloChinaLocal.class)
@ExcludeDefaultInterceptors
这里仅仅是不执行默认拦截器,如果自身定义了拦截器,还是要执行的。
拦截生命周期事件