无状态和有状态的企业Java Bean


93

我正在阅读Java EE 6教程,试图理解无状态会话bean和有状态会话bean之间的区别。如果无状态会话bean在方法调用之间没有保持其状态,为什么我的程序按原样运行?

package mybeans;

import javax.ejb.LocalBean;
import javax.ejb.Stateless;

@LocalBean
@Stateless
public class MyBean {

    private int number = 0;

    public int getNumber() {
        return number;
    }

    public void increment() {
        this.number++;
    }
}

客户端

import java.io.IOException;
import javax.ejb.EJB;
import javax.servlet.*;
import javax.servlet.http.*;
import javax.servlet.annotation.WebServlet;
import mybeans.MyBean;
import java.io.PrintWriter;

@WebServlet(name = "ServletClient", urlPatterns = { "/ServletClient" })
public class ServletClient extends HttpServlet {
    private static final long serialVersionUID = 1L;

    @EJB
    MyBean mybean;

    protected void doGet(HttpServletRequest request,
            HttpServletResponse response) throws ServletException, IOException {

        PrintWriter out = response.getWriter();
        mybean.increment();
        out.println(mybean.getNumber());
    }

}

我原本希望getNumber每次都返回0,但它返回1,并且在浏览器中重新加载servlet会使它更多。问题在于我对无状态会话Bean如何工作的理解,而与库或应用程序服务器无关。有人可以给我一个无状态会话bean的简单hello world type示例,当您将其更改为有状态时,其行为会有所不同吗?


6
相关:stackoverflow.com/questions/8887140/…这个答案可能更容易理解。请注意,servlet基本上是应用程序范围的(在整个应用程序范围内只有1个servlet实例在所有HTTP请求/会话之间共享/重用。)
BalusC 2012年

嗨,你先做增量,进而得到价值....所以你不能指望值0
rzur2004

我只想感谢您提出这个问题,它现在解决了我的问题。我再也没有
想过

Answers:


93

重要的区别不是私有成员变量,而是将状态与特定用户相关联(请考虑“购物车”)。

有状态会话Bean的有状态片段就像servlet中的会话一样。有状态会话Bean允许您的应用即使没有Web客户端也仍然具有该会话。当应用服务器从对象池中获取无状态会话bean时,它知道可以将其用于满足ANY请求,因为它与特定用户没有关联。

必须将有状态会话Bean分发给首先获得该会话Bean的用户,因为他们的购物车信息仅应由他们知道。应用服务器确保做到这一点。想象一下,如果您可以开始购物,然后我来的时候,应用服务器就将您的有状态会话bean给我,那么您的应用将会有多受欢迎!

因此,您的私人数据成员确实是“州”,但不是“购物车”。尝试重做您的示例(非常好),以使增量变量与特定用户相关联。增加它,创建一个新用户,并查看他们是否仍然可以看到增加的值。如果操作正确,每个用户都应该只看到他们的计数器版本。


您能否在评论中提供明确的答案?为什么在此示例中,总是将无状态bean保持值并每次增加它?因为只有一个用户?
arjacsoh

2
无论用户数量多少,计数器都会增加。因此,如果user1进入并将计数器递增到1,而user2进入并递增计数器,则值将为2。它实际上应表明user1的值为1和user2的值为1(如果那是您打算做的事情。上面的示例)。
克里希纳

137

无状态会话Bean(SLSB)未绑定到一个客户端,并且不能保证一个客户端每次调用方法都会获得相同的实例(某些容器可能会在每个方法调用会话中创建和销毁Bean,这是特定于实现的决定,但是实例通常是池化的-而且我不提到集群环境)。换句话说,尽管无状态Bean可能具有实例变量,但是这些字段并非特定于一个客户端,因此在远程调用之间不要依赖它们。

相反,有状态会话Bean(Stateful Session Bean,SFSB)在整个生命周期内都专用于一个客户端,没有实例交换或池化(钝化后可能会从内存中退出以节省资源,但这是另一回事了)并保持会话状态。这意味着Bean的实例变量可以在方法调用之间保留相对于客户端的数据。这样就可以进行相互依赖的方法调用(一种方法所做的更改会影响后续的方法调用)。SFSB的典型用例是多步骤流程(注册流程,购物车,预订流程...)。

还有一件事。如果使用的是SFSB,则必须避免将它们注入本质上是多线程的类中,例如Servlet和JSF托管Bean(您不希望所有客户端都共享它)。如果要在Web应用程序中使用SFSB,则需要执行JNDI查找并将返回的EJB实例存储在HttpSession对象中,以备将来使用。像这样:

try {
    InitialContext ctx = new InitialContext();
    myStateful = (MyStateful)ctx.lookup("java:comp/env/MyStatefulBean");
    session.setAttribute("my_stateful", myStateful);
} catch (Exception e) {
    // exception handling
}

感谢您的清理。当我为客户端使用独立的命令行程序时,很明显看到了区别。
斯坦利·凯利2010年

感谢您的评论,它们更富启发性。首先,您给出抽象定义,然后为每种情况指定一些用例,然后指出一些陷阱。伟大+1
亚瑟2012年

EJB 3.1 的避免注入部分也会消失吗?
jacktrades 2012年

7
@Pascal如果“有状态会话Bean(SFSB)在整个生命周期中都专用于一个客户端”,即此功能是在SFSB中内置的,那么为什么需要将它们存储在HttpSession对象上?
user1169587'3

2
为什么我们需要在会话中保留有状态的bean(如果已经“会话”了)?这样,我们可以使每个对象都处于会话状态。说明
乔治·戈博佐夫

18

在这种情况下,无状态和有状态并不意味着您可能期望的那样。

EJB的状态性是指我所说的会话状态。典型的例子是机票预订。如果包含三个步骤:

  • 后备座位
  • 信用卡充值
  • 发行票

想象一下,每个都是对会话bean的方法调用。有状态会话Bean可以维持这种对话,因此它可以记住两次调用之间发生的情况。

无状态会话bean没有这种会话状态的能力。

会话bean内的全局变量(无状态或有状态)完全是另外一些东西。有状态会话bean将创建一个bean池(因为一个bean一次只能在一个对话中使用),而无状态sesion bean通常只有一个实例,这会使全局变量起作用,但是我不认为这是必须保证的。


5

会话bean的两种主要类型之间的主要区别是:

无状态豆

  1. 无状态会话Bean是与调用其方法的客户端没有会话状态的会话Bean。因此,他们可以创建一个对象池,该对象池可用于与多个客户端进行交互 。
  2. 性能明智的无状态Bean 更好,因为它们没有每个客户端的状态。
  3. 他们可以并行处理来自多个客户端的多个请求。

有状态的豆

  1. 有状态会话Bean可以一次维护与多个客户端的对话状态,并且任务不会在客户端之间共享。
  2. 会话完成后,状态不会保留。
  3. 容器可以序列化状态并将其存储为陈旧状态,以备将来使用。这样做是为了节省应用程序服务器的资源并支持Bean故障。

4

发生这种情况的原因是,容器在池中只有一个bean实例,该实例可用于所有调用。如果并行运行客户端,则会看到不同的结果,因为容器将在池中创建更多的bean实例。


4

它有很好的答案。我想补充一点答案。无状态Bean不应用于保存任何客户端数据。应该将其用于“模拟可以一次性完成的动作或过程”。


4

好问题,

尝试以下代码(更改MyBean有状态/无状态):

import javax.ejb.LocalBean;
import javax.ejb.Stateful;
import javax.ejb.Stateless;

@LocalBean 
@Stateless 
public class MyBean {

    private int number = 0;

    public int getNumber() {
        return number;
    }

    public void increment() {
        this.number++;
    }
}

Servlet_1

 import java.io.IOException;
    import javax.ejb.EJB;
    import javax.servlet.*;
    import javax.servlet.http.*;
    import javax.servlet.annotation.WebServlet;

    import java.io.PrintWriter;

    @WebServlet(name = "ServletClient", urlPatterns = { "/ServletClient" })
    public class ServletClient extends HttpServlet {

        private static final long serialVersionUID = 1L;

        @EJB
        MyBean mybean;

        protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

            PrintWriter out = response.getWriter();
            mybean.increment();
            out.println(mybean.getNumber());
        }

    }

Servlet_2

import java.io.IOException;
import javax.ejb.EJB;
import javax.servlet.*;
import javax.servlet.http.*;
import javax.servlet.annotation.WebServlet;

import java.io.PrintWriter;

@WebServlet(name = "NewServletClient", urlPatterns = { "/NewServletClient" })
public class NewServletClient extends HttpServlet {

    private static final long serialVersionUID = 1L;

    @EJB
    MyBean mybean;

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

        PrintWriter out = response.getWriter();
        mybean.increment();
        out.println(mybean.getNumber());
    }

}

案例:MyBean-@ Stateless

http:// localhost:8080 / MYServletDemo / ServletClient

1个

http:// localhost:8080 / MYServletDemo / ServletClient

2

http:// localhost:8080 / MYServletDemo_war_exploded / newServletClient

3

http:// localhost:8080 / MYServletDemo / ServletClient

4

案例:MyBean-@ Stateful

http:// localhost:8080 / MYServletDemo / ServletClient

1个

http:// localhost:8080 / MYServletDemo / ServletClient

2

http:// localhost:8080 / MYServletDemo / newServletClient

1个

http:// localhost:8080 / MYServletDemo / ServletClient

3


1
是的,就是这样,而且有效!非常简单的解释,谢谢!
Nesquik27
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.