SpringMVC的工作流程。最先接觸到請求的是DispatcherServlet,它會將請求根據配置文件轉發到控制器,控制器返回視圖名稱和一個Model表示處理結果。DispatcherServlet再將處理結果發送給視圖模板引擎,由它進行頁面的渲染。下圖是整個過程。
基本配置
聲明servlet。首先要在web.xml中聲明Spring的Servlet,代碼如下:
<servlet>
<servlet-name>spring</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>spring</servlet-name>
<url-pattern>/<url-pattern>
</servlet-mapping>
重要提示:這裏的url-pattern不能用/*,會導致所有的請求都返回404錯誤。因爲我們自己編寫的控制器只會返回一個視圖名稱,而解析視圖名稱的時候還會再次通過servlet獲取資源,如果使用/*,那麼所有的資源都由Spring框架來處理,但是Spring控制器中沒有任何jsp文件,如果設置成/,那麼對於xxx.jsp文件就會優先通過Tomcat自帶的Servlet返回靜態資源。
靜態資源。讓Spring框架處理所有的靜態資源,需要如下的聲明。以下是spring-servlet.xml文件,文件名稱servlet-name有關。
<beans xmlns="...">
<mvc:resources mapping="/res/**" location="/res/"/>
</beans>
註解聲明。爲了開啓註解方式的配置,需要在spring-servlet.xml中聲明:
<mvc:annotation-driven/>
<context:component-scan base-package="com.example"/>
控制器
控制器的編寫非常靈活,下面看幾個例子。
// 最簡單的例子,只要學會這個就能解決大部分問題了
@Controller
public class TestController {
// 映射請求URL,返回一個model和視圖名稱
@RequestMapping("/test1")
public String test1(Model model) {
return "hello";
}
}
// RequestMapping用法
@Controller
@RequestMapping("/myapp")
public class MyAppController {
// 通過/myapp訪問
@RequestMapping(method=RequestMethod.GET)
public Map<String, String> get() {
}
// 通過/myapp/xxx訪問
@RequestMapping(value="/{name}", method=RequestMethod.GET)
public Map<String, String> getForName(@PathVariable("name") String name) {
}
@RequestMapping(method=RequestMethod.POST)
public String add(@Valid AppointmentForm appointment, BindingResult result) {
}
}
// URL中包含多個變量
@Controller
@RequestMapping("/people/{peopleId}")
public class TestController {
@RequestMapping("/order/{orderId}"
public void getOrder(@PathVariable("peopleId") int peopleId, @PathVariable("orderId") String orderId, Model model) {
}
}
// URL正則匹配
@Controller
@RequestMapping("/people/{peopleId:[0-9]+}")
public class TestController {
}
// 匹配請求中的參數
@Controller
public class Test {
@RequestMapping(value="/test", params="name=alice")
public void test() {
}
// 沒有name參數的請求
@RequestMapping(value="/test" params="!name") {
}
}
// 匹配Http請求報頭
@Controller
public class Test() {
@RequestMapping(value="/test", headers="Accept-Encoding=UTF-8")
public void test() {
}
// Header中存在Cookie
@RequestMapping(value="/test", headers="Cookie")
public void test() {
}
}
// 接受json請求
@Controller
public class TestController {
@RequestMapping(value="/test" method=RequestMethod.POST, consumes="application/json")
public void test(@RequestBody People people, Model model) {
}
}
// 返回json結果
@Controller
public class TestController {
@RequestMapping(value="/test", method=RequestMethod.GET, produces="application/json")
public @ResponseBody People getPeople() {
}
}
// Cookie
@RequestMapping("/test")
public void test(@CookieValue("Hello") String hello) {
}
// Http頭
@RequestMapping("/test")
public void test(@RequestHeader("Accept-Encoding") String encoding) {
}
// 文件上傳
@RequestMapping("/test/")
public void test(@RequestParam(value="image", required=false)MultipartFile image) {
}
// 參數驗證
@Controller
public class TestController {
@Size(min=3, max=20, message="xxx")
@Pattern(regexp="^[a-zA-Z][a-zA-Z0-9]*")
private String username;
}
// 錯誤處理
@Controller
public class TestController {
@RequestMapping("/test")
public String test() {
}
@ExceptionHandler(IOException.class)
public ResponseEntity(String) error() {
}
}
視圖
當控制器返回一個視圖名稱之後,需要通過ViewResolver解析視圖。ViewResolver有多種類型:InternalResourceViewResolver、TilesViewResolver、FreemarkerViewResolver、VelocityViewResolver等等。
配置視圖引擎。在xxxx-servlet.xml中加入以下代碼即可。
<!--簡單的JSP模板引擎-->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/views/"/>
<property name="suffix" value=".jsp"/>
</bean>
<!--JSTL模板引擎-->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="viewClass" value="org.springframework.web.servlet.view.JstlView"/>
<property name="prefix" value="/WEB-INF/views/"/>
<property name="suffix" value=".jsp"/>
</bean>
<!--Tiles模板引擎-->
<bean class="org.springframework.web.servlet.view.tiles2.TilesConfigurer">
<property name="definitions">
<list>
<value>/WEB-INF/views/**/views.xml</value>
</list>
</property>
</bean>
如果使用的是InternalResourceViewResolver,那麼不需要額外的配置,只要返回文件名就可以了。比如下面的代碼會渲染/WEB-INF/views/test.jsp
public String test(){
return "test";
}
如果需要跳轉,只要返回redirect:即可,下面請看例子。
public String test() {
return "redirect:/hello/test/xxx";
}
攔截器
攔截器需要在xxx-servlet.xml中聲明,代碼如下:
<mvc:interceptors>
<!--簡單攔截器-->
<bean class="com.example.LoginInterceptor/>
<!--限定攔截URL-->
<mvc:interceptor>
<mvc:mapping path="/admin/*"/>
<bean class="com.example.AdminInterceptor/>
</mvc:interceptor>
</mvc:interceptors>
RESTful集成
REST是一種URL規範,所有的API都不包含QueryString。REST注重資源,而傳統的API注重行爲。REST通過HTTP Method的不同來做出不同的行爲。請看下面幾個REST API的例子。返回的結果可以是XML或者JSON。
http://test.com/user/alice
http://test.com/question/1232/answer/321
Spring MVC由於它的映射機制比較良好,因此實現REST API並不困難。下面請看一個例子。
@Controller
public class TestController {
@RequestMapping("/user/{id}", method=RequestMethod.PUT)
@ResponseStatus(HttpStatus.NO_CONTENT)
public String updateUser(@PathVariable("id") long id, @Valid User user) {
}
@RequestMapping("/user/{id}", method=RequestMethod.DELETE)
@ResponseStatus(HttpStatus.NO_CONTENT)
public void deleteUser(@PathVariable("id") long id) {
{
}
@RequestMapping("/user/{id}", method=RequestMethod.GET)
public @ResponseBody User getUser(@PathVariable("id") long id) {
}
@RequestMapping("/user", method=RequestMethod.POST)
@ResponseStatus(HttpStatus.CREATED)
public @ResponseBody User createUser(@Valid User user, BindingResult result, HttpServletResponse response) throws BindException {
long userId = service.createUser(user);
// 返回資源位置
response.setHeader("Location", "/user/" + userId);
return user;
}
}
資源的表述。資源的表述有多種方式,html/xml/json,html適合給人類看,而xml/json適合用於系統之間的通信。定義資源表述可以通過ContentNegotiatingViewResolver。需要在Spring配置中加入如下代碼。
<bean class="org.springframework.web.servlet.view.ContentNegotiatingViewResolver">
<property name="mediaType">
<map>
<entry key="json" value="application/json"/>
<entry key="xml" value="text/xml"/>
<entry key="htm" value="text/html"/>
</map>
</property>
<property name="defaultContentType" value="text/html"/>
</bean>
Spring通過多種途徑確定該返回哪種格式:URL擴展名,queryString參數,HTTP Header中的Accept字段。