• Share
  • Email
  • Embed
  • Like
  • Save
  • Private Content
Grails 3.0先取り!? Spring Boot入門ハンズオン #jggug_boot
 

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

on

  • 88 views

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

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

----

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

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

Statistics

Views

Total Views
88
Views on SlideShare
88
Embed Views
0

Actions

Likes
2
Downloads
0
Comments
0

1 Embed 0

https://twitter.com 1

Accessibility

Categories

Upload Details

Uploaded via as Adobe PDF

Usage Rights

© All Rights Reserved

Report content

Flagged as inappropriate Flag as inappropriate
Flag as inappropriate

Select your reason for flagging this presentation as inappropriate.

Cancel
  • Full Name Full Name Comment goes here.
    Are you sure you want to
    Your message goes here
    Processing…
Post Comment
Edit your comment

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

    • 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で認証認可を追加しよう 100分だと多分ここまで
    • ハンズオンの流れ 1.Hello WorldアプリでSpring Bootことはじめ 2.Spring BootでREST APIを作ろう 3.Spring Bootで画面のあるアプリを作ろう 4.Spring Securityで認証認可を追加しよう 100分だと多分ここまで 土日にやろう
    • 質問があれば •#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 Hibernate H2 Database画面のあるアプリ REST API
    • 題材のアプリ • 簡易ブックマークシステム
    • 1. Hello Worldアプリで Spring Bootことはじめ
    • 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
    • pom.xmlを編集
    • <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
    • Mavenプロジェクトをインポー ト
    • インポート後
    • いろいろな依存関係が追加され ている
    • 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
    • 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 魔法のアノテーション
    • まずは実行 • 実行方法は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- properties.html 一度作ったjarはそのまま本番環境で使用可能。 配布も可能。
    • • -Drun.arguments="--(プロパティ名)=(プロパティ値)" で指定。 [補足] mavenプラグインの場合 $ mvn spring-boot:run -Drun.arguments="--server.port=8888" http://bit.ly/jggug-01-03
    • [参考] 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
    • [参考] 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が自動 的にアタッチされる
    • アプリ実行中にソースを変更してコンパイル @RequestMapping("/") String home() { return "Hello World!"; } @RequestMapping("/") String home() { return "Hello Spring!"; } 再起動することなく アプリが更新された
    • 注意 • まだまだSpring Loadedは未熟で、リ ロードされないケースや副作用による エラーが発生することもある。 • うまく更新できたらラッキー。 • 開発を暖かく見守りましょう。
    • 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
    • Integration Test 組込Tomcatを起動して、 HTTPリクエストを送り、 HTTPレスポンスをチェック
    • 他のJVM言語の例 • https://github.com/making/spring-boot-demo-jvm- languages • Java/Groovy/Scala/KotlinそれぞれのGradleプロジェク トサンプルがあるのでお好みの言語でSpring Bootアプ リを作りましょう
    • 2. Spring Bootで
 REST APIを作ろう
    • Webブラウザ curl Tomcat Spring Boot Spring Framework SpringSecurity ThymeLeaf SpringMVC Jackson SpringDataJPA Hibernate H2 Database画面のあるアプリ REST API
    • REST APIでBookmark管理 • 「REST」はクライアントとサーバ間でデータをやりと りするためのソフトウェアアーキテクチャスタイルの 一つ。 • RESTでは、「リソース」に対するCRUD操作をHTTP メソッド(POST/GET/PUT/DELETEなど)を使ってWeb APIとしてクライアントに公開する。 • 今回は「ブックマーク」が「リソース」
    • 実装するAPI API HTTP メソッド リソースパス 正常時レスポンス ステータス ブックマーク 全件取得 GET /api/bookmarks 200 OK ブックマーク 新規登録 POST ! /api/bookmarks 201 CREATED ブックマーク 一件削除 DELETE /api/bookmarks/{id} 204 NO CONTENT
    • 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
    • <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
    • 必要な依存関係が追加される
    • 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; @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
    • リポジトリ作成 • 「リポジトリ」は、 ドメインオブジェクト の保存、取得、検索といった操作をカプセ ル化し、”コレクションオブジェクト”のよ うに振る舞う役割をもつ。 • 「リポジトリ」にロジックを含めない。
    • 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
    • サービス作成 • 全件取得、新規作成、一件削除用のメソッ ドを作成する • POJOに@Serviceアノテーションを付け るとDIコンテナに自動登録される(コン ポーネントスキャン)
    • サービス作成 • 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
    • コントローラー作成 • 基本的にSpring MVCを使ったプログラミン グを行う • POJOに@Controllerを付けるとHTTPのリク エストを受けられる • @RestControllerを付けると、Controllerの メソッドの返り値が、シリアライズされ、その ままHTTPレスポンスのボディになる
    • コントローラーの リクエストマッピング • HTTPリクエストとコントローラーのメソッドのマッ ピング表 API HTTP メソッ ド リソースパス メソッド 返り値の型 ブックマー ク全件取得 GET /api/bookmarks getBookmarks List<Bookmark> ブックマー ク新規登録 POST ! /api/bookmarks postBookmarks Bookmark ブックマー ク一件削除 DELET E /api/ bookmarks/{id} deleteBookmark void
    • コントローラー作成 • 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
    • 入力チェックを実施 @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
    • アプリケーションのエントリポイント作成 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
    • アプリケーション実行 • リクエストマッピングのログが出力される ことを確認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)
    • 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のコマンドプロンプトだとシングルクオートが効か ないのでダブルクオートとエスケープしてください
    • 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"}
    • 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: */* > < 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"}]
    • 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: */* > < HTTP/1.1 204 No Content < Server: Apache-Coyote/1.1 < Date: Sat, 26 Jul 2014 17:58:02 GMT <
    • 課題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 password: jpa: hibernate: ddl-auto: update インメモリDB使用時はcreate- dropが指定されており、毎回破 棄・生成が行われていた。今回は updateを指定し、差分があれば 適用する方式に。 DBの実体のファイルパスを指定 する。なかったら作成される。 src/main/resources/application.yml http://bit.ly/jggug-02-11
    • [補足] 設定値一覧(再掲) • 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> <version>0.2.7</version> </dependency> http://bit.ly/jggug-02-12
    • • 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
    • 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名)が重要
    • • 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
    • • アプリケーションを再起動して各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}
    • 課題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-Allow- Origin' header is present on the requested resource. Origin 'http://fiddle.jshell.net' is therefore not allowed access. Same Origin Policy制限!
    • REST APIを任意のクライアント からアクセスできるようにする • Cross-Origin Resource Sharing (CORS) の設定を行うServletFilterを作成 • http://spring.io/guides/gs/rest-service- cors/ • Spring BootではServlet FilterはDIコンテ ナに登録しておけば自動的に有効になる。
    • 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
    • • 再起動後に、再アクセス REST APIを任意のクライアント からアクセスできるようにする
    • 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
    • テストの初期化 @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
    • 全件取得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
    • 新規作成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
    • 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
    • REST編修了 • お疲れ様でした・・・ • 本当は説明したかったけれども省略した内容 • 入力チェック • 例外ハンドリング • ページネーション http://terasolunaorg.github.io/guideline/1.0.x/ja/ ArchitectureInDetail/REST.html ここが詳しい。 URL変わる可能性があるので注意。
    • 3. Spring Bootで
 画面のあるアプリを作ろう
    • Webブラウザ curl Tomcat Spring Boot Spring Framework SpringSecurity ThymeLeaf SpringMVC Jackson SpringDataJPA Hibernate H2 Database画面のあるアプリ REST API
    • DI Container Controller Service Repository use use inject inject ここだけ追加 REST編と同じ
    • 出来上がりイメージ 追加するファイル
    • 画面遷移 リダイレクト 入力エラー
    • 画面遷移 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風にする必要はない。
    • コントローラー作成 • 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
    • ブックマーク一覧表示 @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
    • ブックマーク新規登録 @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
    • ブックマーク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
    • 文字コード設定フィルターを定義 • 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
    • ThymeLeafで画面作成 • ThymeLeafは素のHTMLにth:***属性(または data-th-***属性)をつけることで動的な画面 を作れるテンプレートエンジン。 • http://www.thymeleaf.org/ • テンプレートをブラウザやオーサリングツール でそのまま見れるため、デザイナーフレンド リー。
    • 依存関係追加 <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency> http://bit.ly/jggug-02-26
    • HTML作成 • src/main/resources/templates/bookmark/ list.htmlを作成
    • [補足] ThymeLeafのキャッシュを無効化 • 開発中は変更を即反映してほしいため、 spring.thymeleaf.cache: falseの プロパティを追加 spring: thymeleaf: cache: false application.yml http://bit.ly/jggug-02-27
    • 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
    • 一覧表示画面 <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
    • ブラウザでテンプレートを表示
    • サーバーで表示 REST APIで登録したデータが表示されているはず
    • 新規作成フォーム <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
    • 静的リソースの配置 • JavaScript、CSS、画像などの静的リソースはクラスパ ス直下のstaticフォルダに置くことで、コンテキストルー トからアクセスできる。
    • 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
    • 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
    • ブラウザでテンプレートを表示
    • サーバーで実行
    • サーバーで実行
    • [補足] WebJarを使ってみよう • TODO
    • 課題3 • 新規作成画面と一覧画面を別のペー ジにしてみよう。
    • 画面のあるアプリ編修了 •本当は説明したかったけれども省略した内容 •入力チェック •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変わる可能性があるので注意。
    • 4. Spring Securityで
 認証・認可を追加しよう
    • Webブラウザ curl Tomcat Spring Boot Spring Framework SpringSecurity ThymeLeaf SpringMVC Jackson SpringDataJPA Hibernate H2 Database画面のあるアプリ REST API
    • 出来上がりイメージ 追加するファイル
    • 依存関係追加 <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> http://bit.ly/jggug-04-00
    • 依存性を追加しただけで • サーバーを再起動すると、Basic認証が 有効になる
    • デフォルトの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
    • • security.basic.enabled: falseの プロパティを追加 デフォルトのBasic認証は無効にする security: basic: enabled: false application.yml http://bit.ly/jggug-04-01
    • ログイン画面のある認証・認可 の設定を行う • com.example.SecurityConfigにSpring Securityの 設定を行う @Configuration @EnableWebMvcSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { // override configuration } WebSecurityConfigurerAdapterのメソッドをオーバーライ ドすることでデフォルト設定を上書きできる。 @EnableWebSecurityと間違えないように。 (間違えるとCSRFトークンが設定されない・・) http://bit.ly/jggug-04-02
    • 認証設定 @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
    • 認可設定 (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
    • 認可設定 (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
    • ログイン画面作成 • 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
    • ログイン画面作成 • 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
    • アプリケーション実行
    • CSRFトークンが自動で埋め込まれている
    • セキュアなHTTPレスポンス ヘッダが埋め込まれている
    • 認証・認可編修了 • お疲れ様でした! • 本当は説明したかったけれども省略した内容 • UserDetailsServiceを実装してDBから認証ユーザー取得 • ログインユーザーの表示(@AuthenticationPrincipal) • パスワードハッシュ • 認可制御でパスごとにアクセス制限 • 認可制御で画面表示切り替え • Spring Security OAuthでREST APIにOAuth2導入
    • まとめ
    • まとめ • Spring Bootで • REST APIを作成した • 画面のあるアプリを作成した • 認証・認可処理を追加した Spring Bootによるアプリケーション開発 の基礎が掴めたはず!
    • 是非職場で Spring Bootを この資料を 広めてください!
    • 宣伝 • Spring Bootの入門本(今日の内容 みたいな話+いろいろ)を執筆中。 • 出版されたら是非買ってくださ い!
    • 中級者編やりたい •Bookmarkエンティティに所有者Userをひも 付けし、User毎にブックマーク管理 •Userエンティティを使って認証・認可 •OAuth2でREST APIに認可処理を追加 •FlywayでDBマイグレーション •Spring Boot Actuatorでメトリクス取得 •CodaHale Metricsでメトリクスをさらに取得 会場提供してくれる方、募集中!
    • お疲れ様でした!