山崎屋の技術メモ

IT業界で働く中で、気になること、メモしておきたいことを書いていきます。

【Spring MVC】Model に登録されているオブジェクトの一覧を表示する

前回の記事で、サーバ側(Controller)で設定した値を JSP で表示するサンプルを掲載した。その際に Spring で用意されている、 Model オブジェクトに JSP で使用したいオブジェクトをセットした。

yyama1556.hateblo.jp

今回はこの Model に登録されているオブジェクトの一覧を表示する方法、および、識別名をキーにオブジェクトの存在有無を確認するサンプルをメモしておく。

オブジェクトの一覧を表示する

難しいことは何もないので、まずソース(抜粋)。

	@RequestMapping(value = "/", method = RequestMethod.GET)
	public String home(Model model) {
		// (1) String
		model.addAttribute("att1", "ほげ");

		// (2) List
		List<String> list = new ArrayList<>();
		list.add("リスト1");
		list.add("リスト2");
		list.add("リスト3");
		model.addAttribute("att2", list);

		// (3) 独自クラス
		ClassA classA = new ClassA();
		classA.setName("山崎屋");
		classA.setAge(19);
		model.addAttribute("att3", classA);

		// (4) model に登録されているオブジェクトをすべて表示
		model.asMap().entrySet().stream().forEach(s -> System.out.println(s));

		return "home";
	}

(1)~(3) でそれぞれ String/List/独自クラス のオブジェクトを生成し、model に addAttribute している。

そして (4) で model に登録されているオブジェクトの一覧を標準出力に出力している。Stream を使用しているので、見慣れていない人は何をしているのか分かりにくいかも知れないが、ポイントは「model.asMap()」の部分。ここで、model に登録されているオブジェクトをマップにして返している。

Map のキーが識別名(att1、att2、att3)の文字列で、Map の値が登録したオブジェクトである。標準出力にはこのオブジェクトの toString() の戻り値が表示される。

このメソッドを実行した際の標準出力はこちら。

att1=ほげ
att2=[リスト1, リスト2, リスト3]
att3=org.yyama.web.HomeController$ClassA@20a4e442

ちなみに、Stream を使用しない場合、 (4)は次のようになる。

		// (4) model に登録されているオブジェクトをすべて表示
		for (Map.Entry<String, Object> e : model.asMap().entrySet()) {
			System.out.println(e);
		}

model への登録有無を確認する。

model オブジェクトには containsAttribute というインスタンスメソッドが用意されており、第一引数に識別名を文字列で渡してあげることにより、その識別名が登録されているか否か、bool 値を返してくれる。

サンプルソースはこちら。

	@RequestMapping(value = "/", method = RequestMethod.GET)
	public String home(Model model) {
		// (1) String
		model.addAttribute("att1", "ほげ");

		// (2) List
		List<String> list = new ArrayList<>();
		list.add("リスト1");
		list.add("リスト2");
		list.add("リスト3");
		model.addAttribute("att2", list);

		// (3) 独自クラス
		ClassA classA = new ClassA();
		classA.setName("山崎屋");
		classA.setAge(19);
		model.addAttribute("att3", classA);

		// (5) model への登録有無を確認
		System.out.println(model.containsAttribute("att1")); // true
		System.out.println(model.containsAttribute("att2")); // true
		System.out.println(model.containsAttribute("att3")); // true
		System.out.println(model.containsAttribute("att4")); // false

		return "home";
	}

コメントで記載している値が標準出力に出力される。

↓↓↓ Spring Framework 関連の記事をまとめました。
yyama1556.hateblo.jp

今日はここまで。

わかりやすいJava EE ウェブシステム入門

わかりやすいJava EE ウェブシステム入門

【Spring MVC】サーバー側(Controller)で設定した値を JSP で使用する

Web アプリケーションでの基本的な動きだが、サーバー側で作成したオブジェクトを JSP で表示(利用)することは必ずある。今回は Spring MVC を使用したサンプルを紹介する。

実行結果イメージ

まず、サンプルの実行結果を掲載しておく。
f:id:yyama1556:20170430133140p:plain

Controller 側

コントローラのサンプル(抜粋)がこちら。

@Controller
public class HomeController {

	@RequestMapping(value = "/", method = RequestMethod.GET)
	public String home(Model model) {
		// String
		model.addAttribute("att1", "ほげ");

		// List
		List<String> list = new ArrayList<>();
		list.add("リスト1");
		list.add("リスト2");
		list.add("リスト3");
		model.addAttribute("att2", list);

		// 独自クラス
		ClassA classA = new ClassA();
		classA.setName("山崎屋");
		classA.setAge(19);
		model.addAttribute("classA", classA);

		return "home";
	}

コントローラクラスなので、クラス宣言に @Controller を付与している。home メソッドには @RequestMapping アノテーションを付与し、クライアントからのリクエストを処理することを宣言している。

@RequestMapping については以下の記事で紹介している。
【Spring MVC】@RequestMapping の基本 - 山崎屋の技術メモ

さて本題。
home メソッドでは Model オブジェクトを引数で受け取っている。これは Spring が用意したクラスで、JSP に渡したいオブジェクトを addAttribute することになる。

サンプルでは String、リスト、独自クラスの3つを addAttribute している。

独自クラスのフィールドには、対応する Getter が必要なので注意する(JSP が Getterを使用してフィールドの値を取得している)。独自クラスのソースは後で紹介する。

addAttribute の第一引数は識別子。JSP でオブジェクトを取り出すときに使う名前。値はなんでも良いが、分かりやすい名前を付ける。サンプルでは att1、att2、att3 としている。

第二引数は JSP に渡したいオブジェクトを指定する。

JSP

こちらもまずソースコード(抜粋)。

	<h3>String</h3>
	${att1}<br />
	<br />
	
	<h3>List 要素指定</h3>
	${att2[0]}<br />
	${att2[1]}<br />
	${att2[2]}<br />
	<br />
	
	<h3>List ForEachでまわす</h3>
	<c:forEach var="str" items="${att2}">
		<c:out value="${str}" /><br />
	</c:forEach>
	<br />
	
	<h3>独自クラス</h3>
	${att3.name}<br />
	${att3.age}<br />
	<br />

Model に addAttribute したときに第一引数に指定した識別子を ${} で囲むことで、オブジェクトの値を取得できる。リストの要素を指定して取得するときは ${att2[0]} のように [] の中に要素を数字で指定する。forEach でまわすときは JSTL の c:forEach を使用する。

独自クラスのフィールドにアクセスしたい場合、 ${} 内で「識別子.フィールド名」のように指定する。

ソース全量

最後にソースの全量を掲載しておく。

Controller。

package org.yyama.web;

import java.util.ArrayList;
import java.util.List;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

@Controller
public class HomeController {

	@RequestMapping(value = "/", method = RequestMethod.GET)
	public String home(Model model) {
		// String
		model.addAttribute("att1", "ほげ");

		// List
		List<String> list = new ArrayList<>();
		list.add("リスト1");
		list.add("リスト2");
		list.add("リスト3");
		model.addAttribute("att2", list);

		// 独自クラス
		ClassA classA = new ClassA();
		classA.setName("山崎屋");
		classA.setAge(19);
		model.addAttribute("att3", classA);

		return "home";
	}

	// 独自クラスの宣言
	public class ClassA {
		private String name;
		private int age;

		public void setName(String name) {
			this.name = name;
		}

		public String getName() {
			return name;
		}

		public void setAge(int age) {
			this.age = age;
		}

		public int getAge() {
			return age;
		}

	}
}

JSP

<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
<%@ page language="java" contentType="text/html; charset=utf-8"
	pageEncoding="utf-8"%>
<html>
<head>
<title>Home</title>
</head>
<body>
	<h3>String</h3>
	${att1}<br />
	<br />
	
	<h3>List 要素指定</h3>
	${att2[0]}<br />
	${att2[1]}<br />
	${att2[2]}<br />
	<br />
	
	<h3>List ForEachでまわす</h3>
	<c:forEach var="str" items="${att2}">
		<c:out value="${str}" /><br />
	</c:forEach>
	<br />
	
	<h3>独自クラス</h3>
	${att3.name}<br />
	${att3.age}<br />
	<br />
	
</body>
</html>

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<groupId>org.yyama</groupId>
	<artifactId>web</artifactId>
	<name>Sample</name>
	<packaging>war</packaging>
	<version>1.0.0-BUILD-SNAPSHOT</version>
	<properties>
		<org.springframework-version>4.3.8.RELEASE</org.springframework-version>
		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
		<maven.compiler.source>1.8</maven.compiler.source>
		<maven.compiler.target>1.8</maven.compiler.target>
	</properties>
	<dependencies>
		<!-- Spring -->
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-context</artifactId>
			<version>${org.springframework-version}</version>
		</dependency>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-webmvc</artifactId>
			<version>${org.springframework-version}</version>
		</dependency>
		<!-- Servlet -->
		<dependency>
			<groupId>javax.servlet</groupId>
			<artifactId>javax.servlet-api</artifactId>
			<version>3.1.0</version>
			<scope>provided</scope>
		</dependency>
		<dependency>
			<groupId>javax.servlet.jsp</groupId>
			<artifactId>javax.servlet.jsp-api</artifactId>
			<version>2.3.1</version>
			<scope>provided</scope>
		</dependency>
		<dependency>
			<groupId>javax.servlet</groupId>
			<artifactId>jstl</artifactId>
			<version>1.2</version>
		</dependency>

	</dependencies>
</project>

以上。

↓↓↓ Spring Framework 関連の記事をまとめました。
yyama1556.hateblo.jp

SpringによるWebアプリケーションスーパーサンプル 第2版

SpringによるWebアプリケーションスーパーサンプル 第2版

java の例外設計

プロジェクトの設計フェーズ序盤で、例外の扱いについて方針を決める必要がある。

もし、自分が方針を決めるとしたらこうするというものをメモしておく。

1.検査例外と非検査例外の使い分け

最近では検査例外を悪と考え、すべて非検査例外にラップしてスローしなおすという場合もあるようだ。

これを機能として提供しているフレームワークもある。

しかし、検査例外と非検査例外は理由があって分かれているので、私はちゃんと使い分けるべきと考えている。

検査例外

プログラムが正しくても起こりうる例外。

例えば、

  • 読み込もうとしたファイルが他のプログラムにより消された
  • デッドロックした
  • ユーザがブラウザの戻るボタンを使って、想定外の操作をした

などである。こういう場合には検査例外を使用する。

「オイラの処理には防ぎきれないエラーが発生する可能性があるので、注意しておいてね」というのがtry ~ catch の強制(または throws 区の強制)である。

なので特別な理由がなければ非検査例外にラップしてスローなどは行わない。

非検査例外

プログラム内で発生を防げるにもかかわらず発生した例外。

Null チェックを怠ったために発生した NullPointerException 。配列のインデックスをチェックしていなかったために発生した
ArrayIndexOutOfBoundsException 等。

これらの例外が発生したのであれば、プログラムのバグとして、プログラムを修正する。

2.例外のスローの仕方

できるだけ、細かくスローする。

FileNotFoundException と IOException の両方が発生する可能性がある場合、メソッド宣言の throws 区には IOException だけを書くこともできる。(IOException は FileNotFoundException の親クラス。)

だけど、特別な理由がない限りは両方書くべき。

例外を受け取ったクラスが、その例外をどのように扱うかは呼び出し先のクラスは関知できない。

だから、例外のハンドリングに必要と思われる情報は、なるべく細かく伝えてあげる必要がある。

3.例外のキャッチの仕方

細かくキャッチするか、親クラスでまとめてキャッチするか、極論を言えば「catch (Exception e)」としてしまうか、全体の方針として明確にしない。

責任を持ってハンドリングするという目的が達成できるのであれば、どのようにキャッチするかは各処理の設計にゆだねる。

4.独自例外

なるべく作らない。Java 標準の例外で目的に沿うものがあればそれを使う。

業務独自の例外を作らなければ対応できない場合のみ作る。

無条件に独自例外にラップしてスローしているシステムも見かけたことがあるが、まるで意味がなかった。そのプロジェクトで出力される例外のスタックトレースには、まず独自例外の出力があり、その後、Java 標準の例外出力がある。そして結局 Java 標準の例外メッセージを見て解決策を考えることになる。

まとめ

今のところこんな感じで整理している。追加や変更は随時行っていく。

もちろんプロジェクトの特性により、当記事の方針では実装できない(しにくい)場合も必ずあるので、絶対に守らないとダメっていうものでもない。

でわ。

Effective Java 第2版 (The Java Series)

Effective Java 第2版 (The Java Series)

リーダブルコード ―より良いコードを書くためのシンプルで実践的なテクニック (Theory in practice)

リーダブルコード ―より良いコードを書くためのシンプルで実践的なテクニック (Theory in practice)

Eclipse で Maven プロジェクトの作成から slf4J + logback の設定

どのようなプログラムでもログ出力は必ず必要になります。したがって、Eclipse でプロジェクトを作成したら、まずログ出力できるような設定を行ってしまいましょう。

今回は現時点でロギングフレームワークデファクトスタンダードである slf4j + logback でログ出力が行えるような設定を行います。

参考にさせていただきました。
SLF4J+Lobackの基本 | Java好き

Maven プロジェクトの作成

パッケージエクスプローラで右クリックして [New] → [Other] を選択すると、[New]ダイアログが表示されます。( Eclipse を日本語化している人は脳内翻訳して読んでください。)
f:id:yyama1556:20170428140336p:plain

Maven フォルダ内にある、[Maven Project]を選択して[Next]ボタン押下します。

すると[New Maven Project]ダイアログが表示されます。今回は学習のため、一番シンプルに作りたいので、[Create a simple project]にチェックを入れます。
f:id:yyama1556:20170428140737p:plain

[Next]ボタンをクリックするとアーティファクトの情報を入力する画面になるので、グループ ID とかアーティファクト ID とか、適当なものを入力してください。私の場合、以下のようにしました。
f:id:yyama1556:20170428141057p:plain

[Finish]ボタンを押下するとpom ファイル1個だけのシンプルなプロジェクトが作成されます。
f:id:yyama1556:20170428141647p:plain

Java バージョンの修正

Eclipse で Maven プロジェクトの作成からJDKバージョンの設定 - 山崎屋の技術メモ

以前、記事にもしましたが、Maven で作成したプロジェクトの Java バージョンは 1.5 になっているので、 1.8 に変更します。

修正前の pom.xml

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<groupId>org.yyama</groupId>
	<artifactId>sample</artifactId>
	<version>0.0.1-SNAPSHOT</version>
</project>

修正後の pom.xml

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<groupId>org.yyama</groupId>
	<artifactId>sample</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<properties>
		<maven.compiler.source>1.8</maven.compiler.source>
		<maven.compiler.target>1.8</maven.compiler.target>
	</properties>
</project>

pom ファイルを保存し、パッケージエクスプローラ内でプロジェクトを右クリック、[Maven]→[Update Project...]を選択。
f:id:yyama1556:20170428142436p:plain

[Update Maven Project]ダイアログが表示されるので、作成中のプロジェクトにチェックが入っていることを確認して、[OK]ボタンを押下してください。

次のように、JRE System Library の箇所に [JaveSE-1.8]と表示されていれば OK です。
f:id:yyama1556:20170428142837p:plain

slf4J + logback の設定

pom.xml を再度編集し、グループ ID [ch.qos.logback]、アーティファクト ID [logback-classic] を依存先( dependency )に追加します。

バージョンは現時点(2017年4月28日)で最新の 1.2.3 にします。

修正後の pom.xml

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<groupId>org.yyama</groupId>
	<artifactId>sample</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<properties>
		<maven.compiler.source>1.8</maven.compiler.source>
		<maven.compiler.target>1.8</maven.compiler.target>
	</properties>
	<!-- ↓↓↓↓↓ ここを追加 ↓↓↓↓↓ -->
	<dependencies>
		<dependency>
			<groupId>ch.qos.logback</groupId>
			<artifactId>logback-classic</artifactId>
			<version>1.2.3</version>
		</dependency>
	</dependencies>
	<!-- ↑↑↑↑↑ ここを追加 ↑↑↑↑↑ -->
</project>

Maven Dependencies に logback-classic-1.2.3.jar、logback-core-1.2.3.jar、slf4j-api-1.7.25.jar が追加されていることが分かります。
f:id:yyama1556:20170428144217p:plain

これで準備は整いました。

ログ出力してみる

さっそくログ出力してみましょう。

Main クラスを作って、次のように記述します。

package org.yyama;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class Main {
	public static final Logger log = LoggerFactory.getLogger(Main.class);

	public static void main(String... args) {
		log.trace("trace!");
		log.debug("debug!");
		log.info("info!");
		log.warn("warn!");
		log.error("error!");
	}
}

実行結果。

14:46:26.223 [main] DEBUG org.yyama.Main - debug!
14:46:26.228 [main] INFO org.yyama.Main - info!
14:46:26.228 [main] WARN org.yyama.Main - warn!
14:46:26.228 [main] ERROR org.yyama.Main - error!

まだ logback.xml 等の設定ファイルがないので、デフォルトのログレベルと出力先(アペンダーという)が設定されています。

ログレベルは debug (だから実行結果に trace レベルのログが出力されていない)で、出力先はコンソール(標準出力)です。

lombock で楽をする

ロギングフレームワークを使用して開発を進めていくと、「public static final Logger log =...」の記述がすべてのクラスで登場することになります。lombock を使用すると、この決まり文句をアノテーションで指定できます。記述ミスもなくなるのでぜひ使いたいです。

以下の記述は Eclipse に lombock がインストールされていることを前提としています。やり方はここで紹介されています。

まず pom.xml を変更します。

変更後の pom.xml

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<groupId>org.yyama</groupId>
	<artifactId>sample</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<properties>
		<maven.compiler.source>1.8</maven.compiler.source>
		<maven.compiler.target>1.8</maven.compiler.target>
	</properties>
	<dependencies>
		<dependency>
			<groupId>ch.qos.logback</groupId>
			<artifactId>logback-classic</artifactId>
			<version>1.2.3</version>
		</dependency>
		<!-- ↓↓↓↓↓ ここを追加 ↓↓↓↓↓ -->
		<dependency>
			<groupId>org.projectlombok</groupId>
			<artifactId>lombok</artifactId>
			<version>1.16.16</version>
			<scope>provided</scope>
		</dependency>
		<!-- ↑↑↑↑↑ ここを追加 ↑↑↑↑↑ -->
	</dependencies>
</project>

Maven Dependencies に lombock-1.16.16.jar が追加されました。
f:id:yyama1556:20170428150320p:plain

すると Main クラスを次のように簡潔に書けます。

package org.yyama;

import lombok.extern.slf4j.Slf4j;

@Slf4j
public class Main {
	public static void main(String... args) {
		log.trace("trace!");
		log.debug("debug!");
		log.info("info!");
		log.warn("warn!");
		log.error("error!");
	}
}


このように、プロジェクト生成時にはついでにログ出力の設定も行ってしまいましょう。

以上です。

スッキリわかるJava入門 第2版 スッキリわかるシリーズ

スッキリわかるJava入門 第2版 スッキリわかるシリーズ

Maven3のはじめかた

Maven3のはじめかた

java オーバーロードの条件

オーバーロードの話です。オーバーライドではありません。

違いを簡単に

オーバーライドは、親クラスのメソッドを子クラスで定義しなおすことです。通常、メソッドに @Override アノテーションを付与します。

package org.yyama;

// 親クラス
public class Oya {

	public void method() {
		System.out.println("hoge");
	}

}

このような親クラスがあったとして、

package org.yyama;

// 子クラス
public class Ko extends Oya {

	@Override
	public void method() {
		System.out.println("fuga");
	}

}

このように子クラスで method メソッドを再定義することがオーバーライド。

一方、オーバーロードは、同一クラス内で、同じメソッド名を複数定義することを指します。

今回はこの「オーバーロード」できる条件についての記事です。

オーバーロードの条件

同一クラス内ですでに使われているメソッド名を再び定義することをオーバーロードするといいます。ただし、引数の数・型・順番の少なくともひとつが異なる必要があります。

具体的に見てみましょう。

まずは、オーバーロードできるケースです。

	public int methodA(int i, String s) {
		return 1;
	}

	public int methodA(int i) {
		return 1;
	}

引数の数が異なっているのでOK。

	public int methodA(int i, String s) {
		return 1;
	}

	public int methodA(long i, String s) {
		return 1;
	}

一個目の引数の型が異なるのでOK。

	public int methodA(int i, String s) {
		return 1;
	}

	public int methodA(String s, int i) {
		return 1;
	}

引数の順番が異なるのでOK。



それでは、オーバーロードできない場合の具体例を見てみましょう。

	public int methodB(int i) {
		return i;
	}

	public int methodB(int i) {
		return i;
	}

まったく同じ。論外。

	public int methodB(int i) {
		return i;
	}

	public String methodB(int i) {
		return "a";
	}

戻り値の型が異なるだけ。NG。
なんか許していい気もするが、言語仕様的にだめです。

	public int methodB(int i) {
		return i;
	}

	public int methodB(int i) throws Exception {
		return 1;
	}

片方のメソッドで throws を宣言しているが引数が同じ。NG。

	public int methodB(int i) {
		return i;
	}

	public static int methodB(int i) {
		return 1;
	}

片方のメソッドは static メソッドで引数は同じ。
許していい気もするが、NG。

まとめ

一見どのメソッドを呼べばいいのか分かりにくくなりそうですが、オーバーロードをうまく使いこなすと、非常に使いやすい API ができます。GoFデザインパターンでも使用されています。

でわ。

スッキリわかるJava入門 第2版 スッキリわかるシリーズ

スッキリわかるJava入門 第2版 スッキリわかるシリーズ

リーダブルコード ―より良いコードを書くためのシンプルで実践的なテクニック (Theory in practice)

リーダブルコード ―より良いコードを書くためのシンプルで実践的なテクニック (Theory in practice)

↓もう第6版だw。