Как мы видели из наших предыдущих постов, мы могли бы создать актера, используя actorOf
метод ActorSystem
. На самом деле с ActorSystem можно сделать гораздо больше. Мы коснемся только бита Конфигурация и Планирование в этой статье
Давайте посмотрим на подмножества методов, доступных в ActorSystem .
1. КОНФИГУРАЦИЯ УПРАВЛЕНИЯ
Помните application.conf
файл, который мы использовали для настройки уровня нашего журнала в предыдущей записи ? Этот файл конфигурации похож на эти .properties
файлы в приложениях Java и многое другое. Скоро мы увидим, как мы могли бы использовать этот файл конфигурации для настройки наших диспетчеров, почтовых ящиков и т. Д. (Я даже близко не оцениваю мощь конфигурации типа safeafe . Пожалуйста, ознакомьтесь с некоторыми примерами, чтобы по-настоящему оценить его удивительность)
Таким образом, когда мы создаем ActorSystem с использованием объектом ActorSystem в apply
метод без указания какой — либо конфигурации, она высматривает application.conf
, application.json
и application.properties
в корне и загружают классы их автоматически.
Так,
val system=ActorSystem("UniversityMessagingSystem")
такой же как
val system=ActorSystem("UniversityMessagingSystem", ConfigFactory.load())
Чтобы предоставить доказательства этому аргументу, проверьте метод apply в ActorSystem.scala
def apply(name: String, config: Option[Config] = None, classLoader: Option[ClassLoader] = None, defaultExecutionContext: Option[ExecutionContext] = None): ActorSystem = { val cl = classLoader.getOrElse(findClassLoader()) val appConfig = config.getOrElse(ConfigFactory.load(cl)) new ActorSystemImpl(name, appConfig, cl, defaultExecutionContext).start() }
A. ПЕРЕХОДНАЯ КОНФИГУРАЦИЯ ПО УМОЛЧАНИЮ
Если вы не заинтересованы в использовании application.conf
(как в тестовых случаях) или хотели бы иметь свой собственный файл конфигурации (как при тестировании другой конфигурации или развертывании в других средах), вы можете переопределить это, передав вместо этого свою собственную конфигурацию желая одного из classpath.
ConfigFactory.parseString является одним из вариантов
val actorSystem=ActorSystem("UniversityMessageSystem", ConfigFactory.parseString("""akka.loggers = ["akka.testkit.TestEventListener"]"""))
или же
просто в вашем тесте как
class TeacherTestLogListener extends TestKit(ActorSystem("UniversityMessageSystem", ConfigFactory.parseString("""akka.loggers = ["akka.testkit.TestEventListener"]"""))) with WordSpecLike with MustMatchers with BeforeAndAfterAll {
Там также ConfigFactory.load
val system = ActorSystem("UniversityMessageSystem", ConfigFactory.load("uat-application.conf"))
Если вам нужен доступ к вашим собственным параметрам конфигурации во время выполнения, вы можете сделать это через его API следующим образом:
val system=ActorSystem("UniversityMessageSystem", ConfigFactory.parseString("""akka.loggers = ["akka.testkit.TestEventListener"]""")) println (system.settings.config.getValue("akka.loggers")) // Results in > SimpleConfigList(["akka.testkit.TestEventListener"])
B. РАСШИРЕНИЕ КОНФИГУРАЦИИ ПО УМОЛЧАНИЮ
Помимо переопределения, вы также можете расширить конфигурацию по умолчанию с помощью пользовательской конфигурации, используя withFallback
метод Config .
Допустим, ваша application.conf
внешность выглядит так:
akka{ loggers = ["akka.event.slf4j.Slf4jLogger"] loglevel = DEBUG arun="hello" }
и вы решили переопределить akka.loggers
свойство как:
val config=ConfigFactory.parseString("""akka.loggers = ["akka.testkit.TestEventListener"]""") val system=ActorSystem("UniversityMessageSystem", config.withFallback(ConfigFactory.load()))
В итоге вы получите объединенную конфигурацию:
println (system.settings.config.getValue("akka.arun")) //> ConfigString("hello") println (system.settings.config.getValue("akka.loggers")) //> SimpleConfigList(["akka.testkit.TestEventListener"])
Итак, почему я рассказал всю эту историю о конфигурации? Потому что наш ActorSystem
загружает и предоставляет доступ ко всей информации о конфигурации.
ВАЖНАЯ ЗАМЕТКА :
Следите за порядком отступления здесь — это значение по умолчанию и конфигурация расширения. Помните, что вы должны вернуться к конфигурации по умолчанию. Так,
config.withFallback(ConfigFactory.load())
должно сработать
но
ConfigFactory.load().withFallback(config)
не получит результаты, которые вам могут понадобиться.
2. РАСПИСАНИЕ
Как вы можете видеть из API ActorSystem , в ActorSystem есть небольшой мощный метод, который вызывает scheduler
a Scheduler
. Планировщик имеет целый ряд schedule
методов , с которыми мы могли бы сделать некоторые прикольные вещи внутри окружения актера.
А. ГРАФИК ЧТО-ТО, ЧТОБЫ ВЫПОЛНИТЬ ОДИН РАЗ
На нашем примере Student-Teacher , предположим, что StudentActor захочет отправить сообщение учителю только через 5 секунд после его получения InitSignal
из нашего Testcase, а не сразу, наш код выглядит следующим образом:
class StudentDelayedActor (teacherActorRef:ActorRef) extends Actor with ActorLogging { def receive = { case InitSignal=> { import context.dispatcher context.system.scheduler.scheduleOnce(5 seconds, teacherActorRef, QuoteRequest) //teacherActorRef!QuoteRequest } ... ... } }
Прецедент
Давайте подготовим тестовый сценарий, чтобы проверить это:
"A delayed student" must { "fire the QuoteRequest after 5 seconds when an InitSignal is sent to it" in { import me.rerun.akkanotes.messaging.protocols.StudentProtocol._ val teacherRef = system.actorOf(Props[TeacherActor], "teacherActorDelayed") val studentRef = system.actorOf(Props(new StudentDelayedActor(teacherRef)), "studentDelayedActor") EventFilter.info (start="Printing from Student Actor", occurrences=1).intercept{ studentRef!InitSignal } } }
Увеличение времени ожидания перехвата Eventfilter
Уч. Время ожидания по умолчанию для EventFilter для ожидания появления сообщения EventStream
составляет 3 секунды. Давайте увеличим это до 7 секунд, чтобы проверить наш тестовый пример. Свойство filter-leeway
конфигурации помогает нам достичь этого.
class RequestResponseTest extends TestKit(ActorSystem("TestUniversityMessageSystem", ConfigFactory.parseString(""" akka{ loggers = ["akka.testkit.TestEventListener"] test{ filter-leeway = 7s } } """))) with WordSpecLike with MustMatchers with BeforeAndAfterAll with ImplicitSender { ... ...
Б. ГРАФИК ЧТО-ТО, ЧТОБЫ ВЫПОЛНИТЬ ПОВТОРНО
Для повторного выполнения чего-либо вы используете schedule
метод планировщика.
Одна из часто используемых перегрузок метода расписания — это то, что отправляет сообщение актеру на регулярной основе. Он принимает 4 параметра:
- Как долго должна быть начальная задержка до начала первого выполнения
- Частота последующих казней
- Целевой ActorRef, на который мы собираемся отправить сообщение
- Сообщение
case InitSignal=> { import context.dispatcher context.system.scheduler.schedule(0 seconds, 5 seconds, teacherActorRef, QuoteRequest) //teacherActorRef!QuoteRequest }
TRIVIA
Импорт import context.dispatcher
здесь очень важен.
Методы расписания требуют очень важного неявного параметра ExecutionContext
, причина которого будет очевидна, как только мы увидим реализацию schedule
метода:
final def schedule( initialDelay: FiniteDuration, interval: FiniteDuration, receiver: ActorRef, message: Any)(implicit executor: ExecutionContext, sender: ActorRef = Actor.noSender): Cancellable = schedule(initialDelay, interval, new Runnable { def run = { receiver ! message if (receiver.isTerminated) throw new SchedulerException("timer active for terminated actor") } })
Метод schedule просто оборачивает tell
объект, Runnable
который в конечном итоге исполняется с помощью ExecutionContext, который мы передаем.
Чтобы сделать ExecutionContext доступным в области видимости как неявный, мы используем неявный диспетчер, доступный в контексте.
Из ActorCell.scala (Контекст)
/** * Returns the dispatcher (MessageDispatcher) that is used for this Actor. * Importing this member will place an implicit ExecutionContext in scope. */ implicit def dispatcher: ExecutionContextExecutor
КОД
Как всегда, весь проект можно скачать с github здесь .