131K Views
June 02, 22
スライド概要
JJUGナイトセミナーの資料です。
https://jjug.doorkeeper.jp/events/137944
Java、Spring、IntelliJ IDEA
初めてのひとのための Spring/Spring Boot (株)クレディセゾン 多田真敏 2022年6月2日 JJUGナイトセミナー1
このセッションについて ▸ ざっくりとSpringの全体像を掴みつつ、 初めに理解しておくべき重要事項を解説します ▸ 詳細な情報は「ためになる資料たち」を参照してください ▸ このセッションは【初級者向け】です ▸ ただし、サーブレット・JDBCについては、 ある程度知っていることを前提に解説します ▸ 使用しているバージョン ▸ JDK 17 ▸ Spring Framework 5.3 ▸ Spring Boot 2.7 将来のバージョンでは、 内容が異なる可能性があります 2
資料などのリンク ▸ 講義編 ▸ 動画はこちら ▸ スライドはこちら ▸ 実演編 ▸ 動画はこちら ▸ ソースコードはこちら 3
自己紹介 ▸ 多田真敏 (@suke̲masa) ▸ 元・研修トレーナーの Javaプログラマー ▸ Springとは2015年からの お付き合い ▸ IntelliJ IDEAが好き ▸ 日本Springユーザ会(JSUG) スタッフ 4
目次 ① Springって何? ② Springのコア: コンテナ・DI・AOP ③ よく使われるSpringサブプロジェクト ④ Spring Bootって何? ⑤ ためになる資料たち 5
目次 ① Springって何? ② Springのコア: コンテナ・DI・AOP ③ よく使われるSpringサブプロジェクト ④ Spring Bootって何? ⑤ ためになる資料たち 6
フレームワークって何? ▸ 多くのシステムで共通的に必要な機能を 作成済みにしてある半完成のアプリケーション ▸ Model-View-Controllerのひな形 ここだけ作ればOK ▸ 入力チェック ▸ トランザクション View Cont roller Model ▸ セキュリティ ▸ ・・・など フレームワーク 7
世の中にある色々なフレームワーク ▸ Spring ▸ Jakarta EE (旧Java EE・J2EE) ▸ Struts 1・2 ▸ Seasar2 ▸ ・・・など 8
[参考]フレームワークとライブラリ ▸ ライブラリ=便利なプログラムのかたまり ▸ Javaでは多くのものがオープンソース ▸ JavaではJARファイル形式で提供されることが多い ▸ フレームワーク=ライブラリの中でも、 特にアプリケーション開発の枠組みとなる 半完成のアプリケーション U ▸ つまりライブラリ フレームワーク 9
Springって何? ▸ VMware社が開発している、 オープンソースなJava製フレームワーク ▸ DI・AOP・データアクセス・トランザクション ・Web・セキュリティ・バッチなど、 システム開発に必要な機能のほぼ全てを提供 ▸ Rod Johnson氏が、2002年に出版した 自著のために書いたサンプルコードが元祖 10
Springの歴史 2002 2004 Rod Johnson氏が自著を出版。 Spring 1.0リリース。この頃からコンテナ(XMLでBean定義)・DI・AOP・Web ・DBアクセス・トランザクションなど主要な機能が揃っている。 2006 Spring 2.0リリース。Spring Securityが導入される。 2009 Spring 2.5リリース。アノテーションでのBean定義・Spring MVCが導入される。 2009 Spring 3.0リリース。Java Con g・Spring MVCでのRESTが導入される。 2013 Spring 4.0リリース。WebSocketなどが導入される。Java EE 7対応。 2017 Spring 5.0リリース。リアクティブ機能が導入される。Java EE 8対応。 2022 Spring 6.0リリース予定。Jakarta EE 9対応(jakarta.*パッケージ)。 Java EE 6対応。 JDK 6ベース(8にも対応)。 JDK 8ベース(17にも対応)。 JDK 17ベース。 fi ※『Spring徹底入門』(翔泳社、後述)を参考に作成 11
目次 ① Springって何? ② Springのコア: コンテナ・DI・AOP ③ よく使われるSpringサブプロジェクト ④ Spring Bootって何? ⑤ ためになる資料たち 12
Springはコンテナを持っている ▸ DIコンテナ=インスタンスの入れ物 ※「インスタンス」とは、 もちろんJavaのインスタンスの ことです ▸ Bean=コンテナで管理されたインスタンス DIコンテナ Bean 必要に応じて 取り出して使う Bean Bean Bean Bean 13
Beanを定義する方法 ① コンポーネントスキャン ② Java Con g ③ 関数型Bean定義 (今回は割愛) ④ XML (今回は割愛) すべて併用可能 fi 14
Bean定義方法① コンポーネントスキャン ▸ コンポーネントスキャン =指定されたパッケージから@Componentが 付いたクラスを探す ▸ 見つけたらインスタンス化→DIコンテナに保存 15
Bean定義方法① コンポーネントスキャン ▸ 右記のアノテーションを クラスに付けると、 @Componentを付けた ことと同じになる ▸ クラスの役割に応じて 変える @Repository @Service @Controller @RestController @Configuration @ControllerAdvice @RestControllerAdvice ・・・ ※これらのアノテーションのソースコードを読むと、 @Componentが付いていることが分かります 16
Bean定義方法① コンポーネントスキャン ▸ 設定クラス (Java Con g) @Componentが付いたクラスを 探すパッケージ @Configuration @ComponentScan(basePackages = "com.example") public class AppConfig {} ▸ Beanとしたいクラス package com.example; 必ずコンポーネントスキャン 配下にする @Component public class Hoge { ... } fi 17
Bean定義方法② Java Con g ▸ Java Con gクラスにメソッドを作成し、 @Beanを付加する → メソッドの戻り値がBeanになる // @Componentは不要 public class Hoge { ... } // @ComponentScanは不要 @Configuration public class AppConfig { @Bean public Hoge hoge() { return new Hoge(); } } fi fi 18
Bean定義方法② Java Con g ▸ @Beanメソッドに引数を指定すると、 DIコンテナが型に合致するBeanを代入する @Configuration public class Config1 { @Bean public Fuga fuga() { return new Fuga(); } } @Configuration public class Config2 { @Bean public Hoge hoge(Fuga fuga) { return new Hoge(fuga); } } fi 19
DIコンテナを作る・Beanを取得する ▸ Java Con gクラスを指定して ApplicationContext (=DIコンテナ)を作成 ▸ コンポーネントスキャン・Java Con gどちらでも共通 ▸ getBean()でBeanを取得できる // コンテナの作成 ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class); // BeanをDIコンテナから取得(引数は欲しいBeanのデータ型) Hoge hoge = context.getBean(Hoge.class); fi fi 20
DI ▸ コンストラクタに@Autowiredを付加し、 引数に欲しいBeanを指定 ▸ DIコンテナが適切なBeanを引数に代入してくれる ▸ 代入する側もされる側も両方Beanでなければならない @Component DIコンテナ public class Hoge { private final Fuga fuga; } @Autowired public Hoge(Fuga fuga) { this.fuga = fuga; } Hoge DI Fuga DI 21
@Autowiredの省略 ▸ クラス内にコンストラクタが1つの場合、 @Autowiredは省略可能 ▸ 近年はほとんどこの書き方 コンストラクタが 1つだけなので @Autowiredは省略可能 @Component public class Hoge { private final Fuga fuga; } // @Autowiredは付けない public Hoge(Fuga fuga) { this.fuga = fuga; } 22
いろんなDI ▸ コンストラクタインジェクション @Component public class Hoge { private final Fuga fuga; } @Autowired public Hoge(Fuga fuga) { this.fuga = fuga; } ▸ フィールドインジェクション @Component public class Hoge { } @Autowired Fuga fuga; ▸ メソッドインジェクション @Component public class Hoge { private Fuga fuga; } @Autowired public setFuga(Fuga fuga) { this.fuga = fuga; } 唯一Hogeを変更不能に できるので コンストラクタ インジェクション推奨 ※変更不能(イミュータブル)=フィールドの値を書換不可能 23
AOP (Aspect Oriented Programming) ▸ Beanに対して割り込み処理を定義できる ▸ 割り込み処理の例 ▸ トランザクションの開始・終了 ←後ほど紹介 ▸ セキュリティ(実行権限のチェック) ▸ ロギング ▸ ・・・など 24
ためになる資料たち https://www.docswell.com/s/MasatoshiTada/ Z818E5-spring-di-aop-for-every-developers https://www.docswell.com/s/MasatoshiTada/ K1XMLK-advanced-spring-for-professionals 25
この章のまとめ ▸ DIコンテナ=インスタンス(Bean)の入れ物 ▸ コンポーネントスキャン ▸ @ComponentでBeanにするクラスを指定 ▸ @ComponentScanで探す範囲を指定 ▸ Java Con g ▸ @Beanを付加したメソッドの戻り値がBeanになる ▸ DI ▸ コンストラクタなどに@Autowiredを付加 ▸ AOP ▸ 割り込み処理の定義 fi 26
目次 ① Springって何? ② Springのコア: コンテナ・DI・AOP ③ よく使われるSpringサブプロジェクト ④ Spring Bootって何? ⑤ ためになる資料たち 27
Spring JDBC ▸ JDBCによるDBアクセスコードを簡潔に書ける ▸ Connection・PreparedStatementは扱う必要なし ▸ ResultSetからのデータ取り出しのみ記述する ▸ 核となるクラスはJdbcTemplateと NamedParameterJdbcTemplate 28
Spring JDBCでのDBアクセス
データアクセスクラス
であることを示す(Beanになる)
@Repository
public class MemoRepository {
SQLを文字列で書く
private static final String SQL_FIND_ALL = """
SELECT id, content, created_at
FROM memo
ORDER BY id
""";
private static final String SQL_ADD = """
INSERT INTO memo(content) VALUES(:content)
""";
private final NamedParameterJdbcTemplate jdbcTemplate;
public MemoRepository(NamedParameterJdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
// 続く
NamedParameterJdbcTemplateをDI
(Spring Bootの場合はBean定義済みになっている)
29
Spring JDBCでのDBアクセス
// 続き
}
query()メソッドで検索
public List<Memo> findAll() {
List<Memo> memoList = jdbcTemplate.query(SQL_FIND_ALL,
(rs, rowNum) -> new Memo(
rs.getLong("id"),
rs.getString("content"),
rs.getObject("created_at", LocalDateTime.class)));
return memoList;
}
ResultSetからの取り出しのみ記述
(ちょっとめんどい)
public Memo add(Memo unregisteredMemo) {
MapSqlParameterSource params = new MapSqlParameterSource(
"content", unregisteredMemo.content());
KeyHolder keyHolder = new GeneratedKeyHolder();
jdbcTemplate.update(SQL_ADD, params, keyHolder, new String[]{"id"});
long newId = keyHolder.getKey().longValue();
Memo registeredMemo = unregisteredMemo.withNewId(newId);
return registeredMemo;
}
update()メソッドで追加・更新・削除
30
Spring JDBCをもっと便利に
▸ DataClassRowMapper
▸ SELECTしている列と同じ名前の
recordコンポーネントに代入してくれる
public record Memo(Long id,
String content,
LocalDateTime createdAt) {
}
SELECTする列と同名にする
(スネークケース
→キャメルケース変換は
勝手にやってくれる)
public List<Memo> findAll() {
List<Memo> memoList = jdbcTemplate.query(SQL_FIND_ALL,
new DataClassRowMapper<>(Memo.class);
return memoList;
}
※recordでないクラスに変換する場合はBeanPropertyRowMapperを使います
31
Spring Tx ▸ トランザクションの開始・コミット ・ロールバックをほぼ自動で行う ▸ @Transactionalアノテーションを 付加したメソッドに、AOPで割り込み処理する ※正確に言うと「Spring Tx」というサブプロジェクトは存在しないのですが、 説明の都合上、そのように呼んでいます(ライブラリ名が「spring-tx」であることから) 32
@Transactionalの効果 readOnly要素をtrueにすると SELECTしか実行できなくなる メソッド開始直前に トランザクション開始が 割り込み処理される メソッド終了直後に コミットが 割り込み処理される @Transactional(readOnly = false) public Memo add(Memo unregisteredMemo) ... ... } メソッド内で非チェック例外が発生した場合、 ロールバックが割り込み処理される ※チェック例外が発生した場合、デフォルトではロールバックされません (rollbackFor要素に例外クラスを指定すればロールバック可能) 実際に開始・コミット・ロールバックなどを行う トランザクションマネージャーは、Spring BootではBean定義済み 33
Spring Txでトランザクション管理 ビジネスロジッククラス であることを示す(Beanになる) @Service public class MemoService { private final MemoRepository memoRepository; RepositoryクラスをDI public MemoService(MemoRepository memoRepository) { this.memoRepository = memoRepository; } トランザクション管理を 割り込み処理する @Transactional(readOnly = true) public List<Memo> findAll() { List<Memo> memoList = memoRepository.findAll(); return memoList; } } @Transactional(readOnly = false) public Memo add(Memo unregisteredMemo) { Memo registeredMemo = memoRepository.add(unregisteredMemo); return registeredMemo; } 34
Spring MVC ▸ Webアプリケーションフレームワーク ▸ サーブレットベース ▸ 主な機能 ▸ Model-View-Controllerのひな形 ▸ Controllerの作成 ▸ Viewとの連携(View自体の機能は無い) ▸ 入力チェックとの連携 ▸ ・・・など 35
Controllerの作成
Controllerであることを示す
(Beanになる)
@Controller
public class MemoController {
ビジネスロジックをDI
private final MemoService memoService;
public MemoController(MemoService memoService) {
this.memoService = memoService;
}
リクエストメソッド+URLを
メソッドに対応付ける
@GetMapping("/")
public String index(Model model) {
List<Memo> memoList = memoService.findAll();
model.addAttribute("memoList", memoList);
return "index";
}
ビューのパスの一部を返す
(index.htmlなら"index")
}
@PostMapping("/add")
public String add(MemoForm memoForm) {
Memo unregisteredMemo = memoForm.toEntity();
memoService.add(unregisteredMemo);
return "redirect:/";
}
36
利用できるView ▸ Thymeleaf ← 今回はコレ ▸ Freemarker ▸ Groovy Templates ▸ JSP ▸ ・・・など 37
Thymeleafでの画面作成
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>・・・</head>
<body>
・・・
素のHTMLに th: で始まる属性を記述
<table>
<thead>
・・・
</thead>
<tbody>
<tr th:each="memo : ${memoList}" th:object="${memo}">
<td th:text="*{id}">1</td>
<td th:text="*{content}">ダミーのメモ1</td>
<td th:text="*{#temporals.format(createdAt, 'yyyy年M月d日 H時m分s秒')}">
2022年1月1日 10時10分10秒
</td>
</tr>
・・・
※詳細な記法は公式ドキュメント(3〜7章)を参照
38
Spring MVCのアーキテクチャ(ざっくり) ①リクエスト ②URLごとに 処理を振り分ける Dispatcher Servlet ⑥レスポンス Controller Controller Controller ③ビューのパスの 一部を返す ⑤ビューの完全なパス ④ビューのパスの一部 ViewResolver Spring BootではBean定義済み 39
RESTful Web Servicesの作成
@RestController
@RequestMapping("/memos")
public class MemoRestController {
REST用のController
であることを示す
private final MemoService memoService;
public MemoRestController(MemoService memoService) {
this.memoService = memoService;
}
戻り値がJSONに変換されて
レスポンスされる
@GetMapping
public List<MemoResponse> findAll() {
List<Memo> memoList = memoService.findAll();
List<MemoResponse> memoResponseList = memoList.stream()
.map(memo -> MemoResponse.fromEntity(memo))
.toList();
リクエストボディのJSONを
return memoResponseList;
}
引数の型に変換して受け取る
}
@PostMapping
public ResponseEntity<?> add(@RequestBody MemoRequest memoRequest) {
Memo unregisteredMemo = memoRequest.toEntity();
Memo registeredMemo = memoService.add(unregisteredMemo);
URI location = ServletUriComponentsBuilder.fromCurrentRequest()
.pathSegment(registeredMemo.id().toString())
.build().encode().toUri();
return ResponseEntity.created(location).build();
}
40
Spring MVCのアーキテクチャ(RESTの場合) ①リクエスト ⑥レスポンス Dispatcher Servlet ②URLごとに 処理を振り分ける RestController Controller Controller ③JSON化する値を 返す ⑤JSON ④JSON化する値 HttpMessage Converter Spring BootではBean定義済み 41
ためになる資料 https://speakerdeck.com/otty375/architecture-of-spring-mvc 42
その他のサブプロジェクトたち ▸ Spring Security ▸ 認証・認可など ▸ Spring Data ▸ あらゆるデータアクセスを抽象化 ▸ Spring Batch ▸ バッチ処理フレームワーク ▸ Spring Cloud ▸ マイクロサービス開発 ▸ ・・・など 43
この章のまとめ ▸ Spring JDBCはDBアクセスを楽にする ▸ Spring Txはトランザクション管理を楽にする ▸ Spring MVCはWebアプリケーション開発を 楽にする ▸ 他にも便利なサブプロジェクトがいっぱい 44
目次 ① Springって何? ② Springのコア: コンテナ・DI・AOP ③ よく使われるSpringサブプロジェクト ④ Spring Bootって何? ⑤ ためになる資料たち 45
Springでの開発の問題点 ① 使用するライブラリが大量にある ‣ spring-beans、spring-aop、spring-jdbc、 spring-tx、spring-webmvc、 その他のサードパーティライブラリ・・・など ② 記述すべきJava Con gが大量にある ‣ JDBCのためのBean定義、TxのためのBean定義、 ※これらのBean定義については、 MVCのためのBean定義、・・・など 今回の資料では解説していません 一言で言うと、開発の一歩目が非常に大変 fi 46
Spring Bootの2大機能 ① Starterライブラリ ▸ ライブラリをまとめたライブラリ ▸ 開発者が指定するライブラリが少なくて済む ② Auto Con gurationクラス ▸ 記述済みのJava Con gクラスを大量に提供 ▸ 開発者が記述するJava Con gが少なくて済む(0にはならない) 開発の一歩目がとても楽になった! fi fi fi 47
①Starterライブラリ Web用のStarterを指定するだけで、 こんなに多くのライブラリを追加できる ▸ Web用・JDBC用・ Security用など、 用途に応じたものを 大量に提供 ▸ Starterライブラリの 中身は空なことが多い ▸ 依存ライブラリのみを 指定している 48
②Auto Con gurationクラス ▸ spring-bootautocon gure.jarに、 大量の XxxAutoConfiguration クラスがある ▸ この中に大量のBean定義が 記述されている fi fi 49
Auto Con gurationクラスの例
この資料では説明していない
アノテーションが付いているものの、
アノテーションも付いているものの、
いたって普通のJava
普通のJava ConCon
gクラス
gクラス
@Configuration(proxyBeanMethods
= false)
@ConditionalOnProperty(prefix = "spring.aop", name = "auto",
havingValue = "true", matchIfMissing = true)
public class AopAutoConfiguration {
@Configuration(proxyBeanMethods
= false)
@ConditionalOnMissingClass("org.aspectj.weaver.Advice")
@ConditionalOnProperty(prefix = "spring.aop", name = "proxy-target-class",
havingValue = "true", matchIfMissing = true)
static class ClassProxyingConfiguration {
Bean定義
@Bean
static BeanFactoryPostProcessor forceAutoProxyCreatorToUseClassProxying() {
return (beanFactory) -> {
if (beanFactory instanceof BeanDefinitionRegistry) {
BeanDefinitionRegistry registry = (BeanDefinitionRegistry) beanFactory;
AopConfigUtils.registerAutoProxyCreatorIfNecessary(registry);
AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry);
}
};
}
}
...
fi
fi
fi
50
よくある勘違い ▸ ❌ Spring BootはSpringをラップしたフレームワークである ⭕ StarterライブラリやAuto Con gurationクラスを 提供しているのみ ▸ ❌ Spring Bootを使っていれば、 Spring自体を理解する必要はない ⭕ リポジトリ・サービス・コントローラーなどの作り方は 一切変わらないので、理解は必要 ▸ ❌ Spring Bootを使えば、生産性が劇的に高まる ⭕ 生産性が高まるのは、開発の一歩目のみ fi 51
その他の便利機能 ③ 実行可能JAR ‣ ライブラリ・組み込みサーバー・作成したクラスを 全て含んだJARファイルを作成できる →デプロイが非常に楽 ④ 設定の外部化 ‣ 設定値は全てapplication.propertiesに記述 →実行時に環境変数などで上書き可能 ⑤ Actuatorによる監視 ‣ メトリクスなどをWeb APIとして提供 52
③実行可能JAR ▸ spring-boot-maven-pluginが実現している ▸ 以下のコマンドで実行可能JARを作成できる ▸ Mavenの場合: mvn clean package ▸ Gradleの場合: gradle clean build ▸ 以下のコマンドで実行できる ▸ java -jar target/spring-memo-0.0.1SNAPSHOT.jar 53
④設定の外部化 ▸ 決められたプロパティをapplication.propertiesに記述する ▸ 例: JDBC URLはspring.datasource.url、 JDBCユーザー名はspring.datasource.username、 JDBCパスワードはspring.datasource.password ▸ 実行時に、プロパティを環境変数などで上書きできる ▸ JVMシステムプロパティやコマンドライン引数などでも上書き可能 $ $ $ $ export SPRING_DATASOURCE_URL=jdbc:postgresql://本番環境DBのURL:5432/memo export SPRING_DATASOURCE_USERNAME=本番環境DBのユーザー名 export SPRING_DATASOURCE_PASSWORD=本番環境DBのパスワード java -jar target/spring-memo-0.0.1-SNAPSHOT.jar 54
⑤Actuatorによる監視
1. 依存性にspring-boot-starter-actuatorを追加
2. application.propertiesに下記の設定を追加
management.endpoints.web.exposure.include=*
management.endpoint.health.show-details=ALWAYS
3. curlなどでアクセス
$ curl -X GET http://localhost:8080/actuator/health | jq
{
"status": "UP",
"components": {
"db": {
"status": "UP",
"details": {
"database": "PostgreSQL",
"validationQuery": "isValid()"
}
},
"diskSpace": {
"status": "UP",
"details": {
"total": 499963174912,
"free": 339734417408,
...
55
この章のまとめ ▸ Starterライブラリは、ライブラリをまとめたライブラリ ▸ Auto Con gurationクラスは、 記述済みのJava Con gクラス ▸ 実行可能JARを作成できる ▸ application.propertiesの設定を環境変数などで 上書きできる ▸ Actuatorで監視のためのWeb APIが作られる fi fi 56
目次 ① Springって何? ② Springのコア: コンテナ・DI・AOP ③ よく使われるSpringサブプロジェクト ④ Spring Bootって何? ⑤ ためになる資料たち 57
JSUG重要資料 スライド: https://www.docswell.com/s/MasatoshiTada/Z818E5-spring-di-aop- スライド: https://www.docswell.com/s/MasatoshiTada/K1XMLK-advanced- for-every-developers 動画: https://www.youtube.com/watch?v=LGtdpsmMfvI spring-for-professionals 動画: https://www.youtube.com/watch?v=c5ha8FmdNmw スライド: https://speakerdeck.com/otty375/architecture- スライド: https://www.slideshare.net/KouheiToki/spring- of-spring-mvc 動画: https://youtu.be/szqIWhPnzjM?t=1635 boot-250772616 動画: https://www.youtube.com/watch?v=8saUt8sxgsI 58
Spring Fest 2020のBeginner Track動画 ▸ https://springfest2020.springframework.jp/#sessions ▸ 特にSpring Security・Spring Testは神セッション 59
書籍 ▸ どちらもSpring 4時代の本なので、僕のブログ👇も合わせて読んでください ▸ 令和時代に「Spring入門」「Spring徹底入門」を読むとき気をつけるべきN個のこと https://www.amazon.co.jp/dp/B01H12Z40U/ https://www.amazon.co.jp/dp/B01IEWNLBU/ 60
公式ドキュメント ▸ Spring ※Springに限らず、日本語訳は最新バージョンに追従していないこともあるため、 なるべく公式の英語版を読む習慣を付けましょう ▸ 英語(公式) https://docs.spring.io/spring-framework/docs/current/ reference/html/ ▸ 日本語訳(非公式) https://spring.pleiades.io/spring-framework/docs/ current/reference/html/ ▸ Spring Boot ▸ 英語(公式) https://docs.spring.io/spring-boot/docs/current/ reference/htmlsingle/ ▸ 日本語訳(非公式) https://spring.pleiades.io/spring-boot/docs/ current/reference/html/ 61
この次は! ▸ このスライドで解説したサンプルプログラムの ライブコーディングを行います ▸ 動画はこちら ▸ ソースコードはこちら 62