1. 序章

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

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

2. Akkaでの依存性注入

Akka は、Actor同時実行モデルに基づく強力なアプリケーションフレームワークです。 フレームワークはScalaで書かれているため、Javaベースのアプリケーションでも完全に使用できます。 そのため、 Akkaを既存のSpringベースのアプリケーションと統合するか、単にSpringを使用してBeanをアクターに配線することがよくあります。

Spring / Akka統合の問題は、SpringでのBeanの管理とAkkaでのアクターの管理の違いにあります。アクターには、通常のSpringBeanライフサイクルとは異なる特定のライフサイクルがあります。

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

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

3. Mavenの依存関係

SpringプロジェクトでのAkkaの使用法を示すには、最小限のSpring依存関係( spring-context ライブラリ、および akka-actor ライブラリ)が必要です。 ライブラリのバージョンは、 のセクション 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>

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

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

4. 春の豆をAkka俳優に注入する

一人の俳優で構成された簡単なSpring/Akkaアプリケーションを作成して、その人に挨拶をすることでその人の名前に答えることができます。 挨拶のロジックは別のサービスに抽出されます。 このサービスをアクターインスタンスに自動配線する必要があります。 春の統合は、このタスクに役立ちます。

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 メッセージタイプは、このアクター内の静的内部クラスとして定義されていることに注意してください。これは、グッドプラクティスと見なされます。 受け入れられるメッセージタイプは、このアクターが処理できるメッセージタイプの混乱を避けるために、アクターのできるだけ近くで定義する必要があります。

Springアノテーション@Componentおよび@Scopeにも注意してください。これらはクラスをprototypeスコープを持つSpring管理対象Beanとして定義します。

この動作はAkkaのアクターのライフサイクルと一致するため、すべてのbean取得要求により、新しく作成されたインスタンスが生成されるため、スコープは非常に重要です。 このBeanを他のスコープで実装すると、Akkaでアクターを再起動する一般的なケースが正しく機能しない可能性があります。

最後に、明示的に @Autowire GreetingService インスタンスを作成する必要がないことに注意してください。これは、 Implicit ConstructorInjectionと呼ばれる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拡張機能を使用することです。

拡張機能は、アクターシステムごとに作成されるシングルトンインスタンスです。マーカーインターフェイス Extension を実装する拡張機能クラス自体と、通常はAbstractExtensionIdを継承する拡張機能IDクラスで構成されます。

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

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

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

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

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

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

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

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

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

おそらくすでにお察しのとおり、直接インスタンス化する代わりに、SpringのApplicationContextから常にアクターインスタンスを取得します。 アクターをプロトタイプスコープの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<? extends Actor> actorClass() {
        return (Class<? extends Actor>) applicationContext
          .getType(beanActorName);
    }
}

4.3. すべてを一緒に入れて

あとは、Spring構成クラス( @Configuration アノテーションでマーク)を作成して、現在のパッケージをすべてのネストされたパッケージと一緒にスキャンするように指示するだけです(これはによって保証されます)。 @ComponentScan アノテーション)、Springコンテナを作成します。

追加のBean( ActorSystem インスタンス)を1つ追加し、この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-WiredActorsの取得

すべてが正しく機能することをテストするために、 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));

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

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

5. 結論

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

この記事のソースコードは、GitHubから入手できます。