98

我正在阅读 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 如何工作的理解,而不是库或应用程序服务器,当然。有人可以给我一个简单的 hello world 类型的无状态会话 bean 示例,当您将其更改为有状态时,它的行为会有所不同?

4

7 回答 7

138

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

相比之下,有状态会话 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-02-28T15:21:30.623 回答
97

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

有状态会话 bean 的有状态部分类似于 servlet 中的会话。有状态会话 bean 允许您的应用程序仍然拥有该会话,即使没有 Web 客户端。当应用服务器从对象池中获取无状态会话 bean 时,它知道它可以用于满足任何请求,因为它不与特定用户关联。

一个有状态的会话 bean 必须首先分发给获得它的用户,因为他们的购物车信息应该只有他们自己知道。应用服务器确保如此。想象一下,如果您可以开始购物,然后当我出现时,应用服务器将您的有状态会话 bean 提供给我,您的应用会多么受欢迎!

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

于 2010-02-28T13:57:09.053 回答
18

在这种情况下,无状态和有状态并不完全符合您的期望。

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

  • 预留座位
  • 充值信用卡
  • 出票

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

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

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

于 2010-02-28T13:59:06.547 回答
7

好问题,

试试这段代码(更改 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

于 2018-10-23T08:22:16.720 回答
5

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

无状态 Bean

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

有状态的 Bean

  1. 有状态会话 bean 可以一次维护与多个客户端的会话状态,并且任务不在客户端之间共享。
  2. 会话完成后,状态不会保留。
  3. 容器可以将状态序列化并存储为陈旧状态以供将来使用。这样做是为了节省应用程序服务器的资源并支持 bean 故障。
于 2017-02-20T20:08:31.937 回答
4

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

于 2013-05-28T22:25:29.883 回答
4

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

于 2016-03-27T05:26:22.487 回答