How to create an Akka Actor given the class name

2019-06-26 17:23发布

问题:

I would like to create an Akka actor by using the class name, as shown. I've tried many variations of system.actorOf(new Props(theProcessor.getClass), name = "Test"), but I just cannot get this to work. Any idea in creating an Actor from the class loader

package com.test

import akka.actor.{Props, Actor, ActorRef, ActorSystem}

object Main {
  def main(args: Array[String]) {
   ActorFromString("Test")
  }
}

object ActorFromString {
  implicit val system = ActorSystem("Test")
  def apply(name: String): ActorRef = {
    val className = "com.test." + name + "Processor"
    val theProcessor: Actor = Class.forName(className).newInstance().asInstanceOf[Actor]
    system.actorOf(new Props(theProcessor.getClass), name = "Test")
  }
}

class TestProcessor extends Actor {
  def receive = {
    case data => println("processing data")
  }
}

Exception in thread "main" akka.actor.ActorInitializationException:
You cannot create an instance of [com.test.TestProcessor] explicitly using the constructor (new).
You have to use one of the factory methods to create a new actor. Either use:
'val actor = context.actorOf(Props[MyActor])'        (to create a supervised child actor from    within an actor), or
'val actor = system.actorOf(Props(new MyActor(..)))' (to create a top level actor from the   ActorSystem)
at akka.actor.ActorInitializationException$.apply(Actor.scala:166)
at akka.actor.Actor$class.$init$(Actor.scala:377)
at com.test.TestProcessor.<init>(ActorFromString.scala:20)
at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
at  sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:57)
at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
at java.lang.reflect.Constructor.newInstance(Constructor.java:525)
at java.lang.Class.newInstance0(Class.java:374)
at java.lang.Class.newInstance(Class.java:327)
at com.test.ActorFromString$.apply(ActorFromString.scala:15)
at com.test.Main$.main(ActorFromString.scala:7)
at com.test.Main.main(ActorFromString.scala)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:601)
at com.intellij.rt.execution.application.AppMain.main(AppMain.java:120)

Process finished with exit code 143

回答1:

You could use the Java API (just for the Props instantiation):

val myActor = system.actorOf( 
  new Props( Class.forName( "myActorClassName" ).asInstanceOf[Class[Actor]] ) 
)

The important part is the new keyword. If you ommit it, you will use the Scala API instead, which does not allow instantiation by class.



回答2:

Sounds like you want to use the same ClassLoader as the one that is used by your ActorSystem, so this is easily solved with a custom Akka Extension (Warning, not actually compiled but you'll get the gist of it)

import akka.actor.{ Extension, ExtensionId, ExtensionIdProvider, ExtendedActorSystem, DynamicAccess }
// This will be instantiated once per ActorSystem instance
class Reflection(access: DynamicAccess) extends Extension {
  // Loads the Class with the provided Fully Qualified Class Name using the given DynamicAccess
  // Throws exception if it fails to load
  def actorClassFor(fqcn: String) = access.getClassFor[Actor](fqcn).get
}
// This is how we access the Reflection extension, you can view it as an ActorSystemLocal
object Reflect extends ExtensionId[Reflection] with ExtensionIdProvider {
  override def lookup = Reflect
  override def createExtension(system: ExtendedActorSystem) = new Reflection(system.dynamicAccess)
}
// Load the extension if not already loaded, return the instance for this ActorSystem and call the actorClassFor
system.actorOf(Props(Reflect(system).actorClassFor("com.test." + name + "Processor"))


回答3:

(As indicated in the stacktrace) you have to use either

  • system.actorOf(Props[TestProcessor]) (no new keyword, you pass a type)
  • or system.actorOf(Props(new TestProcessor())) (new keyword, you pass an instance)


回答4:

If you can get a Props from a String then you can get an actor from a String. Here is how to get a Props from a String:

Props.apply(Class.forName( "myActorClassName" ).asInstanceOf[Class[Actor]])

I put this up because none of the other answers use Props.apply - a relatively recent enhancement to Akka. It is easy to pass in constructor arguments, for instance the current actorRef, for example:

def selfActorRef = context.self
val gateKeeperRef = context.actorOf(
  Props.apply(Class.forName(findGateKeeperName).asInstanceOf[Class[Actor]],
  selfActorRef)
)


标签: scala akka