Java
webapplication

Java の Micro Web Application Framework を探す

More than 2 years have passed since last update.

概要

Web API やちょっとした Web ツールを構築する際には Micro Web Application Framework が役立ちます。Java でも種類がいろいろとあるようですので、今回は Hello world を表示するまでの手順を調べてみました。

条件

今回調査するにあたり、調査対象を下記の条件で絞りました。

  1. build.gradle に依存を書くだけで構築・動作可能であること……Frameworkの実行環境を別にダウンロードすることなく動かせるものを調査対象とします。fatjar を本番環境にデプロイしてそのまま動かせるものを調べます。
  2. Java SE8 & Gradle 2.12 で動作すること
  3. ソースコードが公開されていること
  4. ライセンスが非 GPL であること……企業で使う際に効いてくる部分です。

調査環境

項目
Java Version 1.8.0_91
OS Windows10
Gradle 2.12

早見表

Name License Default Port
Spring Boot Apache 8080
Spark Framework Apache 4567
Siden Apache 8080
Pippo Apache 8338

Spring Boot

私が書くこともないくらい著名な Web Application Framework です。ライセンスは Apache License Version 2.0 です。

Main クラス

このクラス名を、あとで build.gradle の mainClassName に指定します。

Main.java
package jp.toastkid.springv;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

@Configuration
@EnableAutoConfiguration
@ComponentScan
public class Main {
    public static void main(String[] args) throws Exception {
        SpringApplication.run(Main.class, args);
    }
}

Controller クラス

Controller.java
package jp.toastkid.springv;

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

@RestController
public class Controller {

    @RequestMapping("/")
    public String index() {
        return "this is Spring Boot Sample!";
    }
}

build.gradle

公式ドキュメントの getting-started と、application プラグインで動かすには最低限下記が必要となるようです。

build.gradle
apply plugin: 'application'
apply plugin: 'spring-boot'

mainClassName = "jp.toastkid.springv.Main"

repositories {
    jcenter()
    maven { url "http://repo.spring.io/snapshot" }
    maven { url "http://repo.spring.io/milestone" }
}

buildscript {
    repositories {
        jcenter()
        maven { url "http://repo.spring.io/snapshot" }
        maven { url "http://repo.spring.io/milestone" }
    }
    dependencies {
        classpath("org.springframework.boot:spring-boot-gradle-plugin:1.4.0.BUILD-SNAPSHOT")
    }
}

jar {
    baseName = 'spring_boot_verification'
    version =  '0.0.1'
}

dependencies {
    compile("org.springframework.boot:spring-boot-starter-web")
    testCompile("org.springframework.boot:spring-boot-starter-test")
}

動かす

$ gradle run

:compileJava UP-TO-DATE
:processResources UP-TO-DATE
:classes UP-TO-DATE
:run

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::  (v1.4.0.BUILD-SNAPSHOT)

2016-06-07 12:45:01.964  INFO 3856 --- [           main] jp.toastkid.springv.Main                 : Starting Main on DESKTOP-O3NC92G with PID 3856 (started by Toast kid in C:\Users\Toast kid\Documents\workspace\spring_boot_verification)
2016-06-07 12:45:01.975  INFO 3856 --- [           main] jp.toastkid.springv.Main                 : No active profile set, falling back to default profiles: default
2016-06-07 12:45:02.035  INFO 3856 --- [           main] ationConfigEmbeddedWebApplicationContext : Refreshing org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext@80169cf: startup date [Tue Jun 07 12:45:02 JST 2016]; root of context hierarchy
2016-06-07 12:45:03.647  INFO 3856 --- [           main] s.b.c.e.t.TomcatEmbeddedServletContainer : Tomcat initialized with port(s): 8080 (http)
2016-06-07 12:45:03.670  INFO 3856 --- [           main] o.apache.catalina.core.StandardService   : Starting service Tomcat
2016-06-07 12:45:03.672  INFO 3856 --- [           main] org.apache.catalina.core.StandardEngine  : Starting Servlet Engine: Apache Tomcat/8.0.33
2016-06-07 12:45:04.163  INFO 3856 --- [ost-startStop-1] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring embedded WebApplicationContext
2016-06-07 12:45:04.163  INFO 3856 --- [ost-startStop-1] o.s.web.context.ContextLoader            : Root WebApplicationContext: initialization completed in 2132 ms
2016-06-07 12:45:04.494  INFO 3856 --- [ost-startStop-1] o.s.b.w.servlet.ServletRegistrationBean  : Mapping servlet: 'dispatcherServlet' to [/]
2016-06-07 12:45:04.499  INFO 3856 --- [ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean   : Mapping filter: 'characterEncodingFilter' to: [/*]
2016-06-07 12:45:04.499  INFO 3856 --- [ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean   : Mapping filter: 'hiddenHttpMethodFilter' to: [/*]
2016-06-07 12:45:04.500  INFO 3856 --- [ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean   : Mapping filter: 'httpPutFormContentFilter' to: [/*]
2016-06-07 12:45:04.500  INFO 3856 --- [ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean   : Mapping filter: 'requestContextFilter' to: [/*]
2016-06-07 12:45:04.786  INFO 3856 --- [           main] s.w.s.m.m.a.RequestMappingHandlerAdapter : Looking for @ControllerAdvice: org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext@80169cf: startup date [Tue Jun 07 12:45:02 JST 2016]; root of context hierarchy
2016-06-07 12:45:04.864  INFO 3856 --- [           main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/]}" onto public java.lang.String jp.toastkid.springv.Controller.index()
2016-06-07 12:45:04.870  INFO 3856 --- [           main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/error]}" onto public org.springframework.http.ResponseEntity<java.util.Map<java.lang.String, java.lang.Object>> org.springframework.boot.autoconfigure.web.BasicErrorController.error(javax.servlet.http.HttpServletRequest)
2016-06-07 12:45:04.870  INFO 3856 --- [           main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/error],produces=[text/html]}" onto public org.springframework.web.servlet.ModelAndView org.springframework.boot.autoconfigure.web.BasicErrorController.errorHtml(javax.servlet.http.HttpServletRequest,javax.servlet.http.HttpServletResponse)
2016-06-07 12:45:04.910  INFO 3856 --- [           main] o.s.w.s.handler.SimpleUrlHandlerMapping  : Mapped URL path [/webjars/**] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
2016-06-07 12:45:04.910  INFO 3856 --- [           main] o.s.w.s.handler.SimpleUrlHandlerMapping  : Mapped URL path [/**] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
2016-06-07 12:45:04.963  INFO 3856 --- [           main] o.s.w.s.handler.SimpleUrlHandlerMapping  : Mapped URL path [/**/favicon.ico] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
2016-06-07 12:45:05.105  INFO 3856 --- [           main] o.s.j.e.a.AnnotationMBeanExporter        : Registering beans for JMX exposure on startup
2016-06-07 12:45:05.193  INFO 3856 --- [           main] s.b.c.e.t.TomcatEmbeddedServletContainer : Tomcat started on port(s): 8080 (http)
2016-06-07 12:45:05.199  INFO 3856 --- [           main] jp.toastkid.springv.Main                 : Started Main in 3.578 seconds (JVM running for 4.046)
2016-06-07 12:45:23.960  INFO 3856 --- [nio-8080-exec-1] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring FrameworkServlet 'dispatcherServlet'
2016-06-07 12:45:23.960  INFO 3856 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet        : FrameworkServlet 'dispatcherServlet': initialization started
2016-06-07 12:45:23.980  INFO 3856 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet        : FrameworkServlet 'dispatcherServlet': initialization completed in 20 ms

このようにぞろぞろと出力がされます。止まったところで http://localhost:8080/ にアクセスし、

this is Spring Boot Sample!

と画面に表示されていればよいです。実に簡単でした。

参考

  1. 春だから!Javaでの開発にSpring Bootを使おう!
  2. Spring Boot 使い方メモ
  3. SpringBootとGradleでHelloWorld
  4. Spring Boot Reference Guide

Spark Framework

私の一押しです。 MVC ですらない超軽量級 Web Application Framework です。ライセンスが Apache Software License 2.0 なので企業でも採用しやすいです。

main メソッドで routing し、 Route オブジェクトの処理結果をレンダリングするというシンプルな仕組みです。Framework 固有の部分がごく小さいので、学習コストが非常に低いことが大きな特徴とも言えます。開発者が自由なスタイルでアプリケーションを大きくしていけるので、特に個人での開発に向いています。役割を分担しての大きなアプリケーション開発をするなら、別のFrameworkを使った方がよいでしょう。

喩えるなら Spark Framework は面相筆であり、壁を塗りつぶすには適した道具と言えませんが、人形の顔を描くのにはこの上ない道具です。

Main クラスを定義する

Main.java
package jp.toastkid.sparkv;

import static spark.Spark.*;

public class Main {
    public static void main(final String[] args) {
        get("/", (req, res) -> "Hello world.");
    }
}

build.gradle を定義する

build.gradle
apply plugin: 'application'
mainClassName = "jp.toastkid.sparkv.Main"

sourceCompatibility = 1.8
targetCompatibility = 1.8

// In this section you declare where to find the dependencies of your project
repositories {
    mavenCentral()
}

// In this section you declare the dependencies for your production and test code
dependencies {
    compile 'com.sparkjava:spark-core:2.3'
}

動かす

$ gradle run

:processResources UP-TO-DATE
:classes
:run
[Thread-0] INFO org.eclipse.jetty.util.log - Logging initialized @227ms
[Thread-0] INFO spark.webserver.JettySparkServer - == Spark has ignited ...
[Thread-0] INFO spark.webserver.JettySparkServer - >> Listening on 0.0.0.0:4567
[Thread-0] INFO org.eclipse.jetty.server.Server - jetty-9.3.2.v20150730
[Thread-0] INFO org.eclipse.jetty.server.ServerConnector - Started ServerConnector@3c518466{HTTP/1.1,[http/1.1]}{0.0.0.0:4567}
[Thread-0] INFO org.eclipse.jetty.server.Server - Started @353ms
[qtp1403862408-17] INFO spark.webserver.MatcherFilter - The requested route [/favicon.ico] has not been mapped in Spark
[qtp1403862408-15] INFO spark.webserver.MatcherFilter - The requested route [/favicon.ico] has not been mapped in Spark
> Building 75% > :run

http://localhost:4567/ にアクセスして

Hello world.

と表示されれば OK です。どうでもいいですが、4567 というポート番号は結構変わっていますね。

参考

Spark Framework使い方メモ


Siden

サンプルコードを見ていると、基本的な部分は先ほどの Spark Framework に似ている感じがしてきました。実際に Sparkjava からインスパイアされたとドキュメントに書いてあります。 WebSocket に対応している点が特徴的です。ライセンスは Apache License Version 2.0 です。

Main クラスを作る

App インスタンスに route を登録していく形式のようです。

Main.java
package jp.toastkid.siden;

import ninja.siden.App;

public class Main {
    public static void main(String[] args) {
        App app = new App();
        app.get("/hello", (req, res) -> "Hello world");
        app.listen();
    }
}

build.gradle を定義する

依存は1つだけのようです。

build.gradle
apply plugin: 'application'

mainClassName       = 'jp.toastkid.siden.Main'

sourceCompatibility = 1.8
targetCompatibility = 1.8

repositories {
    jcenter()
}

dependencies {
    compile 'ninja.siden:siden-core:0.6.0'
}

動かす

$ gradle run

:compileJava UP-TO-DATE
:processResources UP-TO-DATE
:classes UP-TO-DATE
:run
6 07, 2016 6:20:45 午後 org.xnio.Xnio <clinit>
INFO: XNIO version 3.3.1.Final
6 07, 2016 6:20:45 午後 org.xnio.nio.NioXnio <clinit>
INFO: XNIO NIO Implementation Version 3.3.1.Final
6 07, 2016 6:20:56 午後 ninja.siden.internal.SecurityHandler lambda$handleRequest$26
WARN: /favicon.ico Content-Type header doesn't exist.
> Building 75% > :run

http://localhost:8080/hello にアクセスし、

Hello world

と表示されていることを確認してください。

参考

  1. ラムダ式を駆使してSSR対応のフレームワークを作ってみた
  2. SidenでHello Worldとテスト

Pippo

ルーマニア発の Framework です。GitHub で 「Java Micro Framework」で検索すると上位に来ます。repository の説明がまさに「Micro Java Web Framework」でした。なお、ライセンスは Apache Software License version 2.0 です。

Application.java を定義

Application だと名前が衝突するので注意してください。

Application.java
package jp.toastkid.pippo;

public class Application extends ro.pippo.core.Application {
    @Override
    protected void onInit() {
        GET("/", (routeContext) -> routeContext.send("Hello World"));
    }
}

Main.java を定義

先ほどの Application クラスを呼び出します。今回はわかりやすくするためにクラスを分けましたが、1つのクラスでまとめて書くこともできそうです。

Main.java
package jp.toastkid.pippo;

import ro.pippo.core.Pippo;

public class Main {
    public static void main(String[] args) {
        Pippo pippo = new Pippo(new Application());
        pippo.start();
    }
}

build.gradle を記述

build.gradle
apply plugin: 'application'

mainClassName = 'jp.toastkid.pippo.Main'

repositories {
    mavenCentral()
}

dependencies {
    compile 'ro.pippo:pippo-core:0.8.0'
    compile 'ro.pippo:pippo-jetty:0.8.0'
}

動かす

$ gradle run して起動します。 http://localhost:8338 にアクセスし、

Hello World

と表示されることを確認してください。

サーバの選択

公式ドキュメントの Server によると、アプリケーションを載せるサーバを Jetty や Tomcat 等から選べます。

Server name Name in pippo Gradle dependencies sample
Jetty pippo-jetty compile 'ro.pippo:pippo-jetty:0.8.0'
Undertow pippo-undertow compile 'ro.pippo:pippo-undertow:0.8.0'
Tomcat pippo-tomcat compile 'ro.pippo:pippo-tomcat:0.8.0'
TJWS pippo-tjws compile 'ro.pippo:pippo-tjws:0.8.0'

ちなみに、サーバの依存が存在しないと下記の例外が発生します。

$ gradle run
:compileJava UP-TO-DATE
:processResources UP-TO-DATE
:classes UP-TO-DATE
:run
SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder".
SLF4J: Defaulting to no-operation (NOP) logger implementation
SLF4J: See http://www.slf4j.org/codes.html#StaticLoggerBinder for further details.
Exception in thread "main" ro.pippo.core.PippoRuntimeException: Cannot find a WebServer
        at ro.pippo.core.Pippo.getServer(Pippo.java:61)
        at ro.pippo.core.Pippo.start(Pippo.java:74)
        at jp.toastkid.pippo.Main.main(Main.java:8)
:run FAILED

FAILURE: Build failed with an exception.

……以下略……

参考

Pippo(GitHub repository)


ほか

1. enkan

Clojure 好きが Java で Web アプリケーション開発をする際に選んでほしい Framework だそうです。REPL での操作が特徴的です。詳しくはenkanとkotowari 〜 Java9時代の新しいマイクロフレームワークをご覧ください。

Hello world 程度の動かし方は拙作 最小構成の enkan を Gradle で動かす をご覧くださいますと幸いです。

2. DropWizard

今回主に取り上げた Framework に比べるとややチーム開発向きな構造です。


まとめ

今回は Hello world までの道のりをさまざまな Framework で調べてみました。開発したいアプリケーションに合ったものを採用するのが仕事では一番ですが、世の中にはたくさんの Framework がありますので、自分が使いやすいと思ったものを使いこんでみるのもよいでしょう。