具有实例变量的无状态会话bean

时间:2009-10-27 00:07:12

标签: java java-ee glassfish stateless-session-bean

我有一个无状态会话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%的时间按预期工作。但是,。01%对我造成了一些严重的问题/担忧。

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

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

9 个答案:

答案 0 :(得分:24)

当我阅读J2EE 1.4教程的What is a Session Bean?部分时:

  

无状态会话豆

     

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

在您的情况下,来自methodB()的{​​{1}}来电将在同一个实例上,相当于methodA()。因此,我倾向于说this.methodB()无法输出传递给methodB()的值的其他内容。

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

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

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

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

答案 1 :(得分:10)

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

答案 2 :(得分:5)

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

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

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

答案 3 :(得分:3)

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

实例变量

一般情况下,我们不应该在无状态会话bean中保持状态。实例变量引用的对象,如果在使用后未被清零,则会保持活动直到请求结束,如果我们的EJB容器将会话bean汇集到重用中,则更长。在后一种情况下,我们需要确保在后续请求期间正确地重新初始化实例变量。因此,使用实例变量可能会导致以下问题:

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

类变量

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

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

答案 5 :(得分:0)

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

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

答案 7 :(得分:0)

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

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

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

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

答案 8 :(得分:0)

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

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

希望帮助别人:)