无状态和有状态企业Java Bean

98

我正在学习Java EE 6教程,试图理解无状态(stateless)和有状态(stateful)会话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示例,在将其更改为有状态时行为不同吗?


6
相关链接:https://dev59.com/YGox5IYBdhLWcg3w6on0这个答案可能更容易理解。请注意,servlet基本上是应用程序作用域(整个HTTP请求/会话期间只有一个servlet实例在应用程序范围内共享/重复使用)。 - BalusC
1
嗨,你先进行了递增操作,然后再获取值...因此你不能期望值为0。 - rzur2004
我只想感谢你提出这个问题,它解决了我目前的问题。我无法更好地提出这个问题。 - kholofelo Maloma
7个回答

138
无状态会话Bean(SLSB)不与一个客户端绑定,也不能保证每个方法调用都会获得相同的实例(一些容器可能会在每个方法调用会话中创建和销毁Bean,这是一个具体实现决策,但实例通常是池化的 - 我没有提到集群环境)。换句话说,尽管无状态Bean可能有实例变量,但这些字段不针对一个特定客户端,因此在远程调用之间不要依赖它们。
相比之下,有状态会话Bean(SFSB)专门为一个客户端服务其整个生命周期,不会交换或池化实例(它可能在经过停用后从内存中清除以节省资源,但这是另一回事),并维护会话状态。这意味着Bean的实例变量可以在方法调用之间保留与客户端相关的数据。这使得具有相互依赖的方法调用成为可能(一个方法所做的更改会影响后续方法调用)。多步骤过程(注册过程、购物车、预订过程等)是SFSB的典型用例。
还有一件事。如果您正在使用SFSB,则必须避免将它们注入到本质上是多线程的类中,如Servlets和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
}

感谢您的澄清。当我使用独立的命令行程序作为客户端时,很容易看出区别。 - Stanley kelly
谢谢您的评论,它们非常有启发性。首先,您给出了抽象定义,然后为每种情况指定了一些用例,最后指出了一些陷阱。太棒了!+1 - arthur
避免注入的部分在 EJB 3.1 中也适用吗? - jacktrades
7
如果“有状态会话Bean(SFSB)专门为其整个生命周期服务于一个客户端”,那么这种能力是内置于SFSB中的,那么为什么需要将它们存储在HttpSession对象上呢? - user1169587
2
为什么如果已经“有了session”,我们需要将有状态的bean保存在会话中?这样,我们可以使每个对象都具有会话状态。请解释一下。 - Georgy Gobozov
这是J2EE最令人恼火的事情:我想使用Stateful ejbs,并在我的servlet中进行注入。我期望框架执行一些“threadLocal”工作 - 因为ejb链接已经被代理 - 只返回我的http会话的真实会话bean,而不是任何一个!所以,Georgy是完全正确的 - 如果我们可以创建简单的bean,将其放入http会话并在任何地方使用它,那么我们为什么需要SFSB?只是为了获得允许EJB的JPA和其他员工吗? - bitec

97
重要的区别不在于私有成员变量,而是将状态与特定用户关联(比如“购物车”)。
有状态会话Bean中的状态组件类似于Servlet中的会话。有状态会话Bean使得您的应用即使没有Web客户端也可以仍然具有该会话。当应用服务器从对象池中获取无状态会话Bean时,它知道它可以用于满足任何请求,因为它与特定用户无关。
有状态会话Bean必须分配给最初获得它的用户,因为他们的购物车信息应该仅为他们所知。应用服务器确保这样做。想象一下,如果你开始购物,然后应用服务器将你的有状态会话Bean交给我,那么你的应用会有多受欢迎!
因此,您的私有数据成员确实是“状态”,但不是“购物车”。试着用一个新的用户来重新运行你(非常好的)示例,让递增变量与特定用户相关。递增它,创建一个新用户,看看他们是否仍然可以看到递增的值。如果正确地完成,每个用户应该只看到他们的计数器版本。

你能在评论中提供一个明确的答案吗?为什么这个例子中的无状态bean总是保存值并在每次增加时都会增加它?因为只有一个用户吗? - arjacsoh
2
计数器将会增加,无论用户数量如何。因此,如果用户1进来并将计数器增加到1,同时用户2也进来并增加它,那么值将为2。实际上,它应该显示用户1有1个,用户2有1个(如果这是您想要的话,就像上面的购物车示例)。 - Krishna

18

在这个上下文中,“有状态(stateful)”和“无状态(stateless)”并不是你想象中的意思。

EJB中的状态(state)指的是我所谓的“对话状态(conversational state)”。一个经典的例子是航班预订,如果它包括三个步骤:

  • 预留座位
  • 收取信用卡费用
  • 发行机票

想象一下,每个步骤都是会话bean的方法调用。 有状态会话bean可以维护这种“对话(conversation)”,因此它记得在调用之间发生的事情。

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

会话bean(无论是有状态还是无状态)内的全局变量是另一回事。有状态会话bean将创建一个bean池(因为一个bean一次只能在一个对话中使用),而无状态会话bean通常只有一个实例,这使得全局变量可以正常工作,但我认为这不一定是保证的。


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

3

http://localhost:8080/MYServletDemo/ServletClient

4


1
是的,就是这样,它可以工作!非常简单易懂的解释,谢谢! - Nesquik27

5
两种会话bean之间的主要差异是:
无状态Bean
1. 无状态会话bean是那些与调用其方法的客户端没有对话状态的bean。因此,他们可以创建一个对象池,用于与多个客户端进行交互。
2. 性能方面,无状态bean更好,因为它们不具有每个客户端的状态。
3. 它们可以并行处理来自多个客户端的多个请求。
有状态Bean
1. 有状态会话bean可以同时与多个客户端维护对话状态,任务不在客户端之间共享。
2. 会话完成后,状态不会保留。
3. 容器可以将状态序列化并存储为“过期状态”以供将来使用。这是为了节省应用程序服务器的资源并支持bean故障。

4

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


4

它有好的答案。我想补充一些小的回答。无状态Bean不应该用于保存任何客户端数据。它应该用于“建模可以在一次提交中完成的操作或过程”。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接