在学习EJB的时候,我们知道,EJB里面有stateless(无状态)bean和stateful(有状态)bean,有状态bean和无状态bean到底有什么特性呢,今天我们来探讨一下这两种bean的区别。
Stateless Bean,也叫做无状态会话bean。顾名思义,这种bean只为用户提供操作,不负责保存用户的状态。所以任何用户都可以调用,一般情况下,无状态bean会存在于对象池中,用户调用时,从中取出实例进行操作,当用户操作完毕时,bean会重新回到对象池中(这里特别要注意,即使调用它的用户不存在了,bean也有可能存在)。也就是说,如果无状态会话bean中有成员变量的话,这个变量会受到所有可能调用它的用户的影响。
下面我们看一个有成员变量的stateless bean的例子:
Interface:
package org.chou.mickey.servers.demo;
public interface StatelessBeanService {
public void addOne();
public int getCount();
}
stateless bean:
package org.chou.mickey.servers.impl.demo;
import javax.ejb.Remote;
import javax.ejb.Stateless;
import org.apache.log4j.Logger;
import org.chou.mickey.servers.demo.StatelessBeanService;
@Stateless
@Remote(StatelessBeanService.class)
public class StatelessBeanServiceImpl implements StatelessBeanService {
public static final String CLASS_NAME = StatelessBeanServiceImpl.class.getName();
public static Logger logger = Logger.getLogger(StatelessBeanServiceImpl.class);
private int count = 0;
public void addOne() {
String methodName = "addOne";
logger.info("<<<<< " + CLASS_NAME + " " + methodName + " Start >>>>>");
count++;
logger.info("<<<<< " + CLASS_NAME + " " + methodName + " End >>>>>");
}
public int getCount() {
String methodName = "getCount";
logger.info("<<<<< " + CLASS_NAME + " " + methodName + " Start >>>>>");
logger.info("<<<<< " + CLASS_NAME + " " + methodName + " End >>>>>");
return count;
}
}
调用EJB代码:
package org.chou.movie.actions;
import javax.naming.InitialContext;
import org.apache.log4j.Logger;
import org.chou.mickey.servers.demo.StatelessBeanService;
import org.chou.movie.utils.InitialContextCreater;
import com.opensymphony.xwork2.ActionSupport;
public class TestStatelessAction extends ActionSupport {
private static final long serialVersionUID = 2218164559073115228L;
private static final String CLASS_NAME = TestStatelessAction.class.getName();
private static Logger logger = Logger.getLogger(TestStatelessAction.class);
public String execute() throws Exception {
String methodName = "execute";
logger.info("<<<<< " + CLASS_NAME + " " + methodName + "Start >>>>>");
InitialContext context = InitialContextCreater.getInitialContext();
StatelessBeanService service = (StatelessBeanService)context.lookup("StatelessBeanServiceImpl/remote");
service.addOne();
int count = service.getCount();
logger.info("☆☆☆☆☆ count = " + count);
logger.info("<<<<< " + CLASS_NAME + " " + methodName + "Start >>>>>");
return ActionSupport.SUCCESS;
}
}
我们在第1次调用时打印如下:
2014-11-18 21:09:03 INFO TestStatelessAction:execute:21 - <<<<< org.chou.movie.actions.TestStatelessAction executeStart >>>>>
2014-11-18 21:09:04 INFO TestStatelessAction:execute:29 - ☆☆☆☆☆ count = 1
2014-11-18 21:09:04 INFO TestStatelessAction:execute:30 - <<<<< org.chou.movie.actions.TestStatelessAction executeStart >>>>>
第二次调用:
2014-11-18 21:10:41 INFO TestStatelessAction:execute:21 - <<<<< org.chou.movie.actions.TestStatelessAction executeStart >>>>>
2014-11-18 21:10:41 INFO TestStatelessAction:execute:29 - ☆☆☆☆☆ count = 2
2014-11-18 21:10:41 INFO TestStatelessAction:execute:30 - <<<<< org.chou.movie.actions.TestStatelessAction executeStart >>>>>
我们换个浏览器继续进行第三次调用(换个浏览器相当于我们切换到了另一个用户来调用):
2014-11-18 21:12:33 INFO TestStatelessAction:execute:21 - <<<<< org.chou.movie.actions.TestStatelessAction executeStart >>>>>
2014-11-18 21:12:33 INFO TestStatelessAction:execute:29 - ☆☆☆☆☆ count = 3
2014-11-18 21:12:33 INFO TestStatelessAction:execute:30 - <<<<< org.chou.movie.actions.TestStatelessAction executeStart >>>>>
看到这些打印的结果,我们能想到什么呢?三次调用,调用的是同一个实例,即使我们切换了用户,也无法改变。
这充分说明,一个stateless bean的实例,可能被多个用户进行多次调用。根据上面所说的stateless bean的特性,我们可以知道,每次调用完后,bean实例会回到对象池中,等待下一次调用。第一次调用的时候,count+1=1;第二次调用count+1=2,第三次调用count+1=3。这也印证了,statelss bean中的状态,会被多个用户影响,而不是单纯的只记录一个用户的状态;这也就是stateless bean无法负责保存用户状态信息的原因,因为它无法在其生命周期中保证只为一个用户负责。
接下来我们看看stateful bean:
stateful bean,也叫状态会话bean。从名字就可以看出,状态会话bean不但可以为用户提供操作,还可以保存一个用户的状态。状态会话bean会为每一个用户创建一个实例,在一个状态会话bean实例的生命周期中,它只为一个用户服务,所以,它能够保存用户的状态。
在使用会话状态bean的过程中,每次我们lookup时,都会创建一个新的bean实例,如果希望一直使用某个状态bean的实例,我们可以在操作完后,将其存入客户端中,下次使用的时候再取出来使用即可。而且,你不用担心别的用户会影响到这个bean的状态。它只归一个用户所有。
OK,我们下面来看一个状态会话bean的实例:
interface:
package org.chou.mickey.servers.demo;
public interface StatefulBeanService {
public void addOne();
public int getCount();
}
stateful bean:
package org.chou.mickey.servers.impl.demo;
import javax.ejb.Remote;
import javax.ejb.Stateful;
import org.apache.log4j.Logger;
import org.chou.mickey.servers.demo.StatefulBeanService;
import org.chou.mickey.servers.demo.StatelessBeanService;
@Stateful
@Remote(StatefulBeanService.class)
public class StatefulBeanServiceImpl implements StatelessBeanService {
public static final String CLASS_NAME = StatefulBeanServiceImpl.class.getName();
public static Logger logger = Logger.getLogger(StatefulBeanServiceImpl.class);
private int count = 0;
public void addOne() {
String methodName = "addOne";
logger.info("<<<<< " + CLASS_NAME + " " + methodName + " Start >>>>>");
count++;
logger.info("<<<<< " + CLASS_NAME + " " + methodName + " End >>>>>");
}
public int getCount() {
String methodName = "getCount";
logger.info("<<<<< " + CLASS_NAME + " " + methodName + " Start >>>>>");
logger.info("<<<<< " + CLASS_NAME + " " + methodName + " End >>>>>");
return count;
}
}
调用stateful bean:
package org.chou.movie.actions;
import java.util.Map;
import javax.naming.InitialContext;
import org.apache.log4j.Logger;
import org.apache.struts2.interceptor.SessionAware;
import org.chou.mickey.servers.demo.StatefulBeanService;
import org.chou.movie.utils.InitialContextCreater;
import com.opensymphony.xwork2.ActionSupport;
public class TestStatefulAction extends ActionSupport implements SessionAware{
private static final long serialVersionUID = 2218164559073115228L;
private static final String CLASS_NAME = TestStatefulAction.class.getName();
private static Logger logger = Logger.getLogger(TestStatefulAction.class);
private Map<String, Object> session;
public String execute() throws Exception {
String methodName = "execute";
logger.info("<<<<< " + CLASS_NAME + " " + methodName + "Start >>>>>");
InitialContext context = InitialContextCreater.getInitialContext();
StatefulBeanService service = (StatefulBeanService)session.get("Count");
if ( service == null ) {
service = (StatefulBeanService)context.lookup("StatefulBeanServiceImpl/remote");
}
service.addOne();
session.put("Count", service);
int count = service.getCount();
logger.info("☆☆☆☆☆ count = " + count);
logger.info("<<<<< " + CLASS_NAME + " " + methodName + "Start >>>>>");
return ActionSupport.SUCCESS;
}
public void setSession(Map<String, Object> session) {
this.session = session;
}
}
执行第一次调用结果:
2014-11-18 21:32:27 INFO TestStatefulAction:execute:23 - <<<<< org.chou.movie.actions.TestStatefulAction executeStart >>>>>
2014-11-18 21:32:28 INFO TestStatefulAction:execute:35 - ☆☆☆☆☆ count = 1
2014-11-18 21:32:28 INFO TestStatefulAction:execute:36 - <<<<< org.chou.movie.actions.TestStatefulAction executeStart >>>>>
同浏览器第二次调用结果:
2014-11-18 21:33:20 INFO TestStatefulAction:execute:23 - <<<<< org.chou.movie.actions.TestStatefulAction executeStart >>>>>
2014-11-18 21:33:20 INFO TestStatefulAction:execute:35 - ☆☆☆☆☆ count = 2
2014-11-18 21:33:20 INFO TestStatefulAction:execute:36 - <<<<< org.chou.movie.actions.TestStatefulAction executeStart >>>>>
不同浏览器第三次调用结果:
2014-11-18 21:34:05 INFO TestStatefulAction:execute:23 - <<<<< org.chou.movie.actions.TestStatefulAction executeStart >>>>>
2014-11-18 21:34:05 INFO TestStatefulAction:execute:35 - ☆☆☆☆☆ count = 1
2014-11-18 21:34:05 INFO TestStatefulAction:execute:36 - <<<<< org.chou.movie.actions.TestStatefulAction executeStart >>>>>
仔细看看,我们上面执行产生的结果,当第一次,第二次同浏览器调用时,调用的是同一个stateful bean的实例,第三次,换个浏览器调用时,调用的是另一个stateful bean的实例,这就验证了,stateful bean会为每一个用户创造一个实例,而他们之间不会互相影响,从而使stateful bean可以为用户的状态负责。
细心的童鞋也许会看到,我们在action中调用stateful bean时,和stateless bean稍微有点不一样。调用stateless bean时,直接用lookup进行查找。而调用stateful bean时,我们首先在session里面取,如果取不到,我们才用lookup进行查找,这就是我们所说的,如果想一直使用同一个stateful bean实例,要把它存在客户端。否则,按照stateful bean的性格,你直接查找,它会直接产生一个新的实例,那么之前的状态还是丢失了,无法在程序里面传递。要想在程序里面传递stateful bean的状态,就要按照这种方法进行调用。当然,如果在同一个用户里你不想要它的状态了,你也可以向stateless bean一样,直接lookup,它会给你一个崭新的实例。