当前位置: 首页 > 知识库问答 >
问题:

为什么容器在不同的会话之间共享我的EJB有状态会话bean?

祖新觉
2023-03-14

我有一个简单的有状态会话bean(一个单操作堆栈计算器):

package beans;
import java.util.EmptyStackException;
import java.util.Stack;
import javax.ejb.LocalBean;
import javax.ejb.Stateful;
@Stateful
@LocalBean
public class StackCalcBean {
    private static int instanceCounter = 0;
    private int instanceID;
    private Stack<Double> stack;
    public StackCalcBean() {
        instanceCounter++;
        instanceID = instanceCounter;
        stack = new Stack<>();
    }
    public void push(double d) {
        stack.push(d);  
    }
    public String plus() {
        try {
            double d = stack.pop() + stack.pop();
            stack.push(d);
            return Double.toString(d);
        } catch (EmptyStackException e) {
            return "Empty Stack !";
        }
    }
    public String myToString() {
        return "StackCalc [instanceID=" + instanceID + ", stack=" + stack + "]";
    }
}

这个servlet工作正常:

package servlets;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.EmptyStackException;
import java.util.Scanner;

import javax.ejb.EJB;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import beans.StackCalcBean;

@WebServlet("/StackCalc")
public class StackCalcServlet extends HttpServlet {
    private static final long serialVersionUID = 1L;
    @EJB 
    private StackCalcBean calc;

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

    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        System.out.println("In doPost");
        String input = request.getParameter("input");
        boolean newResult = false;
        String value = "no value";
        String boxStyle = "font-size:18pt;padding:30px;width:650px;margin:auto;"
                + "border:solid;border-width:1px;border-radius:15px;" 
                + "margin-top:40px;";
        if (input != null) {
            Scanner in = new Scanner(input);
            if (in.hasNextDouble()) {
                double n = Double.parseDouble(input);
                calc.push(n);
            } else {
                switch (input) {
                case "+" : value = calc.plus(); break;
                }
                newResult = true;
            }
            in.close();
        }
        response.setContentType( "text/html" );
        try ( PrintWriter out = response.getWriter() ) {
            out.println("<!DOCTYPE html>");
            out.println("<html><head><title>StackCalc</title></head><body>");
            out.println("<div style='"+boxStyle+"'>Session: "+ 
                        request.getSession() + " " + request.getSession().getId() + "</div>");
            out.println("<div style='"+boxStyle+"'>StackCalcBean: "+ calc.myToString() + "</div>");
            out.println( "<div style='"+boxStyle+"'><form method='POST' action='StackCalc'>" );
            if (newResult) {
                out.println("<div>"+value+"</div>");
            }
            out.println( "Input :" ); 
            out.println( "<input name='input' type='text' autofocus />" );
            out.println( "<input name='btnSubmit' type='submit' value='Send' /><br/>" );
            out.println( "</div></body></html>" );
        }
    }
}

除了容器在不同的会话中提供相同的bean之外,这种方法工作得很好。我知道会话不一样,因为我打印会话ID,并且在不同的机器上使用不同的客户端。我知道bean是相同的,因为它具有相同的instanceID值和相同的堆栈内容。

我希望如果在不同的机器上运行客户机,我会得到一个新的计算器实例。

我尝试了WildFly 21和Glassfish 5,我得到了同样的行为。

很明显,我遗漏了一些东西。

编辑:解决方案可能包括显式地将calc实例与Web会话关联:

StackCalcBean calc = null;
if (request.getSession().getAttribute("calc") == null) {
     try {
        InitialContext ctx= new InitialContext();
        calc = (StackCalcBean) ctx.lookup("java:module/StackCalcBean!beans.StackCalcBean");
        request.getSession().setAttribute("calc", calc);
    } catch (NamingException e) {
        e.printStackTrace();
    }
} else {
   calc = (StackCalcBean) request.getSession().getAttribute("calc");
}

共有1个答案

姚棋
2023-03-14

“有状态会话EJB”中的“会话”与servlet容器中的“会话”无关!

我的意思是(因为,是的,这是一个令人困惑的问题): servlet容器可以维护一个web“会话”,由Http会话接口表示。这可以被视为服务器内存中的一个区域,其中存储了任何需要比单个HTTP请求寿命更长的信息。此信息可由后续HTTP请求共享。请求必须携带某种标识,将其链接到服务器端的“会话”内存。这通常是饼干。

“有状态会话EJB”实现了一个类似的概念:一段服务器内存加功能,它会持续存在,直到显式删除或由于不活动而超时。有状态会话EJB的每个实例都有一个id,类似于用于标识Web会话的id(例如cookie),但是这个id绝对没有与Web会话的连接!事实上,它根本不需要网络会话!

通过home接口手动创建EJB的旧方法使这种区别更加明确。每次调用主创建方法时,都会得到一个有状态EJB的新实例。您可以从不属于web请求/会话的代码(例如客户端、计时器、消息处理程序)调用它,因此没有活动的web会话;或者您可以多次调用它,并获得对有状态EJB的各个实例的尽可能多的引用。如果传递引用,您甚至可以在web会话之间共享有状态EJB的同一实例。

使用@EJB时,有状态EJB实例似乎绑定到它被注入的组件的生命周期。在这种情况下,只要servlet实例存在,它就使用相同的有状态EJB实例。servlet实例通常是全局的(无论如何,它们是由应用程序服务器处理的,因此一个servlet实例可能服务于多个请求,甚至是并行的)。这就是为什么你会有这种行为。

解决这个问题的一种方法是使用CDI。您可以将EJB绑定到web会话,方法是将其标记为@SessionScoped

@SessionScoped
@Stateful
@LocalBean
public class StackCalcBean {
    ...
}

并将其注入CDI:

@WebServlet("/StackCalc")
public class StackCalcServlet extends HttpServlet {
    ...
    @Inject
    private StackCalcBean calc;
    ...
}

注:在我看来,CDI比EJB更通用、更可靠。如果使用CDI解决方案,甚至可以完全跳过EJB注释,从而使StackCalcBean成为真正的CDI组件。换句话说,您不再需要它成为EJB。如果您这样做了,请记住有一些功能上的差异,例如EJB在默认情况下是事务性的,CDIBean只需要@transactional注释。

 类似资料:
  • 我有一个简单的pojo,其中有一个字段: 现在,我使用两个不同的浏览器窗口(firefox和chrome)作为两个不同的用户登录到我的web应用程序。令我惊讶的是,当我从一个会话设置配额的值(使用)时,新的值将可用于另一个会话(当调用时)。我希望每个用户会话都有自己的bean实例;这不是spring中会话作用域bean的用途吗? 我一定错过了什么。可能是什么? 编辑: 实现类如下所示: 最后,这里

  • 在我以前做编码的时候,我主要使用无状态的会话bean,所有跨页遍历所需的信息都放在HTTP Session对象中。当时(甚至现在),我从来没有理解过EJB的“USP”对于“业务层”实现是“透明的”和“安全的”,它处理骨架和存根以及其他行话的各种方法,这些行话是以易用性/安全性为名的矫枉过正的伪装。我只是想知道,如果可以通过SLSB+HttpSession实现同样的功能,那么为什么要使用有状态会话E

  • 问题内容: 我们希望将一个正在运行的应用程序拆分为两个不同的文件,以便能够更新一个应用程序而不影响另一个应用程序。每个Web应用程序将具有不同的UI,不同的用户和不同的部署时间表。 最简单的路径似乎是共享同一会话,因此如果应用程序A设置了应用程序B,则可以看到它。 有没有办法在同一个Tomcat实例中共享两个应用程序的状态? 我们的应用程序在专用的Tomcat 5.5上运行,在同一tomcat实例

  • 对于有状态会话bean(SFSB)和无状态会话bean(SLSB)的用法,我有点困惑。 我知道SFSB与客户保持状态。这很有帮助:什么时候使用有状态会话bean而不是无状态会话bean? 这里和许多其他地方提供的示例是SFSB的购物车。 “如果一个任务需要一系列方法调用(不止一次),并且您需要保留以前的结果以在下一次调用中使用它们,那么就可以使用SFSB”--Source。这将更像是签出(页面之间

  • 我正在学习j2ee,如果问题看起来很基本请原谅。 在httpsession中,会话ID存储在客户端,与之相关的数据存储在服务器端。 现在,当我在POJO上使用CDI@SessionScoped时,这是否意味着EJB容器(?)在会话中存储pojo。(Session.SetAttribute(POJO)) CDI可以区分SFB、SLB和POJO吗?

  • 问题内容: 是否有使用节点,表达和redis / predis共享PHPSESSID的最新指南(或示例代码)? 我发现有1-2年的一些教程,它们都使用旧版本的Express或不使用Express。 Express cookie解析器也已弃用。 https://simplapi.wordpress.com/2012/04/13/php-and-node-js-session-share- redi/