В отличие от RMI не имеет встроенного механизма для отправки кода класса
удаленная машина. Но Акка делает это очень легко сделать самостоятельно.
ActorSystem может быть создан с загрузчиком классов, который может загружать необходимые классы:
val system = ActorSystem(
"TestSystem",
ConfigFactory.load(),
new ByteClassloader(
Thread.currentThread().getContextClassLoader()))
Ваш загрузчик классов может сотрудничать с актером, который принимает отправленные ему классы:
import akka.actor.Actor
// messages with classcode
case class RegisterRemoteMsg(name: String, clazzB: Array[Byte])
class RegistryActor extends Actor {
def receive = {
case RegisterRemoteMsg(name, bytes) =>
ByteClassLoader.register(name, bytes)
}
}
Этот актер хранит классы на карте, загрузчик классов получает классы с карты:
import java.net.URLClassLoader
import java.net.URL
class ByteArrayClassloader(parent: ClassLoader) extends URLClassLoader(Array[URL](), parent) {
import ByteClassLoader._
override
protected def findClass(name: String) : Class[_] = {
var result = findLoadedClass(name);
if (result == null) {
try {
result = findSystemClass(name);
} catch {
case e: /* ignore */
}
}
if (result == null) {
try {
val classBytes = registeredClasses(name)
result = defineClass(name, classBytes, 0, classBytes.length);
} catch {
case e: Exception => {
throw new ClassNotFoundException(name);
}
}
}
result;
}
}
object ByteClassLoader {
var registeredClasses : Map[String, Array[Byte]] = Map()
def register(name : String, classBytes : Array[Byte]) {
registeredClasses += (name -> classBytes)
}
}
Обратите внимание, что отправляющая сторона должна отправлять байт-код, а не объекты класса.
Объекты класса (classOf [SomeClass]) не сериализуемы, потому что
они «связаны» с отправляющей JVM.
Код класса находится на диске. Обратите внимание, что у класса Scala обычно есть
классы, которые находятся в отдельных файлах. Отправитель может найти все необходимые классы
с помощью:
object LoadClassBytes {
def apply(clazz: Class[_]) : Map[String, Array[Byte]] = {
val basePath : Path = Paths.get(clazz.getProtectionDomain.getCodeSource.getLocation.toURI)
val relName = clazz.getName().replace('.', '/')
val fullPath = basePath.resolve(relName)
val fileName = fullPath.getFileName()
val fileNameHead = (fileName.toString()).split("\\.")(0)
val parentDir = fullPath.getParent()
var res : Map[String, Array[Byte]] = Map()
// find class file and class files of inner classes
val ds = Files.newDirectoryStream(
parentDir,
new DirectoryStream.Filter[Path]{
def accept(file: Path) : Boolean = file.getFileName().toString().matches(fileNameHead+"(\\$.+)?\\.class")
})
try {
val iter = ds.iterator()
while (iter.hasNext()) {
val p = iter.next()
res += (((basePath.relativize(p)).toString().split("\\.")(0).replace('/', '.')) -> Files.readAllBytes(p))
}
} finally {
ds.close
}
res
}
}
и отправьте его RegistryActor на удаленной стороне в RegisterRemoteMsg
val registryActorR = ... retrieve remote registry actor ...
val classesToBeSent = LoadClassBytes(classOf[SomeClass])
for ((name, bytes) <- classesToBeSent) {
registryActorR ! RegisterRemoteMsg(name, bytes)
}