下面我们修改客户端代码来验证一下无状态会话Bean的变量是怎么回事。
服务端代码修改如下,修改后重新打包部署。
package ejb.sessionBean.impl; import java.util.HashMap; import java.util.Map; import javax.ejb.Stateless; import ejb.sessionBean.Hello;
@Stateless public class HelloEAOImpl implements Hello {
// 访问次数 private int num;
private Map<String, Integer> buyInfo = new HashMap<String, Integer>();
@Override public String hello(String name) {
return "欢迎你:" + name; }
@Override public int countNum() { num++; return num; }
public void addItem(String item) { if (buyInfo.containsKey(item)) { buyInfo.put(item, buyInfo.get(item) + 1); } else { buyInfo.put(item, 1); } System.out.println("Stateless-buyInfo:" + buyInfo); }
} |
测试代码增加几个方法如下
public void test02() throws NamingException { context = init(); Hello hello = (Hello) context.lookup("HelloEAOImpl/remote"); System.out.println("test02访问" + hello.countNum()); System.out.println("test02访问" + hello.countNum()); System.out.println("test02访问" + hello.countNum()); System.out.println("test02访问" + hello.countNum()); System.out.println("test02访问" + hello.countNum()); }
public void test03() throws NamingException { context = init(); Hello hello = (Hello) context.lookup("HelloEAOImpl/remote"); System.out.println("test03访问" + hello.countNum()); System.out.println("test03访问" + hello.countNum()); System.out.println("test03访问" + hello.countNum()); System.out.println("test03访问" + hello.countNum()); System.out.println("test03访问" + hello.countNum());
}
public void test04() throws NamingException { context = init(); Hello hello = (Hello) context.lookup("HelloEAOImpl/remote"); hello.addItem("叶小钗"); hello.addItem("一页书");
}
public void test05() throws NamingException { context = init(); Hello hello = (Hello) context.lookup("HelloEAOImpl/remote"); hello.addItem("一页书");
} |
用junit单独先执行test02方法之后再执行test03方法结果如下:
test2:
test02访问1 test02访问2 test02访问3 test02访问4 test02访问5 |
test3:
test03访问6 test03访问7 test03访问8 test03访问9 test03访问10 |
之后单独先执行test04之后再执行test05
test04服务端控制台效果如下:
10:22:56,063 INFO [STDOUT] Stateless-buyInfo:{叶小钗=1} 10:22:56,079 INFO [STDOUT] Stateless-buyInfo:{一页书=1, 叶小钗=1} |
test05
10:24:03,644 INFO [STDOUT] Stateless-buyInfo:{一页书=2, 叶小钗=1} |
由此观之,客户端单线程访问EJB容器的时候,对于客户端来说无状态的会话Bean会将实例变量当做类似于静态变量来处理的。无论是几个客户端,总之只要是非多线程并发访问无状态SessionBean的时候,确实该变量类似于容器全局的静态变量。如果有多个并发访问会如何呢?
我们一气呵成走完junit测试用例(运行2遍)服务端控制台如下。
效果如下:
10:29:00,506 INFO [STDOUT] Stateless-buyInfo:{一页书=2, 叶小钗=2} 10:29:00,506 INFO [STDOUT] Stateless-buyInfo:{一页书=3, 叶小钗=2} 10:29:00,538 INFO [STDOUT] Stateless-buyInfo:{一页书=4, 叶小钗=2} 10:29:19,618 INFO [STDOUT] Stateless-buyInfo:{叶小钗=1} 10:29:19,634 INFO [STDOUT] Stateless-buyInfo:{一页书=5, 叶小钗=2} 10:29:19,650 INFO [STDOUT] Stateless-buyInfo:{一页书=6, 叶小钗=2} |
可以看出此时的buyInfo变量不再是单一静态的了,并发访问的时候,对象池会再生出(new)一个新的实例变量,该实例变量的生命是由应用服务器的对象池来管理的。而客户端调用的时候具体获得和调用哪个实例变量,这个不好说,只能看应用服务的JVM调度了。所以来说很多人建议甚至是规定不要在无状态的SessionBean中定义实例变量,服务端无法维护其通讯状态是这个意思!有状态的会话Bean会记录一次会话调用中,实例变量的状态的实质含义就是说保证在客户端获取、调用有状态的Bean时,保证里面的实例变量和上次访问的时候是一样的、是线程安全的、对于你这个客户端来说是独一无二的!因此有状态的开销也是很大的。无状态的对象池仅仅维护并发所产生的这几个变量罢了,但是我无状态的SessionBean可不能保证你客户端下次访问的就一定是你上次所操作的变量哦………………所以一般在无状态的SessionBean中定义实例变量其实没什么意义,因为客户端真正操作的是哪个实例变量,只有对象池根据并发的实际情况而定,所以很不稳定,所以EJB标准给出的结论就是“不允许”在无状态的SessionBean中定义实例变量。
1. 无状态的会话Bean的本地、远程调用
EJB组件都会分为本地调用(local)和远程调用(remote)2种。
1)一般客户端程序无论是手机、ATM机、电信的终端设备、web服务器的调用会话Bean的时候都是调用的在另一个地理位置的远程EJB容器中的SessionBean。此时就需要远程调用remote。在接口的类名上加入@Remote注解就可以了。
2)在EJB容器内部,EJB组件和EJB组件进行相互调用的时候,那么本身就是“兄弟联”内部的,好说话。此时只需要本地的local方式调用就可以了。在接口的类名上加入@Local注解就可以了。在本地调用的时候其实就是保证在同一个JVM上运行就可以了。
2. 会话Bean的发布
上面提到了SessionBean的打包,下面我们来个截图看看,笔者用的是MyEclipse8.6。打包过程如下
在开发EJB项目右键,export
选中EJB的接口和实现类,导出后是一个jar,将此jar包拷贝到${JBOSS_HOME} \server\default下面即可完成EJB的发布。
注:如果 EJB 调用到了辅助类,此辅助类也需要打包到 jar 中。还有就是客户端调用此 EJB 接口的时候,切记接口的包结构一定要和服务端的包结构一致。