通过手动调用构造函数来创建 props 对象是否安全且推荐?



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

case class Helper(name: String)
object MyNonserializableObject {
val helper = Helper("the helper")
val props7 = Props(new MyActor(helper))
}

所以建议不要像这样创造Actor。上面的答案与 Akka 文档危险变体有关。 另一方面,当我们处理值类作为构造函数参数时,Akka docs 建议通过手动调用构造函数来创建 props,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
}

这两个概念在我看来是自相矛盾的。 顺便说一句,由于我的排名,我不能将这个问题作为评论。

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

同时,如果你看看AnyVal是什么,你会看到这门课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;
}

所以Argument类型只存在于编译时(嗯,大多数情况下,有时 Scala 会实例化它(,如果你想调用 JVM 实际看到的构造函数,你需要传递String不是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

危险吗?文档 说:

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

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

相反,您必须创建一个混合特征的命名类,例如.class MyActor 将 Actor 与 Stash 扩展。

这意味着只要您只使用命名类并为其提供参数,而匿名子类上没有任何 minxins,您就可以删除一个潜在的问题。为了避免闭包问题,您可以执行文档中所说的操作,并在伴随对象中创建该Prop构造。

问题是,当您尝试创建Prop如果您通过互联网将其发送到应用程序的另一部分(如果您有例如 Akka 集群(,它可能会被序列化。如果你尝试序列化一个函数(这里:匿名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是解释中的快捷方式,因为开箱即用object是可序列化的,您必须对它们做一些奇怪的事情才能使它们不可序列化。 例如,如果你这样做了

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,则闭包也将是可序列化的,并且使用将是安全的。就像文档推荐说的那样。

长话短说:

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

是安全的。

最新更新