Spring Bootで簡単なWebアプリケーションを書いてみる
JavaでWebアプリケーションを開発する際のフレームワークとして、近年Apache Strutsに代わりSpring Frameworkが広く使われている。 ここでは、Springが提供するBootstrapフレームワークSpring Bootを用いて、簡単なWebアプリケーションを書いてみる。
環境
Windows 10 Pro、Java SE 8、Spring Framework 4.3.7.RELEASE(Spring Boot 1.5.2.RELEASE)
>systeminfo OS 名: Microsoft Windows 10 Pro OS バージョン: 10.0.14393 N/A ビルド 14393 OS ビルドの種類: Multiprocessor Free システムの種類: x64-based PC プロセッサ: 1 プロセッサインストール済みです。 [01]: Intel64 Family 6 Model 69 Stepping 1 GenuineIntel ~1596 Mhz >java -version java version "1.8.0_121" Java(TM) SE Runtime Environment (build 1.8.0_121-b13) Java HotSpot(TM) 64-Bit Server VM (build 25.121-b13, mixed mode)
JDK 8のインストール
まず、Java開発環境であるJDK 8をインストールする。 JDKをインストールすると、実行環境であるJREも同時にインストールされる。
Spring Tool Suite (STS) のダウンロード
次に、EclipseベースのIDEであるSpring Tool Suite (STS) をダウンロードする。
spring-tool-suite-3.8.4.RELEASE-e4.6.3-win32-x86_64.zipを展開し、STS.exeを実行する。 初回起動時にWorkspace(プロジェクトの保存先)を聞かれるので、適当なパスを指定しOKを選択すると、STSが起動する。
Spring FrameworkとSpring Boot
Spring Frameworkは、Dependency Injection(DI)とAspect-oriented programming(AOP)と呼ばれる設計手法を活用したJavaフレームワークである。 これらの手法により、従来のフレームワークに対して、比較的簡潔にプログラムを実装することができる。
Spring Bootは、Springの各種プロジェクトを使って簡単にWebアプリケーションを書くことができるBootstrapフレームワークである。 Spring BootにはApache Tomcatが付属しており、従来のWARファイルを作成してアプリケーションサーバにデプロイする方法の他に、実行可能JARを作成して単独でTomcatサーバを起動する方法を選ぶことができる。
Spring Bootで簡単なWebアプリケーションを書いてみる
ここでは、Webアプリケーションとして簡単な掲示板を作ることにする。 また、構築を簡単にするために、データベースとしてMySQL等の代わりにJava製インメモリDBであるH2を利用する。
まず、STSでプロジェクトを作成する。
- メニューバーから「File」→「New」→「Spring Starter Project」を選択
- Nameを適当に設定し(ここではdemoのままとする)、PackagingにWarを選択してNext
- DependenciesとしてWeb、Thymeleaf、JPA、H2を選択してNext、Finish
Spring Bootのテンプレートが展開されるので、順に必要なクラスファイルを作成していく。 クラスファイルを新規作成するには、例えばsrc/main/java/com.example/MessageController.javaの場合次のようにする。
- 「src/main/java/com.example」を右クリックして「New」→「Class」を選択
- NameにMessageControllerを入力してFinish
以降に述べるすべてのファイルを作成した後の、Package Explorerのスクリーンショットを次に示す。
Web (Spring MVC)
Spring MVCは、MVCフレームワークでWebアプリケーション開発を行うためのライブラリであり、Spring Frameworkの中核を担うものである。 RubyにおけるRails、PythonにおけるDjangoに対応。
HTTPリクエストを処理するControllerは次のようになる。
package com.example; import java.util.List; import javax.servlet.http.HttpServletRequest; import javax.validation.Valid; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.validation.BindingResult; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; @Controller public class MessageController { @Autowired private MessageService service; @GetMapping("/messages") public String messages(Model model) { model.addAttribute("messageForm", new MessageForm()); List<Message> messages = service.getRecentMessages(100); model.addAttribute("messages", messages); return "messages"; } @PostMapping("/messages") public String messagesPost(Model model, @Valid MessageForm messageForm, BindingResult bindingResult, HttpServletRequest request) { if (bindingResult.hasErrors()) { List<Message> messages = service.getRecentMessages(100); model.addAttribute("messages", messages); return "messages"; } service.save(new Message(messageForm.getName(), messageForm.getText(), request.getRemoteAddr())); return "redirect:/messages"; } }
@GetMapping
および@PostMapping
はそれぞれHTTPのエンドポイントに対応しており、アノテーションが付けられた関数がリクエストに応じて実行される。
returnで返される文字列はViewのテンプレートファイル名を表しており、redirect:
がついている場合はそのエンドポイントにHTTPリダイレクトが行われる。
また、@Autowired
はDependency Injectionを意味しており、MessageServiceのインスタンスが実行時に代入される。
フォームから送信されるパラメータを定義するクラスは次のようになる。
package com.example; import javax.validation.constraints.Size; public class MessageForm { @Size(max=80) private String name; @Size(min=1, max=140) private String text; public String getName() { return name; } public void setName(String name) { this.name = name; } public String getText() { return text; } public void setText(String text) { this.text = text; } }
@Size
アノテーションにより、各パラメータの制約が指定されている。
この制約はControllerの@Valid
アノテーションによりチェックされ、エラーがある場合はエラーメッセージがViewに渡される。
Thymeleaf
Thymeleafはテンプレートエンジンであり、従来のJSPに代わるものである。 RubyにおけるERB、PythonにおけるJinja2に対応。
テンプレートファイルは次のようになる。
- src/main/resources/templates/messages.html
<!DOCTYPE html> <html xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8" /> <title>Hello, Spring Boot!</title> </head> <body> <h1>Hello, Spring Boot!</h1> <form action="#" th:action="@{/messages}" th:object="${messageForm}" method="post"> <p>Name (optional): <input type="text" th:field="*{name}" /> <em th:if="${#fields.hasErrors('name')}" th:errors="*{name}">Name Error</em></p> <p><textarea cols="40" rows="4" placeholder="Type anything" th:field="*{text}"></textarea> <em th:if="${#fields.hasErrors('text')}" th:errors="*{text}">Text Error</em></p> <p><input type="submit" value="Submit" /></p> </form> <h2>Recent messages</h2> <dl> <th:block th:each="message : ${messages}"> <dt> <span class="name" th:text="${message.name}" th:attr="title=${message.remoteAddr}">John Doe</span> <small th:text="${#dates.format(message.createdAt, '(yyyy-MM-dd HH:mm:ss)')}">(1970-01-01 00:00:00)</small> </dt> <dd th:text="${message.text}">Lorem ipsum dolor sit amet</dd> </th:block> </dl> </body> </html>
Thymeleafではth名前空間を用いて構造を記述する。
要素テキストの出力にth:text
を用いることで、HTMLエスケープが自動で行われる。
このとき、テンプレート中の要素テキストは無視されるため、例示テキストを記述しておく。
変数は${messages}
のようにして参照する。
また、th:object
でオブジェクトを指定し、その下位要素で*{name}
のように記述することで指定したオブジェクトのプロパティを参照できる。
HTML要素に対応しないブロック構造はth:block
で表すことができる。
JPA
JPAはJavaのオブジェクトとDBのリレーションを結び付けるO/Rマッパーである。 JPAを用いることで、DBに依存したSQL文を直接記述することなくデータの取得や保存ができる。 RubyにおけるActive Record、PythonにおけるSQLAlchemyに対応。
一般に、JPAではEntity、Repository、Serviceの三つが実装される。 テーブル定義に対応するEntityクラスは次のようになる。
package com.example; import java.util.Date; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; import javax.persistence.PrePersist; import javax.persistence.Temporal; import javax.persistence.TemporalType; @Entity public class Message { @Id @GeneratedValue(strategy = GenerationType.AUTO) private Long id; @Column(nullable = false) private String name; @Column(nullable = false) private String text; @Column(nullable = false) private String remoteAddr; @Temporal(TemporalType.TIMESTAMP) @Column(updatable = false) private Date createdAt; // JPA requirement protected Message() {} public Message(String name, String text, String remoteAddr) { this.name = name; this.text = text; this.remoteAddr = remoteAddr; } @PrePersist public void prePersist() { this.createdAt = new Date(); } @Override public String toString() { return String.format("Message[id=%d, name='%s', text='%s']", id, name, text); } public Long getId() { return id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getText() { return text; } public void setText(String text) { this.text = text; } public String getRemoteAddr() { return remoteAddr; } public void setRemoteAddr(String remoteAddr) { this.remoteAddr = remoteAddr; } public Date getCreatedAt() { return createdAt; } }
JPAの仕様に従い空のコンストラクタをprotectedで実装する必要があること、Date型には@Temporal(TemporalType.TIMESTAMP)
をつける必要があることに注意。
nullが入ることを期待しないフィールドには@Column(nullable = false)
をつけておくのが無難である。
Insert時、Update時の処理は、@PrePersist
、@PreUpdate
アノテーションをつけたメソッドで定義できる。
ここでは、@PrePersist
でcreatedAtメンバに作成日時をセットし、@Column(updatable = false)
かつsetter未定義とすることで更新できないようにしている。
また、idメンバも@GeneratedValue(strategy = GenerationType.AUTO)
により自動生成するため、setter未定義としている。
なお、getter/setterメソッドは右クリックから「Source」→「Generate Getter and Setters...」を選択して自動生成すると楽である。
DB操作に対応するRepositoryインタフェースは次のようになる。
package com.example; import java.util.List; import org.springframework.stereotype.Repository; import org.springframework.data.domain.Pageable; import org.springframework.data.repository.CrudRepository; @Repository public interface MessageRepository extends CrudRepository<Message, Long> { List<Message> findByOrderByIdDesc(Pageable pageable); }
CrudRepositoryインタフェースを継承することで、findAll()
やsave()
、delete()
等のメソッドが暗黙に定義される。
また、findByName(String name)
のようなメソッドを定義すると、SELECT * FROM messages WHERE name = ?
に相当する操作を行うメソッドとなる。
上のコードにおけるfindByOrderByIdDesc()
はByの後のカラム名を抜いたもので、SELECT * FROM messages ORDER BY id DESC
に相当する。
なお、インタフェースの実装は実行時に自動で提供される。
Controllerに対して公開するServiceクラスは次のようになる。
package com.example; import java.util.List; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.PageRequest; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @Service public class MessageService { @Autowired private MessageRepository repository; public List<Message> getRecentMessages(Integer n) { return repository.findByOrderByIdDesc(new PageRequest(0, n)); } @Transactional public void save(Message message) { repository.save(message); } }
save()
のようなDBに変更を加えるメソッドは、@Transactional
をつけて例外発生時にロールバックされるようにする。
Controllerと同様に、上のコードでも@Autowired
によるDependency Injectionが定義されており、MessageRepositoryのインスタンスが実行時に代入される。
付属のTomcatサーバで動かしてみる
付属のTomcatサーバでWebアプリケーションを動かし、ブラウザからアクセスしてみる。
- 「demo [boot]」を右クリックして「Run As」→「Spring Boot App」を選択
- Tomcatが起動したら、ブラウザから http://localhost:8080/messages にアクセス
ブラウザで表示した後のスクリーンショットを次に示す。
実行を中止しTomcatサーバを停止するには、Stopボタンを押せばよい。
Pivotal tc Serverで動かしてみる
STSでは、デプロイ用サーバとしてPivotal tc Serverが用意されている。 このサーバの上でWebアプリケーションを動かすには、次のようにする。
- 「demo [boot]」を右クリックして「Run As」→「1 Run on Server」を選択
- サーバとしてlocalhostのPivotal tc Serverを選択してNext
- 右側のConfiguredに作成したプロジェクト(ここではdemo)が入っていることを確認してFinish
- サーバが起動した後、STSの中央ペインでWebブラウザが開くのでそのまま http://localhost:8080/demo/messages にアクセス
サーバを停止させるには、付属のTomcatサーバの場合と同様にStopボタンを押せばよい。
WARファイルを作成してみる
他のアプリケーションサーバにデプロイするためのWARファイルを作成するには次のようにする。
- 「demo [boot]」を右クリックして「Export...」を選択
- 「Web」→「WAR file」を選択してNext
- Destinationに保存先ディレクトリを指定してFinish
関連リンク
- Getting Started · Serving Web Content with Spring MVC
- Getting Started · Handling Form Submission
- Getting Started · Validating Form Input
- Getting Started · Accessing Data with JPA
- 必要最小限のサンプルでThymeleafを完全マスター - Java EE 事始め!
- SpringBoot入門:JPAでデータアクセス - Web系開発メモ
- java - Why does JPA require a no-arg constructor for domain objects? - Stack Overflow
- Eclipseショートカットキーまとめ - wyukawa’s blog