Введение в весну с Аккой

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

1. Вступление

В этой статье мы сосредоточимся на интеграции Akka со Spring Framework, чтобы обеспечить возможность внедрения сервисов на основе Spring в акторы Akka.

Перед чтением этой статьи рекомендуется предварительно ознакомиться с основами Akka.

Дальнейшее чтение:

Введение в Akka Актеры в Java

Узнайте, как создавать параллельные и распределенные приложения с использованием Akka Actors на Java.

Read more

Путеводитель по ручьям Акка

Краткое и практическое руководство по преобразованию потоков данных в Java с использованием библиотеки Akka Streams.

Read more

Весенний ботинок и котлин

Узнайте, как использовать Kotlin вместе с Spring Boot 2.x.

Read more

2. Внедрение зависимостей в Akka

Akka - это мощная платформа приложений, основанная на модели параллелизма акторов. Фреймворк написан на Scala, что, конечно, делает его полностью применимым и в приложениях на основе Java. Итак,it’s very often we will want to integrate Akka with an existing Spring-based application или просто используйте Spring для подключения bean-компонентов к акторам.

Проблема с интеграцией Spring / Akka заключается в разнице между управлением bean-компонентами в Spring и управлением акторами в Akka:actors have a specific lifecycle that differs from typical Spring bean lifecycle.

Более того, акторы делятся на самого актора (который является внутренней деталью реализации и не может управляться Spring) и на ссылку на актера, которая доступна клиентским кодом, а также сериализуема и переносима между различными средами исполнения Akka.

К счастью, Akka предоставляет механизм, а именноAkka extensions, который делает использование фреймворков для внедрения внешних зависимостей довольно простой задачей.

3. Maven Зависимости

Чтобы продемонстрировать использование Akka в нашем проекте Spring, нам понадобится минимум зависимости Spring - библиотекаspring-context, а также библиотекаakka-actor. Версии библиотеки можно извлечь в раздел<properties> файлаpom:


    4.3.1.RELEASE
    2.4.8



    
        org.springframework
        spring-context
        ${spring.version}
    

    
        com.typesafe.akka
        akka-actor_2.11
        ${akka.version}
    

Обязательно проверьте Maven Central на наличие последних версий зависимостейspring-context иakka-actor.

И обратите внимание, что зависимостьakka-actor имеет постфикс_2.11 в своем имени, что означает, что эта версия инфраструктуры Akka была создана на основе Scala версии 2.11. Соответствующая версия библиотеки Scala будет транзитивно включена в вашу сборку.

4. Введение Spring Beans в Akka Actors

Давайте создадим простое приложение Spring / Akka, состоящее из одного актера, который может отвечать на имя человека, отправляя ему приветствие. Логика приветствия будет извлечена в отдельный сервис. Мы хотим автоматически связать этот сервис с экземпляром актера. Весенняя интеграция поможет нам в этой задаче.

4.1. Определение актера и услуги

Чтобы продемонстрировать внедрение службы в актор, мы создадим простой классGreetingActor, определенный как нетипизированный актор (расширяющий базовый класс AkkaUntypedActor). Основным методом каждого актора Akka является методonReceive, который получает сообщение и обрабатывает его в соответствии с определенной логикой.

В нашем случае реализацияGreetingActor проверяет, имеет ли сообщение предопределенный типGreet, затем берет имя человека из экземпляраGreet, затем используетGreetingService чтобы получить приветствие для этого человека и ответить отправителю полученной строкой приветствия. Если сообщение относится к другому неизвестному типу, оно передается предопределенному методу актораunhandled.

Давайте посмотрим:

@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 определяется как статический внутренний класс внутри этого субъекта, что считается хорошей практикой. Принятые типы сообщений должны быть определены как можно ближе к субъекту, чтобы избежать путаницы, какие типы сообщений может обрабатывать этот субъект.

Also notice the Spring annotations @Component and @Scope - они определяют класс как управляемый Spring bean-компонент с областью действияprototype.

Область видимости очень важна, потому что каждый запрос на извлечение bean-компонента должен приводить к новому созданному экземпляру, так как это поведение соответствует жизненному циклу актора Akka. Если вы реализуете этот bean-компонент с другой областью действия, типичный случай перезапуска действующих лиц в Akka, скорее всего, будет работать неправильно.

Наконец, обратите внимание, что нам не нужно явно указывать@Autowire для экземпляраGreetingService - это возможно благодаря новой функции Spring 4.3 под названиемImplicit Constructor Injection.

РеализацияGreeterService довольно проста, обратите внимание, что мы определили его как bean-компонент, управляемый Spring, добавив к нему аннотацию@Component (с областью действия по умолчаниюsingleton):

@Component
public class GreetingService {

    public String greet(String name) {
        return "Hello, " + name;
    }
}

4.2. Добавление поддержки Spring через расширение Akka

Самым простым способом интеграции Spring с Akka является расширение Akka.

An extension is a singleton instance created per actor system. Он состоит из самого класса расширения, который реализует интерфейс маркераExtension, и класса идентификатора расширения, который обычно наследуетAbstractExtensionId.

Поскольку эти два класса тесно связаны, имеет смысл реализовать классExtension, вложенный в классExtensionId:

public class SpringExtension
  extends AbstractExtensionId {

    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);
        }
    }
}

First -SpringExtension реализует единственный методcreateExtension из классаAbstractExtensionId, который отвечает за создание экземпляра расширения, объектаSpringExt.

КлассSpringExtension также имеет статическое полеSPRING_EXTENSION_PROVIDER, которое содержит ссылку на его единственный экземпляр. Часто имеет смысл добавить частный конструктор, чтобы явно указать, чтоSpringExtention должен быть одноэлементным классом, но мы опускаем его для ясности.

Secondly, статический внутренний классSpringExt является самим расширением. ПосколькуExtension - это просто интерфейс маркера, мы можем определить содержимое этого класса по своему усмотрению.

В нашем случае нам понадобится методinitialize для хранения экземпляра SpringApplicationContext - этот метод будет вызываться только один раз при инициализации расширения.

Также нам понадобится методprops для создания объектаProps. ЭкземплярProps является планом для актера, и в нашем случае методProps.create получает классSpringActorProducer и аргументы конструктора для этого класса. Это аргументы, с которыми будет вызываться конструктор этого класса.

Методprops будет выполняться каждый раз, когда нам понадобится ссылка на актера, управляемую Spring.

The third, и последний кусок головоломки - это классSpringActorProducer. Он реализует интерфейс AkkaIndirectActorProducer, который позволяет переопределить процесс создания экземпляра для актора путем реализации методовproduce иactorClass.

Как вы, наверное, уже догадались,instead of direct instantiation, it will always retrieve an actor instance from the Spring’s ApplicationContext. Поскольку мы сделали актера bean-компонентомprototype-scoped, каждый вызов методаproduce будет возвращать новый экземпляр актера:

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 actorClass() {
        return (Class) applicationContext
          .getType(beanActorName);
    }
}

4.3. Собираем все вместе

Единственное, что осталось сделать, это создать класс конфигурации Spring (помеченный аннотацией@Configuration), который сообщит Spring сканировать текущий пакет вместе со всеми вложенными пакетами (это обеспечивается аннотацией@ComponentScan ) и создайте контейнер Spring.

Нам нужно только добавить один дополнительный bean - экземпляр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 result = ask(greeter, new Greet("John"), timeout);

Assert.assertEquals("Hello, John", Await.result(result, duration));


Здесь мы используем типичный шаблонakka.pattern.Patterns.ask, который возвращает экземпляр ScalaFuture. После завершения вычисленияFuture разрешается со значением, которое мы вернули в нашем методеGreetingActor.onMessasge.

Мы можем либо дождаться результата, применив метод ScalaAwait.result кFuture, либо, что более предпочтительно, построить все приложение с использованием асинхронных шаблонов.

5. Заключение

В этой статье мы показали, как интегрировать Spring Framework с Akka и bean-компонентами autowire в актеров.

Исходный код статьи доступенon GitHub.