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
. Версии библиотеки можно извлечь в раздел <properties>
файла 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 Actors
Давайте создадим простое приложение Spring/Akka, состоящее из одного актора, который может отвечать на имя человека, отправляя приветствие этому человеку. Логика приветствия будет выделена в отдельный сервис. Мы хотим автоматически связать эту службу с экземпляром актора. Интеграция с Spring поможет нам в этой задаче.
4.1. Определение Актера и Сервиса
Чтобы продемонстрировать внедрение службы в действующее лицо, мы создадим простой класс GreetingActor
, определенный как нетипизированное действующее лицо (расширение базового класса UntypedActor Akka ).
Основным методом каждого актора 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
— они определяют класс как управляемый Spring bean-компонент с областью действия прототипа
.
Область действия очень важна, потому что каждый запрос на получение компонента должен приводить к созданию вновь созданного экземпляра, поскольку такое поведение соответствует жизненному циклу актера 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.
Расширение — это экземпляр singleton, созданный для каждой системы акторов. Он состоит из самого класса расширения, который реализует интерфейс маркера Extension
, и класса идентификатора расширения, который обычно наследует 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
. Он реализует интерфейс Akka IndirectActorProducer
, который позволяет переопределить процесс создания экземпляра для актора путем реализации методов product
и ActorClass
. ``
Как вы, наверное, уже догадались, вместо прямого создания экземпляра всегда будет извлекаться экземпляр актора из ApplicationContext
Spring . Так как мы сделали актор прототипом
bean-компонента, каждый вызов метода 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
, который возвращает экземпляр Scala Future .
После завершения вычисления Future
разрешается со значением, которое мы вернули в нашем методе GreetingActor.onMessasge .
Мы можем либо дождаться результата, применив к Future метод Await.result Scala ,
либо ,
что более предпочтительно, построить все приложение с асинхронными шаблонами. ``
5. Вывод
В этой статье мы показали, как интегрировать Spring Framework с Akka и bean-компонентами autowire в актеры.
Исходный код статьи доступен на GitHub .