派遣で働くエンジニアのスキルアップを応援するサイト

PRODUCED BY RECRUIT

最近のJava Webフレームワークはどうなってるの?2024年版【前編】

JavaのWebフレームワーク事情

変わらないなーと思っていても、気が付いたら大きく変わっているのがJavaの世界です。Webフレームワークも例外ではありません。今からWebフレームワークを採用する基準は、Webフレームワークの元祖ともいえるStrutsが出てきた時期からは大きく変わっています。

しかし、そこで最新事情をチェックしておくか、と「Java Webフレームワーク」などで検索をしても、検索で出てきたものを並べてみただけのようなものが多く、なかなか参考になるようなサイトにたどりつけません。

ということで、前編・後編の2回に分けて最近のJavaのWebフレームワークがどうなっているかをまとめてみます。

今回はHTTP処理を記述するフレームワークについてまとめ、後編ではアプリケーションサーバー全体をカバーするフルスタックフレームワークの傾向を見ていきます

Webサーバーの基本

Webフレームワークは、Webサーバーの処理を書きやすくするためのフレームワークですが、そうなると、まずWebサーバーはプログラム的にどういうものかを知っておくとわかりやすくなります。そこで、簡単なWebサーバーについて実装を確認してみましょう。一行ずつ追わなくても、だいたいどのような処理が必要になるかを見れば大丈夫です。

java

void main() throws Exception{
  var server = new ServerSocket(8080);
  for (;;) {
    var soc = server.accept();
    Thread.ofVirtual().start(() -> {
      try (soc;
        var isr = new InputStreamReader(soc.getInputStream());
        var bur = new BufferedReader(isr);
        var w = new PrintWriter(soc.getOutputStream())) 
      {
        // ヘッダーの解析
        var line = bur.readLine();
        if (line == null) return; // exit thread
        println("Connect from %s for %s".formatted(soc.getInetAddress(), line));
        while (!bur.readLine().isEmpty()) {}
        // urlによって処理の振り分け
        var url = line.split(" ")[1];
        var message = switch(url) {
          case "/hello" -> "Hello!!!";
          case "/date" -> LocalDate.now();
          default -> "It works!";
        };
        // ヘッダー出力
        w.println("""
                  HTTP/1.1 200 OK
                  content-type: text/html
                  """);
        w.println();
        // コンテンツ出力
        w.println("""
                  <html><head><title>Hello</title></head>
                  <body><h1>Hello</h1>%s</body></html>
                  """.formatted(message));
      } catch (IOException ex) { throw new UncheckedIOException(ex); }
    });
  }
}

実行してlocalhost:8080にアクセスすると次のように表示され、Webサーバーが動いていることがわかります。/hello/dateにアクセスするとそれぞれの表示になります。

ところでこのコードは、server.javaなどのファイル名で保存すると、Java 23の早期アクセス版で次のようなコマンドで実行できます。

java --enable-preview --source 23 server.java

Java 21からはクラスを定義せずにmainメソッドを記述する機能がプレビューとして入りました。9月にリリースされたJava 23では直接mainメソッドを書く場合にはjava.baseモジュールのクラスが自動的にimportされるので、上記のコードではimport文が不要になります。またSystem.out.printlnprintlnだけで書けるようになります。

フレームワークの種類

ではフレームワークを見ていきましょう。フレームワークとして一番重要な部分は、HTTP処理をどのように書けるかという部分です。

先ほどのコードでは、次のコードでURLから実際の処理に振り分けて処理を実装していました。

java

var message = switch(url) {
  case "/hello" -> "Hello!!!";
  case "/date" -> LocalDate.now();
  default -> "It works!";
};

「Webフレームワーク」が指すのは主にこの部分を書きやすくするものです。

URLなどから処理を振り分けることをルーティングといいますが、フレームワークではルーティングやリクエスト情報の受け渡し、結果の出力方法などを管理します。方針の違いが出やすいのもこの部分で、大きく次の3つに分類することができます。

  • 命令形式
  • 宣言形式
  • コンポーネント形式

それぞれについてどのようなフレームワークがあるかを紹介します。

命令形式のフレームワーク

Javaの標準的なメソッド呼び出しだけで動作を記述するフレームワークを、ここでは命令形式のフレームワークと呼びます。

先ほどの素朴なHTTPサーバーからひとがんばりすれば実現できるような、シンプルな記述ができるフレームワークになっています。Webサーバーも組み込まれているので、mainメソッドにコードを書いて実行すればサーバーが起動して動かすことができます。

もちろん、HTTP処理の記述はシンプルであるものの、同様のフレームワークを実際に使えるところまで開発するのは大変です。

■Javalin

命令形式の代表的なフレームワークにJavalinがあります。

次のようにメソッド呼び出しを連結する形でURLと処理の対応を登録していて、mainメソッドだけで完結しています。

java

import io.javalin.Javalin;
import java.time.LocalDate;

void main() {
    Javalin.create()
            .get("/hello", ctx -> ctx.result("Hello!!!"))
            .get("/date", ctx -> ctx.result(LocalDate.now().toString()))
            .get("/*", ctx -> ctx.result("It works!!"))
            .start(8080);
}

次のようなdependencyを追加すれば使えるというのも手軽です。

xml

<dependency>
    <groupId>io.javalin</groupId>
    <artifactId>javalin</artifactId>
    <version>6.1.3</version>
</dependency>

WebサーバーとしてはJettyを使っています。

Javalinは元々Spark Frameworkの派生プロダクトでしたが、Spark Frameworkは開発が止まっています。Spark FrameworkはJavaで書かれていますが、JavalinはKotlinに書き直されているため、すでに元のコードはほとんど残っていません。

Webサイトを構築するには十分な機能を持っていますが、次に紹介するHelidon SEと比べれば、小規模なサイトやサービス向けであると言えます。

■Helidon SE

もうひとつの命令形式のフレームワークはHelidon SEです。HelidonはOracleが中心に開発するマイクロサービス向けフレームワークで、命令形式で記述するHelidon SEと、MicroProfileに準拠して後述のJAX-RSで記述するHelidon MEがあります。

Helidon SEはJavalinに比べると少しコードが多くなりますが、ほとんど似たようなコードになって、こちらもmainメソッドで完結させることができています。

java

import io.helidon.webserver.WebServer;
import java.time.LocalDate;

void main() {
    WebServer.builder()
            .port(8080)
            .routing(b -> 
                b.get("/hello", (req, res) -> res.send("Hello Helidon!"))
                 .get("/date", (req, res) -> res.send(LocalDate.now().toString()))
                 .get("/*", (req, res) -> res.send("It Works!")))
            .build().start();
}

利用も次のdependencyを追加するだけです。

xml

<dependency>
    <groupId>io.helidon.webserver</groupId>
    <artifactId>helidon-webserver</artifactId>
    <version>4.0.6</version>
</dependency>

Helidon MEの基盤になるAPIという位置づけで、機能的には規模が大きいシステムにも十分対応していると言えますが、規模が大きいのであればHelidon MEを使うことになるように思います。

他のライブラリに依存せず、すべてHelidonプロジェクトのJarファイルで動くので、ライブラリの依存が気になる場合には使いやすいです。

宣言形式のフレームワーク

宣言形式のフレームワークは、メソッドに対してアノテーションを付けてURLなどを対応させるようなフレームワークです。いま主流の形式といってもいいでしょう。

メソッドへの入力や出力、ルーティングの設定はHTTPに従い同じようなものになるため、宣言ベースのフレームワークは似通っています。簡単な処理であればアノテーションが違うだけです。

■Spring MVC

宣言形式のフレームワークとして代表的なのがSpring MVCです。

Spring MVCはSpring Frameworkと組み合わせて使うフレームワークで、次のような形式になります。

java

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class HelloController {
    @GetMapping(value = "/hello", produces = "text/plain")
    public String hello() {
        return "Hello Spring Boot!!";
    }
}

アノテーションを除けば次のような基本的なJavaコードになっています。これは以降に紹介するフレームワークでも共通です。

java

public class HelloController {
    public String hello() {
        return "Hello Spring Boot!!";
    }
}

試す場合は、Spring InitializrでSpring Bootプロジェクトを始めるのが楽です。

Spring Webで導入されるSpring MVCを含める必要があります。


https://start.spring.io/より

QuarkusやMicronautなどSpring Frameworkを組み込めない場合でもSpring MVCのアノテーションに対応させることができるため、Java標準ではないものの幅広く使えます。

■JAX-RS

Java標準の宣言的フレームワークがJAX-RSです。

JAX-RSはJava API for RESTful Web Servicesの略ということなのですが、Xはどこから来たんだとか、Wはどこに消えたんだとか、ちょっと無理のある略称ですね。もともとはJAXP(Java API for XML Processing)というXML処理の仕様の派生として、XMLをWebで扱えるAPIという位置づけだったのでXが残っています。けれども今ではXMLを扱うことはあまりなく、JSONをやりとりするほうが主流になっていますね。

さらにいえば、Java EEからJakarta EEへ移行したことにより、名称はJakarta RESTful Web Servicesになっていて正式な略称はないようです。Jakarta RESTful Web Servicesは長すぎるので、JAX-RSと呼ばれ続けるのだと思います。

コードは次のように、Spring MVCに似た形式です。

java

import jakarta.ws.rs.*;
import jakarta.ws.rs.core.MediaType;

@Path("/hello")
public class HelloController {

    @GET
    @Produces(MediaType.TEXT_PLAIN)
    public String hello() {
        return "Hello JAX-RS!!";
    }
}

JAX-RSはJava標準だけあって、多くの環境で使えます。Spring Bootでも例えばInitializrからであればJerseyを導入すれば使えますが、Quarkusを使うのが手軽でしょう。なにも選ばなくてもJAX-RSが含まれます。Quarkusについては、後編で取り上げます。


https://code.quarkus.io/より

■Micronaut

もうひとつ宣言的なフレームワークとして、Micronautをとりあげます。Micronautはフルスタックフレームワークで、独自のWebフレームワークを持っています。

これもJavaの処理にアノテーションでHTTPリクエストを対応させる形になっています。

java

import io.micronaut.http.MediaType;
import io.micronaut.http.annotation.*;

@Controller("/hello")
public class HelloController {
    @Get
    @Produces(MediaType.TEXT_PLAIN)
    public String hello() {
        return "Hello Micronaut!!";
    }
}

MicronautはMicronaut Lunchを使って始めるのが楽です。Micronautも次回取り上げます。

https://micronaut.io/launch/より

■サーブレット

実のところ、サーブレットも宣言的なフレームワークといえます。次のように、アノテーションやweb.xmlへの記述でクラスとURLへの対応を定義します。

java

import jakarta.servlet.annotation.*;
import jakarta.servlet.http.*;
import java.io.IOException;
import java.io.PrintWriter;

@WebServlet(urlPatterns = "/hello")
public class HelloServlet extends HttpServlet {
    @Override
    public void doGet(HttpServletRequest request, HttpServletResponse response)
            throws IOException {
        response.setContentType("text/html; charset=UTF-8");
        try (PrintWriter out = response.getWriter()) {
            out.println("""                
                        <html><head><title>Hello</title></head>
                        <body><h1>Hello</h1>Hello!!</body></html>
                        """);
        }
    }
}

ここまでに紹介したJAX-RSなどの宣言的フレームワークではメソッド単位でリクエストの処理を記述し、ひとつのクラスで複数のエンドポイントの処理を書けました。サーブレットではひとつのクラスでひとつのエンドポイントを記述します。

また、他のフレームワークではアノテーション以外に特別なAPIは使いませんでしたが、サーブレットではHttpServletの継承が必要で、リクエストやレスポンスをHttpServletRequestHttpServletResponseといった専用オブジェクトの操作として処理する必要があります。

サーブレットは処理を書くには煩雑だったため、Strutsをはじめとした初期のJavaのフレームワークはサーブレット上でWebアプリケーションを書きやすくするものでした。また、サーブレットはリクエスト処理の記述だけではなくサーブレットコンテナへの配備まで含めた、範囲の広いフレームワークです。

そのため、サーブレットに依存するフレームワークはHttpServletRequest経由でさまざまな情報にアクセスする前提にもなっていました。JAX-RSなど新しいフレームワークはサーブレットに依存しないようになってきています。

コンポーネント形式のフレームワーク

コンポーネント形式のフレームワークは、Java以外の言語にはあまりない形式のフレームワークです。コンポーネントを配置して、データやイベントをJavaコードと結び付けるような記述になります。

XMLやHTMLで画面構成を記述してJavaコードに画面への結び付けを記述するような形式は、JavaFXやReactといったGUIフレームワークに近いものがあります。

コンポーネントとしてHTMLやJavaScript、そして必要なJava側の処理もまとめられているので、高機能な入力フォームを、JavaScriptなしに実装できるようになります。

宣言形式のフレームワークが似通っていたのに比べて、コンポーネントベースのフレームワークではコンポーネントの扱いの作りこみに個性が出るため、それぞれ独自色が強いものになっています。

入力フォームが大量にある場合には非常に使いやすいので企業内部システムで使われることが多い一方で、裏側の通信が複雑になり動作や通信量が重くなることから、一般向けのWebサイトではあまり使われません。

■JSF

コンポーネントベースのフレームワークの代表は、Java EEに含まれていたJSF(Java Server Faces)です。現在はJakarta EEに移行してJakarta Facesが正式名称になっていますが、ここではJSFと記述します。 次のように、XHTMLファイルにタグとしてコンポーネントを記述します。このテンプレートはFaceletsという仕様になっています。

xhtml

<f:view>
<h:form>
    <div>
        <h:inputText id="in" value="#{helloBean.input}"/>
        <h:commandButton value="Hello" actionListener="#{helloBean.go()}">
            <f:ajax execute="in" render="out"/>
        </h:commandButton>
    </div>
    <h:outputLabel id="out" value="#{helloBean.message}"/>
</h:form>
</f:view>

commandButtonタグのactionListener属性outputLabelタグのvalue属性にJavaコードへの結び付けが書かれています。 Faceletに記述したコンネーネントに結びつく次のようなJavaコードを用意しておくと、commandButtonが押されたときにはactionListenerに指定されたgo()メソッドが呼び出され、outputLabelにはmessageのデータが結び付けられます。

java

import jakarta.enterprise.context.RequestScoped;
import jakarta.inject.Named;

@Named
@RequestScoped
public class HelloBean {
    // 実際にはそれぞれのsetter/getterが必要
    private String message = "Hello!!!";
    private String input = "";

    public void go() {
        message = "hello " + input;
    }
}

実行すると次のように、画面遷移なしでの処理が行われます。

画面遷移なしで表示を更新する場合は、<f:ajax>タグなどで処理に関わるコンポーネントを指定する必要があります。Javaコードには値しか記述せず、コンポーネントとの結び付けはフレームワークにまかせる必要があるので、制御が難しいことがあります。

標準ではコンポーネントとして簡素なものしか用意されておらず、実用的な画面をつくるのは難しいです。そこで、実際の開発では外部コンポーネント集であるPrimeFacesを使って高度な画面構築を行います。

JSFはCDIというDI仕様に依存しているため、独自のDIをもったSpring Frameworkとは相性が悪くSpring Bootで使うのは避けたほうがいいでしょう。

試すにはJakarta EEサーバーを使うか、Quarkusを使うのが手っ取り早いと思います。Tomcatなどのアプリケーションサーバーに配備して動かすことも多いです。それぞれの手順はこちらのブログにまとめています。

■ZK

独自仕様でのコンポーネントベースのフレームワークにはZKがあります。 ZKでは、UIをZULというXML形式で画面定義を行います。ウィンドウのような画面を作る想定になっているようです。

xml

<zk>
  <window title="My Page" border="normal"
      apply="com.example.demo.HelloComposer">
    It works!!
    <vlayout>
      <hlayout>
        <textbox id="input"/>
        <button id="exec" label="Go"/>
      </hlayout>
      <label id="output" value="hello"/>
    </vlayout>
  </window>
</zk>

JavaのコードもJSFに近いです。JSFでは扱う値をJavaコードに書いていましたが、ZKではコンポーネントを指定して、そのコンポーネントオブジェクトを介して値を操作します。

java

public class HelloComposer extends SelectorComposer<Component>{
    @Wire
    private Label output;
    @Wire
    private Textbox input;
    
    @Listen("onClick = #exec")
    public void go() {
        output.setValue(input.getValue());
    }
}

実行すると、次のような動きになります。

ZKではJavaコードで直接コンポーネントを扱う分、仕組上は不審な挙動が少ないかもしれません。ただ、ZULという独自形式で画面を記述するので、すこし不便です。有償で画面デザイナが用意されているようです。

ZKは標準でPrimeFacesと同じようなコンポーネントを持っているため、そのまま高機能な画面を構築できます。

試す際は、公式のWikiにSpring Bootでの動かし方の説明があります。

ただ、上記の公式の手順には説明が足りない部分があるので、こちらのブログに今回のサンプルを動かす手順をまとめています。

■Vaadin

コンポーネントベースのもうひとつのフレームワークがVaadinです。

画面の構築もJavaコードで行うため、記述はJavaだけで完結します。

java

@Component
@Route("")
public class MainView extends VerticalLayout{
    public MainView() {
        TextField textField = new TextField("Enter text");
        Span label = new Span("Hello");
        Button button = new Button("Go");
        button.addClickListener(e -> label.setText(textField.getValue()));

        add(textField, button, label);
    }
}

これで、次のような画面ができます。

Javaコードで完結するため、標準的なIDEがあれば開発できます。コンポーネントも充実しているため、そのまま高機能な画面を構築できます。

簡単な雛型は次のサイトで作ることができます。この画面もVaadinで作られているようです。

Vaadinも有償ライセンスを購入すれば画面デザイナが使えます。

Spring Bootでの導入はこちらに公式にまとめられています。

もうすこし具体的な手順はこちらにまとめておきました。

前編のまとめ

今回はJavaのWebフレームワークのうち、リクエスト処理を実装する部分について解説しました。

けれども高度化した現在のWeb環境では、リクエスト処理の書きやすさだけではフレームワークの良さを判断できなくなっています。宣言形式のSpring MVCやJAX-RSを使えば差も出にくくなっています。

そこで、運用に必要な機能や実行環境の構築なども含めたフルスタックのフレームワークが重要になっています。

後編では、そういった背景になる変化も含めて、フルスタックのフレームワークの状況を紹介します。

後編はこちら

【筆者】きしだ なおき さん
九州芸術工科大学芸術工学部音響設計学科を8年で退学後、フリーランスでの活動を経て、2015年から大手IT会社勤務。著書に、『プロになるJava 』(共著、技術評論社)、『みんなのJava OpenJDKから始まる大変革期!』(共著、技術評論社)、『創るJava』(マイナビ)など。

※本記事に記載されている会社名、製品名はそれぞれ各社の商標および登録商標です。

知っておきたいJavaの話:Javaとは何か?

知っておきたいJavaの話:どうやって実行する?