«1. Введение
В этой статье мы сосредоточимся на интеграции Akka с Spring Framework, чтобы разрешить внедрение сервисов на основе Spring в актеры Akka.
Прежде чем читать эту статью, рекомендуется ознакомиться с основами Akka.
2. Внедрение зависимостей в Akka
Akka — это мощная среда приложений, основанная на модели параллелизма актеров. Фреймворк написан на Scala, что, конечно же, делает его полностью пригодным для использования в приложениях на основе Java. И поэтому очень часто нам нужно интегрировать Akka с существующим приложением на основе Spring или просто использовать Spring для подключения bean-компонентов к акторам.
Проблема с интеграцией Spring/Akka заключается в различии между управлением bean-компонентами в Spring и управлением акторами в Akka: у акторов есть определенный жизненный цикл, который отличается от типичного жизненного цикла bean-компонентов Spring.
Кроме того, акторы разделены на сам актор (который является деталью внутренней реализации и не может управляться Spring) и ссылку на актора, доступную для клиентского кода, а также сериализуемую и переносимую между различными средами выполнения Akka.
К счастью, Akka предоставляет механизм, а именно расширения Akka, которые делают использование внешних сред внедрения зависимостей довольно простой задачей.
3. Зависимости Maven
Чтобы продемонстрировать использование Akka в нашем проекте Spring, нам понадобится минимальная зависимость Spring — библиотека spring-context, а также библиотека akka-actor. Версии библиотеки можно извлечь в раздел \u003cproperties\u003e pom:
<properties>
<spring.version>4.3.1.RELEASE</spring.version>
<akka.version>2.4.8</akka.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>com.typesafe.akka</groupId>
<artifactId>akka-actor_2.11</artifactId>
<version>${akka.version}</version>
</dependency>
</dependencies>
Обязательно проверьте Maven Central на наличие последних версий зависимостей spring-context и akka-actor.
Обратите внимание, что зависимость akka-actor имеет постфикс _2.11 в своем имени, что означает, что эта версия фреймворка Akka была создана для Scala версии 2.11. Соответствующая версия библиотеки Scala будет транзитивно включена в вашу сборку.
4. Внедрение Spring Beans в актеров Akka
Давайте создадим простое приложение Spring/Akka, состоящее из одного актера, который может отвечать на имя человека, отправляя приветствие этому человеку. Логика приветствия будет выделена в отдельный сервис. Мы хотим автоматически связать эту службу с экземпляром актора. Интеграция с Spring поможет нам в этой задаче.
4.1. Определение актора и службы
Чтобы продемонстрировать внедрение службы в актор, мы создадим простой класс GreetingActor, определенный как нетипизированный актор (расширение базового класса Akka UntypedActor). Основным методом каждого актора Akka является метод onReceive, который получает сообщение и обрабатывает его в соответствии с определенной логикой.
В нашем случае реализация GreetingActor проверяет, относится ли сообщение к предопределенному типу Greet, затем берет имя человека из экземпляра Greet, затем использует GreetingService для получения приветствия для этого человека и отвечает отправителю полученным сообщением. приветственная строка. Если сообщение имеет какой-то другой неизвестный тип, оно передается предварительно определенному необработанному методу актора.
Давайте посмотрим:
@Component
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class GreetingActor extends UntypedActor {
private GreetingService greetingService;
// constructor
@Override
public void onReceive(Object message) throws Throwable {
if (message instanceof Greet) {
String name = ((Greet) message).getName();
getSender().tell(greetingService.greet(name), getSelf());
} else {
unhandled(message);
}
}
public static class Greet {
private String name;
// standard constructors/getters
}
}
Обратите внимание, что тип сообщения Greet определен как статический внутренний класс внутри этого актора, что считается хорошей практикой. Принятые типы сообщений должны быть определены как можно ближе к актору, чтобы избежать путаницы в отношении того, какие типы сообщений может обрабатывать этот актор.
Также обратите внимание на аннотации Spring @Component и @Scope — они определяют класс как bean-компонент, управляемый Spring, с областью действия прототипа.
Область действия очень важна, потому что каждый запрос на получение компонента должен приводить к созданию вновь созданного экземпляра, поскольку такое поведение соответствует жизненному циклу актера Akka. Если вы реализуете этот бин с какой-то другой областью видимости, типичный случай перезапуска акторов в Akka, скорее всего, будет работать некорректно.
Наконец, обратите внимание, что нам не нужно было явно @Autowire экземпляр GreetingService — это возможно благодаря новой функции Spring 4.3 под названием Implicit Constructor Injection.
«Реализация GreeterService довольно проста, обратите внимание, что мы определили его как компонент, управляемый Spring, добавив к нему аннотацию @Component (с одноэлементной областью действия по умолчанию):
@Component
public class GreetingService {
public String greet(String name) {
return "Hello, " + name;
}
}
4.2. Добавление поддержки Spring через расширение Akka
Самый простой способ интегрировать Spring с Akka — через расширение Akka.
Расширение — это отдельный экземпляр, созданный для каждой системы акторов. Он состоит из самого класса расширения, который реализует расширение интерфейса маркера, и класса идентификатора расширения, который обычно наследует AbstractExtensionId.
Поскольку эти два класса тесно связаны, имеет смысл реализовать класс Extension, вложенный в класс ExtensionId:
public class SpringExtension
extends AbstractExtensionId<SpringExtension.SpringExt> {
public static final SpringExtension SPRING_EXTENSION_PROVIDER
= new SpringExtension();
@Override
public SpringExt createExtension(ExtendedActorSystem system) {
return new SpringExt();
}
public static class SpringExt implements Extension {
private volatile ApplicationContext applicationContext;
public void initialize(ApplicationContext applicationContext) {
this.applicationContext = applicationContext;
}
public Props props(String actorBeanName) {
return Props.create(
SpringActorProducer.class, applicationContext, actorBeanName);
}
}
}
Во-первых, SpringExtension реализует единственный метод createExtension из класса AbstractExtensionId, который учитывает для создания экземпляра расширения объект SpringExt.
Класс SpringExtension также имеет статическое поле SPRING_EXTENSION_PROVIDER, которое содержит ссылку на его единственный экземпляр. Часто имеет смысл добавить закрытый конструктор, чтобы явно указать, что SpringExtention должен быть одноэлементным классом, но мы его опустим для ясности.
Во-вторых, статический внутренний класс SpringExt является самим расширением. Поскольку расширение — это просто маркерный интерфейс, мы можем определить содержимое этого класса по своему усмотрению.
В нашем случае нам понадобится метод initialize для сохранения экземпляра Spring ApplicationContext — этот метод будет вызываться только один раз при инициализации расширения.
Также нам потребуется метод props для создания объекта Props. Экземпляр Props является чертежом актера, и в нашем случае метод Props.create получает класс SpringActorProducer и аргументы конструктора для этого класса. Это аргументы, с которыми будет вызываться конструктор этого класса.
Метод props будет выполняться каждый раз, когда нам понадобится ссылка на актор, управляемый Spring.
Третья и последняя часть головоломки — это класс SpringActorProducer. Он реализует интерфейс IndirectActorProducer от Akka, который позволяет переопределить процесс создания экземпляра актора путем реализации методов product и ActorClass.
Как вы, наверное, уже догадались, вместо прямого создания экземпляра всегда будет извлекаться экземпляр актора из Spring ApplicationContext. Поскольку мы сделали актор компонентом с областью действия прототипа, каждый вызов метода product будет возвращать новый экземпляр актора:
public class SpringActorProducer implements IndirectActorProducer {
private ApplicationContext applicationContext;
private String beanActorName;
public SpringActorProducer(ApplicationContext applicationContext,
String beanActorName) {
this.applicationContext = applicationContext;
this.beanActorName = beanActorName;
}
@Override
public Actor produce() {
return (Actor) applicationContext.getBean(beanActorName);
}
@Override
public Class<? extends Actor> actorClass() {
return (Class<? extends Actor>) applicationContext
.getType(beanActorName);
}
}
4.3. Собираем все вместе
Единственное, что осталось сделать, это создать класс конфигурации Spring (помеченный аннотацией @Configuration), который сообщит Spring сканировать текущий пакет вместе со всеми вложенными пакетами (это обеспечивается аннотацию @ComponentScan) и создайте контейнер Spring.
Нам нужно только добавить один дополнительный компонент — экземпляр ActorSystem — и инициализировать расширение Spring в этой ActorSystem:
@Configuration
@ComponentScan
public class AppConfiguration {
@Autowired
private ApplicationContext applicationContext;
@Bean
public ActorSystem actorSystem() {
ActorSystem system = ActorSystem.create("akka-spring-demo");
SPRING_EXTENSION_PROVIDER.get(system)
.initialize(applicationContext);
return system;
}
}
4.4. Извлечение акторов Spring-Wired
Чтобы проверить, что все работает правильно, мы можем внедрить экземпляр ActorSystem в наш код (либо код приложения, управляемого Spring, либо тест на основе Spring), создать объект Props для актора, используя наш расширения, получить ссылку на актора через объект Props и попытаться поприветствовать кого-нибудь:
ActorRef greeter = system.actorOf(SPRING_EXTENSION_PROVIDER.get(system)
.props("greetingActor"), "greeter");
FiniteDuration duration = FiniteDuration.create(1, TimeUnit.SECONDS);
Timeout timeout = Timeout.durationToTimeout(duration);
Future<Object> result = ask(greeter, new Greet("John"), timeout);
Assert.assertEquals("Hello, John", Await.result(result, duration));
Здесь мы используем типичный шаблон akka.pattern.Patterns.ask, который возвращает экземпляр Future Scala. После завершения вычисления Future разрешается со значением, которое мы вернули в нашем методе GreetingActor.onMessasge.
Мы можем либо дождаться результата, применив метод Scala Await.result к Future, либо, что более предпочтительно, построить все приложение с асинхронными шаблонами.
5. Заключение
В этой статье мы показали, как интегрировать Spring Framework с Akka и bean-компонентами autowire в актеры.
Исходный код статьи доступен на GitHub.