Search code examples
scalaakkascala-reflect

Dynamically Load an Actor if it's there


I have the following code

lazy val restEndpoint = context.actorOf(Props[RestEndpoint], "RestEndpoint")

However, I want to dynamically load the actor if it's there for a few reasons:

  1. It may not be on the classpath, so I would have to ask the class loader if it's there.
  2. Even if it's on the classpath, I may not want to load it for configuration reasons.
  3. RestEndpoint is in a different JAR file that already has dependencies on this JAR file, so I can't have circular dependencies.

Is there some 'easy' way to do this reflectively? Please don't point me at the documentation on reflection in Scala, because there is nothing easy there. If there is a Scala Reflection for Dummies discussion I would appreciate looking at that.

A working example would be greatly appreciated.


Solution

  • Really glad we have a Typesafe support contract. Here is the solution we came up with. I have tested the code, and it works. Note: reflection was not necessary, which made me very happy.

    def actorRefForName(className: String) = try {
      val actorClass = Class.forName(className)
      Some(context.actorOf(Props(actorClass), actorClass.getSimpleName))
    } catch {
      case classNotFoundException: ClassNotFoundException =>
      log.info(s"class $className not found. This actor will not be used")
      None
    }
    
    . . .
    
    lazy val kinesisProducer =
      actorRefForName("com._3tierlogic.KinesisManager.producer.KinesisProducer")
    
    . . .
    
    def receive = {
    
      case Start =>
    
        kinesisProducer match {
          case Some(kinesisProducerRef) =>
            log.info("Starting " + kinesisProducerRef.path.name)
            kinesisProducerRef ! Start
          case None =>
            log.info("There is no Kinesis Producer actor to start.")
        }
    
      case Started =>
    
        // foreach is a little confusing when there is only Some or None, but
        // basically we can only use our actorRef if there is one. EK
        kinesisProducer.foreach { kinesisProducerRef =>
          if (sender.equals(kinesisProducerRef)) {
            log.info(kinesisProducerRef.path.name + " Started")
            log.info("Starting " + restEndpointRef.path.name)
            IO(Http)(context.system) ! Http.Bind(restEndpointRef, "0.0.0.0", port = 8061)
          }
    }
    

    While this adds a little extra boilerplate, I don't think it's too bad. There are probably ways I could reduce the boilerplate more.

    Typesafe also recommended I look at Akka Extensions and ExtendedActorSystem