Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.
Grails 3.0先取り!?
Spring Boot入門ハンズオン
2014-08-01 日本Grails/Groovyユーザーグループ
G*ワークショップ"Z" 第14弾
槙 俊明(@making)
最初にことわっておきますが
Groovy/Grails/Gradle
使いません(キリ)
Java/Mavenで許して😜
※ GroovyやGradleを使っても勧められますが、
自己責任でお願いします。
自己紹介
• @making
• http://blog.ik.am
• 日本Javaユーザーグループ(JJUG)幹事
• Groovy書けません
• Spring Boot本書いています
http://amzn.to/hajiboo
今日のハッシュタグ
#jggug_boot
今日のコンテンツ
• Spring Boot超概要(5分)
• Spring Bootハンズオン(100分)
• まとめ(5分)
Spring Boot超概要
Spring Bootとは?
• 簡単にいうと、「Spring Boot」
とは「Spring Framework」で
アプリケーションを簡単に作る
た めの仕組み
Grails3のベースになるらしい
Spring Boot以前のSpring Framework
Spring Boot以前のSpring Framework
色々ありすぎて、
組み合わせがわからない!
初期セットアップが大変!
Spring Boot
• あらかじめオススメの組み合わせが決まっ
ている
• 依存ライブラリを同梱するだけで自動で
設定がきまる
• 組み込みサーバーを同梱し、アプリを即
実行可能
Spring Bootで何が変わる?
• アプリの設定が変わる
• アプリのデプロイが変わる
Spring Bootで何が変わる?
• アプリの設定が変わる
• アプリのデプロイが変わる
ほとんど設定不要!
Spring Bootで何が変わる?
• アプリの設定が変わる
• アプリのデプロイが変わる
ほとんど設定不要!
jarを実行するだけ!
Spring Bootに関する詳しい話はまた今度!
• 2014-08-14に日本Springユー
ザー会 勉強会でSpring Bootにつ
いて話します。
• https://atnd.org/events/53770
(今から登録は厳しい...
体験してみましょう
Spring Bootハンズオン
ハンズオンの流れ
1.Hello WorldアプリでSpring Bootことはじめ
2.Spring BootでREST APIを作ろう
3.Spring Bootで画面のあるアプリを作ろう
4.Spring Securityで認証認可を追加...
ハンズオンの流れ
1.Hello WorldアプリでSpring Bootことはじめ
2.Spring BootでREST APIを作ろう
3.Spring Bootで画面のあるアプリを作ろう
4.Spring Securityで認証認可を追加...
ハンズオンの流れ
1.Hello WorldアプリでSpring Bootことはじめ
2.Spring BootでREST APIを作ろう
3.Spring Bootで画面のあるアプリを作ろう
4.Spring Securityで認証認可を追加...
質問があれば
•#jggug_boot でつぶいや
いてくればいつか回答し
ます
JDK 8のインストール
• http://www.oracle.com/technetwork/java/javase/
downloads/jdk8-downloads-2133151.html
• JAVA_HOMEを設定してね!
Spring Tool Suite(STS)
のインストール
• http://spring.io/tools
Mavenのインストール
• http://maven.apache.org/
• ダウンロードして、PATHに追加
curlのインストール
• http://curl.haxx.se/
• Windowsの人はダウンロードして、PATHに追加
本ハンズオン扱う技術
Webブラウザ
curl
Tomcat
Spring Boot
Spring Framework
SpringSecurity
ThymeLeaf
SpringMVC
Jackson
SpringDataJPA
Hiber...
題材のアプリ
• 簡易ブックマークシステム
1. Hello Worldアプリで
Spring Bootことはじめ
Mavenアーキタイプでプロジェ
クト雛形生成
$ mvn -B archetype:generate -DgroupId=com.example
-DartifactId=jggug-helloworld -Dversion=1.0.0-
S...
pom.xmlを編集
<parent>	
<groupId>org.springframework.boot</groupId>	
<artifactId>spring-boot-starter-parent</artifactId>	
<version>1.1.4...
Mavenプロジェクトをインポー
ト
インポート後
いろいろな依存関係が追加され
ている
package com.example;	
!
import org.springframework.boot.SpringApplication;	
import org.springframework.boot.autoconfigure....
package com.example;	
!
import org.springframework.boot.SpringApplication;	
import org.springframework.boot.autoconfigure....
まずは実行
• 実行方法は2通り
または
$ mvn spring-boot:run
ログ
組込Tomcatが起動した
http://localhost:8080 にアクセス
実行可能jarを作成
$ mvn package
jarを実行
$ java -jar target/jggug-helloworld-1.0.0-
SNAPSHOT.jar
プロパティを変更して実行
$ java -jar target/jggug-helloworld-1.0.0-
SNAPSHOT.jar --server.port=8888
--(プロパティ名)=(プロパティ値)
予め用意されている沢山のプロ
パティを変更可能
• http://docs.spring.io/spring-boot/docs/
1.1.4.RELEASE/reference/html/common-application-
propert...
• -Drun.arguments="--(プロパティ名)=(プロパティ値)"
で指定。
[補足] mavenプラグインの場合
$ mvn spring-boot:run -Drun.arguments="--server.port=8888"...
[参考] Spring LoadedでHot Reload
<plugin>	
<groupId>org.springframework.boot</groupId>	
<artifactId>spring-boot-maven-plugin<...
[参考] Spring LoadedでHot Reload
$ mvn spring-boot:run
[INFO] Attaching agents: [/Users/****/.m2/repository/org/springframewo...
アプリ実行中にソースを変更してコンパイル
@RequestMapping("/")	
String home() {	
return "Hello World!";	
}
@RequestMapping("/")	
String home() ...
注意
• まだまだSpring Loadedは未熟で、リ
ロードされないケースや副作用による
エラーが発生することもある。
• うまく更新できたらラッキー。
• 開発を暖かく見守りましょう。
Integration Test
@RunWith(SpringJUnit4ClassRunner.class)	
@SpringApplicationConfiguration(classes = App.class)	
@WebAppCon...
Integration Test
組込Tomcatを起動して、
HTTPリクエストを送り、
HTTPレスポンスをチェック
他のJVM言語の例
• https://github.com/making/spring-boot-demo-jvm-
languages
• Java/Groovy/Scala/KotlinそれぞれのGradleプロジェク
トサンプルがあるの...
2. Spring Bootで

REST APIを作ろう
Webブラウザ
curl
Tomcat
Spring Boot
Spring Framework
SpringSecurity
ThymeLeaf
SpringMVC
Jackson
SpringDataJPA
Hibernate
H2
Dat...
REST APIでBookmark管理
• 「REST」はクライアントとサーバ間でデータをやりと
りするためのソフトウェアアーキテクチャスタイルの
一つ。
• RESTでは、「リソース」に対するCRUD操作をHTTP
メソッド(POST/GET...
実装するAPI
API
HTTP
メソッド
リソースパス
正常時レスポンス
ステータス
ブックマーク
全件取得
GET /api/bookmarks 200 OK
ブックマーク
新規登録
POST

! /api/bookmarks 201 C...
Mavenアーキタイプでプロジェ
クト雛形生成
$ mvn -B archetype:generate -DgroupId=com.example
-DartifactId=bookmark -Dversion=1.0.0-SNAPSHOT -...
<parent>	
<groupId>org.springframework.boot</groupId>	
<artifactId>spring-boot-starter-parent</artifactId>	
<version>1.1.4...
必要な依存関係が追加される
Mavenプロジェクトのインポート
出来上がりイメージ
レイヤー化アプリケーション
DI Container
Controller Service Repository
use use
inject inject
レイヤー化アプリケーション
DI Container
Controller Service Repository
use use
inject inject
この順で実装する
ドメインオブジェクト作成
• ブックマークサービスに必要な情報
• ID
• ブックマーク名
• URL
ドメインオブジェクト作成
• com.example.domain.Bookmark
@Entity	
public class Bookmark {	
@Id	
@GeneratedValue	
private Long id;	
@NotN...
リポジトリ作成
• 「リポジトリ」は、 ドメインオブジェクト
の保存、取得、検索といった操作をカプセ
ル化し、”コレクションオブジェクト”のよ
うに振る舞う役割をもつ。
• 「リポジトリ」にロジックを含めない。
Spring Data JPAでリポジトリ作成
• com.example.repository.BookmarkRepository
package com.example.repository;	
!
import com.example....
サービス作成
• 全件取得、新規作成、一件削除用のメソッ
ドを作成する
• POJOに@Serviceアノテーションを付け
るとDIコンテナに自動登録される(コン
ポーネントスキャン)
サービス作成
• com.example.service.BookmarkService
@Service	
@Transactional	
public class BookmarkService {	
@Autowired	
Bookmar...
コントローラー作成
• 基本的にSpring MVCを使ったプログラミン
グを行う
• POJOに@Controllerを付けるとHTTPのリク
エストを受けられる
• @RestControllerを付けると、Controllerの
メソッド...
コントローラーの
リクエストマッピング
• HTTPリクエストとコントローラーのメソッドのマッ
ピング表
API
HTTP
メソッ
ド
リソースパス メソッド 返り値の型
ブックマー
ク全件取得
GET /api/bookmarks getBo...
コントローラー作成
• com.example.api.BookmarkRestController
@RestController	
@RequestMapping("api/bookmarks")	
public class Bookmar...
入力チェックを実施
@RestController	
@RequestMapping("api/bookmarks")	
public class BookmarkRestController {	
@Autowired	
BookmarkSe...
アプリケーションのエントリポイント作成
package com.example;	
!
import org.springframework.boot.SpringApplication;	
import org.springframework...
アプリケーション実行
• リクエストマッピングのログが出力される
ことを確認s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/api/
bookmarks],methods=[GET],...
APIチェック
• ブックマーク新規作成
$ curl http://localhost:8080/api/bookmarks
-v -X POST -H 'Content-Type:application/
json' -d '{"name"...
APIチェック
• ブックマーク新規作成
> POST /api/bookmarks HTTP/1.1	
> User-Agent: curl/7.30.0	
> Host: localhost:8080	
> Accept: */*	
> C...
APIチェック
• ブックマーク全件取得
$ curl http://localhost:8080/api/bookmarks
-v -X GET
http://bit.ly/jggug-02-09
APIチェック
• ブックマーク全件取得
> GET /api/bookmarks HTTP/1.1	
> User-Agent: curl/7.30.0	
> Host: localhost:8080	
> Accept: */*	
>	
<...
APIチェック
• ブックマーク1件削除
$ curl http://localhost:8080/api/bookmarks/
1 -v -X DELETE
http://bit.ly/jggug-02-10
APIチェック
• ブックマーク1件削除
> DELETE /api/bookmarks/1 HTTP/1.1	
> User-Agent: curl/7.30.0	
> Host: localhost:8080	
> Accept: */*	...
課題1
• ブックマーク1件取得APIを実装してみよう
API
HTTP
メソッ
ド
リソースパス メソッド 返り値の型
ブックマー
ク一件取得
GET
/api/
bookmarks/{id}
getBookmark Bookmark
アプリケーションをカスタマイズしよう
• 設定ファイルの設定方法
• JavaConfigでBean定義を行う方法
• ログの設定変更方法
これらを試していきます
JDBCドライバの設定値を変更
• インメモリH2からファイルベースH2へ
• 設定ファイルはクラスパス直下の
application.ymlまたは
application.properties
YAMLが便利
application.yml作成
spring:	
datasource:	
driverClassName: org.h2.Driver	
url: jdbc:h2:file:/tmp/bookmark	
username: sa	
pas...
[補足] 設定値一覧(再掲)
• http://docs.spring.io/spring-boot/docs/
1.1.4.RELEASE/reference/html/common-
application-properties.html
Log4JDBCでSQLログを出力しよう
• pom.xmlに以下を追加
<dependency>	
<groupId>org.lazyluke</groupId>	
<artifactId>log4jdbc-remix</artifactId...
• Bean定義を行うJavaConfigクラスを作成
Log4JDBCでSQLログを出力しよう
package com.example;	
!
import org.springframework.context.annotation.Bean...
Log4JDBCでSQLログを出力しよう
@Configuration	
public class AppConfig {	
@Autowired	
DataSourceProperties properties;	
DataSource da...
• src/main/resources/logback.xmlを
作成
<?xml version="1.0" encoding="UTF-8"?>	
<configuration>	
<include resource="org/sprin...
• アプリケーションを再起動して各APIを実行
Log4JDBCでSQLログを出力しよう
jdbc.sqltiming :
org.hibernate.engine.jdbc.internal.ResultSetReturnImpl.execu...
課題2
• bookmarkアプリケーションのjarを作成
し、実行時にspring.datasource.*プ
ロパティを変更して、接続先DBを変更し
よう(MySQLやPostgreSQLで試してみる
と面白い)。
REST APIを任意のクライアント
からアクセスできるようにする
• Angular.jsで作成したSingle Page
Applicationからアクセスしてみよう
• http://jsfiddle.net/Ca2g2/
作っておきました
REST APIを任意のクライアント
からアクセスできるようにする
REST APIを任意のクライアント
からアクセスできるようにする
XMLHttpRequest cannot load http://localhost:
8080/api/bookmarks. No 'Access-Control-Allo...
REST APIを任意のクライアント
からアクセスできるようにする
• Cross-Origin Resource Sharing (CORS)
の設定を行うServletFilterを作成
• http://spring.io/guides/...
REST APIを任意のクライアント
からアクセスできるようにする
• AppConfigに以下のBean定義を追加
@Bean	
Filter corsFilter() {	
return new Filter() {	
public void...
• 再起動後に、再アクセス
REST APIを任意のクライアント
からアクセスできるようにする
REST APIのIntegrationTest
@RunWith(SpringJUnit4ClassRunner.class)	
@SpringApplicationConfiguration(classes = App.class)	
@W...
テストの初期化
@Autowired	
BookmarkRepository bookmarkRepository;	
@Value("${local.server.port}")	
int port;	
String apiEndpoint;...
全件取得APIのテスト
@Test	
public void testGetBookmarks() throws Exception {	
ResponseEntity<List<Bookmark>> response = restTempla...
新規作成APIのテスト
@Test	
public void testPostBookmarks() throws Exception {	
Bookmark google = new Bookmark();	
google.setName("...
1件削除APIのテスト
@Test	
public void testDeleteBookmarks() throws Exception {	
ResponseEntity<Void> response = restTemplate.exch...
REST編修了
• お疲れ様でした・・・
• 本当は説明したかったけれども省略した内容
• 入力チェック
• 例外ハンドリング
• ページネーション
http://terasolunaorg.github.io/guideline/1.0.x/...
3. Spring Bootで

画面のあるアプリを作ろう
Webブラウザ
curl
Tomcat
Spring Boot
Spring Framework
SpringSecurity
ThymeLeaf
SpringMVC
Jackson
SpringDataJPA
Hibernate
H2
Dat...
DI Container
Controller Service Repository
use use
inject inject
ここだけ追加 REST編と同じ
出来上がりイメージ
追加するファイル
画面遷移
リダイレクト
入力エラー
画面遷移
API
HTTP
メソッド
パス
コントローラー
のメソッド
VIEW
ブックマー
ク一覧表示
GET /bookmark list bookmark/list
ブックマー
ク新規登録
POST

!
/bookmark/
creat...
コントローラー作成
• com.example.web.BookmarkController
@Controller	
@RequestMapping("bookmark")	
public class BookmarkController {...
ブックマーク一覧表示
@RequestMapping(value = "list", method = RequestMethod.GET)	
String list(Model model) {	
List<Bookmark> bookmar...
ブックマーク新規登録
@RequestMapping(value = "create", method = RequestMethod.POST)	
String create(@Validated Bookmark bookmark, Bin...
ブックマーク1件削除
@RequestMapping(value = "delete", method = RequestMethod.POST)	
String delete(@RequestParam("id") Long id) {	
b...
文字コード設定フィルターを定義
• AppConfigにCharacterEncodingFilterの定義を
追加。コレがないとPOSTで日本語が文字化けする。
@Bean	
@Order(Ordered.HIGHEST_PRECEDENCE...
ThymeLeafで画面作成
• ThymeLeafは素のHTMLにth:***属性(または
data-th-***属性)をつけることで動的な画面
を作れるテンプレートエンジン。
• http://www.thymeleaf.org/
• テン...
依存関係追加
<dependency>	
<groupId>org.springframework.boot</groupId>	
<artifactId>spring-boot-starter-thymeleaf</artifactId>	
...
HTML作成
• src/main/resources/templates/bookmark/
list.htmlを作成
[補足] ThymeLeafのキャッシュを無効化
• 開発中は変更を即反映してほしいため、
spring.thymeleaf.cache: falseの
プロパティを追加
spring:	
thymeleaf:	
cache: false
ap...
list.html
<!DOCTYPE html>	
<html xmlns:th="http:///www.thymeleaf.org">	
<head>	
<meta charset="UTF-8" />	
<title>Bookmarks...
一覧表示画面
<ul>	
<li th:each="bookmark : ${bookmarks}"><a	
th:href="${bookmark.url}" th:text="${bookmark.name}">dummy</a>	
<fo...
ブラウザでテンプレートを表示
サーバーで表示
REST APIで登録したデータが表示されているはず
新規作成フォーム
<form th:action="@{/bookmark/create}" th:object="${bookmark}"	
method="post">	
<dl>	
<dt><label for="name">Name</...
静的リソースの配置
• JavaScript、CSS、画像などの静的リソースはクラスパ
ス直下のstaticフォルダに置くことで、コンテキストルー
トからアクセスできる。
CSS作成
• src/main/resources/static/css/style.css
.error-input {	
border-color: #b94a48;	
margin-left: 5px;	
}	
!
.error-mes...
CSSの読み込み
<link rel="stylesheet" type="text/css"	
href="../../static/css/style.css"	
th:href="@{/css/style.css}" />
サーバーで実行...
ブラウザでテンプレートを表示
サーバーで実行
サーバーで実行
[補足] WebJarを使ってみよう
• TODO
課題3
• 新規作成画面と一覧画面を別のペー
ジにしてみよう。
画面のあるアプリ編修了
•本当は説明したかったけれども省略した内容
•入力チェック
•http://terasolunaorg.github.io/guideline/1.0.x/ja/
ArchitectureInDetail/Validat...
4. Spring Securityで

認証・認可を追加しよう
Webブラウザ
curl
Tomcat
Spring Boot
Spring Framework
SpringSecurity
ThymeLeaf
SpringMVC
Jackson
SpringDataJPA
Hibernate
H2
Dat...
出来上がりイメージ
追加するファイル
依存関係追加
<dependency>	
<groupId>org.springframework.boot</groupId>	
<artifactId>spring-boot-starter-security</artifactId>	
<...
依存性を追加しただけで
• サーバーを再起動すると、Basic認証が
有効になる
デフォルトのBasic認証
• ユーザー名はuser
• パスワードは起動時にランダム値が生成され、ログに出
力される
2014-07-27 23:01:33.306 INFO 15121 --- [ost-startStop-1]
b.a.s...
• security.basic.enabled: falseの
プロパティを追加
デフォルトのBasic認証は無効にする
security:	
basic:	
enabled: false
application.yml
http://bit...
ログイン画面のある認証・認可
の設定を行う
• com.example.SecurityConfigにSpring Securityの
設定を行う
@Configuration	
@EnableWebMvcSecurity	
public cl...
認証設定
@Override	
@SuppressWarnings({ "rawtypes", "unchecked" })	
protected void configure(AuthenticationManagerBuilder auth...
認可設定 (1/2)
@Override	
public void configure(WebSecurity web) throws Exception {	
web.ignoring().antMatchers("/css/**", "/j...
認可設定 (2/2)
@Override	
protected void configure(HttpSecurity http) throws Exception {	
http.authorizeRequests().antMatchers...
ログイン画面作成
• com.example.web.LoginController
package com.example.web;	
!
import org.springframework.stereotype.Controller;	
...
ログイン画面作成
• src/main/resources/templates/login/loginForm.html
<!DOCTYPE html>	
<html xmlns:th="http:///www.thymeleaf.org">	...
アプリケーション実行
CSRFトークンが自動で埋め込まれている
セキュアなHTTPレスポンス
ヘッダが埋め込まれている
認証・認可編修了
• お疲れ様でした!
• 本当は説明したかったけれども省略した内容
• UserDetailsServiceを実装してDBから認証ユーザー取得
• ログインユーザーの表示(@AuthenticationPrincipal)
•...
まとめ
まとめ
• Spring Bootで
• REST APIを作成した
• 画面のあるアプリを作成した
• 認証・認可処理を追加した
Spring Bootによるアプリケーション開発
の基礎が掴めたはず!
是非職場で
Spring Bootを
この資料を
広めてください!
宣伝
• Spring Bootの入門本(今日の内容
みたいな話+いろいろ)を執筆中。
• 出版されたら是非買ってくださ
い!
中級者編やりたい
•Bookmarkエンティティに所有者Userをひも
付けし、User毎にブックマーク管理
•Userエンティティを使って認証・認可
•OAuth2でREST APIに認可処理を追加
•FlywayでDBマイグレーション
•S...
お疲れ様でした!
Upcoming SlideShare
Loading in …5
×

Grails 3.0先取り!? Spring Boot入門ハンズオン #jggug_boot

32,974 views

Published on

Spring Bootのハンズオン資料です。

----

Grailsの次期バージョン3.0でベースになることが予定されている、Spring界隈の新しいトレンド"Spring Boot"のハンズオンを通じて、Spring Bootのイメージを掴んでもらいたいと思います。内容は以下の通りです。

Spring Boot概要説明
Spring Bootを用いて簡単なアプリケーションを実際に作ってみる
(合計で約二時間弱)

Published in: Technology
  • https://github.com/making/jggug-handson-bookmark Herokuボタンに対応させました。本ハンズオンで扱っているBookmarkアプリを2クリックでHerokuにデプロイできます。
       Reply 
    Are you sure you want to  Yes  No
    Your message goes here
  • 訂正

    P.88 Gradleだとlog4jdbcをdependencyにいれるとJUnitのバージョンが4.7になってしまうみたいです。嫌な方はexcludeしてください。

    P.122
    delete?id=${bookmark.id}
    ではなく
    delete(id=${bookmark.id})
    でした

    P.145
    defaultSuccessUrlの値は
    /book/listではなく、/bookmark/list
       Reply 
    Are you sure you want to  Yes  No
    Your message goes here
  • ソースコードはこちら

    https://github.com/making/jggug-workshop-springboot/tree/jggug-workshop-20140801
       Reply 
    Are you sure you want to  Yes  No
    Your message goes here

Grails 3.0先取り!? Spring Boot入門ハンズオン #jggug_boot

  1. 1. Grails 3.0先取り!? Spring Boot入門ハンズオン 2014-08-01 日本Grails/Groovyユーザーグループ G*ワークショップ"Z" 第14弾 槙 俊明(@making)
  2. 2. 最初にことわっておきますが
  3. 3. Groovy/Grails/Gradle
  4. 4. 使いません(キリ)
  5. 5. Java/Mavenで許して😜 ※ GroovyやGradleを使っても勧められますが、 自己責任でお願いします。
  6. 6. 自己紹介 • @making • http://blog.ik.am • 日本Javaユーザーグループ(JJUG)幹事 • Groovy書けません • Spring Boot本書いています http://amzn.to/hajiboo
  7. 7. 今日のハッシュタグ #jggug_boot
  8. 8. 今日のコンテンツ • Spring Boot超概要(5分) • Spring Bootハンズオン(100分) • まとめ(5分)
  9. 9. Spring Boot超概要
  10. 10. Spring Bootとは? • 簡単にいうと、「Spring Boot」 とは「Spring Framework」で アプリケーションを簡単に作る た めの仕組み Grails3のベースになるらしい
  11. 11. Spring Boot以前のSpring Framework
  12. 12. Spring Boot以前のSpring Framework 色々ありすぎて、 組み合わせがわからない! 初期セットアップが大変!
  13. 13. Spring Boot • あらかじめオススメの組み合わせが決まっ ている • 依存ライブラリを同梱するだけで自動で 設定がきまる • 組み込みサーバーを同梱し、アプリを即 実行可能
  14. 14. Spring Bootで何が変わる? • アプリの設定が変わる • アプリのデプロイが変わる
  15. 15. Spring Bootで何が変わる? • アプリの設定が変わる • アプリのデプロイが変わる ほとんど設定不要!
  16. 16. Spring Bootで何が変わる? • アプリの設定が変わる • アプリのデプロイが変わる ほとんど設定不要! jarを実行するだけ!
  17. 17. Spring Bootに関する詳しい話はまた今度! • 2014-08-14に日本Springユー ザー会 勉強会でSpring Bootにつ いて話します。 • https://atnd.org/events/53770 (今から登録は厳しいかも・・・)
  18. 18. 体験してみましょう
  19. 19. Spring Bootハンズオン
  20. 20. ハンズオンの流れ 1.Hello WorldアプリでSpring Bootことはじめ 2.Spring BootでREST APIを作ろう 3.Spring Bootで画面のあるアプリを作ろう 4.Spring Securityで認証認可を追加しよう
  21. 21. ハンズオンの流れ 1.Hello WorldアプリでSpring Bootことはじめ 2.Spring BootでREST APIを作ろう 3.Spring Bootで画面のあるアプリを作ろう 4.Spring Securityで認証認可を追加しよう 100分だと多分ここまで
  22. 22. ハンズオンの流れ 1.Hello WorldアプリでSpring Bootことはじめ 2.Spring BootでREST APIを作ろう 3.Spring Bootで画面のあるアプリを作ろう 4.Spring Securityで認証認可を追加しよう 100分だと多分ここまで 土日にやろう
  23. 23. 質問があれば •#jggug_boot でつぶいや いてくればいつか回答し ます
  24. 24. JDK 8のインストール • http://www.oracle.com/technetwork/java/javase/ downloads/jdk8-downloads-2133151.html • JAVA_HOMEを設定してね!
  25. 25. Spring Tool Suite(STS) のインストール • http://spring.io/tools
  26. 26. Mavenのインストール • http://maven.apache.org/ • ダウンロードして、PATHに追加
  27. 27. curlのインストール • http://curl.haxx.se/ • Windowsの人はダウンロードして、PATHに追加
  28. 28. 本ハンズオン扱う技術 Webブラウザ curl Tomcat Spring Boot Spring Framework SpringSecurity ThymeLeaf SpringMVC Jackson SpringDataJPA Hibernate H2 Database画面のあるアプリ REST API
  29. 29. 題材のアプリ • 簡易ブックマークシステム
  30. 30. 1. Hello Worldアプリで Spring Bootことはじめ
  31. 31. Mavenアーキタイプでプロジェ クト雛形生成 $ mvn -B archetype:generate -DgroupId=com.example -DartifactId=jggug-helloworld -Dversion=1.0.0- SNAPSHOT -DarchetypeArtifactId=maven-archetype- quickstart Spring Bootに関係のない汎用的な手順 http://bit.ly/jggug-01-00
  32. 32. pom.xmlを編集
  33. 33. <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.1.4.RELEASE</version> </parent> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> <properties> <java.version>1.8</java.version> </properties> この設定を追加 http://bit.ly/jggug-01-01
  34. 34. Mavenプロジェクトをインポー ト
  35. 35. インポート後
  36. 36. いろいろな依存関係が追加され ている
  37. 37. package com.example; ! import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; ! @RestController @EnableAutoConfiguration public class App { ! @RequestMapping("/") String home() { return "Hello World!"; } ! public static void main(String[] args) { SpringApplication.run(App.class, args); } } App.javaの編集 http://bit.ly/jggug-01-02
  38. 38. package com.example; ! import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; ! @RestController @EnableAutoConfiguration public class App { ! @RequestMapping("/") String home() { return "Hello World!"; } ! public static void main(String[] args) { SpringApplication.run(App.class, args); } } App.javaの編集 http://bit.ly/jggug-01-02 魔法のアノテーション
  39. 39. まずは実行 • 実行方法は2通り または $ mvn spring-boot:run
  40. 40. ログ 組込Tomcatが起動した
  41. 41. http://localhost:8080 にアクセス
  42. 42. 実行可能jarを作成 $ mvn package
  43. 43. jarを実行 $ java -jar target/jggug-helloworld-1.0.0- SNAPSHOT.jar
  44. 44. プロパティを変更して実行 $ java -jar target/jggug-helloworld-1.0.0- SNAPSHOT.jar --server.port=8888 --(プロパティ名)=(プロパティ値)
  45. 45. 予め用意されている沢山のプロ パティを変更可能 • http://docs.spring.io/spring-boot/docs/ 1.1.4.RELEASE/reference/html/common-application- properties.html 一度作ったjarはそのまま本番環境で使用可能。 配布も可能。
  46. 46. • -Drun.arguments="--(プロパティ名)=(プロパティ値)" で指定。 [補足] mavenプラグインの場合 $ mvn spring-boot:run -Drun.arguments="--server.port=8888" http://bit.ly/jggug-01-03
  47. 47. [参考] Spring LoadedでHot Reload <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <dependencies> <dependency> <groupId>org.springframework</groupId> <artifactId>springloaded</artifactId> <version>1.2.0.RELEASE</version> </dependency> </dependencies> </plugin> maven pluginに追加 http://bit.ly/jggug-01-04
  48. 48. [参考] Spring LoadedでHot Reload $ mvn spring-boot:run [INFO] Attaching agents: [/Users/****/.m2/repository/org/springframework/ springloaded/1.2.0.RELEASE/springloaded-1.2.0.RELEASE.jar] objc[11505]: Class JavaLaunchHelper is implemented in both /Library/Java/ JavaVirtualMachines/jdk1.8.0_11.jdk/Contents/Home/jre/bin/java and /Library/ Java/JavaVirtualMachines/jdk1.8.0_11.jdk/Contents/Home/jre/lib/ libinstrument.dylib. One of the two will be used. Which one is undefined. ! . ____ _ __ _ _ / / ___'_ __ _ _(_)_ __ __ _ ( ( )___ | '_ | '_| | '_ / _` | / ___)| |_)| | | | | || (_| | ) ) ) ) ' |____| .__|_| |_|_| |___, | / / / / =========|_|==============|___/=/_/_/_/ :: Spring Boot :: (v1.1.4.RELEASE) Hot Reload用のagentが自動 的にアタッチされる
  49. 49. アプリ実行中にソースを変更してコンパイル @RequestMapping("/") String home() { return "Hello World!"; } @RequestMapping("/") String home() { return "Hello Spring!"; } 再起動することなく アプリが更新された
  50. 50. 注意 • まだまだSpring Loadedは未熟で、リ ロードされないケースや副作用による エラーが発生することもある。 • うまく更新できたらラッキー。 • 開発を暖かく見守りましょう。
  51. 51. Integration Test @RunWith(SpringJUnit4ClassRunner.class) @SpringApplicationConfiguration(classes = App.class) @WebAppConfiguration @IntegrationTest("server.port:0") public class AppTest { @Value("${local.server.port}") int port; ! RestTemplate restTemplate = new TestRestTemplate(); ! @Test public void testHome() { ResponseEntity<String> response = restTemplate.getForEntity( "http://localhost:" + port, String.class); assertThat(response.getStatusCode(), is(HttpStatus.OK)); assertThat(response.getBody(), is("Hello World!")); } } 空いているポートを使用 実際に使ったポート番号 テスト用HTTPクライアント エントリポイントのクラス指定 http://bit.ly/jggug-01-05
  52. 52. Integration Test 組込Tomcatを起動して、 HTTPリクエストを送り、 HTTPレスポンスをチェック
  53. 53. 他のJVM言語の例 • https://github.com/making/spring-boot-demo-jvm- languages • Java/Groovy/Scala/KotlinそれぞれのGradleプロジェク トサンプルがあるのでお好みの言語でSpring Bootアプ リを作りましょう
  54. 54. 2. Spring Bootで
 REST APIを作ろう
  55. 55. Webブラウザ curl Tomcat Spring Boot Spring Framework SpringSecurity ThymeLeaf SpringMVC Jackson SpringDataJPA Hibernate H2 Database画面のあるアプリ REST API
  56. 56. REST APIでBookmark管理 • 「REST」はクライアントとサーバ間でデータをやりと りするためのソフトウェアアーキテクチャスタイルの 一つ。 • RESTでは、「リソース」に対するCRUD操作をHTTP メソッド(POST/GET/PUT/DELETEなど)を使ってWeb APIとしてクライアントに公開する。 • 今回は「ブックマーク」が「リソース」
  57. 57. 実装するAPI API HTTP メソッド リソースパス 正常時レスポンス ステータス ブックマーク 全件取得 GET /api/bookmarks 200 OK ブックマーク 新規登録 POST ! /api/bookmarks 201 CREATED ブックマーク 一件削除 DELETE /api/bookmarks/{id} 204 NO CONTENT
  58. 58. Mavenアーキタイプでプロジェ クト雛形生成 $ mvn -B archetype:generate -DgroupId=com.example -DartifactId=bookmark -Dversion=1.0.0-SNAPSHOT - DarchetypeArtifactId=maven-archetype-quickstart $ mkdir bookmark/src/main/resources artifactId以外helloworldのときと同じ src/main/resourcesを作成しておく http://bit.ly/jggug-02-00
  59. 59. <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.1.4.RELEASE</version> </parent> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <dependency> <groupId>com.h2database</groupId> <artifactId>h2</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <dependencies> <dependency> <groupId>org.springframework</groupId> <artifactId>springloaded</artifactId> <version>1.2.0.RELEASE</version> </dependency> </dependencies> </plugin> </plugins> </build> <properties> <java.version>1.8</java.version> </properties> pom修正 JPAを使用したい場合は、 spring-boot-starter-data-jpa を追加するだけ。 JDBCドライバも必要 http://bit.ly/jggug-02-01
  60. 60. 必要な依存関係が追加される
  61. 61. Mavenプロジェクトのインポート
  62. 62. 出来上がりイメージ
  63. 63. レイヤー化アプリケーション DI Container Controller Service Repository use use inject inject
  64. 64. レイヤー化アプリケーション DI Container Controller Service Repository use use inject inject この順で実装する
  65. 65. ドメインオブジェクト作成 • ブックマークサービスに必要な情報 • ID • ブックマーク名 • URL
  66. 66. ドメインオブジェクト作成 • com.example.domain.Bookmark @Entity public class Bookmark { @Id @GeneratedValue private Long id; @NotNull @Size(min = 1, max = 255) private String name; @NotNull @Size(min = 1, max = 255) @URL private String url; // omitted setter & getter } JPAのエンティティとして アノテーションをつける デフォルトでは インメモリ組み込みDBが使用 され、DDLも自動生成&実行 http://bit.ly/jggug-02-02
  67. 67. リポジトリ作成 • 「リポジトリ」は、 ドメインオブジェクト の保存、取得、検索といった操作をカプセ ル化し、”コレクションオブジェクト”のよ うに振る舞う役割をもつ。 • 「リポジトリ」にロジックを含めない。
  68. 68. Spring Data JPAでリポジトリ作成 • com.example.repository.BookmarkRepository package com.example.repository; ! import com.example.domain.Bookmark; import org.springframework.data.jpa.repository.JpaRepository; ! public interface BookmarkRepository extends JpaRepository<Bookmark, Long> { ! } エンティティクラス、主キークラス たったこれだけでJPAのEntityManagerを使用した 基本的なDBのCRUD操作を利用できる。(SQL不要) http://bit.ly/jggug-02-03
  69. 69. サービス作成 • 全件取得、新規作成、一件削除用のメソッ ドを作成する • POJOに@Serviceアノテーションを付け るとDIコンテナに自動登録される(コン ポーネントスキャン)
  70. 70. サービス作成 • com.example.service.BookmarkService @Service @Transactional public class BookmarkService { @Autowired BookmarkRepository bookmarkRepository; ! public List<Bookmark> findAll() { return bookmarkRepository.findAll(new Sort(Sort.Direction.ASC, "id")); } ! public Bookmark save(Bookmark bookmark) { return bookmarkRepository.save(bookmark); } ! public void delete(Long id) { bookmarkRepository.delete(id); } } リポジトリをインジェクション 宣言的トランザクション管理 IDで昇順に検索 http://bit.ly/jggug-02-04
  71. 71. コントローラー作成 • 基本的にSpring MVCを使ったプログラミン グを行う • POJOに@Controllerを付けるとHTTPのリク エストを受けられる • @RestControllerを付けると、Controllerの メソッドの返り値が、シリアライズされ、その ままHTTPレスポンスのボディになる
  72. 72. コントローラーの リクエストマッピング • HTTPリクエストとコントローラーのメソッドのマッ ピング表 API HTTP メソッ ド リソースパス メソッド 返り値の型 ブックマー ク全件取得 GET /api/bookmarks getBookmarks List<Bookmark> ブックマー ク新規登録 POST ! /api/bookmarks postBookmarks Bookmark ブックマー ク一件削除 DELET E /api/ bookmarks/{id} deleteBookmark void
  73. 73. コントローラー作成 • com.example.api.BookmarkRestController @RestController @RequestMapping("api/bookmarks") public class BookmarkRestController { @Autowired BookmarkService bookmarkService; ! @RequestMapping(method = RequestMethod.GET) List<Bookmark> getBookmarks() { return bookmarkService.findAll(); } @RequestMapping(method = RequestMethod.POST) @ResponseStatus(HttpStatus.CREATED) Bookmark postBookmarks(@RequestBody Bookmark bookmark) { return bookmarkService.save(bookmark); } @RequestMapping(value = "{id}", method = RequestMethod.DELETE) @ResponseStatus(HttpStatus.NO_CONTENT) void deleteBookmarks(@PathVariable("id") Long id) { bookmarkService.delete(id); } } サービスをインジェクション パスやHTTPメソッド等の組み 合わせとコントローラーのメソッ ドを結びつける リクエストボディ をJavaBeanに マッピング プレースホルダの 値を取得 http://bit.ly/jggug-02-05
  74. 74. 入力チェックを実施 @RestController @RequestMapping("api/bookmarks") public class BookmarkRestController { @Autowired BookmarkService bookmarkService; ! @RequestMapping(method = RequestMethod.GET) List<Bookmark> getBookmarks() { return bookmarkService.findAll(); } @RequestMapping(method = RequestMethod.POST) @ResponseStatus(HttpStatus.CREATED) Bookmark postBookmarks(@Validated @RequestBody Bookmark bookmark) { return bookmarkService.save(bookmark); } @RequestMapping(value = "{id}", method = RequestMethod.DELETE) @ResponseStatus(HttpStatus.NO_CONTENT) void deleteBookmarks(@PathVariable("id") Long id) { bookmarkService.delete(id); } } 詳細は割愛・・・ 参照URLを後述 http://bit.ly/jggug-02-06
  75. 75. アプリケーションのエントリポイント作成 package com.example; ! import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.context.annotation.ComponentScan; ! @EnableAutoConfiguration @ComponentScan public class App { ! public static void main(String[] args) { SpringApplication.run(App.class, args); } } http://bit.ly/jggug-02-07
  76. 76. アプリケーション実行 • リクエストマッピングのログが出力される ことを確認s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/api/ bookmarks],methods=[GET],params=[],headers=[],consumes=[],produces=[],cust om=[]}" onto java.util.List<com.example.domain.Bookmark> com.example.api.BookmarkRestController.getBookmarks() s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/api/ bookmarks],methods=[POST],params=[],headers=[],consumes=[],produces=[],cus tom=[]}" onto com.example.domain.Bookmark com.example.api.BookmarkRestController.postBookmarks(com.example.domain.Bo okmark) s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/api/bookmarks/ {id}],methods=[DELETE],params=[],headers=[],consumes=[],produces=[],custom =[]}" onto void com.example.api.BookmarkRestController.deleteBookmarks(java.lang.Long)
  77. 77. APIチェック • ブックマーク新規作成 $ curl http://localhost:8080/api/bookmarks -v -X POST -H 'Content-Type:application/ json' -d '{"name":"Google", "url":"http:// google.com"}' http://bit.ly/jggug-02-08 Windowsのコマンドプロンプトだとシングルクオートが効か ないのでダブルクオートとエスケープしてください
  78. 78. APIチェック • ブックマーク新規作成 > POST /api/bookmarks HTTP/1.1 > User-Agent: curl/7.30.0 > Host: localhost:8080 > Accept: */* > Content-Type:application/json > Content-Length: 44 > < HTTP/1.1 201 Created < Server: Apache-Coyote/1.1 < Content-Type: application/json;charset=UTF-8 < Transfer-Encoding: chunked < Date: Sat, 26 Jul 2014 17:44:11 GMT < {"id":1,"name":"Google","url":"http://google.com"}
  79. 79. APIチェック • ブックマーク全件取得 $ curl http://localhost:8080/api/bookmarks -v -X GET http://bit.ly/jggug-02-09
  80. 80. APIチェック • ブックマーク全件取得 > GET /api/bookmarks HTTP/1.1 > User-Agent: curl/7.30.0 > Host: localhost:8080 > Accept: */* > < HTTP/1.1 200 OK < Server: Apache-Coyote/1.1 < Content-Type: application/json;charset=UTF-8 < Transfer-Encoding: chunked < Date: Sat, 26 Jul 2014 17:55:48 GMT < [{"id":1,"name":"Google","url":"http://google.com"}]
  81. 81. APIチェック • ブックマーク1件削除 $ curl http://localhost:8080/api/bookmarks/ 1 -v -X DELETE http://bit.ly/jggug-02-10
  82. 82. APIチェック • ブックマーク1件削除 > DELETE /api/bookmarks/1 HTTP/1.1 > User-Agent: curl/7.30.0 > Host: localhost:8080 > Accept: */* > < HTTP/1.1 204 No Content < Server: Apache-Coyote/1.1 < Date: Sat, 26 Jul 2014 17:58:02 GMT <
  83. 83. 課題1 • ブックマーク1件取得APIを実装してみよう API HTTP メソッ ド リソースパス メソッド 返り値の型 ブックマー ク一件取得 GET /api/ bookmarks/{id} getBookmark Bookmark
  84. 84. アプリケーションをカスタマイズしよう • 設定ファイルの設定方法 • JavaConfigでBean定義を行う方法 • ログの設定変更方法 これらを試していきます
  85. 85. JDBCドライバの設定値を変更 • インメモリH2からファイルベースH2へ • 設定ファイルはクラスパス直下の application.ymlまたは application.properties YAMLが便利
  86. 86. application.yml作成 spring: datasource: driverClassName: org.h2.Driver url: jdbc:h2:file:/tmp/bookmark username: sa password: jpa: hibernate: ddl-auto: update インメモリDB使用時はcreate- dropが指定されており、毎回破 棄・生成が行われていた。今回は updateを指定し、差分があれば 適用する方式に。 DBの実体のファイルパスを指定 する。なかったら作成される。 src/main/resources/application.yml http://bit.ly/jggug-02-11
  87. 87. [補足] 設定値一覧(再掲) • http://docs.spring.io/spring-boot/docs/ 1.1.4.RELEASE/reference/html/common- application-properties.html
  88. 88. Log4JDBCでSQLログを出力しよう • pom.xmlに以下を追加 <dependency> <groupId>org.lazyluke</groupId> <artifactId>log4jdbc-remix</artifactId> <version>0.2.7</version> </dependency> http://bit.ly/jggug-02-12
  89. 89. • Bean定義を行うJavaConfigクラスを作成 Log4JDBCでSQLログを出力しよう package com.example; ! import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; ! @Configuration public class AppConfig { ! @Bean SomeBean someBean() { returen new SomeBean(); } } JavaConfigの記法 JavaConfig宣言 Bean定義宣言 ※この部分は書かなくて良い com.example.AppConfig
  90. 90. Log4JDBCでSQLログを出力しよう @Configuration public class AppConfig { @Autowired DataSourceProperties properties; DataSource dataSource; ! @ConfigurationProperties(prefix = DataSourceAutoConfiguration.CONFIGURATION_PREFIX) @Bean(destroyMethod = "close") DataSource realDataSource() { DataSourceBuilder factory = DataSourceBuilder .create(this.properties.getClassLoader()) .url(this.properties.getUrl()) .username(this.properties.getUsername()) .password(this.properties.getPassword()); this.dataSource = factory.build(); return this.dataSource; } @Bean DataSource dataSource() { return new Log4jdbcProxyDataSource(this.dataSource); } } Spring Bootが内部 で行っている、 DataSourceの作成 方法。 難しい場合は気にし なくて良い。 作成しDataSource にログ出力処理をラッ プする。こちらを使 う。 http://bit.ly/jggug-02-13 このメソッド名 (=Bean名)が重要
  91. 91. • src/main/resources/logback.xmlを 作成 <?xml version="1.0" encoding="UTF-8"?> <configuration> <include resource="org/springframework/boot/logging/logback/base.xml" /> <logger name="jdbc" level="OFF" /> <logger name="jdbc.sqltiming" level="DEBUG" /> </configuration> Log4JDBCでSQLログを出力しよう http://bit.ly/jggug-02-14
  92. 92. • アプリケーションを再起動して各APIを実行 Log4JDBCでSQLログを出力しよう jdbc.sqltiming : org.hibernate.engine.jdbc.internal.ResultSetReturnImpl.executeUpdate(ResultSetReturnImpl.java: 187) 3. insert into bookmark (id, name, url) values (null, 'Google', 'http://google.com') {executed in 3 msec} jdbc.sqltiming : org.hibernate.engine.jdbc.internal.ResultSetReturnImpl.extract(ResultSetReturnImpl.java:80) 4. select bookmark0_.id as id1_0_, bookmark0_.name as name2_0_, bookmark0_.url as url3_0_ from bookmark bookmark0_ order by bookmark0_.id asc {executed in 0 msec} jdbc.sqltiming : org.hibernate.engine.jdbc.internal.ResultSetReturnImpl.extract(ResultSetReturnImpl.java:80) 5. select bookmark0_.id as id1_0_0_, bookmark0_.name as name2_0_0_, bookmark0_.url as url3_0_0_ from bookmark bookmark0_ where bookmark0_.id=1 {executed in 0 msec} jdbc.sqltiming : org.hibernate.engine.jdbc.internal.ResultSetReturnImpl.executeUpdate(ResultSetReturnImpl.java: 187) 5. delete from bookmark where id=1 {executed in 2 msec}
  93. 93. 課題2 • bookmarkアプリケーションのjarを作成 し、実行時にspring.datasource.*プ ロパティを変更して、接続先DBを変更し よう(MySQLやPostgreSQLで試してみる と面白い)。
  94. 94. REST APIを任意のクライアント からアクセスできるようにする • Angular.jsで作成したSingle Page Applicationからアクセスしてみよう • http://jsfiddle.net/Ca2g2/ 作っておきました
  95. 95. REST APIを任意のクライアント からアクセスできるようにする
  96. 96. REST APIを任意のクライアント からアクセスできるようにする XMLHttpRequest cannot load http://localhost: 8080/api/bookmarks. No 'Access-Control-Allow- Origin' header is present on the requested resource. Origin 'http://fiddle.jshell.net' is therefore not allowed access. Same Origin Policy制限!
  97. 97. REST APIを任意のクライアント からアクセスできるようにする • Cross-Origin Resource Sharing (CORS) の設定を行うServletFilterを作成 • http://spring.io/guides/gs/rest-service- cors/ • Spring BootではServlet FilterはDIコンテ ナに登録しておけば自動的に有効になる。
  98. 98. REST APIを任意のクライアント からアクセスできるようにする • AppConfigに以下のBean定義を追加 @Bean Filter corsFilter() { return new Filter() { public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException { HttpServletRequest request = (HttpServletRequest) req; HttpServletResponse response = (HttpServletResponse) res; String method = request.getMethod(); response.setHeader("Access-Control-Allow-Origin", "*"); response.setHeader("Access-Control-Allow-Methods", "POST,GET,OPTIONS,DELETE"); response.setHeader("Access-Control-Max-Age", Long.toString(60 * 60)); response.setHeader("Access-Control-Allow-Credentials", "true"); response.setHeader( "Access-Control-Allow-Headers", "Origin,Accept,X-Requested-With," + "Content-Type,Access-Control-Request-Method," + "Access-Control-Request-Headers,Authorization"); if ("OPTIONS".equals(method)) { response.setStatus(HttpStatus.OK.value()); } else { chain.doFilter(req, res); } } public void init(FilterConfig filterConfig) { } public void destroy() { } }; } 別クラスにして @Componentを付 ければ定義は不要 FilterRegistra tionBeanを使え ばurl-pattern等 の指定もできる。 http://bit.ly/jggug-02-15
  99. 99. • 再起動後に、再アクセス REST APIを任意のクライアント からアクセスできるようにする
  100. 100. REST APIのIntegrationTest @RunWith(SpringJUnit4ClassRunner.class) @SpringApplicationConfiguration(classes = App.class) @WebAppConfiguration @IntegrationTest({ "server.port:0", "spring.datasource.url:jdbc:h2:mem:bookmark;DB_CLOSE_ON_EXIT=FALSE" }) public class BookmarkRestControllerIntegrationTest { // write test code } test用にインメモリDBを使用 http://bit.ly/jggug-02-16
  101. 101. テストの初期化 @Autowired BookmarkRepository bookmarkRepository; @Value("${local.server.port}") int port; String apiEndpoint; RestTemplate restTemplate = new TestRestTemplate(); Bookmark springIO; Bookmark springBoot; ! @Before public void setUp() { bookmarkRepository.deleteAll(); springIO = new Bookmark(); springIO.setName("Spring IO"); springIO.setUrl("http://spring.io"); springBoot = new Bookmark(); springBoot.setName("Spring Boot"); springBoot.setUrl("http://projects.spring.io/spring-boot"); ! bookmarkRepository.save(Arrays.asList(springIO, springBoot)); apiEndpoint = "http://localhost:" + port + "/api/bookmarks"; } // write test case リポジトリを使って、デー タ削除&登録。 テストの順番は不定なので、 毎回初期化すべき。 http://bit.ly/jggug-02-17
  102. 102. 全件取得APIのテスト @Test public void testGetBookmarks() throws Exception { ResponseEntity<List<Bookmark>> response = restTemplate.exchange( apiEndpoint, HttpMethod.GET, null /* body,header */, new ParameterizedTypeReference<List<Bookmark>>() { }); assertThat(response.getStatusCode(), is(HttpStatus.OK)); assertThat(response.getBody().size(), is(2)); ! Bookmark bookmark1 = response.getBody().get(0); assertThat(bookmark1.getId(), is(springIO.getId())); assertThat(bookmark1.getName(), is(springIO.getName())); assertThat(bookmark1.getUrl(), is(springIO.getUrl())); ! Bookmark bookmark2 = response.getBody().get(1); assertThat(bookmark2.getId(), is(springBoot.getId())); assertThat(bookmark2.getName(), is(springBoot.getName())); assertThat(bookmark2.getUrl(), is(springBoot.getUrl())); } ちょっと面倒くさい・・・ http://bit.ly/jggug-02-18
  103. 103. 新規作成APIのテスト @Test public void testPostBookmarks() throws Exception { Bookmark google = new Bookmark(); google.setName("Google"); google.setUrl("http://google.com"); ! ResponseEntity<Bookmark> response = restTemplate.exchange(apiEndpoint, HttpMethod.POST, new HttpEntity<>(google), Bookmark.class); assertThat(response.getStatusCode(), is(HttpStatus.CREATED)); Bookmark bookmark = response.getBody(); assertThat(bookmark.getId(), is(notNullValue())); assertThat(bookmark.getName(), is(google.getName())); assertThat(bookmark.getUrl(), is(google.getUrl())); ! assertThat(restTemplate.exchange(apiEndpoint,HttpMethod.GET,null, new ParameterizedTypeReference<List<Bookmark>>() { }).getBody().size(), is(3)); } http://bit.ly/jggug-02-19
  104. 104. 1件削除APIのテスト @Test public void testDeleteBookmarks() throws Exception { ResponseEntity<Void> response = restTemplate.exchange(apiEndpoint + "/{id}", HttpMethod.DELETE, null /* body,header */, Void.class, Collections.singletonMap("id", springIO.getId())); assertThat(response.getStatusCode(), is(HttpStatus.NO_CONTENT)); ! assertThat(restTemplate.exchange(apiEndpoint, HttpMethod.GET, null, new ParameterizedTypeReference<List<Bookmark>>() { }).getBody().size(), is(1)); } http://bit.ly/jggug-02-20
  105. 105. REST編修了 • お疲れ様でした・・・ • 本当は説明したかったけれども省略した内容 • 入力チェック • 例外ハンドリング • ページネーション http://terasolunaorg.github.io/guideline/1.0.x/ja/ ArchitectureInDetail/REST.html ここが詳しい。 URL変わる可能性があるので注意。
  106. 106. 3. Spring Bootで
 画面のあるアプリを作ろう
  107. 107. Webブラウザ curl Tomcat Spring Boot Spring Framework SpringSecurity ThymeLeaf SpringMVC Jackson SpringDataJPA Hibernate H2 Database画面のあるアプリ REST API
  108. 108. DI Container Controller Service Repository use use inject inject ここだけ追加 REST編と同じ
  109. 109. 出来上がりイメージ 追加するファイル
  110. 110. 画面遷移 リダイレクト 入力エラー
  111. 111. 画面遷移 API HTTP メソッド パス コントローラー のメソッド VIEW ブックマー ク一覧表示 GET /bookmark list bookmark/list ブックマー ク新規登録 POST ! /bookmark/ create create redirect:/ bookmark/list ブックマー ク一件削除 POST /bookmark/ delete?id={id} delete redirect:/ bookmark/list 普通の画面遷移アプリであればREST風にする必要はない。
  112. 112. コントローラー作成 • com.example.web.BookmarkController @Controller @RequestMapping("bookmark") public class BookmarkController { @Autowired BookmarkService bookmarkService; ! @ModelAttribute Bookmark setUp() { Bookmark bookmark = new Bookmark(); return bookmark; } // 続く } フォームオブジェク トの初期化。ここで は簡単のため Bookmarkクラスを 使用する。 ※ 本当はドメインオブジェクトをフォームとして使わ ない方がよい。画面にドメインが汚染されないよう に。(BookmarkFormクラスを作ってコピー推奨) 普通の画面遷移には @Controllerアノ テーションを使用。 http://bit.ly/jggug-02-21
  113. 113. ブックマーク一覧表示 @RequestMapping(value = "list", method = RequestMethod.GET) String list(Model model) { List<Bookmark> bookmarks = bookmarkService.findAll(); model.addAttribute("bookmarks", bookmarks); return "bookmark/list"; } Modelオブジェクトに追加すること で画面(view)からアクセスできる。 View名を返す。Spring Bootではデフォルトで、クラスパス下の templates/bookmark/list.htmlがViewとして使用される。 bookmark/listをGETでアクセスすると呼ばれるメソッド http://bit.ly/jggug-02-22
  114. 114. ブックマーク新規登録 @RequestMapping(value = "create", method = RequestMethod.POST) String create(@Validated Bookmark bookmark, BindingResult bindingResult, Model model) { if (bindingResult.hasErrors()) { return list(model); } bookmarkService.save(bookmark); return "redirect:/bookmark/list"; } bookmark/createをPOSTでアクセスすると呼ばれるメソッド フォームの入力チェック 入力エラーがある場合は、 一覧表示へ。 PRG(POST-Redirect-GET)パターンを用いる。 /bookmakr/listへリダイレクト。 http://bit.ly/jggug-02-23
  115. 115. ブックマーク1件削除 @RequestMapping(value = "delete", method = RequestMethod.POST) String delete(@RequestParam("id") Long id) { bookmarkService.delete(id); return "redirect:/bookmark/list"; } bookmark/deleteをPOSTでアクセスすると呼ばれるメソッド クエリパラメータからidを 取得する。 http://bit.ly/jggug-02-24
  116. 116. 文字コード設定フィルターを定義 • AppConfigにCharacterEncodingFilterの定義を 追加。コレがないとPOSTで日本語が文字化けする。 @Bean @Order(Ordered.HIGHEST_PRECEDENCE) CharacterEncodingFilter characterEncodingFilter() { CharacterEncodingFilter filter = new CharacterEncodingFilter(); filter.setEncoding("UTF-8"); return filter; } フィルターの先頭にくるよ うに優先順位を設定 http://bit.ly/jggug-02-25
  117. 117. ThymeLeafで画面作成 • ThymeLeafは素のHTMLにth:***属性(または data-th-***属性)をつけることで動的な画面 を作れるテンプレートエンジン。 • http://www.thymeleaf.org/ • テンプレートをブラウザやオーサリングツール でそのまま見れるため、デザイナーフレンド リー。
  118. 118. 依存関係追加 <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency> http://bit.ly/jggug-02-26
  119. 119. HTML作成 • src/main/resources/templates/bookmark/ list.htmlを作成
  120. 120. [補足] ThymeLeafのキャッシュを無効化 • 開発中は変更を即反映してほしいため、 spring.thymeleaf.cache: falseの プロパティを追加 spring: thymeleaf: cache: false application.yml http://bit.ly/jggug-02-27
  121. 121. list.html <!DOCTYPE html> <html xmlns:th="http:///www.thymeleaf.org"> <head> <meta charset="UTF-8" /> <title>Bookmarks</title> </head> <body> <div> <h1>Bookmarks</h1> <div> <!-- 新規作成フォームを書く --> </div> <div> <!-- 一覧表示テーブルを書く --> </div> </div> </body> </html> ThymeLeafの名前空間 デフォルトでは、XHTMLでないと エラーになる。 タグの閉じ忘れに注意。 http://bit.ly/jggug-02-28
  122. 122. 一覧表示画面 <ul> <li th:each="bookmark : ${bookmarks}"><a th:href="${bookmark.url}" th:text="${bookmark.name}">dummy</a> <form style="display: inline" method="post" th:action="@{/bookmark/delete?id=${bookmark.id}}"> <input type="submit" value="Remove" /> </form></li> </ul> 繰り返し要素に th:each属性を設定。 そのままブラウザでみるとdummyが表示されるが、サーバー経由だと th:text属性に指定した値で置換される(HTMLエスケープ有) URLを表示する際は@{}を使うことで、 コンテキストルート相対パスを指定できる。 Modelに設定した属性値に${…}でアクセス。 http://bit.ly/jggug-02-29
  123. 123. ブラウザでテンプレートを表示
  124. 124. サーバーで表示 REST APIで登録したデータが表示されているはず
  125. 125. 新規作成フォーム <form th:action="@{/bookmark/create}" th:object="${bookmark}" method="post"> <dl> <dt><label for="name">Name</label></dt> <dd><input type="text" id="name" name="name" th:field="*{name}" th:errorclass="error-input" /> <span th:if="${#fields.hasErrors('name')}" th:errors="*{name}" class="error-messages">error!</span></dd> </dl> <dl> <dt><label for="url">URL</label></dt> <dd><input type="url" id="url" name="url" th:field="*{url}" th:errorclass="error-input" /> <span th:if="${#fields.hasErrors('url')}" th:errors="*{url}" class="error-messages">error!</span></dd> </dl> <input type="submit" value="Add" /> </form> th:object属性にフォームオブジェクトを指定 th:field="{*フィールド名}"でバインドするフィールドを指定 th:errors="{*フィールド名}"でエラーメッセージを表示 http://bit.ly/jggug-02-30
  126. 126. 静的リソースの配置 • JavaScript、CSS、画像などの静的リソースはクラスパ ス直下のstaticフォルダに置くことで、コンテキストルー トからアクセスできる。
  127. 127. CSS作成 • src/main/resources/static/css/style.css .error-input { border-color: #b94a48; margin-left: 5px; } ! .error-messages { color: #b94a48; } http://bit.ly/jggug-02-31
  128. 128. CSSの読み込み <link rel="stylesheet" type="text/css" href="../../static/css/style.css" th:href="@{/css/style.css}" /> サーバーで実行した場合に th:href属性でhref属性を置換する ブラウザで見るときはこちらの設定が有効になる http://bit.ly/jggug-02-32
  129. 129. ブラウザでテンプレートを表示
  130. 130. サーバーで実行
  131. 131. サーバーで実行
  132. 132. [補足] WebJarを使ってみよう • TODO
  133. 133. 課題3 • 新規作成画面と一覧画面を別のペー ジにしてみよう。
  134. 134. 画面のあるアプリ編修了 •本当は説明したかったけれども省略した内容 •入力チェック •http://terasolunaorg.github.io/guideline/1.0.x/ja/ ArchitectureInDetail/Validation.html •例外ハンドリング •http://terasolunaorg.github.io/guideline/public_review/ ArchitectureInDetail/ExceptionHandling.html •ページネーション •http://terasolunaorg.github.io/guideline/public_review/ ArchitectureInDetail/Pagination.html • ThymeLeafでレイアウトの使い方 • https://github.com/spring-projects/spring-boot/tree/master/ spring-boot-samples/spring-boot-sample-web-ui/src/main/ URL変わる可能性があるので注意。
  135. 135. 4. Spring Securityで
 認証・認可を追加しよう
  136. 136. Webブラウザ curl Tomcat Spring Boot Spring Framework SpringSecurity ThymeLeaf SpringMVC Jackson SpringDataJPA Hibernate H2 Database画面のあるアプリ REST API
  137. 137. 出来上がりイメージ 追加するファイル
  138. 138. 依存関係追加 <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> http://bit.ly/jggug-04-00
  139. 139. 依存性を追加しただけで • サーバーを再起動すると、Basic認証が 有効になる
  140. 140. デフォルトのBasic認証 • ユーザー名はuser • パスワードは起動時にランダム値が生成され、ログに出 力される 2014-07-27 23:01:33.306 INFO 15121 --- [ost-startStop-1] b.a.s.AuthenticationManagerConfiguration : ! Using default security password: 8aa11dda-d9ba-4f98-8a53-d1ec58e67584
  141. 141. • security.basic.enabled: falseの プロパティを追加 デフォルトのBasic認証は無効にする security: basic: enabled: false application.yml http://bit.ly/jggug-04-01
  142. 142. ログイン画面のある認証・認可 の設定を行う • com.example.SecurityConfigにSpring Securityの 設定を行う @Configuration @EnableWebMvcSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { // override configuration } WebSecurityConfigurerAdapterのメソッドをオーバーライ ドすることでデフォルト設定を上書きできる。 @EnableWebSecurityと間違えないように。 (間違えるとCSRFトークンが設定されない・・) http://bit.ly/jggug-04-02
  143. 143. 認証設定 @Override @SuppressWarnings({ "rawtypes", "unchecked" }) protected void configure(AuthenticationManagerBuilder auth) throws Exception { UserDetailsManagerConfigurer configurer = new InMemoryUserDetailsManagerConfigurer(); configurer.withUser("hoge").password("hoge").roles("USER"); configurer.withUser("admin").password("demo").roles("ADMIN"); configurer.configure(auth); UserDetailsService userDetailsService = configurer .getUserDetailsService(); ! auth.userDetailsService(userDetailsService); } AuthenticationManagerBuilder を引数にもつconfigureメソッド 認証ユーザーを取得するメソッドを持つ UserDetailsServiceインタフェースを 設定し、ユーザーの取得方式を決める。 今回はメモリ上にユーザー情報 を持つUserDetailsServiceを 使用する。 http://bit.ly/jggug-04-03
  144. 144. 認可設定 (1/2) @Override public void configure(WebSecurity web) throws Exception { web.ignoring().antMatchers("/css/**", "/js/**", "/image/**", "/api/**"); } WebSecurityを引数にもつconfigureメソッド 静的リソースは認可制御の対象外にする 今回はREST APIも認可制御の対象外にする http://bit.ly/jggug-04-04
  145. 145. 認可設定 (2/2) @Override protected void configure(HttpSecurity http) throws Exception { http.authorizeRequests().antMatchers("/loginForm").permitAll() .anyRequest().authenticated(); http.formLogin().loginProcessingUrl("/login").loginPage("/loginForm") .failureUrl("/loginForm?error").defaultSuccessUrl("/book/list") .usernameParameter("username").passwordParameter("password") .permitAll(); http.logout().logoutUrl("/logout").permitAll(); } HttpSecurityを引数に もつconfigureメソッド ログイン画面は常にアクセス許可、 それ以外のページは要認証 ログイン画面のURLやログイン処理のURL、 パラメータ名等を設定 ログアウト設定 http://bit.ly/jggug-04-05
  146. 146. ログイン画面作成 • com.example.web.LoginController package com.example.web; ! import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; ! @Controller public class LoginController { @RequestMapping("/loginForm") String loginForm() { return "login/loginForm"; } } http://bit.ly/jggug-04-06
  147. 147. ログイン画面作成 • src/main/resources/templates/login/loginForm.html <!DOCTYPE html> <html xmlns:th="http:///www.thymeleaf.org"> <head> <meta charset="UTF-8"/> <title>Login Page</title> <link rel="stylesheet" type="text/css" href="../../static/css/style.css" th:href="@{/css/style.css}"/> </head> <body> <div th:if="${param.error}"> <span class="error-messages">Invalid username and password.</span> </div> <form th:action="@{/login}" method="post"> <dl> <dt><label for="username">Username</label></dt> <dd><input type="text" id="username" name="username"/></dd> </dl> <dl> <dt><label for="password">Password</label></dt> <dd><input type="password" id="password" name="password"/></dd> </dl> <input type="submit" value="Login"/> </form> </body> </html> http://bit.ly/jggug-04-07
  148. 148. アプリケーション実行
  149. 149. CSRFトークンが自動で埋め込まれている
  150. 150. セキュアなHTTPレスポンス ヘッダが埋め込まれている
  151. 151. 認証・認可編修了 • お疲れ様でした! • 本当は説明したかったけれども省略した内容 • UserDetailsServiceを実装してDBから認証ユーザー取得 • ログインユーザーの表示(@AuthenticationPrincipal) • パスワードハッシュ • 認可制御でパスごとにアクセス制限 • 認可制御で画面表示切り替え • Spring Security OAuthでREST APIにOAuth2導入
  152. 152. まとめ
  153. 153. まとめ • Spring Bootで • REST APIを作成した • 画面のあるアプリを作成した • 認証・認可処理を追加した Spring Bootによるアプリケーション開発 の基礎が掴めたはず!
  154. 154. 是非職場で Spring Bootを この資料を 広めてください!
  155. 155. 宣伝 • Spring Bootの入門本(今日の内容 みたいな話+いろいろ)を執筆中。 • 出版されたら是非買ってくださ い!
  156. 156. 中級者編やりたい •Bookmarkエンティティに所有者Userをひも 付けし、User毎にブックマーク管理 •Userエンティティを使って認証・認可 •OAuth2でREST APIに認可処理を追加 •FlywayでDBマイグレーション •Spring Boot Actuatorでメトリクス取得 •CodaHale Metricsでメトリクスをさらに取得 会場提供してくれる方、募集中!
  157. 157. お疲れ様でした!

×