本文為「Apache Geronimo 與 Spring 框架」系列第 5 部分,聚焦 Spring MVC 核心模組,透過擴充電話簿應用示範控制器、表單綁定、驗證等關鍵機制。
傳統 Web 開發中,視圖(JSP)直接嵌入業務邏輯與資料存取,導致:
MVC 引入 Controller 作為中介層:
DispatcherServlet 為核心前端控制器,職責:
ModelAndView| 介面 | 核心實作 | 說明 |
|---|---|---|
| Controller | SimpleFormController, MultiActionController | 處理請求、綁定表單、驗證、決定視圖 |
| Validator | 自訂實作 | 表單資料驗證,錯誤收集至 Errors 物件 |
| ViewResolver | InternalResourceViewResolver, ResourceBundleViewResolver | 視圖名稱 → 具體 View 實例 |
PhonebookHomeControllerpublic class PhonebookHomeController extends SimpleFormController {
private IPhonebookDataProvider pbDataProvider;
protected Object formBackingObject(HttpServletRequest request) {
// GET 請求時提供表單後端物件:此處直接回傳列表供顯示
return pbDataProvider.getPhonebookEntries();
}
protected ModelAndView onSubmit(Object command) {
return new ModelAndView(new RedirectView(getSuccessView()));
}
// setter 注入 pbDataProvider
}
<bean id="phonebookHomeController" class="phonebook.controller.PhonebookHomeController">
<property name="sessionForm" value="true"/>
<property name="bindOnNewForm" value="true"/>
<property name="formView" value="home-mvc"/>
<property name="successView" value="addentry-mvc"/>
<property name="commandName" value="phonebookEntries"/>
<property name="commandClass" value="java.util.List"/>
<property name="pbDataProvider" ref="phonebook"/> <!-- AOP 代理 -->
</bean>
home-mvc.jsp(JSTL + Spring Tags)<c:forEach items="${phonebookEntries}" var="pbEntry">
<tr>
<td><input type="radio" name="data_i" onclick="setId(${pbEntry.entryID},${pbEntry.rowID})"/></td>
<td><c:out value="${pbEntry.firstName}"/> <c:out value="${pbEntry.lastName}"/></td>
...
</tr>
</c:forEach>
public class AddEntryFormController extends SimpleFormController {
protected Object formBackingObject(HttpServletRequest request) {
return new PhonebookEntry(); // 空表單物件
}
protected ModelAndView onSubmit(Object command) {
PhonebookEntry entry = (PhonebookEntry) command;
pbDataProvider.addEntry(entry);
return new ModelAndView(new RedirectView(getSuccessView() + ".act"));
}
}
// 驗證器
public class PhonebookEntryValidator implements Validator {
public boolean supports(Class clazz) {
return PhonebookEntry.class.equals(clazz);
}
public void validate(Object obj, Errors errors) {
PhonebookEntry e = (PhonebookEntry) obj;
if (e.getFirstName() == null || e.getFirstName().isEmpty())
errors.rejectValue("firstName", "error.firstName.empty", "First name cannot be empty");
if (e.getLastName() == null || e.getLastName().isEmpty())
errors.rejectValue("lastName", "error.lastName.empty", "Last name cannot be empty");
}
}
addentry-mvc.jsp<spring:nestedPath path="phonebookEntry">
<spring:bind path="firstName">
<input type="text" name="${status.expression}" value="${status.value}"/>
<c:if test="${status.error}"><span class="error"><c:out value="${status.errorMessage}"/></span></c:if>
</spring:bind>
...
</spring:nestedPath>
web.xml<servlet>
<servlet-name>phonebook</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>phonebook</servlet-name>
<url-pattern>*.act</url-pattern>
</servlet-mapping>
spring:bind、spring:nestedPath 消除手工表單處理樣板碼第 6 部分將展示如何在同一應用中整合 JSP、Tiles、Velocity、PDF 多種視圖技術,並示範視圖技術的熱切換能力。