理解 Spring MVC 容器啟動流程,有助於排查配置不生效、攔截器未註冊等疑難雜症。本文先解析 DispatcherServlet 初始化關鍵步驟,再示範註解式 Controller 開發全流程:從 web.xml 配置、掃描包路徑、視圖解析器、通配符映射、HTTP 方法限定、參數綁定到物件屬性自動裝配。
web.xml 宣告 DispatcherServlet,指定 contextConfigLocation 載入 Spring 配置,load-on-startup=1 容器啟動時實例化。DispatcherServlet 繼承 FrameworkServlet → HttpServletBean,init() 呼叫 initWebApplicationContext() 建立 WebApplicationContext。@Controller、@RequestMapping,建立 HandlerMapping 與 HandlerAdapter。DispatcherServlet.doDispatch() → 尋找 Handler → 執行攔截器預處理 → 執行 Controller → 處理返回值(ModelAndView/String/void/@ResponseBody) → 視圖解析 → 渲染響應。public class MyController implements Controller {
@Override
public ModelAndView handleRequest(HttpServletRequest req, HttpServletResponse resp) {
ModelAndView mv = new ModelAndView();
mv.addObject("msg", "學習啊!");
mv.setViewName("/WEB-INF/jsp/index.jsp"); // 邏輯視圖名
return mv;
}
}
// Spring XML 註冊
<bean name="/hello.do" class="cn.happy.action.MyController" />
<servlet>
<servlet-name>springmvc</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:applicationContext.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>springmvc</servlet-name>
<url-pattern>*.do</url-pattern>
</servlet-mapping>
<context:component-scan base-package="cn.happy.*" />
<mvc:annotation-driven />
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/jsp/" />
<property name="suffix" value=".jsp" />
</bean>
@Controller
@RequestMapping("/hr")
public class MyMultiActionController {
@RequestMapping("/first.do")
public ModelAndView doFirst() {
return new ModelAndView("index", "msg", "學習啊!");
}
@RequestMapping("/second.do")
public ModelAndView doSecond() {
return new ModelAndView("index", "msg", "吃飯啊!");
}
@RequestMapping("/*third.do") // 任意前綴結尾 third.do
public String third() { return "index"; }
@RequestMapping("/fourth*.do") // 以 fourth 開頭
public String fourth() { return "index"; }
@RequestMapping("/**/fiveth.do") // 多層路徑
public String fiveth() { return "index"; }
@RequestMapping("/*/sixth*.do") // 單層路徑 + sixth 開頭
public String sixth() { return "index"; }
}
| 屬性 | 說明 | 範例 |
|---|---|---|
| value | 請求 URL 模式,支援 URI Template | /user/{id} |
| method | 限定 HTTP 方法 | RequestMethod.POST |
| consumes | 請求 Content-Type | application/json |
| produces | 響應 Content-Type(需匹配 Accept) | application/json;charset=UTF-8 |
| params | 請求必須包含指定參數 | params="action=save" |
| headers | 請求必須包含指定 Header | headers="X-Requested-With=XMLHttpRequest" |
@Controller
@RequestMapping("/hr")
public class MyController {
@RequestMapping(value="/first.do", method=RequestMethod.POST)
public String doFirst(@RequestParam("uname") String username, Model model) {
model.addAttribute("uname", username);
return "index";
}
@RequestMapping(value="/save.do", method=RequestMethod.POST)
public String save(UserInfo info, Model model) { // 直接綁定物件
model.addAttribute("uname", info.getUname());
return "index";
}
}
JSP 表單:
<form action="${pageContext.request.contextPath}/first.do" method="post">
<input name="uname" /><br/>
<input type="submit" value="提交"/>
</form>



component-scan 的 base-package 必須包含 Controller 所在包,否則掃描不到。prefix/suffix 與 Controller 返回的邏輯視圖名拼接為完整路徑。@RequestMapping 類上標註為父路徑,方法上為子路徑,最終 URL = 父 + 子。* 匹配單層,** 匹配多層,? 匹配單字元。name、實體屬性名、Controller 參數名「三者合一」才能自動裝配;否則需 @RequestParam 指定。