JAX-RSのコードを書いた

中身としてはタイトル通りなのですが。
Java8, maven, lombokを使います。
Java8, maven ,lombok成分はあまりありません。

非同期Responseでデータ返すところまでやってみます。
urlのマッピングとしてはこんな感じになってます。
(URLのマッピングは適当です)

localhost:8080
-  /test/user/{id} --> User(json data)
-  /assets/ --> 静的ファイル

設定周り

dependencyはこんな感じ

<dependency>
  <groupId>org.glassfish.jersey.containers</groupId>
  <artifactId>jersey-container-grizzly2-http</artifactId>
  <version>2.23.2</version>
</dependency>
<dependency>
  <groupId>org.glassfish.jersey.media</groupId>
  <artifactId>jersey-media-json-jackson</artifactId>
  <version>2.23.2</version>
</dependency>
<dependency>
  <groupId>org.glassfish.hk2</groupId>
  <artifactId>hk2-metadata-generator</artifactId>
  <version>2.5.0-b05</version>
</dependency>
<dependency>
  <groupId>org.projectlombok</groupId>
  <artifactId>lombok</artifactId>
  <version>1.16.10</version>
  <scope>provided</scope>
</dependency>

jerseyのcontainer、grizzly実装です。
Request/ResponseのJSON Serialize, Desirializeはjacksonの実装を使います。

ベースはこちらのリポジトリを参考にソース書いてます。
github.com

コードを書いていく

JAX-RSの設定のクラスです。DIのためのクラス登録みたいなことやってる感じですね。
JacksonFeatureはJacksonのモジュールを登録してJAX-RSシリアライズ・デシリアライズするためのモジュールです。

public class App {

    private static final URI BASE_URI = URI.create("http://localhost:8080/");

    public static void main(String[] args) throws IOException {
        final HttpServer server = GrizzlyHttpServerFactory
            .createHttpServer(BASE_URI, create());
        // /assetsに静的ファイルマッピングを行う
        server.getServerConfiguration()
            .addHttpHandler(new StaticHttpHandler(),"/assets");  // "assets"にしてると動かなくてアレって思って調べたら"/hogehoge"っぽい
        System.in.read();
        server.shutdownNow();
    }

    public static ResourceConfig create() throws IOException {
        ResourceConfig resourceConfig = new ResourceConfig();
        //jackson
        resourceConfig.register(JacksonFeature.class);
        resourceConfig.register(DiscoveryFeature.class);
        resourceConfig.packages("com.wreulicke.jaxrs");
        return resourceConfig;
    }

}

上のコードに出てきたDiscoveryFeatureはこちらのサイトを参考に書いた
以下のコードになります。

public class DiscoveryFeature implements Feature {
  @Override
  public boolean configure(FeatureContext context) {
    ServiceLocator locator = ServiceLocatorProvider
       .getServiceLocator(context);
    DynamicConfigurationService dcs = locator
       .getService(DynamicConfigurationService.class);
    Populator populator = dcs.getPopulator();

    try {
      populator.populate(new ClasspathDescriptorFileFinder(this.getClass()
        .getClassLoader()), new DuplicatePostProcessor());
    } catch (IOException | MultiException ex) {
      throw new RuntimeException(ex);
    }
    return true;
  }
}

リンク先で設定されているように、mavenのbuildの設定をやってもいいのですが
annotation processingでやってくれてる(っぽい)hk2-metadata-generatorを使いました。

で、ここまでがプロジェクトの設定部分ですが
残りの実コードは大したことないのでまとめて以下に書いておきます。

// Resourceクラス
@Path("/test")
public class Resource {
    @Inject
    UserRepository repository;
    
    @GET
    @Path("/user/{id}")
    @Consumes(MediaType.APPLICATION_JSON)
    @Produces(MediaType.APPLICATION_JSON)
    public void findUser(@Suspended AsyncResponse response, @PathParam("id") String user) {
      repository.find(user).thenAcceptAsync(response::resume);
    }
}
// interafaceとimplementation

@Contract
public interface UserRepository {
  public CompletionStage<User> find(String id);
}

@Service
public class UserRepository implements com.wreulicke.jaxrs.service.UserRepository{

  @Inject
  ExecutorService service;
  
  Function<String, User> supplier=User::new;
  
  @Override
  public CompletionStage<User> find(String id) {
    // stub:とりあえず入ってきたidでuser返すだけ
    return CompletableFuture.supplyAsync(()->supplier.apply(id), service);
  }

}
// とりあえずのvalue object
@AllArgsConstructor
@Data
public class User {
  String id;
}

こんなところです。
@Contractと@Serviceでインターフェースと実装の紐づけが行われます。(正確にはその設定が書かれたファイルが吐かれる)
静的ファイルを設定するところでurl mappingの仕様が分かっておらず、詰まって頭抱えてました。

まとめ

Java SEの延長線のJAX-RSでした。少ない機能でとりあえず書きました。
しかし、これではJAX-RSがどうやって成り立ってるかはあまり分かりません。
とりあえず、使えるまで、でした。

今回書いたコードは以下のリポジトリに置いてあります。
github.com