Spring MVC 基於模型-視圖-控制器(Model-View-Controller)模式實現,它可以幫你構建靈活和鬆耦合的應用程序。html
每當用戶在 Web 瀏覽器中點擊連接或提交表單是,請求就開始工做,從離開瀏覽器開始到獲取響應返回,請求在 Spring MVC 框架中會通過如下過程:前端
咱們已經大概瞭解了 Spring MVC 的工做流程,但其間涉及到多種組件,它們的任務分別是:java
DispatcherServlet 是 Spring MVC 的核心,它負責將請求轉到其餘組件中。但它是一個 Servlet,按照傳統方式,須要在 web.xml 文件中配置 DispatcherServlet ,藉助 Servlet 3 的規範和 Spring 3.1 的功能加強,咱們可使用 Java 的方式進行配置 DispatcherServlet。web
package spitter.config; import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer; /** * 該類繼承了 AbstractAnnotationConfigDispatcherServletInitializer ,會自動地配置 DispatcherServlet 和 Spring 應用上下文,Spring應用上下文會位於應用程序的 Servlet 上下文之中; */ public class SpitterWebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer { /* 用來裝配非Web組件的bean */ @Override protected Class<?>[] getRootConfigClasses() { return new Class[]{RootConfig.class}; } /* 該方法指定配置類 當 DispatcherServlet 啓動時,會建立Spring應用上下文,並加載配置文件或配置類中所聲明的 bean。 */ @Override protected Class<?>[] getServletConfigClasses() { return new Class[]{WebConfig.class}; } /* 該方法會將一個或多個路徑映射到 DispatcherServlet 上 將 DispatcherServlet 映射到 「/」,表示應用的默認Servlet,會處理進入應用的全部請求 */ @Override protected String[] getServletMappings() { return new String[]{"/"}; } }
package spitter.config; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.ViewResolver; import org.springframework.web.servlet.config.annotation.DefaultServletHandlerConfigurer; import org.springframework.web.servlet.config.annotation.EnableWebMvc; import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; import org.springframework.web.servlet.view.InternalResourceViewResolver; /** * 該類爲Spring MVC配置類,具備如下配置: * 一、啓用Spring MVC; * 二、啓用組件掃描;若是沒有啓用,Spring 只會找到顯式聲明在配置類中的控制器; * 三、配置 JSP 視圖解析;若是沒有配置,會默認使用 BeanNameViewResolver 解析器; * 四、配置靜態資源的處理; */ @Configuration @EnableWebMvc //啓用Spring MVC; @ComponentScan("spitter.config") public class WebConfig extends WebMvcConfigurerAdapter { /* 配置 JSP 視圖解析器 */ @Bean public ViewResolver viewResolver(){ InternalResourceViewResolver resolver = new InternalResourceViewResolver(); resolver.setPrefix("/views/"); resolver.setSuffix(".jsp"); resolver.setExposeContextBeansAsAttributes(true); return resolver; } /* 配置靜態資源的處理 該配置是將對靜態資源的請求轉發到Servlet容器中默認的Servlet上, 而不是使用 DispatcherServlet 來處理此類請求 */ @Override public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer){ configurer.enable(); } }
package spitter.config; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.ComponentScan.Filter; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.FilterType; import org.springframework.web.servlet.config.annotation.EnableWebMvc; /** * 用來裝配非Web組件的配置; */ @Configuration @ComponentScan(basePackages = {"spitter"}, excludeFilters = {@Filter(type = FilterType.ANNOTATION,value = EnableWebMvc.class) }) public class RootConfig { }
基於 Java 配置雖然新穎,但對於初學者很不友好。因此,這裏也提供了在 web.xml 中裝配核心控制器 DispatcherServletajax
<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd" version="4.0"> <!-- SpringMVC的核心控制器 --> <servlet> <!-- 配置Servlet的初始化參數 --> <servlet-name>dispatcherServlet</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <!--讀取spring mvc的配置文件,建立spring容器--> <init-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:springMvcConfig.xml</param-value> </init-param> <!-- 配置servlet啓動時加載對象 --> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>dispatcherServlet</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping> </web-app>
Spring MVC配置文件:springMvcConfig.xmlspring
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:mvc="http://www.springframework.org/schema/mvc" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd"> <!-- 配置spring開啓註解mvc的支持 --> <mvc:annotation-driven/> <!-- 配置spring建立容器時要掃描的包 --> <context:component-scan base-package="spitter.controller"/> <!-- 配置視圖解析器 --> <bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="prefix" value="/views/"/> <property name="suffix" value=".jsp"/> </bean> </beans>
在接下來的實踐中,本人是根據 XML 配置的。數據庫
如何建立控制器?如何根據請求路徑找到控制器?數組
聲明控制器瀏覽器
在類級別上使用註解 @Controller
,該註解與註解 @Component
做用同樣,目標是輔助實現組件掃描;spring-mvc
定義方法
方法內進行請求處理,返回值類型爲 String,該字符串爲要渲染的視圖名,提供給視圖解析器進行解析。固然返回值類型也能夠是其它類型,稍後再講。
使用註解 @RequestMapping
設置請求路徑映射控制器;
RequestMapping註解:指定方法要處理的請求路徑,和細化所處理的HTTP方法等,註解內有如下屬性:
name:爲此 mapping 設置名稱;
path/value:複數形式,限制請求路徑匹配時調用;當請求路徑與該屬性內容匹配,則訪問;
可同時在類級別和方法級別標註,至關於兩級路徑:/類設置路徑/方法設置路徑;
method:限制請求方法匹配時調用;使用 RequestMethod 枚舉的屬性;
params:限制請求參數;當請求中包含相同的參數名和相同參數值才匹配;
headers:限制請求頭;當請求頭中包含相同的請求頭才匹配;
下面是一個簡單的例子
package spitter.controller; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; @Controller @RequestMapping({"/home"}) public class HomeController { //訪問如下方法的請求路徑爲:/home/homepage @RequestMapping(value = {"/homepage"}, method = RequestMethod.GET) public String home() { return "home"; // 被Spring MVC解讀爲要渲染的視圖名稱 } }
當請求到控制器,如何將請求中的參數傳遞到控制器的方法中?Spring MVC提供了請求參數綁定機制。
@ResponseBody
實現;HttpServletRequest
對象和 HttpServletResponse
對象獲取請求參數和響應結果;案例
下面是表單數據提交的 JSP 代碼,例子中所使用的 POJO 類並未展現。
<a href="<c:url value="/reqParamBind/get?method=GET&num=10"/>" >GET方式</a><br/><br/> <!--POST方式--> <form action="reqParamBind/post?method=POST" method="post"> 用戶名:<input type="text" name="userName"/><br/> 密碼:<input type="password" name="password"/><br/> <input type="submit" value="提交"/> </form> <!--POJO類型參數綁定,name屬性的值必須與POJO類屬性名一致--> <form action="reqParamBind/pojoBind" method="post"> 用戶名:<input type="text" name="userName"/><br> 密碼:<input type="password" name="password"/><br> 金額:<input type="text" name="money"/><br> <!--POJO對象內引用另外一個POJO對象--> 真實姓名:<input type="text" name="user.realName"/><br> 年齡:<input type="text" name="user.age"/><br> <input type="submit" value="提交"/> </form> <!--集合類型--> <form action="reqParamBind/collectionBind" method="post"> <!--List集合,name屬性的值爲:集合屬性名[下標].屬性名--> 用戶:<input type="text" name="list[0].realName"/><br> 年齡:<input type="text"name="list[0].age"/><br> 用戶:<input type="text" name="list[1].realName"/><br> 年齡:<input type="text" name="list[1].age"/><br> <!--Map集合,name屬性的值爲:集合屬性名['key'].屬性名--> 用戶:<input type="text" name="map['one'].realName"/><br> 年齡:<input type="text"name="map['one'].age"/><br> 用戶:<input type="text" name="map['two'].realName"/><br> 年齡:<input type="text" name="map['two'].age"/><br> <input type="submit" value="提交"/> </form>
表單提交數據訪問的控制器代碼
package spitter.controller; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import spitter.pojo.Account; import spitter.pojo.UserList; import javax.servlet.ServletContext; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; import java.util.Date; @Controller @RequestMapping(value = {"/reqParamBind"}) public class ParamController { /** * get方式請求參數傳遞 * @param method 請求參數 * @param num 請求參數 * @return 要解析的視圖名稱 */ @RequestMapping(value = {"/get"},method = RequestMethod.GET) public String reqParamBind(String method,int num){ System.out.println("Method:"+method+"---num:"+num); return "paramAccept"; } /** * post方式請求參數傳遞 * @param method 請求參數 * @param userName 請求參數 * @param password 請求參數 * @return 要解析的視圖名稱 */ @RequestMapping(value = {"/post"},method = RequestMethod.POST) public String reqParamBind(String method,String userName,String password){ System.out.println("Method:"+method+"---userName:"+userName+"---密碼:"+password); return "paramAccept"; } /** * POJO實體類型參數綁定 * @param account POJO類 * @return 要解析的視圖名稱 */ @RequestMapping(value = {"/pojoBind"},method = RequestMethod.POST) public String pojoBind(Account account){ System.out.println(account); return "paramAccept"; } /** * List、Map等集合類型參數綁定 * @param userList 實體類;實體類中封裝了List集合和Map集合進行綁定 * @return 要解析的視圖名稱 */ @RequestMapping(value = {"/collectionBind"},method = RequestMethod.POST) public String collectionBind(UserList userList,HashMap<String,>){ System.out.println(userList); return "paramAccept"; } /** * 獲取Servlet的請求和響應對象 * @param request HttpServletRequest * @param response HttpServletResponse * @return 要解析的視圖名稱 */ @RequestMapping(value = {"/getReqAndResp"},method = RequestMethod.GET) public String getReqAndResp(HttpServletRequest request, HttpServletResponse response){ System.out.println(request); HttpSession session = request.getSession(); System.out.println(session); ServletContext servletContext = request.getServletContext(); System.out.println(servletContext); System.out.println(response); return "paramAccept"; } }
正如咱們所知,瀏覽器提交的數據都是以字符串類型傳遞的,而 Spring MVC 爲咱們封裝好了經常使用的類型轉換器。但有時,依然會有一些特殊狀況。好比:日期格式轉換。Spring MVC 只接受 yyyy/MM/dd 類型的日期格式;若是換成 yyyy-MM-dd 類型的日期格式就會出現異常;
這是,咱們能夠自定義一個轉換器,請求參數會根據咱們定義的格式進行轉換。
定義轉換器步驟
org.springframework.core.convert.converter.Converter<S,T>
接口;重寫 convert 方法,方法中編寫類型轉換的代碼;org.springframework.context.support.ConversionServiceFactoryBean
;再把剛建立的轉換器(bean)填充到該 bean 的屬性 converters(爲Set集合);案例
轉換器類
package spitter.converter; import org.springframework.core.convert.converter.Converter; import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.Date; public class StringToDate implements Converter<String, Date> { @Override public Date convert(String source) { if (source == null && "".equals(source)) { throw new RuntimeException("請輸入數值"); } else { DateFormat df = new SimpleDateFormat("yyyy-MM-dd"); try { return df.parse(source); } catch (Exception e) { throw new RuntimeException("數據類型轉換錯誤"); } } } }
在 Spring MVC 配置文件進行配置
<!--配置自定義類型轉換器--> <bean id="conversionService2" class="org.springframework.context.support.ConversionServiceFactoryBean"> <property name="converters"> <set> <!--裝配轉換器 Bean對象--> <bean class="spitter.converter.StringToDate"/> </set> </property> </bean> <!-- 配置spring開啓註解mvc的支持,並裝配轉換器--> <mvc:annotation-driven conversion-service="conversionService2"/>
可是:日期轉換器轉換 yyyy-MM-dd 類型後,再使用 yyyy/MM/dd類型會出現錯誤:Bad Request。
控制器方法中處理後的數據信息,如何傳遞到視圖,展現在瀏覽器?
org.springframework.ui.Model
,它會調用子類 BindingAwareModelMap
對象的 addAttribute(key,value)
方法傳遞;HttpServletRequest
對象和 HttpServletResponse
對象進行數據傳遞;/* 將控制器的數據傳遞到視圖中 須要傳遞一個參數:Model或 Map對象,調用對應的addAttribute方法或put方法傳遞; */ @RequestMapping(value = {"/homeParam"}, method = RequestMethod.GET) public String home(/*Map<String,String>*/ Model model) { //model.put("name","Chan"); model.addAttribute("name", "Chan"); return "home"; }
當控制器方法的返回值類型爲如下類型時,實現頁面跳轉的方式:
String類型
Spring MVC 會根據返回的字符串進行視圖名稱解析,好比:jsp的視圖解析後爲:路徑/字符串.jsp,並跳轉到解析後的 JSP 頁面;
返回值是void類型
ModelAndView類型
ModelAndView 是 SpringMVC 爲咱們提供的一個對象,該對象也能夠用做控制器方法的返回值;
該對象中有兩個經常使用的方法:
addObject(String key,Object value)
:將數據以鍵值對的方式存儲到request做用域中;底層也是使用 Model 對象進行傳遞數據;setViewName(String viewName)
:跳轉頁面,給定視圖名稱,通過解析器解析;關鍵字進行請求轉發、重定向;返回值是String類型,字符串內容以下:
forward:URI路徑 :請求轉發;/
表示當前項目路徑;
redirect:URI路徑 :重定向;不須要加項目名稱;
案例
package spitter.controller; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.servlet.ModelAndView; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; @Controller @RequestMapping(value = {"/returnType"}) public class ReturnTypeController { /** * 返回值爲字符串 * @return 字符串爲要解析的視圖名 */ @RequestMapping(value = "/stringType",method = RequestMethod.GET) public String stringType(){ return "returnType"; } /** * 返回值爲 void */ @RequestMapping(value = "/voidType",method = RequestMethod.GET) public void voidType(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { //請求轉發 //request.getRequestDispatcher("/index.jsp").forward(request,response); //重定向 //response.sendRedirect(request.getContextPath()+"/index.jsp"); //直接返回數據 //request.setCharacterEncoding("UTF-8"); //response.setContentType("text/html;charset=utf-8"); //response.getWriter().write("數據..."); } /** * 返回值爲 ModelAndView 類型 * @return ModelAndView對象 */ @RequestMapping(value = "/modelAndView",method = RequestMethod.GET) public ModelAndView modelAndView(){ //建立 modelAndView 對象 ModelAndView modelAndView = new ModelAndView(); //設置Request做用域屬性 modelAndView.addObject("name","Chan"); //跳轉頁面 modelAndView.setViewName("../index"); return modelAndView; } @RequestMapping(value = "/forwardOrRedirect") public String forwardOrRedirect(){ //請求轉發 return "forward:/views/page.jsp"; //重定向 //return "redirect:/index.jsp"; } }
@RequestParam():將指定的請求參數賦給控制器中的參數;
用於請求參數名稱與控制器方法形參名稱不一致時,標註在控制器方法的形參前;
/** * RequestParam註解匹配請求參數名和控制器方法形參名不一致 * @param userName 控制器方法形參名 * @param model 數據傳遞對象,把數據傳遞到視圖層 * @return 要解析的視圖名稱 */ @RequestMapping(value = "/requestParam",method = RequestMethod.GET) public String requestParam(@RequestParam(value = "uname") String userName, Model model){ }
@RequestBody()
讀取 Request
請求的 body
部分數據,直接使用獲得是 key=value&key=value... 結構的數據;get 請求方式不適用;
HttpMessageConverter
解析數據,而後把相應的數據綁定到對象上;@ResponseBody()
將 Controller
的方法返回的對象,經過適當的HttpMessageConverter
轉換爲指定格式後,寫入到Response
對象的body
數據區;
@RequestBody()
和 @ResponseBody()
能夠接收和響應 JSON 數據。例如:Ajax 請求中,可以接收 JSON 數據封裝到對象中,也能夠將對象解析爲 JSON 數據傳送。
@RequestMapping(value = "/ajaxRequest") public @ResponseBody User ajaxRequest(@RequestBody User user){ user.setRealName("xiaoyao"); user.setAge(30); return user; }
@PathVariable():用於從 url 中獲取數據
請求 url 中使用佔位符標記要獲取的數據,例如: /delete/{id},這個 {}
就是 url 佔位符;url 支持佔位符是 spring3.0 以後加入的;是 spring mvc 支持 rest 風格 URL 的一個重要標誌。
@RequestHeader():用於獲取請求消息頭;
@CookieValue():用於把指定 cookie 名稱的值傳入控制器方法參數;
@ModelAttribute():能夠用於修飾方法和參數;
也就是 Model 對象中 Attribute 操做。
出如今方法上,表示當前方法會在控制器方法執行以前,先執行。方法有返回值則把返回值放到 Model中;
出如今參數上,獲取指定的數據賦給方法參數;若是存在表單提交的數據,會優先綁定請求參數;
value屬性:獲取 Model 中數據的 key 。key 能夠是POJO的屬性名稱,也能夠是map結構的 key;
案例:當表單提交的數據不是完整的實體類數據時,爲了保證沒有提交數據的字段使用數據庫對象原來的數據,有數據的字段是不容許修改的,即便用set方法賦值;
@SessionAttribute():用於屢次執行控制器方法間的參數共享;
只能做用在類級別上;將數據存儲在Session做用域;
package spitter.controller; import org.springframework.stereotype.Controller; import org.springframework.ui.ExtendedModelMap; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.support.SessionStatus; import spitter.pojo.User; import java.io.UnsupportedEncodingException; @Controller @RequestMapping(value = {"/anno"}) //(1)將控制器方法中添加到 Model 中的數據存儲到Session域。value中指定數據的 key @SessionAttributes(value = {"session"}) public class AnnoController { //(2) @RequestMapping(value = "/setSessionAttr") public String setSessionAttr(Model model){ model.addAttribute("session","控制器類使用 @SessionAttribute 存儲數據"); return "paramAccept"; } @RequestMapping(value = "/getSessionAttr") public String getSessionAttr(ExtendedModelMap model){ //(3)獲取Session做用域屬性 String session = (String) model.get("session"); System.out.println(session); return "paramAccept"; } @RequestMapping(value = "/delSessionAttr") public String delSessionAttr(SessionStatus status){ //(4)清空session域 status.setComplete(); return "paramAccept"; } }