Java Web系列:Spring MVC基礎

王剛發表於2016-01-15

1.Web MVC基礎

MVC的本質是表現層模式,我們以檢視模型為中心,將檢視和控制器分離出來。就如同分層模式一樣,我們以業務邏輯為中心,把表現層和資料訪問層程式碼分離出來是一樣的方法。框架只能在技術層面上給我們幫助,無法在思考和過程上幫助我們,而我們很多人都不喜歡思考和嘗試。

2.實現Web MVC的基礎

實現Web MVC基礎可以概括為1個前段控制器和2個對映。

(1)前端控制器FrontController

ASP.NET和JSP都是以Page路徑和URL一一對應,Web MVC要通過URL對映Controller和View,就需要一個前端控制器統一接收和解析請求,再根據的URL將請求分發到Controller。由於ASP.NET和Java分別以IHttpHandler和Servlet作為核心,因此ASP.NET MVC和Spring MVC分別使用實現了對應介面的MvcHandlerDispatcherServlet作為前段控制器。

ASP.NET中通過HttpModule的實現類處理URL對映,UrlRoutingModule根據URL將請求轉發給前端控制器MvcHandler。Spring MVC中,則根據URL的配置,直接將請求轉發給前端控制器DispatcherServlet

(2)URL和Contrller的對映

ASP.NET MVC將URL和Controller的對映規則儲存在RouteCollection中,前端控制器MvcHandler通過IController介面查詢控制器。Spring MVC則通過RequestMappingController註解標識對映規則,無需通過介面依賴實現控制i器。

(3)URL和View的對映

ASP.NET MVC 預設通過RazorViewEngine來根據URL和檢視名稱查詢檢視,核心介面是IViewEngine。Spring MVC 通過internalResourceViewResolver根據URL和檢視名稱查詢檢視,核心介面是ViewResolver

3.Spring MVC的基礎配置

(1)前端控制器DispatcherServlet初始化:AbstractAnnotationConfigDispatcherServletInitializer

ASP.NET MVC初始化需要我們在HttpApplication.Application_Start方法中註冊預設的URL和Controller規則,Spring MVC由於採用註解對映URL和Controller,因此沒有對應的步驟。ASP.NET在根web.config中配置了UrlRoutingModule可以將請求轉發給MvcHandler,Spring MVC我們需要我們配置DispatcherServlet以及其對應的URL來達到接管所有請求的目的,Spring已經利用Servlet3.0定義的ServletContainerInitializer機制,為我們提供了內建的AbstractAnnotationConfigDispatcherServletInitializer,只要只需要像繼承HttpApplication的MvcApplication一樣,寫一個MvcInitializer

 1 package s4s;
 2 
 3 import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;
 4 
 5 public class MvcInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
 6 
 7     @Override
 8     protected Class<?>[] getRootConfigClasses() {
 9         return new Class[] { };
10     }
11 
12     @Override
13     protected Class<?>[] getServletConfigClasses() {
14         return new Class[] { MvcConfig.class };
15     }
16 
17     @Override
18     protected String[] getServletMappings() {
19         return new String[] { "/" };
20     }
21 
22 }

(2)URL和View的對映:WebMvcConfigurerAdapter

ASP.NET的RazorViewEngine內建了View的Path和副檔名.cshtml的規則。Spring MVC的internalResourceViewResolver沒有提供預設值,一般我們會指定將View放置在統一的檢視目錄中,使用特定的副檔名。Spring同樣提供了內建的WebMvcConfigurerAdapter,我們只需寫一個自己的MvcConfig繼承它,重寫configureViewResolvers方法即可。

 1 package s4s;
 2 
 3 import org.springframework.context.annotation.ComponentScan;
 4 import org.springframework.context.annotation.Configuration;
 5 import org.springframework.web.servlet.config.annotation.EnableWebMvc;
 6 import org.springframework.web.servlet.config.annotation.ViewResolverRegistry;
 7 import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
 8 import org.springframework.web.servlet.view.InternalResourceViewResolver;
 9 
10 @EnableWebMvc
11 @ComponentScan
12 @Configuration
13 public class MvcConfig extends WebMvcConfigurerAdapter {
14 
15     @Override
16     public void configureViewResolvers(ViewResolverRegistry registry) {
17         InternalResourceViewResolver viewResolver = new InternalResourceViewResolver();
18         viewResolver.setPrefix("/WEB-INF/views/");
19         viewResolver.setSuffix(".jsp");
20         registry.viewResolver(viewResolver);
21     }
22 }

4.Spring MVC的Controller、Model和View

(1)URL和Controller的對映:

Spring MVC和ASP.NET MVC的不同,不通過IController介面標識Controller,也不通過RouteCollection定義URL和Controller,取而代之的是兩個註解:Controller和RequestMapping。因此我們在普通的POJO類上應用@Controller和@RequestMapping即可。

 1 package s4s;
 2 
 3 import javax.validation.Valid;
 4 import org.springframework.security.access.prepost.PreAuthorize;
 5 import org.springframework.security.core.context.SecurityContextHolder;
 6 import org.springframework.stereotype.Controller;
 7 import org.springframework.validation.BindingResult;
 8 import org.springframework.web.bind.annotation.ModelAttribute;
 9 import org.springframework.web.bind.annotation.RequestMapping;
10 import org.springframework.web.bind.annotation.RequestMethod;
11 import org.springframework.web.bind.annotation.RequestParam;
12 import org.springframework.web.bind.annotation.ResponseBody;
13 
14 @Controller
15 public class MyController {
16 
17     @ResponseBody
18     @RequestMapping(value = "/")
19     public String home() {
20         return "home";
21     }
22 
23     @RequestMapping(value = "/register")
24     public String register(@ModelAttribute("model") RegisterUserModel model) {
25         return "register";
26     }
27 
28     @RequestMapping(value = "/register", method = RequestMethod.POST)
29     public String register(@ModelAttribute("model") @Valid RegisterUserModel model, BindingResult result) {
30         if (!result.hasErrors()) {
31             return "redirect:/account";
32         }
33         return "register";
34     }
35 }

(2)Model:

通過使用@ModelAttribute、@Valid和BindingResult引數,我們可以指定Model的Name是否參與驗證並獲取驗證結果。為在Model上使用註解驗證,還需要引入validation-api和hibernate-validator。

ASP.NET將檢視最終編譯為WebViewPage<object>,View和Model是一一對應並且型別匹配的,Model可以是任意的POCO。Spring MVC中View和Model是一對多的,提供了ModelMap和其子類ModelAndView提供類似ASP.NET MVC中ViewResult的功能。ModelMap的基類是LinkedHashMap<String, Object>。

Spring MVC中沒有ViewResult型別。在Spring MVC中,我們一般返回String型別,可以有多種含義:

    a.返回View的名稱。

    b.返回文字:在Action上應用@ResponseBody註解時。

    c.返回跳轉:以"redirect:"開頭時。如:return "redirect:/success"

模型的驗證:

(1)在Model欄位上使用JSR-303定義的註解(需要引入hibernate validator)。

(2)在Controller的Model引數上應用@ModelAttribute、@Valid

(3)在View中使用<form:errors>標籤

Spring MVC需要新增jstl和spring的tag支援才能完成模型相關的操作。由於Spring MVC中的View和ASP.NET MVC中的區別較大,沒有辦法指定View持有的Model型別也就沒有了智慧提示和錯誤檢測的優勢。

 1 package s4s;
 2 
 3 import javax.validation.constraints.NotNull;
 4 import javax.validation.constraints.Size;
 5 
 6 public class RegisterUserModel {
 7     @Size(max = 20, min = 5)
 8     private String userName;
 9     @Size(max = 20, min = 5)
10     private String password;
11     @NotNull
12     private String confirmPassword;
13 
14     public String getUserName() {
15         return userName;
16     }
17 
18     public void setUserName(String userName) {
19         this.userName = userName;
20     }
21 
22     public String getPassword() {
23         return password;
24     }
25 
26     public void setPassword(String password) {
27         this.password = password;
28     }
29 
30     public String getConfirmPassword() {
31         return confirmPassword;
32     }
33 
34     public void setConfirmPassword(String confirmPassword) {
35         this.confirmPassword = confirmPassword;
36     }
37 }

register.jsp

 1 <%@ page language="java" pageEncoding="UTF-8"%>
 2 <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
 3 <%@ taglib uri="http://www.springframework.org/tags" prefix="s"%>
 4 <%@ taglib uri="http://www.springframework.org/tags/form" prefix="form"%>
 5 <!DOCTYPE HTML>
 6 <html>
 7 <head>
 8 <title>Getting Started: Serving Web Content</title>
 9 <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
10 </head>
11 <body>
12     <h2>Register</h2>
13     <form:form modelAttribute="model">
14         <s:bind path="*">
15             <c:if test="${status.error}">
16                 <div id="message" class="error">Form has errors</div>
17             </c:if>
18         </s:bind>
19         <div>
20             <form:label path="userName">userName</form:label>
21             <form:input path="userName" />
22             <form:errors path="userName" cssClass="error" />
23         </div>
24         <div>
25             <form:label path="password">password</form:label>
26             <form:password path="password" />
27             <form:errors path="password" cssClass="error" />
28         </div>
29         <div>
30             <form:label path="confirmPassword">confirmPassword</form:label>
31             <form:password path="confirmPassword" />
32             <form:errors path="confirmPassword" cssClass="error" />
33         </div>
34         <input type="submit" value="submit">
35     </form:form>
36 </html>

 

5.Spring MVC的初始化機制

 Spring實現了Servlet 3.0規範定義的javax.servlet.ServletContainerInitializer介面並通過javax.servlet.annotation.HandlesTypes註解引用了WebApplicationInitializer介面。因此在Servlet容器初始化時,在當前class path路徑下的WebApplicationInitializer實現類的onStartup方法會自動執行(這和ASP.NET的Application_Start作用類似,在系列中的Java Web基礎時曾經提到過)。

ASP.NET中我們在Application_Start中初始化依賴注入容器。在Spring MVC中,我們實現WebApplicationInitializer介面同樣可以執行依賴注入的初始化。在Web環境中,我們使用的ApplicationContext介面的實現類為基於註解的AnnotationConfigWebApplicationContext(在系列中的Spring依賴注入基礎中曾經提到過),但我們無需直接實現WebApplicationInitializer並手動初始化AnnotationConfigWebApplicationContext物件,因為Spring已經定義了AbstractAnnotationConfigDispatcherServletInitializer作為WebApplicationInitializer介面的實現類,已經包含了AnnotationConfigWebApplicationContext的初始化。

採用基於Annotation註解時可以通過@Configurateion指定POJO來替代web.xml配置依賴注入。同樣,@ComponentScan可以替代web.xml中的掃描配置功能,使用ComponentScan配合Configurateion可以達到0xml配置的方式。上文中提到的Contrller相關的註解,都是啟用ComponentScan後才會被掃描生效。

AbstractAnnotationConfigDispatcherServletInitializer類的父類AbstractDispatcherServletInitializer中已經包含DispatcherServlet的初始化。相關類圖如下:

5.Spring MVC的Action Filter

.NET MVC提供了眾多Filter介面和一個ActionFilterAttribute抽象類作為Filter的基礎,其中以實現了IAuthorizationFilter介面的AuthorizeAttribute攔截器最為我們熟知。Spring MVC則提供了基於HandlerInterceptor介面的眾多介面、抽象類和實現類,其中也有和.NET MVC類似的許可權驗證UserRoleAuthorizationInterceptor攔截器。內建的攔截器可以滿足大部分需求,為了省事圖就畫在一張上了,上面是Spring MVC的,下面是.NET MVC的。

總結

(1)MVC實現的要點是前端控制器、URL和Controller的對映、URL和View的對映

(2)MvcHandler和DispatcherServlet

(3)ServletContainerInitializer和HttpApplication.Application_Start

(4)RazorViewEngine和internalResourceViewResolver

(5)IMvcFilter和HandlerInterceptor

目前沒有找到類似ASP.NET中的從特性(註解)生成客戶端JavaScript驗證的方式,如果大家有相關資料分享,提前謝謝大家。

參考:

(1)http://www.ibm.com/developerworks/cn/java/j-lo-jsr303/index.html

(2)http://spring.oschina.mopaas.com/validation.html#validation-binder

(3)http://www.mkyong.com/spring-mvc/spring-3-mvc-and-jsr303-valid-example/

相關文章