AkkaによるSpringの紹介

Akkaを使用したSpringの概要

1. 前書き

この記事では、AkkaとSpring Frameworkの統合に焦点を当て、SpringベースのサービスをAkkaアクターに注入できるようにします。

この記事を読む前に、Akkaの基本についての予備知識をお勧めします。

参考文献:

JavaでのAkkaアクターの紹介

JavaのAkkaアクターを使用して、並行および分散アプリケーションを構築する方法を学びます。

Akka Streamsのガイド

Akka Streamsライブラリを使用したJavaでのデータストリーム変換の迅速かつ実用的なガイド。

春のブーツとコトリン

KotlinをSpring Boot 2.xと併用する方法を学びます。

2. Akkaでの依存性注入

Akkaは、Actor同時実行モデルに基づく強力なアプリケーションフレームワークです。 フレームワークはScalaで記述されているため、Javaベースのアプリケーションでも完全に使用できます。 したがって、it’s very often we will want to integrate Akka with an existing Spring-based applicationを使用するか、単にSpringを使用してBeanをアクターに配線します。

Spring / Akka統合の問題は、SpringでのBeanの管理とAkkaでのアクターの管理の違いにあります:actors have a specific lifecycle that differs from typical Spring bean lifecycle

さらに、アクターは、アクター自体(内部実装の詳細であり、Springで管理できない)と、クライアントコードからアクセス可能で、異なるAkkaランタイム間でのシリアル化と移植が可能なアクター参照に分割されます。

幸いなことに、Akkaは、外部依存性注入フレームワークの使用をかなり簡単なタスクにするメカニズム、つまりAkka extensionsを提供します。

3. Mavenの依存関係

SpringプロジェクトでのAkkaの使用法を示すために、最小限のSpring依存関係(spring-contextライブラリーとakka-actorライブラリー)が必要です。 ライブラリのバージョンは、pom<properties>セクションに抽出できます。


    4.3.1.RELEASE
    2.4.8



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

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

spring-contextおよびakka-actorの依存関係の最新バージョンについては、MavenCentralを確認してください。

また、akka-actor依存関係の名前に_2.11接尾辞が含まれていることに注意してください。これは、このバージョンのAkkaフレームワークがScalaバージョン2.11に対して構築されたことを意味します。 Scalaライブラリの対応するバージョンは、ビルドに一時的に含まれます。

4. AkkaアクターへのSpringBeansの注入

この人に挨拶をすることで人の名前に答えることができる単一の俳優で構成される単純なSpring / Akkaアプリケーションを作成しましょう。 挨拶のロジックは、別のサービスに抽出されます。 このサービスをアクターインスタンスに自動接続します。 Springの統合は、このタスクに役立ちます。

4.1. アクターとサービスの定義

アクターへのサービスの注入を示すために、型指定されていないアクターとして定義された単純なクラスGreetingActorを作成します(AkkaのUntypedActor基本クラスを拡張します)。 すべての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 –これらは、クラスをprototypeスコープを持つSpring管理のBeanとして定義します。

この動作はAkkaのアクターのライフサイクルと一致するため、すべてのBean取得リクエストは新しく作成されたインスタンスになるはずなので、スコープは非常に重要です。 このBeanを他のスコープで実装すると、Akkaでアクターを再起動する典型的なケースは、おそらく正しく機能しなくなります。

最後に、GreetingServiceインスタンスを明示的に@Autowireする必要がないことに注意してください。これは、Implicit Constructor Injectionと呼ばれるSpring4.3の新機能により可能になります。

GreeterServiceの実装は非常に簡単です。@Componentアノテーションを追加することでSpring管理のBeanとして定義したことに注意してください(デフォルトはsingletonスコープ)。

@Component
public class GreetingService {

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

4.2. AkkaExtensionを介したSpringサポートの追加

SpringとAkkaを統合する最も簡単な方法は、Akka拡張機能を使用することです。

An extension is a singleton instance created per actor system.これは、マーカーインターフェイスExtensionを実装する拡張クラス自体と、通常はAbstractExtensionIdを継承する拡張IDクラスで構成されます。

これらの2つのクラスは緊密に結合されているため、ExtensionIdクラス内にネストされたExtensionクラスを実装することは理にかなっています。

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

FirstSpringExtensionは、AbstractExtensionIdクラスから単一のcreateExtensionメソッドを実装します。これは、拡張インスタンス、SpringExtオブジェクトの作成を説明します。

SpringExtensionクラスには、その唯一のインスタンスへの参照を保持する静的フィールドSPRING_EXTENSION_PROVIDERもあります。 プライベートコンストラクターを追加して、SpringExtentionがシングルトンクラスであると明示的に述べることはしばしば理にかなっていますが、わかりやすくするために省略します。

Secondly、静的内部クラスSpringExtは拡張機能そのものです。 Extensionは単なるマーカーインターフェイスであるため、適切と思われる場合は、このクラスのコンテンツを定義できます。

この場合、SpringApplicationContextインスタンスを保持するためにinitializeメソッドが必要になります。このメソッドは、拡張機能の初期化ごとに1回だけ呼び出されます。

また、Propsオブジェクトを作成するには、propsメソッドが必要です。 Propsインスタンスはアクターの青写真であり、この場合、Props.createメソッドはこのクラスのSpringActorProducerクラスとコンストラクター引数を受け取ります。 これらは、このクラスのコンストラクターで呼び出される引数です。

propsメソッドは、Springが管理するアクター参照が必要になるたびに実行されます。

The thirdで、パズルの最後のピースはSpringActorProducerクラスです。 これは、produceおよびactorClassメソッドを実装することにより、アクターのインスタンス化プロセスをオーバーライドできるようにするAkkaのIndirectActorProducerインターフェースを実装します。

おそらくすでにお察しのとおり、instead of direct instantiation, it will always retrieve an actor instance from the Spring’s ApplicationContextです。 アクターをprototypeスコープのBeanにしたので、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アノテーションでマーク)を作成して、ネストされたすべてのパッケージと一緒に現在のパッケージをスキャンするように指示するだけです(これは、@ComponentScanアノテーションによって保証されます)。 )そしてSpringコンテナを作成します。

1つの追加Bean(ActorSystemインスタンス)を追加し、このActorSystemでSpring拡張機能を初期化するだけです。

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


ここでは、ScalaのFutureインスタンスを返す典型的なakka.pattern.Patterns.askパターンを使用します。 計算が完了すると、Futureは、GreetingActor.onMessasgeメソッドで返した値で解決されます。

ScalaのAwait.resultメソッドをFutureに適用して結果を待つか、より好ましくは、非同期パターンを使用してアプリケーション全体を構築します。

5. 結論

この記事では、Spring FrameworkをAkkaと統合し、Beanをアクターに自動接続する方法を示しました。

記事のソースコードはon GitHubで入手できます。