15

我有一个无状态会话 bean,它包含一个公共方法、几个私有方法和一些实例级变量。下面是一个伪代码示例。

private int instanceLevelVar

public void methodA(int x) { 
  this.instanceLevelVar = x;
  methodB();
}

private void methodB() {
  System.out.println(instanceLevelVar);
}

我看到的是methodB正在打印没有传递给MethodA的值。尽我所能告诉它正在打印来自同一 bean 的其他实例的值。什么会导致这个?

我应该指出代码在 99.9% 的情况下都能按预期工作。但是,0.01% 给我带来了一些严重的问题/担忧。

我知道如果我有不同的公共方法,那么我可能不会在调用之间返回相同的 bean,这将导致这种行为。但是,在这种情况下,唯一的调用是对单个公共方法。容器(在这种情况下为 Glassfish)是否仍会在私有方法调用之间交换 bean?

(编辑)我将“类级别”重命名为“实例级别”,因为这引起了一些混乱。

4

9 回答 9

25

当我阅读什么是会话 Bean?J2EE 1.4 教程的部分:

无状态会话 Bean

状态会话bean 不维护特定客户端的会话状态。当客户端调用无状态 bean 的方法时,bean 的实例变量可能包含状态,但仅限于调用期间。当方法完成时,状态不再保留。除了在方法调用期间,无状态 bean 的所有实例都是等效的,允许 EJB 容器将实例分配给任何客户端。

在您的情况下,对methodB()from的调用methodA()将在同一个实例上,等效于this.methodB(). 因此,我倾向于说methodB()不能输出传递给的值的其他东西methodA()

EJB 2.0 规范中第 7.11.8 节的第一句话证实了这一点:“容器必须确保任何时候只有一个线程可以执行实例”。这意味着您不会遇到来自不同客户端(线程)的数据(在您的实例变量中)混合的情况。在返回之前,您可以确保对实例变量的唯一访问methodA()

也就是说,我并不是说你在某个地方没有问题。但我不认为你的伪代码是等价的。

(编辑:在阅读了对 OP 问题的一些评论后,现在显然对所使用的伪代码和语义存在疑问。我在下面澄清可能的后果。)

正如 Rocket Surgeon 所强调的,你所说的class variable到底是什么意思?您真的是指类变量而不是实例变量吗?如果是,则伪代码不会反映它,但这显然会导致不可预知的行为。实际上,从 EJB 2.0 规范中的第 24.1.2 节(和第一点)来看,很明显不允许将数据写入类变量(尽管您可以这样做)。这一定有充分的理由:)

于 2009-10-27T00:56:52.533 回答
10

我根本不会在无状态会话 bean 中使用实例变量。无论您遇到问题的原因是什么,这可能都不是您想要做的事情。只需尝试在整个过程中使用局部变量或在您从无状态会话 bean 业务方法调用的帮助程序类中定义实例变量。

于 2009-10-27T17:08:54.750 回答
5

问题的可能原因是容器同时在两个请求(因此是两个线程)中使用相同的对象。所以第一个线程到达调用methodB的行,然后下一个线程到达调用methodB的代码,然后第一个线程执行对methodB的调用,导致问题。无论如何,这可以解释这种行为。它似乎不符合规范,但这可能只是一个错误。

一般来说,即使允许,在 bean 中保持状态也不是一个好主意。它会导致代码混乱,并且很容易导致您忘记在每个方法调用上重新开始所有状态的错误。

在方法之间传递这些对象会更好,这样可以避免所有问题。

于 2009-10-27T17:19:17.800 回答
3

可能您没有正确重新初始化实例变量。

实例变量

一般来说,我们不应该在无状态会话 bean 中保留状态。实例变量引用的对象,如果在使用后没有为空,则在请求结束之前一直保持活动状态,如果我们的 EJB 容器将会话 bean 池化以重用,则甚至更长时间。在后一种情况下,我们需要确保实例变量在后续请求期间正确重新初始化。因此使用实例变量可能会导致以下问题:

  • 在同一个请求期间,不同方法之间共享的实例变量很容易导致我们忘记在每个方法调用上以正确状态重新开始的错误
  • 如果 EJB 容器池会话 bean 并且我们的代码可能无法正确重新初始化实例变量,我们可能会重用之前请求中设置的陈旧状态
  • 实例变量具有实例范围,这可能会引入内存泄漏问题,其中堆中的空间用于保留不再使用(或不应使用)的对象。

类变量

至于实例变量,不应使用类变量来保持无状态会话 bean 中的共享状态。这并不意味着我们不应该使用 static 关键字,而是我们应该谨慎使用它(例如定义不可变常量、一些静态工厂类等)

于 2014-01-05T20:37:17.433 回答
2

因为这很奇怪,所以我使用 Netbeans 和本地 Glassfish 2.1 进行了快速测试。

  1. 使用 Samples->Java EE->Servlet Stateless 创建一个新项目。这将创建一个企业项目,其中包含一个简单的无状态 bean 和一个使用它的 servlet。
  2. 我将无状态 bean 修改为如下所示,我认为尽可能接近您的示例。

    @Stateless
    public class StatelessSessionBean implements StatelessSession {
    
       String clName;
    
       private void testNa() {
          System.out.println(clName);
       }
    
       public String sayHello(String name) {
          this.clName = name;
          testNa();
          return "Testcase";
       }
    }
    

这可以正常工作。我不知道您使用的是什么编辑器,但如果是 Netbeans,您自己运行它可能会很有趣。

于 2009-10-27T00:54:46.500 回答
0

这一切都取决于“类级别变量”的含义。类变量必须具有 static 修饰符。如果clName没有,那么无状态会话 bean 的每个实例都有自己的clName. testNa()您的 Java EE 服务器可能创建了一个包含两个或多个无状态会话 bean 实例的池,并且您对任意实例的每个调用sayHello()都会发送到任意实例。

于 2009-10-27T02:23:14.870 回答
0

当我遇到同样的问题时,我偶然发现了这个问题。就我而言,私有方法实际上设置了实例变量。我注意到的是,有时实例变量已经设置,显然来自先前的请求。

@Stateless
public class MyBean {
  String someString;

  public void doSomething() {
    internalDoSomething();
  }

  private void internalDoSomething() {
    if (someString != null) {
      System.out.println("oops, someString already contained old data");
    }

    this.someString = "foo";
  }
}

我想这是有道理的。当容器重用缓存的实例时,它应该如何知道如何清除变量...

对我来说,这符合并证实了 Pascal 对 EJB 规范的引用(“支持实例变量”)和 Rocket Surgeon 的建议(“不要这样做,使用局部变量”)。

于 2011-08-24T09:01:42.050 回答
0

在无状态 Bean 中使用实例变量的问题。

根据 JEE 规范,相同的无状态 EJB 实例也可能与另一个客户端共享。经验法则是不要在无状态 EJB 中创建实例变量。

可能同时访问应用程序的两个客户端提供了相同的 EJB 实例,这会产生问题,因为存在数据不一致。

因此,在无状态 EJB bean 中使用实例变量并不是一个好主意。

于 2016-09-23T14:31:52.543 回答
0

我遇到了类似的问题,因为我在我的 ejb 类中使用了全局静态类变量,并且当我同时运行无状态 EJB 时,变量被其他实例覆盖。

静态类字段在特定类的所有实例之间共享,但仅在单个 Java 虚拟机 (JVM) 中共享。更新静态类字段意味着在类的所有实例之间共享该字段的值。

希望帮助别人:)

于 2017-05-15T10:12:39.833 回答