JSP および JSTL

Spring Framework には、JSP および JSTL で Spring MVC を使用するための組み込みの統合があります。

リゾルバーを表示

JSP を使用して開発する場合、通常は InternalResourceViewResolver Bean を宣言します。

InternalResourceViewResolver は、任意のサーブレットリソースへのディスパッチに使用できますが、特に JSP に使用できます。ベストプラクティスとして、JSP ファイルを 'WEB-INF' ディレクトリのディレクトリに配置して、クライアントから直接アクセスできないようにすることを強くお勧めします。

<bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
	<property name="viewClass" value="org.springframework.web.servlet.view.JstlView"/>
	<property name="prefix" value="/WEB-INF/jsp/"/>
	<property name="suffix" value=".jsp"/>
</bean>

JSP と JSTL

JSP 標準タグライブラリ(JSTL)を使用する場合、JSTL は I18N 機能などが機能する前に準備が必要なので、特別なビュークラス JstlView を使用する必要があります。

Spring の JSP タグライブラリ

Spring は、前の章で説明したように、コマンドオブジェクトへのリクエストパラメーターのデータバインディングを提供します。これらのデータバインディング機能と組み合わせて JSP ページの開発を容易にするために、Spring は物事をさらに簡単にするいくつかのタグを提供します。すべての Spring タグには、文字のエスケープを有効または無効にする HTML エスケープ機能があります。

spring.tld タグライブラリ記述子(TLD)は spring-webmvc.jar に含まれています。個々のタグに関する包括的なリファレンスについては、API 参照を参照するか、タグライブラリの説明を参照してください。

Spring のフォームタグライブラリ

バージョン 2.0 以降、Spring は、JSP および Spring Web MVC を使用するときにフォーム要素を処理するためのデータバインディング対応タグの包括的なセットを提供します。各タグは、対応する HTML タグの対応する属性のセットをサポートするため、使い慣れた直感的なタグになります。タグ生成された HTML は、HTML 4.01/XHTML 1.0 に準拠しています。

他のフォーム / 入力タグライブラリとは異なり、Spring のフォームタグライブラリは Spring Web MVC と統合され、コントローラーが処理するコマンドオブジェクトと参照データにタグがアクセスできるようにします。次の例で示すように、フォームタグを使用すると、JSP の開発、読み取り、保守が容易になります。

フォームタグを調べて、各タグの使用方法の例を見てみましょう。特定のタグに追加のコメントが必要な生成された HTML スニペットが含まれています。

構成

フォームタグライブラリは spring-webmvc.jar にバンドルされています。ライブラリ記述子は spring-form.tld と呼ばれます。

このライブラリのタグを使用するには、JSP ページの上部に次のディレクティブを追加します。

<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>

ここで、form は、このライブラリのタグに使用するタグ名のプレフィックスです。

フォームタグ

このタグは、HTML 'form' 要素をレンダリングし、バインディング用の内部タグへのバインディングパスを公開します。コマンドオブジェクトを PageContext に配置して、コマンドオブジェクトに内部タグからアクセスできるようにします。このライブラリの他のすべてのタグは、form タグのネストされたタグです。

User というドメインオブジェクトがあると仮定します。firstName や lastName などのプロパティを持つ JavaBean です。form.jsp を返すフォームコントローラーのフォームバッキングオブジェクトとして使用できます。次の例は、form.jsp がどのように見えるかを示しています。

<form:form>
	<table>
		<tr>
			<td>First Name:</td>
			<td><form:input path="firstName"/></td>
		</tr>
		<tr>
			<td>Last Name:</td>
			<td><form:input path="lastName"/></td>
		</tr>
		<tr>
			<td colspan="2">
				<input type="submit" value="Save Changes"/>
			</td>
		</tr>
	</table>
</form:form>

firstName および lastName の値は、ページコントローラーによって PageContext に配置されたコマンドオブジェクトから取得されます。form タグで内部タグがどのように使用されるかについて、より複雑な例を参照してください。

次のリストは、標準フォームのように見える生成された HTML を示しています。

<form method="POST">
	<table>
		<tr>
			<td>First Name:</td>
			<td><input name="firstName" type="text" value="Harry"/></td>
		</tr>
		<tr>
			<td>Last Name:</td>
			<td><input name="lastName" type="text" value="Potter"/></td>
		</tr>
		<tr>
			<td colspan="2">
				<input type="submit" value="Save Changes"/>
			</td>
		</tr>
	</table>
</form>

上記の JSP では、フォームバッキングオブジェクトの変数名が command であると想定しています。フォームバッキングオブジェクトを別の名前でモデルに配置した場合(間違いなくベストプラクティス)、次の例に示すように、フォームを名前付き変数にバインドできます。

<form:form modelAttribute="user">
	<table>
		<tr>
			<td>First Name:</td>
			<td><form:input path="firstName"/></td>
		</tr>
		<tr>
			<td>Last Name:</td>
			<td><form:input path="lastName"/></td>
		</tr>
		<tr>
			<td colspan="2">
				<input type="submit" value="Save Changes"/>
			</td>
		</tr>
	</table>
</form:form>

input タグ

このタグは、デフォルトでバインドされた値と type='text' を使用して HTML input 要素をレンダリングします。このタグの例については、フォームタグを参照してください。emailteldate などの HTML5 固有の型も使用できます。

checkbox タグ

このタグは、type が checkbox に設定された HTML input タグをレンダリングします。

User には、ニュースレターの購読や趣味のリストなどの設定があると仮定します。次の例は、Preferences クラスを示しています。

  • Java

  • Kotlin

public class Preferences {

	private boolean receiveNewsletter;
	private String[] interests;
	private String favouriteWord;

	public boolean isReceiveNewsletter() {
		return receiveNewsletter;
	}

	public void setReceiveNewsletter(boolean receiveNewsletter) {
		this.receiveNewsletter = receiveNewsletter;
	}

	public String[] getInterests() {
		return interests;
	}

	public void setInterests(String[] interests) {
		this.interests = interests;
	}

	public String getFavouriteWord() {
		return favouriteWord;
	}

	public void setFavouriteWord(String favouriteWord) {
		this.favouriteWord = favouriteWord;
	}
}
class Preferences(
		var receiveNewsletter: Boolean,
		var interests: StringArray,
		var favouriteWord: String
)

対応する form.jsp は、次のようになります。

<form:form>
	<table>
		<tr>
			<td>Subscribe to newsletter?:</td>
			<%-- Approach 1: Property is of type java.lang.Boolean --%>
			<td><form:checkbox path="preferences.receiveNewsletter"/></td>
		</tr>

		<tr>
			<td>Interests:</td>
			<%-- Approach 2: Property is of an array or of type java.util.Collection --%>
			<td>
				Quidditch: <form:checkbox path="preferences.interests" value="Quidditch"/>
				Herbology: <form:checkbox path="preferences.interests" value="Herbology"/>
				Defence Against the Dark Arts: <form:checkbox path="preferences.interests" value="Defence Against the Dark Arts"/>
			</td>
		</tr>

		<tr>
			<td>Favourite Word:</td>
			<%-- Approach 3: Property is of type java.lang.Object --%>
			<td>
				Magic: <form:checkbox path="preferences.favouriteWord" value="Magic"/>
			</td>
		</tr>
	</table>
</form:form>

checkbox タグには 3 つのアプローチがあり、すべてのチェックボックスのニーズを満たす必要があります。

  • アプローチ 1: バウンド値の型が java.lang.Boolean の場合、バウンド値が true の場合、input(checkbox) は checked としてマークされます。value 属性は、setValue(Object) 値プロパティの解決された値に対応します。

  • アプローチ 2: バインドされた値が型 array または java.util.Collection である場合、構成された setValue(Object) 値がバインドされた Collection に存在する場合、input(checkbox) は checked としてマークされます。

  • アプローチ 3: 他のバインド値型の場合、構成された setValue(Object) がバインド値と等しい場合、input(checkbox) は checked としてマークされます。

アプローチに関係なく、同じ HTML 構造が生成されることに注意してください。次の HTML スニペットは、いくつかのチェックボックスを定義しています。

<tr>
	<td>Interests:</td>
	<td>
		Quidditch: <input name="preferences.interests" type="checkbox" value="Quidditch"/>
		<input type="hidden" value="1" name="_preferences.interests"/>
		Herbology: <input name="preferences.interests" type="checkbox" value="Herbology"/>
		<input type="hidden" value="1" name="_preferences.interests"/>
		Defence Against the Dark Arts: <input name="preferences.interests" type="checkbox" value="Defence Against the Dark Arts"/>
		<input type="hidden" value="1" name="_preferences.interests"/>
	</td>
</tr>

各チェックボックスの後に追加の非表示フィールドが表示されるとは思わないかもしれません。HTML ページのチェックボックスがチェックされていない場合、フォームが送信されると、その値は HTTP リクエストパラメーターの一部としてサーバーに送信されないため、Spring フォームデータバインディングが機能するためには、HTML でこの癖を回避する必要があります。checkbox タグは、各チェックボックスにアンダースコア(_)が前に付いた隠しパラメーターを含めるという既存の Spring 規則に従います。これを行うことにより、Spring に「チェックボックスがフォームに表示されていたため、フォームデータがバインドされるオブジェクトにチェックボックスの状態を反映させたい」と効果的に伝えます。

checkboxes タグ

このタグは、type が checkbox に設定された複数の HTML input タグをレンダリングします。

このセクションは、前の checkbox タグセクションの例を基にしています。場合によっては、JSP ページにすべての可能な趣味をリストする必要はありません。実行時に利用可能なオプションのリストを提供し、それをタグに渡します。それが checkboxes タグの目的です。items プロパティで利用可能なオプションを含む ArrayListMap を渡すことができます。通常、バウンドプロパティはコレクションであるため、ユーザーが選択した複数の値を保持できます。次の例は、このタグを使用する JSP を示しています。

<form:form>
	<table>
		<tr>
			<td>Interests:</td>
			<td>
				<%-- Property is of an array or of type java.util.Collection --%>
				<form:checkboxes path="preferences.interests" items="${interestList}"/>
			</td>
		</tr>
	</table>
</form:form>

この例では、interestList が、選択される値の文字列を含むモデル属性として利用可能な List であると想定しています。Map を使用する場合、マップエントリキーが値として使用され、マップエントリの値が表示されるラベルとして使用されます。itemValue を使用して値のプロパティ名を指定し、itemLabel を使用してラベルを指定できるカスタムオブジェクトを使用することもできます。

radiobutton タグ

このタグは、type が radio に設定された HTML input 要素をレンダリングします。

典型的な使用パターンには、次の例に示すように、同じプロパティにバインドされているが値が異なる複数のタグインスタンスが含まれます。

<tr>
	<td>Sex:</td>
	<td>
		Male: <form:radiobutton path="sex" value="M"/> <br/>
		Female: <form:radiobutton path="sex" value="F"/>
	</td>
</tr>

radiobuttons タグ

このタグは、type が radio に設定された複数の HTML input 要素をレンダリングします。

checkboxes タグと同様に、使用可能なオプションをランタイム変数として渡したい場合があります。この使用箇所では、radiobuttons タグを使用できます。items プロパティで利用可能なオプションを含む ArrayListMap を渡します。Map を使用する場合、マップエントリキーが値として使用され、マップエントリの値が表示されるラベルとして使用されます。次の例に示すように、itemValue を使用して値のプロパティ名を、itemLabel を使用してラベルのプロパティ名を指定できるカスタムオブジェクトを使用することもできます。

<tr>
	<td>Sex:</td>
	<td><form:radiobuttons path="sex" items="${sexOptions}"/></td>
</tr>

password タグ

このタグは、バインドされた値で password に設定された型で HTML input タグをレンダリングします。

<tr>
	<td>Password:</td>
	<td>
		<form:password path="password"/>
	</td>
</tr>

デフォルトでは、パスワード値は表示されないことに注意してください。パスワード値を表示したい場合は、次の例に示すように、showPassword 属性の値を true に設定できます。

<tr>
	<td>Password:</td>
	<td>
		<form:password path="password" value="^76525bvHGq" showPassword="true"/>
	</td>
</tr>

select タグ

このタグは、HTML 'select' 要素をレンダリングします。ネストされた option および options タグの使用と同様に、選択されたオプションへのデータバインディングをサポートします。

User にはスキルのリストがあると仮定します。対応する HTML は次のとおりです。

<tr>
	<td>Skills:</td>
	<td><form:select path="skills" items="${skills}"/></td>
</tr>

User’s スキルがハーブ学の場合、「スキル」行の HTML ソースは次のようになります。

<tr>
	<td>Skills:</td>
	<td>
		<select name="skills" multiple="true">
			<option value="Potions">Potions</option>
			<option value="Herbology" selected="selected">Herbology</option>
			<option value="Quidditch">Quidditch</option>
		</select>
	</td>
</tr>

option タグ

このタグは、HTML option 要素をレンダリングします。バインドされた値に基づいて selected を設定します。次の HTML は、その典型的な出力を示しています。

<tr>
	<td>House:</td>
	<td>
		<form:select path="house">
			<form:option value="Gryffindor"/>
			<form:option value="Hufflepuff"/>
			<form:option value="Ravenclaw"/>
			<form:option value="Slytherin"/>
		</form:select>
	</td>
</tr>

User’s 家がグリフィンドールにあった場合、「家」行の HTML ソースは次のようになります。

<tr>
	<td>House:</td>
	<td>
		<select name="house">
			<option value="Gryffindor" selected="selected">Gryffindor</option> (1)
			<option value="Hufflepuff">Hufflepuff</option>
			<option value="Ravenclaw">Ravenclaw</option>
			<option value="Slytherin">Slytherin</option>
		</select>
	</td>
</tr>
1selected 属性が追加されていることに注意してください。

options タグ

このタグは、HTML option 要素のリストをレンダリングします。バインドされた値に基づいて、selected 属性を設定します。次の HTML は、その典型的な出力を示しています。

<tr>
	<td>Country:</td>
	<td>
		<form:select path="country">
			<form:option value="-" label="--Please Select"/>
			<form:options items="${countryList}" itemValue="code" itemLabel="name"/>
		</form:select>
	</td>
</tr>

User が英国に住んでいた場合、"Country" 行の HTML ソースは次のようになります。

<tr>
	<td>Country:</td>
	<td>
		<select name="country">
			<option value="-">--Please Select</option>
			<option value="AT">Austria</option>
			<option value="UK" selected="selected">United Kingdom</option> (1)
			<option value="US">United States</option>
		</select>
	</td>
</tr>
1selected 属性が追加されていることに注意してください。

前の例が示すように、option タグと options タグを組み合わせて使用すると、同じ標準 HTML が生成されますが、表示専用の JSP で値を明示的に指定できます。例: 「- 選択してください」。

items 属性には、通常、アイテムオブジェクトのコレクションまたは配列が設定されます。itemValue および itemLabel は、指定されている場合、それらのアイテムオブジェクトの Bean プロパティを参照します。それ以外の場合、アイテムオブジェクト自体は文字列に変換されます。または、アイテムの Map を指定できます。この場合、マップキーはオプション値として解釈され、マップ値はオプションラベルに対応します。itemValue または itemLabel (または両方)が偶然指定された場合、アイテム値プロパティはマップキーに適用され、アイテムラベルプロパティはマップ値に適用されます。

textarea タグ

このタグは、HTML textarea 要素をレンダリングします。次の HTML は、その典型的な出力を示しています。

<tr>
	<td>Notes:</td>
	<td><form:textarea path="notes" rows="3" cols="20"/></td>
	<td><form:errors path="notes"/></td>
</tr>

hidden タグ

このタグは、バインドされた値で type が hidden に設定された HTML input タグをレンダリングします。非バインドの非表示値を送信するには、type を hidden に設定して HTML input タグを使用します。次の HTML は、その典型的な出力を示しています。

<form:hidden path="house"/>

house 値を隠し値として送信することを選択した場合、HTML は次のようになります。

<input name="house" type="hidden" value="Gryffindor"/>

errors タグ

このタグは、HTML span 要素のフィールドエラーをレンダリングします。コントローラーで作成されたエラー、またはコントローラーに関連付けられたバリデーターによって作成されたエラーへのアクセスを提供します。

フォームを送信した後、firstName および lastName フィールドのすべてのエラーメッセージを表示するとします。次の例が示すように、UserValidator と呼ばれる User クラスのインスタンスのバリデーターがあります。

  • Java

  • Kotlin

public class UserValidator implements Validator {

	public boolean supports(Class candidate) {
		return User.class.isAssignableFrom(candidate);
	}

	public void validate(Object obj, Errors errors) {
		ValidationUtils.rejectIfEmptyOrWhitespace(errors, "firstName", "required", "Field is required.");
		ValidationUtils.rejectIfEmptyOrWhitespace(errors, "lastName", "required", "Field is required.");
	}
}
class UserValidator : Validator {

	override fun supports(candidate: Class<*>): Boolean {
		return User::class.java.isAssignableFrom(candidate)
	}

	override fun validate(obj: Any, errors: Errors) {
		ValidationUtils.rejectIfEmptyOrWhitespace(errors, "firstName", "required", "Field is required.")
		ValidationUtils.rejectIfEmptyOrWhitespace(errors, "lastName", "required", "Field is required.")
	}
}

form.jsp は次のようになります。

<form:form>
	<table>
		<tr>
			<td>First Name:</td>
			<td><form:input path="firstName"/></td>
			<%-- Show errors for firstName field --%>
			<td><form:errors path="firstName"/></td>
		</tr>

		<tr>
			<td>Last Name:</td>
			<td><form:input path="lastName"/></td>
			<%-- Show errors for lastName field --%>
			<td><form:errors path="lastName"/></td>
		</tr>
		<tr>
			<td colspan="3">
				<input type="submit" value="Save Changes"/>
			</td>
		</tr>
	</table>
</form:form>

firstName および lastName フィールドの値が空のフォームを送信すると、HTML は次のようになります。

<form method="POST">
	<table>
		<tr>
			<td>First Name:</td>
			<td><input name="firstName" type="text" value=""/></td>
			<%-- Associated errors to firstName field displayed --%>
			<td><span name="firstName.errors">Field is required.</span></td>
		</tr>

		<tr>
			<td>Last Name:</td>
			<td><input name="lastName" type="text" value=""/></td>
			<%-- Associated errors to lastName field displayed --%>
			<td><span name="lastName.errors">Field is required.</span></td>
		</tr>
		<tr>
			<td colspan="3">
				<input type="submit" value="Save Changes"/>
			</td>
		</tr>
	</table>
</form>

特定のページのエラーのリスト全体を表示したい場合はどうすればよいでしょうか。次の例は、errors タグがいくつかの基本的なワイルドカード機能もサポートしていることを示しています。

  • path="*": すべてのエラーを表示します。

  • path="lastName"lastName フィールドに関連するすべてのエラーを表示します。

  • path を省略すると、オブジェクトエラーのみが表示されます。

次の例では、ページの上部にエラーのリストが表示され、フィールドの横にフィールド固有のエラーが続きます。

<form:form>
	<form:errors path="*" cssClass="errorBox"/>
	<table>
		<tr>
			<td>First Name:</td>
			<td><form:input path="firstName"/></td>
			<td><form:errors path="firstName"/></td>
		</tr>
		<tr>
			<td>Last Name:</td>
			<td><form:input path="lastName"/></td>
			<td><form:errors path="lastName"/></td>
		</tr>
		<tr>
			<td colspan="3">
				<input type="submit" value="Save Changes"/>
			</td>
		</tr>
	</table>
</form:form>

HTML は次のようになります。

<form method="POST">
	<span name="*.errors" class="errorBox">Field is required.<br/>Field is required.</span>
	<table>
		<tr>
			<td>First Name:</td>
			<td><input name="firstName" type="text" value=""/></td>
			<td><span name="firstName.errors">Field is required.</span></td>
		</tr>

		<tr>
			<td>Last Name:</td>
			<td><input name="lastName" type="text" value=""/></td>
			<td><span name="lastName.errors">Field is required.</span></td>
		</tr>
		<tr>
			<td colspan="3">
				<input type="submit" value="Save Changes"/>
			</td>
		</tr>
	</table>
</form>

spring-form.tld タグライブラリ記述子(TLD)は spring-webmvc.jar に含まれています。個々のタグに関する包括的なリファレンスについては、API 参照を参照するか、タグライブラリの説明を参照してください。

HTTP メソッド変換

REST の重要な原則は、「統一インターフェース」の使用です。つまり、すべてのリソース (URL) は、同じ 4 つの HTTP メソッド (GET、PUT、POST、DELETE) を使用して操作できます。各メソッドについて、HTTP 仕様は正確なセマンティクスを定義します。たとえば、GET は常に安全な操作、つまり副作用がない必要があり、PUT または DELETE はべき等である必要があります。つまり、これらの操作を何度も繰り返すことができますが、最終結果は同じになります。HTTP ではこれらの 4 つのメソッドが定義されていますが、HTML では GET と POST の 2 つしかサポートされていません。幸いなことに、2 つの回避策が考えられます。JavaScript を使用して PUT または DELETE を実行するか、「実際の」メソッドを追加パラメーター (HTML フォームの非表示の入力フィールドとしてモデル化) として POST を実行できます。Spring の HiddenHttpMethodFilter は、この後者のトリックを使用します。このフィルターはプレーンなサーブレットフィルターであるため、任意の Web フレームワーク (Spring MVC だけでなく) と組み合わせて使用できます。このフィルターを web.xml に追加すると、非表示の method パラメーターを持つ POST が対応する HTTP メソッドリクエストに変換されます。

HTTP メソッドの変換をサポートするために、Spring MVC フォームタグが更新され、HTTP メソッドの設定をサポートするようになりました。例: 次のスニペットは、Pet Clinic のサンプルからのものです。

<form:form method="delete">
	<p class="submit"><input type="submit" value="Delete Pet"/></p>
</form:form>

上記の例では、HTTP POST を実行し、「実際の」DELETE メソッドをリクエストパラメーターの背後に隠しています。次の例に示すように、web.xml で定義されている HiddenHttpMethodFilter によって取得されます。

<filter>
	<filter-name>httpMethodFilter</filter-name>
	<filter-class>org.springframework.web.filter.HiddenHttpMethodFilter</filter-class>
</filter>

<filter-mapping>
	<filter-name>httpMethodFilter</filter-name>
	<servlet-name>petclinic</servlet-name>
</filter-mapping>

次の例は、対応する @Controller メソッドを示しています。

  • Java

  • Kotlin

@RequestMapping(method = RequestMethod.DELETE)
public String deletePet(@PathVariable int ownerId, @PathVariable int petId) {
	this.clinic.deletePet(petId);
	return "redirect:/owners/" + ownerId;
}
@RequestMapping(method = [RequestMethod.DELETE])
fun deletePet(@PathVariable ownerId: Int, @PathVariable petId: Int): String {
	clinic.deletePet(petId)
	return "redirect:/owners/$ownerId"
}

HTML5 タグ

Spring フォームタグライブラリを使用すると、動的属性を入力できます。つまり、HTML5 固有の属性を入力できます。

フォーム input タグは、text 以外の型属性の入力をサポートしています。これは、emaildaterange などの新しい HTML5 固有の入力型をレンダリングできるようにすることを目的としています。text がデフォルトの型であるため、type='text' を入力する必要はありません。