Edited at

Java の Micro Web Application Framework を探す

More than 3 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 がありますので、自分が使いやすいと思ったものを使いこんでみるのもよいでしょう。