読者です 読者をやめる 読者になる 読者になる

Payara MicroでJacksonを有効にする。そしてAsync Responseを使う

Javaで非同期処理書きたい。それだけです。
あと前回分かったMessageBodyWriterでMoxyが使われているのをJacksonに切り替えます。

これが動かしたい。

  @GET
  @Path("async")
  @Produces(MediaType.APPLICATION_JSON)
  public void asyncTest(@Suspended AsyncResponse response) {
    System.out.println("log1");
    CompletableFuture.runAsync(() -> {
      response.resume(new Test("test"));
      System.out.println("log3");
    }, executor);
    System.out.println("log2");
  }

  @Resource
  private ManagedExecutorService executor;

【Jackson使う】

まずはJacksonを有効にします。Lombokを気持ちよく使いたいからです。
jerseyだとResourceConfigとかいうApplicationクラスを拡張した素敵なクラスがあるのですが
今回はApplicationクラスをいじる形で実装したかったため、そこについては記述いたしません。

今回はJacksonFeatureというJerseyで用意されているクラスを使ってJacksonの機能を有効にします。
めっちゃ簡単です。

@Provider
public class ApplicationFeature implements Feature {

  @Override
  public boolean configure(FeatureContext ctx) {
    ctx.register(JacksonFeature.class);
    return true;
  }

}

ね、簡単でしょう? これだけだと動きません。
これでいけると思ったんじゃー!!!

はい。ソースを見ます。
github.com
ここの行見るとわかるのですが
何やら、ConfigurationからPropertiesを取り出して設定を見ているようです。

手元だとこんな感じになってました。

/*     */ public class JacksonFeature implements Feature {
/*     */    private static final String JSON_FEATURE = JacksonFeature.class.getSimpleName();
/*     */ 
/*     */    public boolean configure(FeatureContext context) {
/*  71 */       Configuration config = context.getConfiguration();
/*     */ 
/*  73 */       String jsonFeature = (String)CommonProperties.getValue(config.getProperties(), config.getRuntimeType(), "jersey.config.jsonFeature", JSON_FEATURE, String.class);
/*     */ 
/*     */ 
/*  76 */       if(!JSON_FEATURE.equalsIgnoreCase(jsonFeature)) {
/*  77 */          return false;
/*     */ 
/*     */ 
/*     */       } else {
/*  81 */          context.property(PropertiesHelper.getPropertyNameForRuntime("jersey.config.jsonFeature", config.getRuntimeType()), JSON_FEATURE);
/*     */ 
/*     */ 
/*     */ 

デバッグして76行目で止めてjsonFeatureを見るとMoxyに関する名前が入っていたので
これをjersey.config.jsonFeatureに変えればええんかーと思いました。

で、どうやって変更すんの?

・案1 JacksonFeatureを登録している、ApplicationFeatureで設定上書きする。

残念!context.getConfiguration().getProperties()して
クラス名確認したところ、UnmodifiableMapちゃんでした!!!
まぁそんな作りになっているわけがないですよね。

・案2 Applicationクラスで設定する。

これで動きました。

@ApplicationPath("/api")
public class MyApplication extends Application {
  private final Map<String, Object> properties;
  public MyApplication() {
    Map<String, Object> properties=new HashMap<>();
    properties=new HashMap<>();
    properties.put(CommonProperties.MOXY_JSON_FEATURE_DISABLE, true);
    this.properties=Collections.unmodifiableMap(properties);
  }
  
  @Override
  public Map<String, Object> getProperties() {
    return properties;
  }
}

Moxyを無効にするだけでいいみたいですね。
ドキュメント的にはこの辺に載ってます。
Appendix A. Configuration Properties

一応MoxyJsonFeatureのソースを覗くと
JacksonFeatureみたいにJSONFeatureは何を使うのか確認する処理の手前に
Disableかどうか確認して使うかどうか確認しているみたいですね。



非同期Response扱いたい。

ManagedExecutorService使いたい。

なるほど。使えるそうです。

うらがみさんから頂いたソースで
@Injectしてるからそのまま書いたら動かないぞーと思ってたらResourceにしたら動いた。
JNDIリソース周りの知識が足りないっぽい。

github.com
github.com

あと、CDI管理下にあるからなのかScopeアノテーションを付けたら動いた。

@RequestScoped
@Path("/example")
public class ApplicationResource {

Profile回りの知識がないと呟いたら
蓮沼さんからスライドを頂きました。

なるほど。ここを見る限りManagedExecutorServiceはConcurrencyに入る?ので
Web Profileだと本来使えない機能のようですね。

【番外編】ServletContainerInitializerの話

JavadocのonStartupの引数から

Parameters:
c - the Set of application classes that extend, implement, or have been annotated with the class types specified by the HandlesTypes annotation, or null if there are no matches, or this ServletContainerInitializer has not been annotated with HandlesTypes
ctx - the ServletContext of the web application that is being started and in which the classes contained in c were found

ふむふむ。ServletContainerInitializerにつけられたHandleTypesアノテーションがない場合はnull
HandleTypesで「指定されたアノテーションで注釈付けされたクラス」もしくは「指定されたクラスをextendsもしくはimplementした型」のSetが入ってくる模様

で、Jerseyで使われてるContainerInitializerを見る。
github.com
PathとProviderとApplicationとApplicationPathが指定されている。

ここのスライドにかかれている、以下の動きに繋がるんですかねー

Java EE環境下では @Path で注釈されたクラスをリソースクラス、 @Provider で注釈されたクラスをプロバイダーと認識して自動で登録してくれる

JAX-RS入門および実践

うらがみさんによる細かい解説




【まとめ】

蓮沼さんとうらがみさんに感謝。

リポジトリはこちらになります。
github.com

【小話】ボケてた。


アノテーションは継承できません。そうでした。
@Inheritで引き継ぐのと勘違いしてました。

ちなみに一応Annotationはインターフェースなので

おまけ:Java EEの中にはこんなのもいたりする。CDI Extension書くときとかにお世話になったイメージ
AnnotationLiteral (Java(TM) EE 7 Specification APIs)