Перейти к основному содержимому

Введение в Spring с Akka

· 7 мин. чтения

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 .