概要
Web API やちょっとした Web ツールを構築する際には Micro Web Application Framework が役立ちます。Java でも種類がいろいろとあるようですので、今回は Hello world を表示するまでの手順を調べてみました。
条件
今回調査するにあたり、調査対象を下記の条件で絞りました。
- build.gradle に依存を書くだけで構築・動作可能であること……Frameworkの実行環境を別にダウンロードすることなく動かせるものを調査対象とします。fatjar を本番環境にデプロイしてそのまま動かせるものを調べます。
- Java SE8 & Gradle 2.12 で動作すること
- ソースコードが公開されていること
- ライセンスが非 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 に指定します。
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 クラス
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 プラグインで動かすには最低限下記が必要となるようです。
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!
と画面に表示されていればよいです。実に簡単でした。
参考
- 春だから!Javaでの開発にSpring Bootを使おう!
- Spring Boot 使い方メモ
- SpringBootとGradleでHelloWorld
- Spring Boot Reference Guide
Spark Framework
私の一押しです。 MVC ですらない超軽量級 Web Application Framework です。ライセンスが Apache Software License 2.0 なので企業でも採用しやすいです。
main メソッドで routing し、 Route オブジェクトの処理結果をレンダリングするというシンプルな仕組みです。Framework 固有の部分がごく小さいので、学習コストが非常に低いことが大きな特徴とも言えます。開発者が自由なスタイルでアプリケーションを大きくしていけるので、特に個人での開発に向いています。役割を分担しての大きなアプリケーション開発をするなら、別のFrameworkを使った方がよいでしょう。
喩えるなら Spark Framework は面相筆であり、壁を塗りつぶすには適した道具と言えませんが、人形の顔を描くのにはこの上ない道具です。
Main クラスを定義する
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 を定義する
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 というポート番号は結構変わっていますね。
参考
Siden
サンプルコードを見ていると、基本的な部分は先ほどの Spark Framework に似ている感じがしてきました。実際に Sparkjava からインスパイアされたとドキュメントに書いてあります。 WebSocket に対応している点が特徴的です。ライセンスは Apache License Version 2.0 です。
Main クラスを作る
App インスタンスに route を登録していく形式のようです。
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つだけのようです。
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
と表示されていることを確認してください。
参考
Pippo
ルーマニア発の Framework です。GitHub で 「Java Micro Framework」で検索すると上位に来ます。repository の説明がまさに「Micro Java Web Framework」でした。なお、ライセンスは Apache Software License version 2.0 です。
Application.java を定義
Application だと名前が衝突するので注意してください。
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つのクラスでまとめて書くこともできそうです。
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 を記述
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.
……以下略……
参考
ほか
1. enkan
Clojure 好きが Java で Web アプリケーション開発をする際に選んでほしい Framework だそうです。REPL での操作が特徴的です。詳しくはenkanとkotowari 〜 Java9時代の新しいマイクロフレームワークをご覧ください。
Hello world 程度の動かし方は拙作 最小構成の enkan を Gradle で動かす をご覧くださいますと幸いです。
2. DropWizard
今回主に取り上げた Framework に比べるとややチーム開発向きな構造です。
まとめ
今回は Hello world までの道のりをさまざまな Framework で調べてみました。開発したいアプリケーションに合ったものを採用するのが仕事では一番ですが、世の中にはたくさんの Framework がありますので、自分が使いやすいと思ったものを使いこんでみるのもよいでしょう。