Как мы видели из наших предыдущих постов, мы могли бы создать Actor, используя метод actorOf
системы ActorSystem
. На самом деле с ActorSystem можно сделать гораздо больше. Мы коснемся только бита Конфигурация и Планирование в этой статье.
Давайте посмотрим на подмножества методов, доступных в ActorSystem .
1. Управление конфигурацией
Помните файл application.conf
который мы использовали для настройки уровня нашего журнала в предыдущей записи ? Этот файл конфигурации похож на файлы .properties
в приложениях Java и многое другое. Скоро мы увидим, как мы могли бы использовать этот файл конфигурации для настройки наших диспетчеров, почтовых ящиков и т. Д. (Я даже близко не оцениваю мощь конфигурации типа safeafe . Пожалуйста, ознакомьтесь с некоторыми примерами, чтобы действительно оценить его удивительность) .
Поэтому, когда мы создаем ActorSystem с помощью метода apply объекта ActorSystem без указания какой-либо конфигурации, он ищет application.conf
, application.json
и application.properties
в корне пути к классам и загружает их автоматически.
Так:
1
|
val system=ActorSystem( "UniversityMessagingSystem" ) |
такой же как:
1
|
val system=ActorSystem( "UniversityMessagingSystem" , ConfigFactory.load()) |
Чтобы предоставить доказательства этому аргументу, проверьте метод apply в ActorSystem.scala :
1
2
3
4
5
|
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() } |
а. Переопределение конфигурации по умолчанию
Если вы не заинтересованы в использовании application.conf
(как в тестовых случаях) или хотели бы иметь свой собственный файл конфигурации (как при тестировании другой конфигурации или развертывании в других средах), вы можете отменить это, передав свой собственная конфигурация вместо того, чтобы хотеть тот из classpath.
ConfigFactory.parseString является одним из вариантов
1
|
val actorSystem=ActorSystem( "UniversityMessageSystem" , ConfigFactory.parseString( "" "akka.loggers = [" akka.testkit.TestEventListener "]" "" )) |
или просто в вашем Testcase как:
1
2
3
4
|
class TeacherTestLogListener extends TestKit(ActorSystem( "UniversityMessageSystem" , ConfigFactory.parseString( "" "akka.loggers = [" akka.testkit.TestEventListener "]" "" ))) with WordSpecLike with MustMatchers with BeforeAndAfterAll { |
Там также ConfigFactory.load
1
|
val system = ActorSystem( "UniversityMessageSystem" , ConfigFactory.load( "uat-application.conf" )) |
Если вам нужен доступ к вашим собственным параметрам конфигурации во время выполнения, вы можете сделать это через его API следующим образом:
1
2
|
val system=ActorSystem( "UniversityMessageSystem" , ConfigFactory.parseString( "" "akka.loggers = [" akka.testkit.TestEventListener "]" "" )) println (system.settings.config.getValue( "akka.loggers" )) // Results in > SimpleConfigList(["akka.testkit.TestEventListener"]) |
б. Расширение конфигурации по умолчанию
Помимо переопределения, вы также можете расширить конфигурацию по умолчанию с помощью своей пользовательской конфигурации, используя метод withFallback
Config .
Допустим, ваш application.conf
выглядит так:
1
2
3
4
5
|
akka{ loggers = [ "akka.event.slf4j.Slf4jLogger" ] loglevel = DEBUG arun= "hello" } |
и вы решили переопределить свойство akka.loggers
например:
1
2
|
val config=ConfigFactory.parseString( "" "akka.loggers = [" akka.testkit.TestEventListener "]" "" ) val system=ActorSystem( "UniversityMessageSystem" , config.withFallback(ConfigFactory.load())) |
В итоге вы получите объединенную конфигурацию:
1
2
|
println (system.settings.config.getValue( "akka.arun" )) //> ConfigString("hello") println (system.settings.config.getValue( "akka.loggers" )) //> SimpleConfigList(["akka.testkit.TestEventListener"]) |
Итак, почему я рассказал всю эту историю о конфигурации? Потому что наша ActorSystem
является той, которая загружает и предоставляет доступ ко всей информации о конфигурации.
ВАЖНАЯ ЗАМЕТКА
Следите за порядком отступления здесь — это значение по умолчанию и конфигурация расширения. Помните, что вы должны вернуться к конфигурации по умолчанию. Так:
1
|
config.withFallback(ConfigFactory.load()) |
будет работать, но:
1
|
ConfigFactory.load().withFallback(config) |
не получит результаты, которые вам могут понадобиться.
2. Планировщик
Как вы можете видеть из API ActorSystem , в ActorSystem есть небольшой мощный метод, называемый scheduler
который возвращает Scheduler
. В планировщике есть множество методов schedule
с помощью которых мы могли бы делать забавные вещи в среде Actor.
а. Запланируйте что-нибудь для выполнения
На нашем примере Student-Teacher , предположим, что StudentActor захочет отправить сообщение учителю только через 5 секунд после получения InitSignal
из нашего Testcase, а не сразу, наш код выглядит следующим образом:
01
02
03
04
05
06
07
08
09
10
11
12
|
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 } ... ... } } |
Прецедент
Давайте подготовим тестовый сценарий, чтобы проверить это:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
|
"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
помогает нам достичь этого.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
|
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 { ... ... |
б. Запланируйте что-нибудь для выполнения повторно
Для повторного выполнения чего-либо вы используете метод расписания Планировщика.
Одна из часто используемых перегрузок метода расписания — это то, что отправляет сообщение актеру на регулярной основе. Он принимает 4 параметра:
- Как долго должна быть начальная задержка до начала первого выполнения
- Частота последующих казней
- Целевой ActorRef, на который мы собираемся отправить сообщение
- Сообщение
1
2
3
4
5
|
case InitSignal=> { import context.dispatcher context.system.scheduler.schedule( 0 seconds, 5 seconds, teacherActorRef, QuoteRequest) //teacherActorRef!QuoteRequest } |
TRIVIA
Импорт- import context.dispatcher
здесь очень важен.
Методы расписания требуют очень важного неявного параметра — ExecutionContext
, причина которого будет очевидна, как только мы увидим реализацию метода schedule
:
01
02
03
04
05
06
07
08
09
10
11
12
13
|
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 просто помещает сообщение в Runnable
которое в конечном итоге исполняется с помощью ExecutionContext, который мы передаем.
Чтобы сделать ExecutionContext доступным в области видимости как неявный, мы используем неявный диспетчер, доступный в контексте.
Из ActorCell.scala (Context):
1
2
3
4
5
|
/** * 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 здесь .
Ссылка: | Akka Notes — ActorSystem (Конфигурирование и планирование) — 4 от нашего партнера JCG Аруна Маниваннана в блоге Rerun.me . |