目次
概要
Springについて学ぶ為にSpringFramework超入門〜やさしくわかるWebアプリ開発〜
*1を読んだので、重要そうなポイントを以下に纏める。
SpringFrameworkとは
Javaアプリケーション開発におけるフレームワーク。単に「Spring」とも呼ばれる。
開発が楽になる様々な機能を提供してくれていて、機能毎に下記の様なプロジェクトが存在する。
SpringBoot
Springアプリケーションを煩雑な設定をせず迅速に作成する為の機能を提供している。
Springが多機能になりすぎて、逆に開発をスタートする際のコストが大きくなってしまったという課題を解決してくれる。
Springプロジェクト
Spring MVC
Webアプリを簡単に作成する為の機能を提供。
Spring Data
データアクセスの為の機能を提供。
Spring Batch
バッチ処理機能を提供。
Spring Security
認証・認可の機能を提供。
Springコア
Spring DI
依存性注入の機能を提供。
Spring AOP
アスペクト指向プログラミングの機能を提供。
アプリケーションのレイヤ化
アプリケーションを理解・管理しやすくする為に、アプリケーション全体をひと纏まりとして捉えるのではなく、役割によってレイヤ化する。
DDD(ドメイン駆動開発)で定義されているレイヤは下記3層。また、各レイヤは複数のコンポーネントを包括する。
- Application層
- Domain層
- Infrastructure層
Application層とInfrastructure層はDomain層に依存して良いが、Domain層は他2層から疎な状態で、再利用可能でなくてはならない。
つまり、Domain層に変更が加わった際に他2層に変更が生じても良いが、他2層に変更が生じた際にDomain層に変更が生じてはいけない。
Application層
クライアントとの入出力となるUIを提供したり、リクエストを基にドメイン層を呼び出すなど、アプリケーションを構築する為のレイヤ。
この層はなるべく薄く保たれるべきなので、ビジネスルールを含んではいけない。
包括するコンポーネントは下記の通り。
Controller
クライアントからのRequestを受け取り、Domain層の適したServiceを呼び出す。
処理結果をViewに返却する。
View
クライアントへのUIを提供する。
クライアントとController間でやり取りするデータを格納する入れ物。
具体的には、HTMLのformタグ内のデータを格納。(「Form」という名前の由来は恐らくこれ。)
Domain層がApplication層に依存しない為に、Application層→Domain層にデータを引き渡す際は、Formのまま渡すのではなく、Domain層のDomain Objectに変換してから引き渡すべき。また、その変換処理はApplication層(Controller)で行う。
Domain層
アプリケーションのコアとなるビジネスルール(業務処理)を担当し、Entityに対するサービス処理を提供するレイヤ。
包括するコンポーネントは下記の通り。
Service
業務処理を提供する。
Domain Object
- 業務処理を行う中で発生する管理すべきデータの入れ物となるクラス。
- その1種である
Entity
は、DBとアプリがデータをやり取り(アプリ→DB
,DB→アプリ
)する際の、データの入れ物となるクラス。
- Application層のFormに格納されていたデータがEntityに格納される。
DAO,DTOデザインパターン
のDTO
がこれに当たる。
- フィールドとアクセッサ(ゲッター,セッター)で構成され、クラス名はDBテーブル名と同じ名前にする。
- DBテーブルの1レコードが1クラスに対応し、クラスが保有する各フィールドが対象レコードの各フィールド(各カラムの値)に対応する。
Repository(リポジトリ)
- DBとアプリがデータをやり取り(
アプリ→DB
,DB→アプリ
)する際に、DBへのCRUD処理機能を提供するインターフェース。
- 実体はInfrastructure層の
RepositoryImpl
で実装するため、 どのようなデータアクセスが行われているかについての情報は持たない。
DAO,DTOデザインパターン
のDAO
がこれに当たる。
Infrastructure層
Domain Object(Entityなど)にCRUD処理(永続化)を提供するレイヤ。
包括するコンポーネントは下記の通り。
RepositoryImpl
- Domain層のRepositoryインターフェースを実装したクラスで、実際のCRUD処理を提供する。
- 必ず、インターフェースを定義したうえで実装する。
- 使用する側(Domain層)のクラスが、クラス依存ではなくインターフェース依存でRepositoryを利用できるようにし、依存性を低くする為。
Repository
インターフェースがCrudRepository
クラス(Springが提供)を継承する事で、自動的にCRUDメソッドが利用可能になる。
O/R Mapper
にSpringDataJDBCを使用する場合は、実装したRepository(インターフェース)から自動生成されるため、実装不要。
O/R Mapper
- Object(Domain Object)とRDBとのデータのマッピング機能を提供する。
- SpringDataJDBCがその一種。
DispatcherServlet
フロントコントローラ
と呼ばれ、クライアントからの全てのリクエストを受信してくれる。
- リクエストを受信した後に、リクエストURLを基に然るべき
リクエストハンドラメソッド
を呼び出す。
- URLとリクエストハンドラメソッドを紐付けるアノテーションが、
Controller
の中に記載されている。
リクエストハンドラメソッド
- MVCの
Controller
の中に定義されるメソッドで、クライアントからのリクエストを処理する。
- リクエストURLとリクエストメソッド(
GET
orPOST
)をアノテーションで記載する事で、リクエスト毎に正しいリクエストハンドラメソッド
を呼び出せる。
- 戻り値として、クライアントに返却すべき
View
の名前をDispatcherServlet
にreturnする事で、DispatcherServlet
はその名前に応じたView
に、HTMLを生成させクライアントに返却させる。
- 危険なコードやデータを変換や除去する事で、無力化する処理。
- 例えば、Webサイトの入力フォームに悪意あるコード等が入力された際に、無力化する。
Thymeleaf(タイムリーフ)
SpringMCV
フレームワークにて推奨されている、動的にView
を生成する為のテンプレートエンジン。
- HTMLベースのテンプレと、動的データをバインド(組み合わせる)してViewを構成する。
- JavaのWebアプリの
View
としてはJSP
がよく利用されるが、JSP
ファイルはWebブラウザが読み込めないため、開発段階でWebブラウザにてViewを表示して明示確認できないという課題が有る。
- 一方で、
Thymeleaf
はHTMLベースのため、ファイルをWebブラウザ上で明示確認しながら開発を進める事ができる。
- そのため、UI(View)デザイナーとの分業が簡単になる。
Viewからのリクエストパラメータの受け取り方
@RequestParam
アノテーションを使う方法とForm
クラスを用いる方法の2種類有る。
@RequestParamアノテーションを利用する方法
- リクエストハンドラメソッドの引数に
@RequestParam
アノテーションを付与する事で、リクエストパラメータを引数として受け取る事ができる。
View
のname
(入力項目名)と同じ引数名を宣言する。
- リクエストパラメータが増えるほど引数も増えるので、冗長になってしまう可能性が有る。
- Formは、リクエストパラメータを格納する為のクラス。
- リクエストパラメータを纏めて引き渡す事ができるので、パラメータ数が増えても冗長にならない。
POJO
で実装する。
View
のリクエストパラメータのname
と、Form
クラスの変数名を同一名にする事で、リクエストパラメータが自動でForm
クラスのフィールドに格納される。
- リクエストパラメータはフィールドの型に自動変換される。
Plain Old Java Object
の略。
- 「ポジョ」と読むらしい。
java.lang.Object
を継承して、それ以外何も継承していない、シンプルなJavaクラスの事。
java.lang.Object
は、全てのJavaクラスが暗黙的に自動で継承しているので、つまりは、明示的には何も継承していないクラスの事。
バリデーションチェック
大きく、単項目チェック
と相関項目チェック
の2つに分かれる。
単項目チェック
- 入力項目(フィールド)1つ1つに対して行う入力チェックの事。
- 多くのフレームワークでは、だいたいの入力チェック機能は、アノテーションを付与するだけで提供されるようになっている。
相関項目チェック
- 複数の入力項目(フィールド)に対して同時に行う入力チェックの事。
- 例えば、左の入力項目が奇数、かつ、右側の入力項目が偶数、の様なモノ。
- 自分で新たなアノテーションを作成するか、
Validator
インターフェースを実装するかの2択。
- バリデーションを行う際に必要となる、HTMLの
form
タグにバインドするForm
クラスのインスタンスの事。
@ModelAttribute
アノテーションを付与したメソッドでform-backing bean
を作成する。
@ModelAttribute
アノテーションが付与されたメソッドは、そのクラス(Controller)のリクエストハンドラメソッドの実行前に呼ばれ、returnするform-backing bean(Formオブジェクト)
はmodel.addAttribute(form)
相当の処理が実行され、Model(ControllerからViewに渡される、表示データ等が格納されるオブジェクト)
に格納される。
- Modelに格納される際、デフォルトではリクエストスコープで格納されるので、クライアントへのResponse後に自動削除される。
実際の内容(値)を後から挿入する為に、とりあえず仮に確保した場所の事。
複数の処理をひと纏まりにしたモノで、トランザクションが完了した際に、処理が全て成功していれば保存(コミット)、1つでも失敗していればトランザクションで行った処理を全て巻き戻す(ロールバック)する。
トランザクションの開始と修了の範囲を「トランザクション境界」と呼び、Domain層(MVCモデルにおけるModel)の入り口であるService(ServiceImplクラス)の中で定義する。
トランザクションを管理したいクラスやメソッドに@Transactional
アノテーションを付与すれば、トランザクションが自動で管理される。
application.properties
SpringBootプロジェクトにおいて、例えばDBへの接続情報等の環境設定を行う為のファイル。
Flash Scope(フラッシュスコープ)
1回のリダイレクトの間のみ有効なスコープ。
Springでは、「@」に続いて特定のキーワードを記載する形式でソースコードにアノテーション(注釈)を記載する事で、Springが標準で提供している何かしらの機能を対象のソースコードに付与する事ができる。
例えば、AOP(アスペクト思考プログラミング)を実現する際や、DIを実現する際などに使用する。
本書で勉強する際に頻出度が高かった一部のアノテーションをいかにメモる。
全体を通して使われるモノ
@Autowired
DI(依存性の注入)を担うアノテーションで、自動生成したインスタンスを、アノテーションを付与した変数に自動代入(注入)してくれる。これを行う事で、「new」キーワードによるインスタンス生成を記載しなくてよくなるため、newされる側のクラスに対する依存性が下がる。
Controllerクラスで使われるモノ
@Controller
クラスに付与することで、Spirngのコンポーネントとして認識される。
@RequestMappng
クラスやクラス内のメソッドに付与する事で、リクエストURLに対してどのメソッドの処理を実行するかのマッピングを行える。
アノテーションの第一引数にURLキーワード、第二引数にHTTPリクエストのメソッドを記載する。
@GetMapping
GETリクエストを処理する@RequestMappngの簡略アノテーション(「@RequestMapping」の第二引数に「Get」を指定した場合と同じ)。
@PostMapping
POSTリクエストを処理する@RequestMappngの簡略アノテーション(「@RequestMapping」の第二引数に「Post」を指定した場合と同じ)。
@ModelAttribute
メソッドか引数に付与する事で、それぞれ違った効果を発揮する。
メソッドに付与した場合は、リクエストハンドラメソッドが呼び出される前に、そのメソッドが呼ばれ、メソッドの戻り値がリクエストスコープでModelに格納される。form-backing bean
の初期化処理等に利用される。
引数に付与した場合は、リクエストハンドラメソッドが呼び出される前に、その引数に
リダイレクト
クライアントからの任意のReqに対応するリクエストハンドラメソッドでクライアントへのResの画面名をreturnする代わりに、他のリクエストハンドラメソッドに処理を引き継ぐ為に、他リクエストハンドラメソッドが対応するURLをreturnする事で、仮想的なReqを投げるイメージ。
例えば、本書のQuizアプリの中では、下記の様に使用していた。
@PostMapping("/insert")
public String insert(@Validated QuizForm quizForm, BindingResult bindingResult, Model model, RedirectAttributes redirectAttributes) {
Quiz quiz = makeQuiz(quizForm);
if(!bindingResult.hasErrors()) {
service.insertQuiz(quiz);
redirectAttributes.addFlashAttribute("complete", "登録が完了しました。");
return "redirect:/quiz";
}else {
return showList(quizForm, model);
}
}
上記は、クイズを登録する際のリクエストハンドラメソッドだが、11行目が該当箇所。
画面名をreturnせず、/quiz
というURLに対するReqが飛んで来た時と同じ処理を後続で走らせている。
クライアントに同じ画面を返却する場合でも、Modelに詰めてるFormをどういう状態で返却したいかに応じて処理の走らせ方を変えている事に気付いたので、以下にメモを残す。
対象部分のソースは下記。
@Controller
@RequestMapping("/quiz")
public class QuizController {
@Autowired
QuizService service;
@ModelAttribute
public QuizForm setUpForm() {
QuizForm form = new QuizForm();
form.setAnswer(true);
return form;
}
@GetMapping
public String showList(QuizForm quizForm, Model model) {
quizForm.setNewQuiz(true);
Iterable<Quiz> list = service.selectAll();
model.addAttribute("list", list);
model.addAttribute("title", "登録用フォーム");
return "crud";
}
@PostMapping("/insert")
public String insert(@Validated QuizForm quizForm, BindingResult bindingResult, Model model, RedirectAttributes redirectAttributes) {
Quiz quiz = makeQuiz(quizForm);
if(!bindingResult.hasErrors()) {
service.insertQuiz(quiz);
redirectAttributes.addFlashAttribute("complete", "登録が完了しました。");
return "redirect:/quiz";
}else {
return showList(quizForm, model);
}
}
}
ユーザがクイズ登録機能を利用した際に、入力したクイズデータがバリデーションチェックを通るか弾かれるかによって、処理の走らせ方を変えている。
具体的には、41行目と44行目。
処理の走り方の違い
それぞれどういう処理の走らせ方をしているか、簡単に記載する。
①バリチェックが通った時の処理
- 41行目で
/quiz
というURLに対してリダイレクト。
- 12行目の
setUpForm()
メソッドが呼ばれる。
- リクエストハンドラメソッドとして、20行目の
showList(QuizForm quizForm, Model model)
メソッドが呼ばれる。
②バリチェックで弾かれた時の処理
- 44行目で、Formオブジェクトを引数として渡して
showList(QuizForm quizForm, Model model)
メソッドを呼ぶ。
- 20行目の
showList(QuizForm quizForm, Model model)
メソッドが走る。
①と②の具体的な違い
具体的には、①と②では下記2点が大きく異なる。
- Formオブジェクトを初期化する、12行目の
setUpForm()
メソッドが呼ばれるかどうか。
- クライアントに返却するView名をreturnする20行目の
showList(QuizForm quizForm, Model model)
メソッドに、Formオブジェクトを渡せるか渡せないか。
処理の走らせ方を変えた意図
結論から言うと、Resした後のUIに、先ほどユーザが入力したデータを表示させたいかどうかによって①と②を使い分けているのだと理解した。
バリチェックが通った時は、UI上のクイズデータ入力フォーム等を初期状態(空文字等)にしておきたいので、先ほどユーザが入力したデータをResに含める必要が無い。つまり、Formオブジェクトは初期化されていてほしいという事。
一方で、バリチェックに弾かれた時は、UI上のクイズデータ入力フォーム等には、先ほどユーザが入力したデータを表示しておきたいので、Formオブジェクトは初期化してはいけないし、先程のReqで受け取ったFormオブジェクトをそのまま持ち回りたい(引数としてshowList(QuizForm quizForm, Model model)
メソッドに渡したい)という事。
所感
- 「Model」という言葉の意味がより分からなくなった。(多分、定義が結構曖昧)
- MVCの「Model」と、クライアントにResする際にFormを詰める「Model」は別のモノを指している。
- Springはアノテーションに着目すると、グッと読みやすくなる。