1

我正在尝试熟悉 Akka演员,但我无法解决这两个问题:首先,正如这里所解释的,闭包会导致序列化问题。下面的示例包含一个不可序列化的 Props 对象,因为它关闭了一个不可序列化的对象:

case class Helper(name: String)

object MyNonserializableObject {

   val helper = Helper("the helper")

   val props7 = Props(new MyActor(helper))
}

所以建议不要创建这样的Actor。上面的答案与 Akka docs危险变体有关。另一方面,当我们将值类作为构造函数参数处理时, Akka 文档建议通过手动调用构造函数来创建道具,props3下面的代码是一个示例:

class Argument(val value: String) extends AnyVal

class ValueClassActor(arg: Argument) extends Actor {
  def receive = { case _ => () }
}

object ValueClassActor {
  def props1(arg: Argument) = Props(classOf[ValueClassActor], arg) // fails at runtime
  def props2(arg: Argument) = Props(classOf[ValueClassActor], arg.value) // ok
  def props3(arg: Argument) = Props(new ValueClassActor(arg)) // ok
}

这两个概念在我看来是自相矛盾的。顺便说一句,由于我的等级,我无法创建这个问题作为评论。

4

1 回答 1

1

如果您了解 JVM 的工作原理,这将更容易理解。如果使用classOf[ValueClassActor]参数列表实例化对象,JVM 必须ConstructorClass对象中提取,然后使用 Java 反射 API 实例化对象。

同时,如果您看一下AnyVals 是什么,您会看到该课程正在上课AnyVal

class Argument(val value: String) extends AnyVal

class ValueClassActor(arg: Argument)

编译为:

Compiled from "test.scala"
public class ValueClassActor {
  public ValueClassActor(java.lang.String);
    Code:
       0: aload_0
       1: invokespecial #14                 // Method java/lang/Object."<init>":()V
       4: return
    LineNumberTable:
      line 3: 0
    LocalVariableTable:
      Start  Length  Slot  Name   Signature
          0       5     0  this   LValueClassActor;
          0       5     1   arg   Ljava/lang/String;
}

所以Argumenttype 只在编译时存在(嗯,大多数情况下,有时 Scala 会实例化它),如果你想调用 JVM 实际看到的构造函数,你需要传递Stringistead of Argument. 这就是为什么你有这种行为的原因:

  def props1(arg: Argument) = Props(classOf[ValueClassActor], arg) // fails at runtime
  def props2(arg: Argument) = Props(classOf[ValueClassActor], arg.value) // ok

为避免处理此问题,您可以使用不依赖运行时反射的Props创建者:

def apply[T <: Actor](creator: => T)(implicit arg0: ClassTag[T]): Props

危险吗?文档说:

CAVEAT:在创建实例时使用匿名 mixin 组合时无法检测到所需的邮箱类型。例如,以下内容不会检测到对 Stash 中定义的 DequeBasedMessageQueueSemantics 的需求:

'Props(new Actor with Stash { ... })

相反,您必须创建一个混合特征的命名类,例如 MyActor 类扩展了带有 Stash 的 Actor。

这意味着只要您将简单地使用命名类并且只为它提供参数而不在匿名子类上使用任何 minxins,您就可以消除一个潜在问题。为避免出现关闭问题,您可以按照文档中的说明Prop进行操作,并在伴随对象中创建该构造。

问题是,当您尝试创建Prop它时,如果您通过 Internet 将它发送到应用程序的另一部分(如果您有例如 Akka Cluster),它可能会被序列化。如果您尝试序列化一个函数(这里:匿名Function,即`new ValueClassActor(arg)),如果您尝试序列化它,它将获取它的整个闭包。由于 Java 的工作方式,这个函数将有一个指向创建它的父对象的指针。

如果你有

class Foo(s: => String)

object Foo {
  def hello: Foo = new Foo("test") // "test" is by-name so it has closure
}

你看看生成的字节码,你会发现有

Compiled from "foo.scala"
public class Foo {
  public static Foo hello();
    Code:
       0: getstatic     #16                 // Field Foo$.MODULE$:LFoo$;
       3: invokevirtual #18                 // Method Foo$.hello:()LFoo;
       6: areturn

  public Foo(scala.Function0<java.lang.String>);
    Code:
       0: aload_0
       1: invokespecial #25                 // Method java/lang/Object."<init>":()V
       4: return
    LineNumberTable:
      line 3: 0
      line 1: 4
    LocalVariableTable:
      Start  Length  Slot  Name   Signature
          0       5     0  this   LFoo;
          0       5     1     s   Lscala/Function0;
}

Compiled from "foo.scala"
public final class Foo$ {
  public static final Foo$ MODULE$;

  public static {};
    Code:
       0: new           #2                  // class Foo$
       3: dup
       4: invokespecial #17                 // Method "<init>":()V
       7: putstatic     #19                 // Field MODULE$:LFoo$;
      10: return
    LineNumberTable:
      line 3: 0

  public Foo hello();
    Code:
       0: new           #23                 // class Foo
       3: dup
       4: invokedynamic #44,  0             // InvokeDynamic #0:apply:()Lscala/Function0;
       9: invokespecial #47                 // Method Foo."<init>":(Lscala/Function0;)V
      12: areturn
    LineNumberTable:
      line 4: 0
    LocalVariableTable:
      Start  Length  Slot  Name   Signature
          0      13     0  this   LFoo$;

  public static final java.lang.String $anonfun$hello$1();
    Code:
       0: ldc           #50                 // String test
       2: areturn
    LineNumberTable:
      line 4: 0
}

意思就是:

  • 当您调用Foo.hello并创建 lambdanew Foo("test")时,该函数的闭包只是伴随对象实例
  • 伴随对象正在实施Serializable
  • 因此它满足函数闭包可序列化的要求

object MyNonserializableObject是一种解释的捷径,因为开箱即用的objects是可序列化的,您必须对它们做一些奇怪的事情才能使它们不可序列化。例如,如果你这样做了

trait Bar {

  object Baz {
    def hello: Foo = new Foo("test")  // "test" is by-name so it has closure
  }
}

闭包将持有对 的引用Baz,它将持有对的引用,Bar并且如果任何扩展Bar都不可序列化,那么闭包也不会如此。但是,如果你将在object顶层生成你的 lambda(不嵌套在其他类中等),那么你的闭包只能依赖于可序列化的东西(因为object它们自己有空的构造函数和实现Serializable接口) ,因此可以自行序列化。

当涉及到Props和按名称参数时,同样的原则也适用。如果你Prop在顶层的伴随对象中创建了一个 using by-name 参数(或者以其他方式保证是可序列化的),那么闭包也将是可序列化的,并且使用将是安全的。就像文档推荐说的那样。

长话短说:

class ValueClassActor(arg: Argument) extends Actor {
  def receive = { case _ => () }
}

object ValueClassActor {
  def props(arg: Argument) = Props(new ValueClassActor(arg))
}

是安全的。

于 2020-05-21T11:16:33.737 回答