DaisukeのITメモ

一人前になる為に。

Springについて_「SpringFramework超入門〜やさしくわかるWebアプリ開発〜」を読んで

目次

概要

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層。また、各レイヤは複数のコンポーネントを包括する。

  1. Application層
  2. Domain層
  3. Infrastructure層

Application層とInfrastructure層はDomain層に依存して良いが、Domain層は他2層から疎な状態で、再利用可能でなくてはならない。

つまり、Domain層に変更が加わった際に他2層に変更が生じても良いが、他2層に変更が生じた際にDomain層に変更が生じてはいけない。

Application層

クライアントとの入出力となるUIを提供したり、リクエストを基にドメイン層を呼び出すなど、アプリケーションを構築する為のレイヤ。

この層はなるべく薄く保たれるべきなので、ビジネスルールを含んではいけない。

包括するコンポーネントは下記の通り。

Controller

クライアントからのRequestを受け取り、Domain層の適したServiceを呼び出す。

処理結果をViewに返却する。

View

クライアントへのUIを提供する。

Form

クライアントと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を基に然るべきリクエストハンドラメソッドを呼び出す。

リクエストハンドラメソッド

  • MVCControllerの中に定義されるメソッドで、クライアントからのリクエストを処理する。
  • リクエストURLとリクエストメソッド(GETorPOST)をアノテーションで記載する事で、リクエスト毎に正しいリクエストハンドラメソッドを呼び出せる。
  • 戻り値として、クライアントに返却すべき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アノテーションを付与する事で、リクエストパラメータを引数として受け取る事ができる。
    • Viewname(入力項目名)と同じ引数名を宣言する。
  • リクエストパラメータが増えるほど引数も増えるので、冗長になってしまう可能性が有る。

Formクラスを利用する方法

  • Formは、リクエストパラメータを格納する為のクラス。
    • リクエストパラメータを纏めて引き渡す事ができるので、パラメータ数が増えても冗長にならない。
  • POJOで実装する。
  • Viewのリクエストパラメータのnameと、Formクラスの変数名を同一名にする事で、リクエストパラメータが自動でFormクラスのフィールドに格納される。
    • リクエストパラメータはフィールドの型に自動変換される。

POJO

  • Plain Old Java Objectの略。
  • 「ポジョ」と読むらしい。
  • java.lang.Objectを継承して、それ以外何も継承していない、シンプルなJavaクラスの事。
    • java.lang.Objectは、全てのJavaクラスが暗黙的に自動で継承しているので、つまりは、明示的には何も継承していないクラスの事。

バリデーションチェック

大きく、単項目チェック相関項目チェックの2つに分かれる。

単項目チェック

  • 入力項目(フィールド)1つ1つに対して行う入力チェックの事。
  • 多くのフレームワークでは、だいたいの入力チェック機能は、アノテーションを付与するだけで提供されるようになっている。

相関項目チェック

  • 複数の入力項目(フィールド)に対して同時に行う入力チェックの事。
    • 例えば、左の入力項目が奇数、かつ、右側の入力項目が偶数、の様なモノ。
  • 自分で新たなアノテーションを作成するか、Validatorインターフェースを実装するかの2択。

form-backing bean(Formクラス)

  • バリデーションを行う際に必要となる、HTMLのformタグにバインドするFormクラスのインスタンスの事。
  • @ModelAttributeアノテーションを付与したメソッドでform-backing beanを作成する。
  • @ModelAttributeアノテーションが付与されたメソッドは、そのクラス(Controller)のリクエストハンドラメソッドの実行前に呼ばれ、returnするform-backing bean(Formオブジェクト)model.addAttribute(form)相当の処理が実行され、Model(ControllerからViewに渡される、表示データ等が格納されるオブジェクト)に格納される。
    • Modelに格納される際、デフォルトではリクエストスコープで格納されるので、クライアントへのResponse後に自動削除される。

プレースホルダ

実際の内容(値)を後から挿入する為に、とりあえず仮に確保した場所の事。

ジェネリッククラス

  • クラスを定義する際に、クラス名の右側にと記載されたクラスの事。

    • Tは「型パラメータ」と呼ばれる。

    • <>内の文字はTでなくても良いらしい。

    • class GenericClass<T> {
          private T data;
      
          public void setData(T value) {
              this.data = value;
          }
      
          public T getData() {
              return data;
          }
      }
      
  • クラスを利用する際に、Tの部分に型を指定すると、クラス内のTが指定した型に置き換わる。

    • 型は参照型(クラスやインターフェースなど)しか指定できず、基本型(intなど)を指定してしまうとコンパイルエラーになる。

    • 下記例では、String型に置き換わる。

    • GenericClass<String> genericClass = new GenericClass<String>;
      

トランザクション

複数の処理をひと纏まりにしたモノで、トランザクションが完了した際に、処理が全て成功していれば保存(コミット)、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アプリの中では、下記の様に使用していた。

   // クイズを1件登録。
    @PostMapping("/insert")
    // 「@Validated」アノテーションで「QuizForm」オブジェクトをバリチェックした結果が「BindingResult」インターフェースに格納される。
    public String insert(@Validated QuizForm quizForm, BindingResult bindingResult, Model model, RedirectAttributes redirectAttributes) {
        // Formの中身をEntityに詰め替える。
        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が飛んで来た時と同じ処理を後続で走らせている。

【メモ】クライアントに同画面を返却する場合でも、Formの状態によって処理の走らせ方を変える

クライアントに同じ画面を返却する場合でも、Modelに詰めてるFormをどういう状態で返却したいかに応じて処理の走らせ方を変えている事に気付いたので、以下にメモを残す。

対象部分のソースは下記。

@Controller
@RequestMapping("/quiz")
public class QuizController {
    @Autowired
    QuizService service;
  
// 「form-backing bean」の初期化。
    // 「ModelAttribute」アノテーションを付与する事で、リクエストハンドラメソッドが実行される前に毎回このメソッドが実行される。
    // returnされるFormクラスは、model.addAttribute(form)相当の処理が実行され、「quizForm」という名前でModelに追加される。
    // 今回は、Formインスタンス内のラジオボタンの初期値設定のみ行っている。Formインスタンス内の他フィールドについては、後続処理の中で設定する。
    @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」への格納。
        model.addAttribute("list", list);
        model.addAttribute("title", "登録用フォーム");

        // クライアントに返却するView名(HTMLのファイル名)をreturnする。
        return "crud";
    }

    // クイズを1件登録。
    @PostMapping("/insert")
    // 「@Validated」アノテーションで「QuizForm」オブジェクトをバリチェックした結果が「BindingResult」インターフェースに格納される。
    public String insert(@Validated QuizForm quizForm, BindingResult bindingResult, Model model, RedirectAttributes redirectAttributes) {
        // Formの中身をEntityに詰め替える。
        Quiz quiz = makeQuiz(quizForm);

        // バリチェック。
        if(!bindingResult.hasErrors()) {
            service.insertQuiz(quiz);
            redirectAttributes.addFlashAttribute("complete", "登録が完了しました。");
            return "redirect:/quiz";
        }else {
            // バリチェックがエラーになった場合は、一覧表示処理を呼ぶ。
            return showList(quizForm, model);
        }
    }
}

ユーザがクイズ登録機能を利用した際に、入力したクイズデータがバリデーションチェックを通るか弾かれるかによって、処理の走らせ方を変えている。

具体的には、41行目と44行目。

処理の走り方の違い

それぞれどういう処理の走らせ方をしているか、簡単に記載する。

①バリチェックが通った時の処理

  1. 41行目で/quizというURLに対してリダイレクト。
  2. 12行目のsetUpForm()メソッドが呼ばれる。
  3. リクエストハンドラメソッドとして、20行目のshowList(QuizForm quizForm, Model model)メソッドが呼ばれる。

②バリチェックで弾かれた時の処理

  1. 44行目で、Formオブジェクトを引数として渡してshowList(QuizForm quizForm, Model model)メソッドを呼ぶ。
  2. 20行目のshowList(QuizForm quizForm, Model model)メソッドが走る。

①と②の具体的な違い

具体的には、①と②では下記2点が大きく異なる。

  1. Formオブジェクトを初期化する、12行目のsetUpForm()メソッドが呼ばれるかどうか。
    • ①では呼ぶが、②では呼ばない。
  2. クライアントに返却する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はアノテーションに着目すると、グッと読みやすくなる。
/* -----codeの行番号----- */