我有一个无状态会话 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?

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

有帮助吗?

解决方案

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

其他提示

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

  

无状态会话豆

     

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

在您的情况下,从 methodA()调用 methodB()将在同一个实例上,并且相当于 this.methodB()。因此,我倾向于说 methodB()不能输出传递给 methodA()的值的其他内容。

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

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

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

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

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

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

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

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

实例变量

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

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

类变量

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

因为这很奇怪,我使用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";
       }
    }
    
  3. 这样可行。我不知道你正在使用什么编辑器,但如果它是Netbeans,自己运行它可能会很有趣。

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

当我遇到同样的问题时,我偶然发现了这个问题。在我的例子中,私有方法实际上设置了实例变量。我注意到有时候实例变量已经设置好了,显然是从之前的请求开始。

@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的建议(“不要这样做,而是使用局部变量”)。

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

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

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

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

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

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

希望帮助别人:)

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top