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
要素をレンダリングします。このタグの例については、フォームタグを参照してください。email
、tel
、date
などの 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
プロパティで利用可能なオプションを含む Array
、List
、Map
を渡すことができます。通常、バウンドプロパティはコレクションであるため、ユーザーが選択した複数の値を保持できます。次の例は、このタグを使用する 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
プロパティで利用可能なオプションを含む Array
、List
、Map
を渡します。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>
1 | selected 属性が追加されていることに注意してください。 |
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>
1 | selected 属性が追加されていることに注意してください。 |
前の例が示すように、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"
}