Java Basic 中級編 ~②メインメソッドを修正しない形を作る~

イントロダクション

前回アプリケーションを作り、それを運用すること、拡張することについて考えてみました。

まとめると次のようなことが必要になるということを記載しました。

  • 各プログラム間(Javaのクラス同士)の依存度を限りなく低くする。
  • プログラムを拡張するのに、元のプログラムをほとんど変更しない。
  • Javaを使えるレベルの知識(技術)がある人なら、誰が見てもわかるようなコードを書く。
  • 各クラス(部品)の単体テスト(UnitTest)ケースを用意しておき、修正したら即テスト、部品を取り換えるだけでよいように、作成した資源(プログラムのコード)を結合テスト・総合テストと実施できるような体制を整える

今回は、プログラムを拡張していくための基本になる考え方と実践方法について記載していきたいと思っています。
例として「〇×あてゲーム」を拡張していく形でプログラム(クラス)間の依存度を低くした形のプログラミング方法について記載していきたいと思います。

コードを修正しなくても良い形を作る

前回作成したプログラムは下のものになります。

public class Lv2Main {
    /** 終了フラグ */
    private static boolean isFinish;

    public static void main(String[] arg) {
        isFinish = false;
        // 標準入力を受け取るクラスをインスタンス化
        Scanner scan = new Scanner(System.in);

        while (true) {
            System.out.println("「〇×あてゲーム」 0: 〇 1: ×。");
            // 標準入力を受け取る
            int input = scan.nextInt();
            // isFinishがtrueならば処理終了。
            if (isFinish) {
                System.out.println("プログラムを終了します。");
                break;
            }
            // 0か1の値を返却する
            Random rdm = new Random();
            int res = rdm.nextInt(1);

            // 0: 〇 1: ×で当たったかどうかの判定
            boolean isAtari = res == input;
            String value = input == 0 ? "〇" : "×";
            if (isAtari) {
                System.out.println("あたり:" + value);
            } else {
                System.out.println("はずれ:" + value);
            }
            System.out.println("続けますか? 0: 続ける  1: やめる");
            int next = scan.nextInt();
            if (next == 1) {
                break;
            }
        }
    }
}

この状態のプログラムはよく見かける。。。と思われる形のプログラムです。
つまりは、処理を1つのファイル内にすべて書いている形のプログラムということです。

このブログ的に表現すると「Javaの基本:上巻」に当たるプログラムの書き方になります。

「Javaの基本:下巻」に当たるプログラムの書き方はこれから説明していきます。

Step1. 役割分担を行う

上のプログラムで行っていることを、例えばチームで作業をするように、役割分担を行いそれぞれのクラスにそれぞれの役割を与えます。

例えば、次のような役割分担を行います。

  1. 〇×あてゲームを起動する役割
  2. 標準入力を受け取る、などの標準入出力をコントロールする役割
  3. 〇×あてゲームの各種判定を行う役割

もともとのプログラムは、全部の処理が書いてあるので、これを切り貼りして改造します。

Step2. クラスを作成する

上記の通り、2つの役割を担当するクラスを作成します。

役割分担を確認

クラス名 役割
Lv2Main ※作成済み 〇×あてゲームを起動する役割
MarubatsuConsole 標準入力を受け取る、などの標準入出力をコントロールする役割
MarubatsuUtils 〇×あてゲームの各種判定、ユーティリティの提供を行う役割

これらのクラスに必要な処理を切り貼りして、各クラスに移植します。具体的には、作成したコードを切り貼りして、それぞれの役割のクラス内のメソッドに移植します。

その結果を以下に記載致します。
※リンクはGithubにアップしたコードです。

Lv2Main>: 〇×あてゲームを起動

package jp.zenryoku.tutorial.level2;

import java.util.Scanner;

/**
 * Javaの基本レベル2:小さなレベルのプログラムを拡張する。
 * 今回は、〇か×か当てるゲーム。
 */
public class Lv2Main {
    /** 終了フラグ */
    private static boolean isFinish;

    public static void main(String[] arg) {
        isFinish = false;
        // 標準入力を受け取るクラスをインスタンス化
        Scanner scan = MarubatsuUtils.getScanner();

        while (true) {
            // ゲーム開始文言
            MarubatsuConsole.printGameStart();
            // 標準入力を受け取る
            int input = scan.nextInt();
            // isFinishがtrueならば処理終了。
            if (isFinish) {
                MarubatsuConsole.printTerminated();
                break;
            }
            // 0か1の値を返却する
            int res = MarubatsuUtils.nextInt(2);

            // 0: 〇 1: ×で当たったかどうかの判定
            boolean isAtari = MarubatsuUtils.judgeAtariOrNot(res, input);
            MarubatsuConsole.printAtatiorNot(isAtari, input);

            // 続けるのかどうか判定する
            if (MarubatsuConsole.printNextPlayOrNot(scan)) {
                break;
            }
        }
    }
}

MarubatsuConsole>:標準入出力をコントロール

※修正した後のコードがアップしてあります。

package jp.zenryoku.tutorial.level2;

import java.util.Scanner;

public class MarubatsuConsole {
    /**
     * 〇×あてゲームの開始文言を表示
     */
    public static void printGameStart() {
        System.out.println("「〇×あてゲーム」 0: 〇 1: ×。");
    }

    /**
     * 〇×あてゲームの終了文言を表示
     */
    public static void printTerminated() {
        System.out.println("プログラムを終了します。");
    }

    /**
     * 当たったかどうかを表示する。
     * @param isAtari 当たり判定の結果
     */
    public static void printAtatiorNot(boolean isAtari, int input) {
        String value = input == 0 ? "〇" : "×";
        if (isAtari) {
            System.out.println("あたり:" + value);
        } else {
            System.out.println("はずれ:" + value);
        }
    }

    /**
     * 〇×あてゲームを続けるかを表示、Yes or Noを取得する
     * @param scan
     * @return true: 続ける  false: やめる
     */
    public static boolean printNextPlayOrNot(Scanner scan) {
        boolean playNext = false;
        System.out.println("続けますか? 0: 続ける  1: やめる");
        int next = scan.nextInt();
        if (next == 1) {
            playNext = true;
        }
        return playNext;
    }
}

MarubatsuUtils>: ユーティリティの提供を行う役割

つまるところは、「便利機能を提供するクラス」ということです。
※修正した後のコードがアップしてあります。

package jp.zenryoku.tutorial.level2;

import java.util.Random;
import java.util.Scanner;

public class MarubatsuUtils {
    /** 標準入力を受け取るクラス */
    private static Scanner scan;
    /** 乱数の生成クラス */
    private static Random rdm;

    /**
     * 標準入力を受け取るクラスを生成、取得する。
     * ※シングルトン実装
     * @return Scanner
     */
    public static Scanner getScanner() {
        if (scan == null) {
            scan = new Scanner(System.in);
        }
        return scan;
    }

    /**
     * 乱数生成クラスがインスタンス化されていなければ、インスタンス化します。
     * すでにインスタンス化しているときは、既存のインスタンスを使用します。
     * ※シングルトン実装
     *
     * @param bound
     * @return 生成した乱数
     * @see <a href="https://docs.oracle.com/javase/jp/8/docs/api/java/util/Random.html">Random</a>
     */
    public static int nextInt(int bound) {
        if (rdm == null) {
             rdm = new Random();
        }
        return rdm.nextInt(bound);
    }

    /**
     * 生成した乱数と、入力値が等しいか判定する。
     *
     * @param res 生成した乱数
     * @param input 入力値
     * @return true: 等しい false: 違う
     */
    public static boolean judgeAtariOrNot(int res, int input) {
        // 0: 〇 1: ×で当たったかどうかの判定
        return res == input;
    }

}
  • メインメソッドは、すっきりしたコードになったと思います。
  • メインメソッドでは、どんな処理をしているかが一目瞭然になり。各クラスはその処理だけが書いてある形になります。

もちろん、引数と返り血があるので、呼び出し元に多少なりとも影響が出ます。

しかし、これでメインメソッドを変更しなくても〇×あてゲームを拡張する準備ができました。

ポイント1

これでプログラムの修正するときには、それぞれのクラス、メソッドを修正してやればよくなりました。
メインメソッドは修正する必要がなくなったということです。

ポイント2

メインメソッドには、大まかな処理の順序を書く。具体的には下のような形です。

// <無限ループ開始>
// ■ゲーム開始文言を表示
// ■標準入力を受け取る
// ■isFinishがtrueならば処理終了。
// ■0か1の値を返却する
// ■0: 〇 1: ×で当たったかどうかの判定
// ■続けるのかどうか判定する

<やったこと>

  1. これらの処理をはじめの状態では、すべてLv2Mainクラスに記述していましたが、これをクラス別に分けました。
  2. 作成した各クラスをメインメソッドで呼び出して実行する。この時に初めの動きと変わらないことを確認しました。

これにより、プログラムのコードがすっきりして(したと自分は思います。。。)、メインメソッドを修正しなくても次の部分の処理が、修正が可能になりました。

1. ■ゲーム開始文言を表示: MarubatsuConsoleの修正
2. ■標準入力を受け取る: MarubatsuUtilsの修正
3. ■0か1の値を返却する: MarubatsuUtilsの修正
4. ■0: 〇 1: ×で当たったかどうかの判定: MarubatsuUtilsの修正
5. ■続けるのかどうか判定する: MarubatsuConsoleの修正

「そのように作ったんだから当たり前だろ?」と思った方、正常でございます。
このような作り方が、クラスを拡張する、アプリケーションを拡張するときに、役立つのです。

今回の「〇×あてゲーム」のような小さなレベルのプログラムはファイル一枚で何も問題はありませんが、会社の業務で使用するような大きなアプリケーションでは、いろんな機能が必要なので、役割分担を行い、機能拡張がしやすい形で、プログラムを組んでいきます。

拡張する準備をする

初めに「必要になること」について記載したのですが、触れていない部分があります。次の部分です。

  • 各クラス(部品)の単体テスト(UnitTest)ケースを用意しておき、修正したら即テスト、部品を取り換えるだけでよいように、作成した資源(プログラムのコード)を結合テスト・総合テストと実施できるような体制を整える

具体的にどのようなことか?これについて、記載したいと思います。

JavaならばJUnit

JavaのテスティングフレームワークといえばJUnitです。
具体的に、どのように使うか記載したいと思います。

役割分担をしたクラスのテストクラス作成

作成した各クラスのテストケースを作成するためのクラスを作成します。
クラス名は、「対象のクラス名 + Test」の形で作成します。

  • MarubatsuConsole: MarubatsuConsoleTest
  • MarubatsuUtils : MarubatsuUtilsTest

作成したコードは次の通りです。ただし、MarubatsuConsoleTestは、標準出力の内容を取得して。。。とちょっと面倒なので、今回は実装しません。

<JUnit実行結果>

package jp.zenryoku.tutorial.level2;

import org.junit.jupiter.api.Test;

import java.util.Scanner;

import static org.hamcrest.core.Is.is;
import static org.junit.jupiter.api.Assertions.*;
import static org.junit.matchers.JUnitMatchers.either;

public class MarubatsuUtilsTest {
    /** テスト対象クラス(全てstaticメソッドなのでインスタンス不要) */
    private static MarubatsuUtils target;

    @Test
    public void testGetScanner() {
        Scanner scan = MarubatsuUtils.getScanner();
        // インスタンスが取得できていることを確認
        assertNotNull(scan);
    }
    @Test
    public void testNextInt() {
        int res = MarubatsuUtils.nextInt(2);
        // インスタンスが取得できていることを確認
        boolean isRondom = res == 0 || res == 1;
        assertTrue(isRondom);
    }

    @Test
    public void testIsAtari() {
        assertTrue(MarubatsuUtils.judgeAtariOrNot(0, 0));
        assertTrue(MarubatsuUtils.judgeAtariOrNot(1, 1));

        assertFalse(MarubatsuUtils.judgeAtariOrNot(0, 1));
        assertFalse(MarubatsuUtils.judgeAtariOrNot(1, 0));
    }
}

でわでわ。。。

<<< 前回 次回 >>>

Java Basic 中級編 ~①アプリケーション設計を考える~

イントロダクション

Javaの基本文法が理解できたところで、実際に動くものを作ってみたいと思うのが人情だと思います。
基本文法だけでも、簡単なプログラムを作ることができます。
例えば、下のようなものです。

単純なゲームループ

1. アプリケーションを作るために

今までに、Javaの基本を学習してきました。大まかに次のようなことを学習しました。

  1. メインメソッドを動かすこと
  2. Stringやintなどのデータ型があり、それぞれに意味があること
  3. 条件分岐、繰り返しなどの文法(if文、switch, while, forなど)
  4. 自分で作ったクラスもデータ型として宣言することが出来て、呼び出す(動かす)事ができること
  5. JavaAPIで提供しているクラス(java.lang.Math, java.util.Scannerなど)を使用できること

これらのことを、このブログでは「Java Basic」と言うカテゴリで作成しています。

お勧めする学習フローも一応あります。Javaの学習をするのにこの順番で学習していくとわかりやすいであろうというものです。
※筆者が紆余曲折した結果、「この順序がおススメ!」というものです。

この基本に対して1段上の領域があります。それは「オブジェクト指向」と呼ばれている考え方なのですが、これの解釈が人によって千差万別なので、この言葉は使わないようにしようと考えております。

そして、開発環境を整えておきましょう。

Eclipseで開発

IntelliJで開発

2. アプリケーションを作り、運用することも考える

オブジェクト指向プログラミングは、世間では色々といいますが、
どちらかといえば、「エクストリーム・プログラミング」のほうが近いように思いますが、自分でよりより良い実装ができれば良いと思います

エクトリーム・プログラミングに関しては上記のリンク先を参照してください。
早い話が、次のようなものです。

ソフトウェア品質 を向上させ、変化する顧客の要求への対応力を高めることを目的としたソフトウェア開発プロセスである。アジャイルソフトウェア開発の一つとして、短い開発サイクルで頻繁に「リリース」することを推奨することで、生産性を向上させ、新しい顧客の要求を採用するためのチェックポイントを導入することを意図している。

これを実現するために、次のようなことが求められます。

  • 各プログラム間(Javaのクラス同士)の依存度を限りなく低くする。
  • プログラムを拡張するのに、元のプログラムをほとんど変更しない。
  • Javaを使えるレベルの知識(技術)がある人なら、誰が見てもわかるようなコードを書く。
  • 各クラス(部品)の単体テスト(UnitTest)ケースを用意しておき、修正したら即テスト、部品を取り換えるだけでよいように、作成した資源(プログラムのコード)を結合テスト・総合テストと実施できるような体制を整える

個人で実現するのは、結構な苦労ですが、一人でアプリのリリースまでやろうと考えるならやっておきたいところです。

3. 基本が大前提

Javaでなくてもそうですが、基本ができないと応用はできません。
基本というのは、上記でいうところの「Java Basic」は、たとえて言うならば、「Javaの基本」という本があったとしたら、その上巻に当たります

「じゃ、実際どの部分が基本なの?」という疑問が出ると思います。
これは、自分の見解ですが、下のように考えています。

つまりは、Javaだけでなくプログラミングの基本には次のような段階があると思うということです。

  • 「Javaの基本:上巻」は小さなレベルのプログラム(アプリケーション)が作れるレベルの基本。
  • 「Javaの基本:下巻」は小さなレベルのプログラムを拡張して、どんどん新しい機能を追加していけるレベルの基本

具体的には、下の通りです。

  • 変数の扱い方
  • クラスの作り方
  • 基本文法
  • javaライブラリの基本的な使い方

    今後は、「Javaの基本」という本の下巻に当たる部分を学習します。

今までの学習方法(古い時代の学習方法だと思います。。。)は、とりあえず何かしらのアプリケーションを組み続けてそこから、自分で上にも書きましたが、次のことを理解していきました。

  • 各プログラム間(Javaのクラス同士)の依存度を限りなく低くする。
  • プログラムを拡張するのに、元のプログラムをほとんど変更しない。
  • Javaを使えるレベルの知識(技術)がある人なら、誰が見てもわかるようなコードを書く。
  • 各クラス(部品)の単体テスト(UnitTest)ケースを用意しておき、修正したら即テスト、部品を取り換えるだけでよいように、作成した資源(プログラムのコード)を結合テスト・総合テストと実施できるような体制を整える

これらのようなことを理解するのには、自分の場合、大体3年くらいかかりました。
あくまでも、基本が理解できたというレベルです。

そこから、このような基本を応用し、実践していくことが必要になりますが、それこそは実際にやってみるしかありません。

基本ができたら、そこから先は自分の作りたいものをどんどん作っていくということが最大の課題になると思います。自分も時間を見つけてやっています。いまだに完成までいかないものがあります。。。

やはり、「モチベーションの維持と、健康の維持」が大きな課題となります。

4. 小さなレベルのプログラムを拡張する

先ほどから、「小さなレベルのプログラムを拡張する」と記載していますが、これは一体どういうことなのか?これについて、記載したいと思います。

基本はプログラムを修正しない

小さなレベルのプログラムを拡張するときに、なるべくプログラムを修正しないようにプログラムを組んでいくというところが、このレベルでの基本になります。
それには、クラスとクラスの関係をうまく作るということがカギになるのですが、具体的なコードを見ていくほうが早いので、サンプルコードを見ていきましょう。

〇×あてゲーム

単純なアプリケーション「〇×あてゲーム」を作成しました。下のようなコードで動きました。
※コードはGithubにあります

/**
 * Javaの基本レベル2:小さなレベルのプログラムを拡張する。
 * 今回は、〇か×か当てるゲーム。
 */
public class Lv2Main {
    /** 週r等フラグ */
    private static boolean isFinish;

    public static void main(String[] arg) {
        isFinish = false;
        // 標準入力を受け取るクラスをインスタンス化
        Scanner scan = new Scanner(System.in);

        while (true) {
            System.out.println("「〇×あてゲーム」 0: 〇 1: ×。");
            // 標準入力を受け取る
            int input = scan.nextInt();
            // isFinishがtrueならば処理終了。
            if (isFinish) {
                System.out.println("プログラムを終了します。");
                break;
            }
            // 0か1の値を返却する
            Random rdm = new Random();
            int res = rdm.nextInt(2);

            // 0: 〇 1: ×で当たったかどうかの判定
            boolean isAtari = res == input;
            String value = input == 0 ? "〇" : "×";
            if (isAtari) {
                System.out.println("あたり:" + value);
            } else {
                System.out.println("はずれ:" + value);
            }
            System.out.println("続けますか? 0: 続ける  1: やめる");
            int next = scan.nextInt();
            if (next == 1) {
                break;
            }
        }
    }
}

次回

このプログラムをなるべく修正しなくても機能拡張できるように修正していきます。
現状のプログラムは、修正しようとしたら必ずメインメソッドを修正する必要があります。これを解消するために試行錯誤します。

でわでわ。。。

次回 >>>

Java SpringBoot + Thymeleaf 画面を作成する ~ControllerからHTMLまで~

イントロダクション

前回はSpringBootでの開発セットアップを行いました。

前回の状態では、エラーページしか確認できませんでした。

これは、サーバーが起動していることを確認するという意味で、必要な確認なのですが。。。動かないのでは面白くありません。

SpringBoot + Thymeleafで画面を作る

毎度おなじみ、「ハローワールド」をやってみようと思います。

ちなみに、「Thymeleaf」は画面(HTML)とMVCのC(コントローラー)を接続するツールです。チュートリアルもあるのでわかりやすいです。

そして、やってみました。こんな感じです。※クラス名、Eclipseのバージョンなどが違いますが。。。

作成するもの

  1. Javaファイル(コントローラークラス)
  2. HTMLファイル(Thymeleaf使用)

準備

まずは、作成して動かすことに注力します。
プロジェクトを作成したら初めに「SampleWebAppApplication」というクラスが作成されていると思います。
※自動生成されているクラスがあるはずです。

このクラスがアプリケーションを起動するクラスです。しかし、このクラスはいじる(修正する)必要がありません

アプリを動かすために

自動生成された「SampleWebAppApplication」はアプリケーションを起動するためのクラスなので、本当にアプリを起動するだけなのです。

起動確認するときに、エラーページしか出ないのは表示するものが何もないからです。

そして、Springframeworkを使用してのウェブアプリケーションはMVCモデルを使用しています。つまりは、「Model(処理クラス)とView(画面)とController(リクエストハンドラー)に分けて処理を分担した形で実装しましょうね」というやり方を採用しています。

今度はMVCの「C]を作りましょうということです。

Controllerクラスを作る

それでは、早速コントローラークラスを作成します。まずは、ルールがあるのでそれに注意します。
先ほど自動生成されたクラス「SampleWebAppApplication」はパッケージ「com.example.demo」というパッケージに作成されています。

パッケージはウィンドウズでいうところの「フォルダ」に相当します。上に貼り付けているものは、パッケージを表示していいます。

一番上にある「src/main/java .... 」の部分はフォルダ構成上下のようになっています。

  1. プロジェクトのフォルダがあります。
  2. その中に、srcというフォルダがあります。
  3. 同様にmainというフォルダ
  4. javaというフォルダ
  5. そして、ここからが「パッケージ」と呼ばれるフォルダになります。「com」フォルダ
  6. 「example」フォルダ
  7. 「demo」フォルダ

このようにフォルダ構成が出来上がってるのですが、これを「パッケージ」と呼び下のように表現することで、どのファイルのクラスを参照するかわかるようにしています。
Javaのプログラムでいうと「インポート文」がそれにあたります。

import con.example.demo.*;

上のインポート文は「con.example.demo」パッケージにあるクラスを全てインポートするという意味です。

Controllerのみで動かす

パッケージに関して意味が分かったところで、上記で記載した「ルール」に関してお話ししたいと思います。
自動生成されたクラス「SampleWebAppApplication」よりも下のパッケージ内にコントローラーなどの読み込むクラスを作成するというルールがあります。

なので、今回作成するコントローラークラスはパッケージ「com.example.demo.controller」に作成することにします。
パッケージの作成方法は下のように行います。

  1. 作成するパッケージの親になるパッケージを右クリック※com.example.demoパッケージを右クリックしました。
  2. そして、「パッケージ」という文言が見つかればよいのですが、残念ながら表示されていませんので「その他」をクリックします。すると下のようなウィンドウが開きます。
  3. そこでパッケージを選択してやれば作成できるというところです。

さらに、ここからクラスも作成します。手順は上と同じですが、選択するものが「クラス」になります。

そして、ここでは「HelloController」というクラスを作成します。

package com.example.demo.controller;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.ModelAndView;

@RestController
public class HelloController {

    @RequestMapping("/")
    public String index(ModelAndView mav) {
        return "Hello World";
    }
}

<表示結果>

ここでは、HTMLの作成は必要がありません。

実は、上記の「@RequestMapping」アノテーションを付けたメソッドは返却文字列を指定した場合はHTMLになります。なので下のようにコードを変更してやると。。。

@RequestMapping("/")
public String index(ModelAndView mav) {
    return "<html>"
            + "<title>Hello World</title>"
            + "<body><h1>こんにちは</h1>"
            + "<p>~PGボックスより~</p>"
            + "</body>"
            + "</html>";
}

<表示結果>

このように表示できます。しかし、「もっとオシャレに!」「もっと機能的に!」ということを考えるとHTMLファイルを使用して動かした方が効率的です。

もちろん、ウェブデザイナーさんの協力を頂き、その実力を発揮してもらうならナチュラルなHTMLがベストです。JSPなどは専門外のはず。。。

View(HTML)を作成する

ウェブサーバーを起動して、ルートにあたるURL「localhost\:8080」にアクセスしたので次は、Controllerクラスを起動して、HTMLファイルを参照する形にプログラムを書き換えます。

実行動画

実装内容

そこで「Thymeleaf」というフレームワークを使用します。これはコントローラーとHTMLを橋渡しするためのフレームワークです。
具体的に見ていきましょう。まずはHTMLをみてみましょう。

<!DOCTYPE html>
<html>
<head lang="ja" xmlns="http://www.thymeleaf.org">
    <meta charset="UTF-8">
    <title>Sample Index Page</title>
</head>
<body>
    <h1>Recommended index</h1>
    <p>おススメの漫画・アニメを検索できます。</p>

</body>
</html>

上のようなHTMLを作成します。この状態ではコントローラーとのやり取りがない状態です。
しかし、まずはこの状態で動かしてみます。作成したHTMLファイルは「src/main/resources/templates」フォルダの下に作成しましょう。

そして、コントローラーも書き換えます。これは、もともと返却値にString(文字列)を設定していましたが、これをModelAndViewクラスに変更しています。もちろんこれらのメソッドは、オーバーライドしているのと同じ形で、「@RequestMapping」アノテーションに紐づいています。

つまり、想定されているメソッドの返り値ということです。そんなわけで、ModelAndViewにHTMLファイルの名前「book」をセットして返却してやれば次のように動いてくれるというわけです。

  1. ルート「/」にアクセスする(localhost:8080)
  2. templateフォルダ以下の「book」というHTMLファイルを取得する
  3. ブラウザに表示
@RequestMapping("/")
public ModelAndView index(ModelAndView mav) {
    mav.setViewName("book");
    return mav;
}

<実行結果>

次は、値を渡す

このように動いたところで、コントローラーから値を渡すことを考えます。

コントローラーからHTMLへ。。。

今回のJavaプログラムはサーバーサイド・プログラムなので(※クライアントでも動かすことができます。)サーバー側での処理を行うことができますが、クライアント(ブラウザ上)でのプログラムはJSとかHTMLに任せることになります。

なので、サーバーでの処理結果をクライアントに渡すにはちょっとテクニックが必要になります。
具体的には、以下の通りです。

  1. リクエストパラメータを使用する
  2. レスポンスに処理結果を埋め込む(渡す)

こんなことを言われても「???」となってしまう人は正常です。ご安心ください。

Controllerクラスを見る

具体的に下のようなコードで処理の結果を渡します。プログラムは微妙に修正しています。
<Java>

@RequestMapping("/")
public ModelAndView index(ModelAndView mav) {
    mav.setViewName("book");
    mav.addObject("key", 12);
    return mav;
}

View(HTML)をみる

<HTML>

<!DOCTYPE html>
<html>
<head lang="ja" xmlns="http://www.thymeleaf.org">
    <meta charset="UTF-8">
    <title>Sample Index Page</title>
</head>
<body>
    <h1>Recommended index</h1>
    <p>おススメの漫画・アニメを検索できます。</p>
    <p th:text="${key}">ここに値が入ります。</p>

</body>
</html>

<実行結果>

赤枠で囲った部分がJava側からHTMLへ渡した値です。

まとめ

SpringBootを使用して、画面を作成しとりあえず動かしてみました。ここまででMVCモデルの「C(Controller)」と「V(View)」がわかったと思います。
残りの「M(Model)」は?と疑問に思うかもしれません。次は、このModelに関して記述したいと思いますが、ちょっと時間がかかります。

中途半端ではありますが、とりあえずは、ここまでに致します。

でわでわ。。。

<<< 前回 次回 >>>

Java SpringBoot セットアップ ~Eclipse All in Oneでやると早い~

SpringBootの開発 セットアップ

SpringBootのセットアップを行うのに、いろいろな手順があり、混乱するので(自分の行った中で)もっとも簡単な方法を記載します。

使用するIDE(開発ツール)はEclipseのAll in Oneです。

これを使うと、GUIでサクッと環境構築ができるというわけです。以下手順を記述します。

実行したことの動画

SpringBootとは

SpringBootはフレームワークと呼ばれるカテゴリに属するものです。「フレームワーク」はアプリケーションを作成するときに1~10まですべてを作らなくても必要な部分を作成すれば、アプリケーションとして動かすことができる便利ツールです。SpringBootSpringframeworkを起動できる、機能を備え付けたパッケージのようです。

詳細はリンク先に記載されていますが、まとめると「Springframeworkで作ったアプリケーションを起動できる」ということです。

初心者向けの説明

具体的には、Springframeworkはウェブアプリケーションを作成するのに「MVCモデル」という形の設計方法を使用しています。現代のウェブシステムはほぼこの形にになっています。

MVCモデル

この形を作ったうえでMVCに当たる各部品を自分で作成すれば、あとは動かすことができる。というものです。
言葉よりもイメージ図がわかりやすいと思います。ちなみに「Container」は「Controller」の間違いです。

フレームワークの役目

上記のように、各MVCに当たる部分を作成すればよいのですが、部分的に見ても全体的なイメージが付きません。
なので、その部分を解説します。上記のイメージを見てもらうとMVCのそれぞれに矢印がありますが、この矢印部分にはプログラムの処理が入っています。
この矢印の部分をフレームワークが行います。

Viewから話を進めていきます。

  1. 画面(View)空の入力データをControllerに送る時HTMLではサブミットを行います。
  2. サブミット=リクエストの送信をしたら、指定した、クラス(Contoller)が、リクエストを受け取ります。
  3. 受け取ったリクエストをクラス(Controller)が担当するクラス(Model)に処理を渡します。
  4. 担当クラス(Model)が処理を行います。
  5. クラス(Controller)がリクエストを画面(View)に戻します。

注意点

ちなみに、プロジェクト作成時に必要なものをチェックしないとうまく動かなかったので、そこが注意点です。

大まかな手順

  1. プロジェクトを作成するときにSpringBootのスタータープロジェクトを作成する
  2. プロジェクト作成時に、ThymeleafとDB(H2DB)とSpringWebを追加する
  3. プロジェクト作成後にPOMファイルを「Maven install」で実行する
  4. Mavenプロジェクトの更新

プロジェクトの作成

Eclipseを起動してから、下のようにプロジェクトを右クリックし「新規」をクリック以下の手順に従います。

  1. Springスタータープロジェクトを選択し、「次へ」をクリック

  2. プロジェクトの名前などを入力する。使用するJDKによって「Javaバージョン」の値を変更する
     ※JDK1.8であれば「8」を選択する

  3. 使用するDBやフレームワーク(ツール)を選択する、今回はDBに「H2DB」、画面の作成に「Thymleaf」を使用するのでそれぞれのチェックボックスにチェックを入れる
    そして、Spring Webにチェックを入れる

  4. 最後に完了を押下する

※画像には「MySampleWebbApp」とあったが、「SampleWebApp」プロジェクトを作成した形で手順を進めます。

作成したプロジェクトは下のような形でEclipseのプロジェクトエクスプローラー、もしくは、パッケージエクスプローラーに表示されるはずです。
作成後のプロジェクト名が「MySampleWebbApp」になっていますが、「SampleWebbApp」での作成も確認しています。

プロジェクトを作成したばかりの状態では、バックグラウンド処理で何かしらのインストールが走っているので、ちょっと待ちます。

最後にプロジェクトを右クリック、実行、Spring Bootアプリケーションをクリック
これだけで、セットアップは完了です。

そして、ブラウザを開き、http://localhost:8080 にアクセスします。
ここでは、エラーページしか出ませんが、Tomcat(Webサーバー)が動いていることを確認するのが目的なのでこれでよいのです。

まとめ

これで、開発を行うために必用な環境がセットアップできました。つまり、次のものが動いていることを確認できました。

  1. ウェブサーバー(Tomcat)の起動確認
  2. ブラウザ(ChromeやEdgeなど)での表示確認

次は、これに自作のページを追加していきます。

次回 >>>

Eclipseでウェブ開発を始める~Java Servlet 二歩目 Servletを動かす~

イントロダクション

前回はEclipseを使用して、JavaServletの起動環境を作成しました。

観光構築だけで、起動確認はしていなかったので、これを確認するところから始めます。

Servletを動かす

前回作成したサーブレットは下のようなもの(コード)です。

/**
 * Servlet implementation class SrvLesson1
 */
@WebServlet("/SrvLesson1")
public class SrvLesson1 extends HttpServlet {
    private static final long serialVersionUID = 1L;

    /**
     * @see HttpServlet#HttpServlet()
     */
    public SrvLesson1() {
        super();
    }

    /**
     * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response)
     */
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        response.getWriter().append("Served at: ").append(request.getContextPath());
    }

    /**
     * @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response)
     */
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        doGet(request, response);
    }
}

これを実行すると、下のような表示ができます。

「?????」と表示されているのは、文字コード(charset)が指定されていないからです。
これを指定するには下の王なコードを書きます。

response.setContentType("text/html; charset=UTF-8;");

そして、実行結果は下のようになります。

Tomcatについて

TomcatはWebコンテナ、APサーバーなどと呼ばれる分類に入る「サーバー」です。
サーバーは複数個所から、アクセスを受けて、それぞれのリクエスト(入力やボタン押下)に対する処理を行い、レスポンスという形で処理の結果をクライアントに返します。

絵にすると下のような感じです ※画力が。。。

コード解説

JavaServeltの実装にはいくつかのポイントになる部分があります。

  1. クライアントとサーバーの関係がある。
    クライアント(ブラウザ(IEやChrome, FireFox, Safariなど))とサーバー(Javaで作成したサーブレットが動くTomcatのようなアプリケーション)。クライアントはサーバーにリクエストを送信、サーバーはクライアントへレスポンスを送信する。

  2. HTMLのFormタグにある「action」属性にサーブレットを指定する。
    action="SukiriWeb2/SrvLesson1"などのように指定すると対象のURLに指定したサーブレットが起動する。

  3. レスポンスは、HTMLでブラウザに表示される。

コード内容

リクエストの種類には「GET」と「POST」があり、サーブレットに実装(オーバーライド)している「doGet()」と「doPost()」がそれぞれに対応する。
つまり。GETリクエストの時は「doGet()」POSTリクエストの時は「doPost()」が動く。

それぞれの処理の最後に「response.getWriter()」とあるが、これは返却するレスポンスに、返却する内容を追加するためのオブジェクトを取得するということです。

具体的には、PrintWriterクラスですが、このクラスにHTMLを書きこむことで、ブラウザに表示するHTMLを返却することができます。

つまりは、そのような仕組みになっている(フレームワーク化されている)ということです。

コードをいじってみる

返却する値を作成しているのは、サンプルにあるコードは下のようなものです。

StringBuilder build = new StringBuilder();
build.append("<!doctype html>");
build.append("<html>");
build.append("    <head>");
build.append("        <title>aaa</title>");
build.append("<body>はじめのサーブレット</body></html>");

そして、HTMLを返却するだけでなく今まで学習してきた処理を行うこともできます。

try {
    PreparedStatement pre = H2dbManager.getInstance().createPreparedStatement(SQLConst.INSERT_USER_TABLE);
    pre.setString(1, userName);
    pre.setString(2, passwd);
    pre.setInt(3, 1);
    if (pre.execute()) {
        throw new SQLException("ユーザー登録に失敗しました。");
    }
} catch (SQLException e) {
    // TODO 自動生成された catch ブロック
    e.printStackTrace();
    return false;
}

「一度書けばどこでも動く」というのがJavaのウリです(笑)

上の処理は、H2DBへのアクセスを行うための「H2dbManager」クラスを作成したので、そこからSQLを発行するために以下のようなことを行っています。

  1. PreparedStatementクラスを取得
  2. 実行するSQLの設定(パラメータを設定できるようにしている)
  3. パラメータの設定
  4. SQLの実行

ユーザー登録の処理です。詳細に関しては、以下に記載します。

H2DBManagerクラス

/**
 * プライベートコンストラクタ、H2DBのコネクションを取得する。
 * {@link H2dbManager#getInstance()}
 */
private H2dbManager() {
    try {
        Class.forName("org.h2.Driver");
        // 「~」はマイドキュメントを示す。
        con = DriverManager.getConnection("jdbc:h2:tcp://localhost/~\\workspace\\SukiriWeb2\\WebContent\\WEB-INF\\database");
        Statement stmt = con.createStatement();
        stmt.execute(SQLConst.CREATE_USER_TBL);
        stmt.execute(SQLConst.CREATE_ITEM_TBL);
        stmt.execute(SQLConst.CREATE_ITEM_TYPE_TBL);
    } catch (SQLException e) {
        e.printStackTrace();
        System.exit(-1);
    } catch (Exception e) {
        e.printStackTrace();
        System.exit(-1);
    }
}

/**
 * このクラスのインスタンスを取得する。
 * @return H2dbManagerインスタンス
 */
public static H2dbManager getInstance() {
    if (instance == null) {
        instance = new H2dbManager();
    }
    return instance;
}