以前、Payara MicroのClustered CDI Event Busについて、エントリーを書きました。
Payara MicroのClustered CDI Event Bus(Hazelcast Topic)の仕組み - CLOVER
CDIのEvent APIで、あるPayara Microで発生したイベントを、別のPayara Microに伝播させる機能です。
内部的には、HazelcastのTopicを使って実現しています。
このClustered CDI Event Busですが、最初に出てきた時には
- 初期化処理(ClusteredCDIEventBus#initializeを呼び出す)を行う必要がある
- イベントが発生したNodeには、イベントが通知されない
という点がありました。
初期化はまあいいとして、自Nodeで発生したイベントが拾えてもいいんじゃないのかなぁと思っていたのですが、
Payara 171でloopbackができるようになっていたみたいです。
What's new in Payara Micro 171?
また、初期化処理も不要になったようです。
ちょっと試してみましょう。
準備
Mavenの定義は、こんな感じで。WARファイルの名前は、「app」とします。
<packaging>war</packaging> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <maven.compiler.source>1.8</maven.compiler.source> <maven.compiler.target>1.8</maven.compiler.target> <failOnMissingWebXml>false</failOnMissingWebXml> </properties> <dependencies> <dependency> <groupId>javax</groupId> <artifactId>javaee-api</artifactId> <version>7.0</version> <scope>provided</scope> </dependency> <dependency> <groupId>fish.payara.extras</groupId> <artifactId>payara-micro</artifactId> <version>4.1.2.172</version> <scope>provided</scope> </dependency> <dependency> <groupId>fish.payara.api</groupId> <artifactId>payara-api</artifactId> <version>4.1.2.172</version> <scope>provided</scope> </dependency> </dependencies> <build> <finalName>app</finalName> </build>
以前は「payara-micro」ひとつあれば良かった気がしますが、「javaee-api」も要るようになったんですねぇ。
Clustered CDI Event Busを使うには、追加で「payara-api」も必要になります。
サンプルアプリケーションを実装する
では、Clustered CDI Event Busを使ったサンプルアプリケーションを実装してみます。
Clustered CDI Event Busについてのドキュメントは、こちら。
Remote CDI Events in Payara Micro
Firing and Listening for remote CDI Events
サンプルアプリケーションは、前回のエントリーと似たような構成にしましょう。基本的な考え方、構成は変わっていないので、Clustered CDI Event Busに
関するAPIの使い方は、前回のエントリーを参照してください。
JAX-RSの有効化。
src/main/java/org/littlewings/payara/JaxrsActivator.java
package org.littlewings.payara; import javax.ws.rs.ApplicationPath; import javax.ws.rs.core.Application; @ApplicationPath("") public class JaxrsActivator extends Application { }
イベントの送受信で使われる、メッセージ用のクラス。
src/main/java/org/littlewings/payara/EventMessage.java
package org.littlewings.payara; import java.io.Serializable; public class EventMessage implements Serializable { private static final long serialVersionUID = 1L; private String value; private String memberId; public EventMessage(String value, String memberId) { this.value = value; this.memberId = memberId; } // getter/setterは省略 }
イベントを発火させる側のクラス。
src/main/java/org/littlewings/payara/EventSendResource.java
package org.littlewings.payara; import javax.enterprise.context.RequestScoped; import javax.enterprise.event.Event; import javax.inject.Inject; import javax.ws.rs.GET; import javax.ws.rs.Path; import javax.ws.rs.Produces; import javax.ws.rs.QueryParam; import javax.ws.rs.core.MediaType; import fish.payara.micro.PayaraMicroRuntime; import fish.payara.micro.cdi.Outbound; @Path("event") @RequestScoped public class EventSendResource { @Inject @Outbound Event<EventMessage> event; @Inject PayaraMicroRuntime runtime; @GET @Path("send") @Produces(MediaType.TEXT_PLAIN) public String send(@QueryParam("message") String message) { event.fire(new EventMessage(message, runtime.getLocalDescriptor().getMemberUUID())); return "OK!"; } }
Clustered CDI Event Busでイベントを送信するには、@Outboundアノテーションを付与します。
@Inject @Outbound Event<EventMessage> event;
受信側は、こちら。
src/main/java/org/littlewings/payara/EventMessageReceiver.java
package org.littlewings.payara; import javax.enterprise.context.Dependent; import javax.enterprise.event.Observes; import fish.payara.micro.cdi.Inbound; @Dependent public class EventMessageReceiver { public void observe(@Observes @Inbound EventMessage event) { System.out.printf("received message = %s, member = %s%n", event.getValue(), event.getMemberId()); } }
Clustered CDI Event Busでのイベント受信時には、@Observersアノテーションとともに、@Inboundアノテーションが付与されていればOKです。
public void observe(@Observes @Inbound EventMessage event) {
とりあえず、ここまでで最低限Clustered CDI Event Busを使うための準備ができました。
動かしてみる
では、パッケージングして起動してみます。Node数は、2つとしましょう。
$ mvn package ## Node 1 $ java -jar payara-micro-4.1.2.172.jar --deploy target/app.war ## Node 2 $ java -jar payara-micro-4.1.2.172.jar --deploy target/app.war --port 9080
curlでアクセスしてみます。リクエストは、最初のNodeに送ります。
$ curl http://localhost:8080/app/event/send?message=Hello OK!
2つのNodeでどうなっているかというと
## Node 1 ## Node 2 received message = Hello, member = 55e0a12e-655f-4dc4-9d18-57943137ff86
となり、2つ目のNodeでしかイベントを受け取っていないことになります。これは、クラスタを構成せず単一のNodeで実行しても同じで、
イベントを発火させたNode自身はイベントを受け取れません。
これが、171以前の挙動だったのですが、@Outboundアノテーションにloopbackパラメーターが追加され、これをtrueにすることで自Nodeも
イベントを受け取ることができるようになりました。
こんな感じです。
@Inject @Outbound(loopBack = true) Event<EventMessage> event;
再度パッケージングして起動、リクエストを送ると、今度は2 Nodeともイベントを受け取れていることが確認できます。
## Node 1 received message = Hello, member = c8411200-d660-47e2-8b44-57a4f2a5fe3b ## Node 2 received message = Hello, member = c8411200-d660-47e2-8b44-57a4f2a5fe3b
loopbackが機能していることが確認できました。
なにが変わったんでしょうか?
とりあえず、@Outboundアノテーションにloopbackが追加されたことにより、これをチェックしてloopbackがtrueであれば自Nodeにもイベントを
発火させるように実装が変わりました。
https://github.com/payara/Payara/blob/payara-server-4.1.2.172/appserver/payara-appserver-modules/payara-micro-service/src/main/java/fish/payara/appserver/micro/services/PayaraInstanceImpl.java#L178
まあ、そりゃそうですよね…。
Topicに対してのListenerは、そんなに変わっていません。
https://github.com/payara/Payara/blob/payara-server-4.1.2.172/nucleus/payara-modules/hazelcast-bootstrap/src/main/java/fish/payara/nucleus/eventbus/TopicListener.java
ところで、このエントリーの最初でも述べましたが、Clustered CDI Event Busを使うにあたっての初期化処理(ClusteredCDIEventBus#initializeの呼び出し)が
なくなりました。前回は初期化用のServletContextListenerを用意していましたが、今回は不要になりました。
確かに、ドキュメントにも要らなくなったと書かれています。
Manual initialization of the event bus
Before version of 4.1.1.171, it was necessary to initialize the event bus using ClusteredCDIEventBus.initialize() manually upon startup. Although this method is still available in the API, it is not necessary anymore, since the event bus is automatically initialized after an application is started.
https://payara.gitbooks.io/payara-server/content/documentation/payara-micro/cdi-events.html
Payara Microのサンプルからも、ClusteredCDIEventBus#initializeの呼び出しは削除されました。
https://github.com/payara/Payara-Examples/blob/master/Payara-Micro/cdi-clustered-events/event-sender/src/main/java/fish/payara/examples/payaramicro/event/sender/SendEventServlet.java#L119-L122
Payara側でどういう実装になったかというと、ServletContextの初期化に合わせて自動的にinitializeを呼び出すように変更されたみたいです。
https://github.com/payara/Payara/blob/payara-server-4.1.2.172/appserver/payara-appserver-modules/payara-micro-cdi/src/main/java/fish/payara/micro/cdi/extension/ClusteredCDIEventBusImpl.java#L117-L122
public void onStart(@Observes @Initialized(ApplicationScoped.class) ServletContext init) { initialize(); if (runtime.isClustered()) { Logger.getLogger(ClusteredCDIEventBusImpl.class.getName()).log(Level.INFO, "Clustered CDI Event bus initialized for " + init.getContextPath()); } }
ところで、以前はどうしてClusteredCDIEventBus#initializeの呼び出しが必要だったかというと、この呼び出し時にClassLoaderを
キャプチャしてTopicのイベント受信時にデシリアライズで問題ならないようにしていたからです。
で、これが現在のPayaraでどうなったかというと、前にキャプチャしたClassLoaderを使っていた箇所はManagedExecutorServiceを使った処理に
ごっそり置き換えられました。
https://github.com/payara/Payara/blob/payara-server-4.1.2.172/appserver/payara-appserver-modules/payara-micro-cdi/src/main/java/fish/payara/micro/cdi/extension/ClusteredCDIEventBusImpl.java#L164-L209
この時、JavaEEContextUtilという内部のインターフェースの実装を使っているのですが、この中で前と似たようなことをやっています。
https://github.com/payara/Payara/blob/payara-server-4.1.2.172/appserver/payara-appserver-modules/payara-micro-service/src/main/java/fish/payara/appserver/context/JavaEEContextUtilImpl.java
こういうカラクリで、初期化処理を不要にしたみたいですね。