Spring MVC【入門】就這一篇!

 
 

MVC 設計概述

在早期 Java Web 的開發中,統一把顯示層、控制層、數據層的操做所有交給 JSP 或者 JavaBean 來進行處理,咱們稱之爲 Model1:javascript

 
 
  • 出現的弊端:
  • JSP 和 Java Bean 之間嚴重耦合,Java 代碼和 HTML 代碼也耦合在了一塊兒
  • 要求開發者不只要掌握 Java ,還要有高超的前端水平
  • 前端和後端相互依賴,前端須要等待後端完成,後端也依賴前端完成,才能進行有效的測試
  • 代碼難以複用

正由於上面的種種弊端,因此很快這種方式就被 Servlet + JSP + Java Bean 所替代了,早期的 MVC 模型(Model2)就像下圖這樣:php

 
 

首先用戶的請求會到達 Servlet,而後根據請求調用相應的 Java Bean,並把全部的顯示結果交給 JSP 去完成,這樣的模式咱們就稱爲 MVC 模式。html

  • M 表明 模型(Model)
    模型是什麼呢? 模型就是數據,就是 dao,bean
  • V 表明 視圖(View)
    視圖是什麼呢? 就是網頁, JSP,用來展現模型中的數據
  • C 表明 控制器(controller)
    控制器是什麼? 控制器的做用就是把不一樣的數據(Model),顯示在不一樣的視圖(View)上,Servlet 扮演的就是這樣的角色。

擴展閱讀:Web開發模式前端

Spring MVC 的架構

爲解決持久層中一直未處理好的數據庫事務的編程,又爲了迎合 NoSQL 的強勢崛起,Spring MVC 給出了方案:java

 
 

傳統的模型層被拆分爲了業務層(Service)和數據訪問層(DAO,Data Access Object)。在 Service 下能夠經過 Spring 的聲明式事務操做數據訪問層,而在業務層上還容許咱們訪問 NoSQL ,這樣就可以知足異軍突起的 NoSQL 的使用了,它能夠大大提升互聯網系統的性能。web

  • 特色:
    結構鬆散,幾乎能夠在 Spring MVC 中使用各種視圖
    鬆耦合,各個模塊分離
    與 Spring 無縫集成

Hello Spring MVC

讓咱們來寫一下咱們的第一個 Spring MVC 程序:spring

第一步:在 IDEA 中新建 Spring MVC 項目

 
 

而且取名爲 【HelloSpringMVC】,點擊【Finish】:數據庫

 
 

IDEA 會自動幫咱們下載好必要的 jar 包,而且爲咱們建立好一些默認的目錄和文件,建立好之後項目結構以下:編程

 
 

第二步:修改 web.xml

咱們打開 web.xml ,按照下圖完成修改:後端

 
 

<url-pattern>元素的值改成 / ,表示要攔截全部的請求,並交由Spring MVC的後臺控制器來處理,改完以後:

<servlet-mapping> <servlet-name>dispatcher</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping> 

第三步:編輯 dispatcher-servlet.xml

這個文件名的開頭 dispatcher 與上面 web.xml 中的 <servlet-name> 元素配置的 dispatcher 對應,這是 Spring MVC 的映射配置文件(xxx-servlet.xml),咱們編輯以下:

<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="simpleUrlHandlerMapping" class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping"> <property name="mappings"> <props> <!-- /hello 路徑的請求交給 id 爲 helloController 的控制器處理--> <prop key="/hello">helloController</prop> </props> </property> </bean> <bean id="helloController" class="controller.HelloController"></bean> </beans> 

第四步:編寫 HelloController

在 Package【controller】下建立 【HelloController】類,並實現 org.springframework.web.servlet.mvc.Controller 接口:

package controller; import org.springframework.web.servlet.ModelAndView; import org.springframework.web.servlet.mvc.Controller; public class HelloController implements Controller{ @Override public ModelAndView handleRequest(javax.servlet.http.HttpServletRequest httpServletRequest, javax.servlet.http.HttpServletResponse httpServletResponse) throws Exception { return null; } } 
  • 出現了問題: javax.servlet 包找不到
  • 解決: 將本地 Tomcat 服務器的目錄下【lib】文件夾下的 servlet-api.jar 包拷貝到工程【lib】文件夾下,添加依賴

Spring MVC 經過 ModelAndView 對象把模型和視圖結合在一塊兒

ModelAndView mav = new ModelAndView("index.jsp"); mav.addObject("message", "Hello Spring MVC"); 

這裏表示視圖的是index.jsp
模型數據的是 message,內容是 「Hello Spring MVC」

package controller; import org.springframework.web.servlet.ModelAndView; import org.springframework.web.servlet.mvc.Controller; public class HelloController implements Controller { public ModelAndView handleRequest(javax.servlet.http.HttpServletRequest httpServletRequest, javax.servlet.http.HttpServletResponse httpServletResponse) throws Exception { ModelAndView mav = new ModelAndView("index.jsp"); mav.addObject("message", "Hello Spring MVC"); return mav; } } 

第五步:準備 index.jsp

將 index.jsp 的內容修改成:

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" isELIgnored="false"%> <h1>${message}</h1> 

內容很簡單,用El表達式顯示 message 的內容。

第六步:部署 Tomcat 及相關環境

在【Run】菜單項下找到【Edit Configurations】

 
 

配置 Tomcat 環境:

 
 

選擇好本地的 Tomcat 服務器,並改好名字:

 
 

在 Deployment 標籤頁下完成以下操做:

 
 

點擊 OK 就行了,咱們點擊右上角的三角形將 Tomcat 服務器運行起來。

  • 出現的問題: Tomcat 服務器沒法正常啓動
  • 緣由: Tomcat 服務器找不到相關的 jar 包
  • 解決方法: 將【lib】文件夾整個剪貼到【WEB-INF】下,並從新創建依賴:
 
 

第七步:重啓服務器

重啓服務器,輸入地址:localhost/hello

 
 

參考資料:Spring MVC 教程(how2j.cn)


跟蹤 Spring MVC 的請求

每當用戶在 Web 瀏覽器中點擊連接或者提交表單的時候,請求就開始工做了,像是郵遞員同樣,從離開瀏覽器開始到獲取響應返回,它會經歷不少站點,在每個站點都會留下一些信息同時也會帶上其餘信息,下圖爲 Spring MVC 的請求流程:

 
 

第一站:DispatcherServlet

從請求離開瀏覽器之後,第一站到達的就是 DispatcherServlet,看名字這是一個 Servlet,經過 J2EE 的學習,咱們知道 Servlet 能夠攔截並處理 HTTP 請求,DispatcherServlet 會攔截全部的請求,而且將這些請求發送給 Spring MVC 控制器。

<servlet> <servlet-name>dispatcher</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>dispatcher</servlet-name> <!-- 攔截全部的請求 --> <url-pattern>/</url-pattern> </servlet-mapping> 
  • DispatcherServlet 的任務就是攔截請求發送給 Spring MVC 控制器。

第二站:處理器映射(HandlerMapping)

  • 問題:典型的應用程序中可能會有多個控制器,這些請求到底應該發給哪個控制器呢?

因此 DispatcherServlet 會查詢一個或多個處理器映射來肯定請求的下一站在哪裏,處理器映射會根據請求所攜帶的 URL 信息來進行決策,例如上面的例子中,咱們經過配置 simpleUrlHandlerMapping 來將 /hello 地址交給 helloController 處理:

<bean id="simpleUrlHandlerMapping" class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping"> <property name="mappings"> <props> <!-- /hello 路徑的請求交給 id 爲 helloController 的控制器處理--> <prop key="/hello">helloController</prop> </props> </property> </bean> <bean id="helloController" class="controller.HelloController"></bean> 

第三站:控制器

一旦選擇了合適的控制器, DispatcherServlet 會將請求發送給選中的控制器,到了控制器,請求會卸下其負載(用戶提交的請求)等待控制器處理完這些信息:

public ModelAndView handleRequest(javax.servlet.http.HttpServletRequest httpServletRequest, javax.servlet.http.HttpServletResponse httpServletResponse) throws Exception { // 處理邏輯 .... } 

第四站:返回 DispatcherServlet

當控制器在完成邏輯處理後,一般會產生一些信息,這些信息就是須要返回給用戶並在瀏覽器上顯示的信息,它們被稱爲模型(Model)。僅僅返回原始的信息時不夠的——這些信息須要以用戶友好的方式進行格式化,通常會是 HTML,因此,信息須要發送給一個視圖(view),一般會是 JSP。

控制器所作的最後一件事就是將模型數據打包,而且表示出用於渲染輸出的視圖名(邏輯視圖名)。它接下來會將請求連同模型和視圖名發送回 DispatcherServlet。

public ModelAndView handleRequest(javax.servlet.http.HttpServletRequest httpServletRequest, javax.servlet.http.HttpServletResponse httpServletResponse) throws Exception { // 處理邏輯 .... // 返回給 DispatcherServlet return mav; } 

第五站:視圖解析器

這樣以來,控制器就不會和特定的視圖相耦合,傳遞給 DispatcherServlet 的視圖名並不直接表示某個特定的 JSP。(實際上,它甚至不能肯定視圖就是 JSP)相反,它傳遞的僅僅是一個邏輯名稱,這個名稱將會用來查找產生結果的真正視圖。

DispatcherServlet 將會使用視圖解析器(view resolver)來將邏輯視圖名匹配爲一個特定的視圖實現,它多是也可能不是 JSP

上面的例子是直接綁定到了 index.jsp 視圖

第六站:視圖

既然 DispatcherServlet 已經知道由哪一個視圖渲染結果了,那請求的任務基本上也就完成了。

它的最後一站是視圖的實現,在這裏它交付模型數據,請求的任務也就完成了。視圖使用模型數據渲染出結果,這個輸出結果會經過響應對象傳遞給客戶端。

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" isELIgnored="false"%> <h1>${message}</h1> 

使用註解配置 Spring MVC

上面咱們已經對 Spring MVC 有了必定的瞭解,而且經過 XML 配置的方式建立了第一個 Spring MVC 程序,咱們來看看基於註解應該怎麼完成上述程序的配置:

第一步:爲 HelloController 添加註解

package controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.servlet.ModelAndView; @Controller public class HelloController{ @RequestMapping("/hello") public ModelAndView handleRequest(javax.servlet.http.HttpServletRequest httpServletRequest, javax.servlet.http.HttpServletResponse httpServletResponse) throws Exception { ModelAndView mav = new ModelAndView("index.jsp"); mav.addObject("message", "Hello Spring MVC"); return mav; } } 

把實現的接口也給去掉。

  • 簡單解釋一下:
  • @Controller 註解:
    很明顯,這個註解是用來聲明控制器的,但實際上這個註解對 Spring MVC 自己的影響並不大。(Spring 實戰說它僅僅是輔助實現組件掃描,能夠用 @Component 註解代替,但我本身嘗試了一下並不行,由於上述例子沒有配置 JSP 視圖解析器我還本身配了一個仍沒有成功...)
  • @RequestMapping 註解:
    很顯然,這就表示路徑 /hello 會映射到該方法上

第二步:取消以前的 XML 註釋

在 dispatcher-servlet.xml 文件中,註釋掉以前的配置,而後增長一句組件掃描:

<?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" 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"> <!--<bean id="simpleUrlHandlerMapping"--> <!--class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">--> <!--<property name="mappings">--> <!--<props>--> <!--&lt;!&ndash; /hello 路徑的請求交給 id 爲 helloController 的控制器處理&ndash;&gt;--> <!--<prop key="/hello">helloController</prop>--> <!--</props>--> <!--</property>--> <!--</bean>--> <!--<bean id="helloController" class="controller.HelloController"></bean>--> <!-- 掃描controller下的組件 --> <context:component-scan base-package="controller"/> </beans> 

第三步:重啓服務器

當配置完成,從新啓動服務器,輸入 localhost/hello 地址仍然能看到效果:

 
 

@RequestMapping 註解細節

若是 @RequestMapping 做用在類上,那麼就至關因而給該類全部配置的映射地址前加上了一個地址,例如:

@Controller @RequestMapping("/wmyskxz") public class HelloController { @RequestMapping("/hello") public ModelAndView handleRequest(....) throws Exception { .... } } 
  • 則訪問地址: localhost/wmyskxz/hello

配置視圖解析器

還記得咱們 Spring MVC 的請求流程嗎,視圖解析器負責定位視圖,它接受一個由 DispaterServlet 傳遞過來的邏輯視圖名來匹配一個特定的視圖。

  • 需求: 有一些頁面咱們不但願用戶用戶直接訪問到,例若有重要數據的頁面,例若有模型數據支撐的頁面。
  • 形成的問題:
    咱們能夠在【web】根目錄下放置一個【test.jsp】模擬一個重要數據的頁面,咱們什麼都不用作,從新啓動服務器,網頁中輸入 localhost/test.jsp 就可以直接訪問到了,這會形成數據泄露...
    另外咱們能夠直接輸入 localhost/index.jsp 試試,根據咱們上面的程序,這會是一個空白的頁面,由於並無獲取到 ${message} 參數就直接訪問了,這會影響用戶體驗

解決方案

咱們將咱們的 JSP 文件配置在【WEB-INF】文件夾中的【page】文件夾下,【WEB-INF】是 Java Web 中默認的安全目錄,是不容許用戶直接訪問的(也就是你說你經過 localhost/WEB-INF/ 這樣的方式是永遠訪問不到的)

可是咱們須要將這告訴給視圖解析器,咱們在 dispatcher-servlet.xml 文件中作以下配置:

<bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="prefix" value="/WEB-INF/page/" /> <property name="suffix" value=".jsp" /> </bean> 

這裏配置了一個 Spring MVC 內置的一個視圖解析器,該解析器是遵循着一種約定:會在視圖名上添加前綴和後綴,進而肯定一個 Web 應用中視圖資源的物理路徑的。讓咱們實際來看看效果:

第一步:修改 HelloController

咱們將代碼修改一下:

 
 

第二步:配置視圖解析器:

按照上述的配置,完成:

<?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" 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"> <!--<bean id="simpleUrlHandlerMapping"--> <!--class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">--> <!--<property name="mappings">--> <!--<props>--> <!--&lt;!&ndash; /hello 路徑的請求交給 id 爲 helloController 的控制器處理&ndash;&gt;--> <!--<prop key="/hello">helloController</prop>--> <!--</props>--> <!--</property>--> <!--</bean>--> <!--<bean id="helloController" class="controller.HelloController"></bean>--> <!-- 掃描controller下的組件 --> <context:component-scan base-package="controller"/> <bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="prefix" value="/WEB-INF/page/" /> <property name="suffix" value=".jsp" /> </bean> </beans> 

第三步:剪貼 index.jsp 文件

在【WEB-INF】文件夾下新建一個【page】文件夾,並將【index.jsp】文件剪貼到裏面:

 
 

第四步:更新資源重啓服務器

訪問 localhost/hello 路徑,看到正確效果:

 
 

 

  • 原理:
 
 

咱們傳入的邏輯視圖名爲 index ,再加上 「/WEB-INF/page/」 前綴和 「.jsp」 後綴,就能肯定物理視圖的路徑了,這樣咱們之後就能夠將全部的視圖放入【page】文件夾下了!

  • 注意:此時的配置僅是 dispatcher-servlet.xml 下的

控制器接收請求數據

使用控制器接收參數每每是 Spring MVC 開發業務邏輯的第一步,爲探索 Spring MVC 的傳參方式,爲此咱們先來建立一個簡單的表單用於提交數據:

<!DOCTYPE html> <%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" import="java.util.*" isELIgnored="false"%> <html> <head> <meta charset="utf-8"> <title>Spring MVC 傳參方式</title> </head> <body> <form action="/param" role="form"> 用戶名:<input type="text" name="userName"><br/> 密碼:<input type="text" name="password"><br/> <input type="submit" value="提 交"> </form> </body> </html> 

醜就醜點兒吧,咱們就是來測試一下:

 
 

使用 Servlet 原生 API 實現:

咱們很容易知道,表單會提交到 /param 這個目錄,咱們先來使用 Servlet 原生的 API 來看看能不能獲取到數據:

@RequestMapping("/param") public ModelAndView getParam(HttpServletRequest request, HttpServletResponse response) { String userName = request.getParameter("userName"); String password = request.getParameter("password"); System.out.println(userName); System.out.println(password); return null; } 

測試成功:

 
 

使用同名匹配規則

咱們能夠把方法定義的形參名字設置成和前臺傳入參數名同樣的方法,來獲取到數據(同名匹配規則):

@RequestMapping("/param") public ModelAndView getParam(String userName, String password) { System.out.println(userName); System.out.println(password); return null; } 

測試成功:

 
 
  • 問題: 這樣又會和前臺產生很強的耦合,這是咱們不但願的
  • 解決: 使用 @RequestParam("前臺參數名") 來注入:
 
 
  • @RequestParam 註解細節:
    該註解有三個變量:valuerequireddefaultvalue
  • value :指定 name 屬性的名稱是什麼,value 屬性均可以默認不寫
  • required :是否必需要有該參數,能夠設置爲【true】或者【false】
  • defaultvalue :設置默認值

使用模型傳參

  • 要求: 前臺參數名字必須和模型中的字段名同樣

讓咱們先來爲咱們的表單建立一個 User 模型:

package pojo; public class User { String userName; String password; /* getter and setter */ } 

而後測試仍然成功:

 
 

中文亂碼問題

  • 注意: 跟 Servlet 中的同樣,該方法只對 POST 方法有效(由於是直接處理的 request)

咱們能夠經過配置 Spring MVC 字符編碼過濾器來完成,在 web.xml 中添加:

<filter> <filter-name>CharacterEncodingFilter</filter-name> <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class> <init-param> <param-name>encoding</param-name> <!-- 設置編碼格式 --> <param-value>utf-8</param-value> </init-param> </filter> <filter-mapping> <filter-name>CharacterEncodingFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> 

控制器回顯數據

經過上面,咱們知道了怎麼接受請求數據,並能解決 POST 亂碼的問題,那麼咱們怎麼回顯數據呢?爲此咱們在【page】下建立一個【test2.jsp】:

<!DOCTYPE html> <%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" import="java.util.*" isELIgnored="false" %> <html> <head> <title>Spring MVC 數據回顯</title> </head> <body> <h1>回顯數據:${message}</h1> </body> </html> 

使用 Servlet 原生 API 來實現

咱們先來測試一下 Servlet 原生的 API 是否能完成這個任務:

@RequestMapping("/value") public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) { request.setAttribute("message","成功!"); return new ModelAndView("test1"); } 

在瀏覽器地址欄中輸入:localhost/value 測試

 
 

使用 Spring MVC 所提供的 ModelAndView 對象

 
 

使用 Model 對象

在 Spring MVC 中,咱們一般都是使用這樣的方式來綁定數據,

 
 
  • 使用 @ModelAttribute 註解:
@ModelAttribute public void model(Model model) { model.addAttribute("message", "註解成功"); } @RequestMapping("/value") public String handleRequest() { return "test1"; } 

這樣寫就會在訪問控制器方法 handleRequest() 時,會首先調用 model() 方法將 message 添加進頁面參數中去,在視圖中能夠直接調用,可是這樣寫會致使該控制器全部的方法都會首先調用 model() 方法,但一樣的也很方便,由於能夠加入各類各樣的數據。


客戶端跳轉

前面不論是地址 /hello 跳轉到 index.jsp 仍是 /test 跳轉到 test.jsp,這些都是服務端的跳轉,也就是 request.getRequestDispatcher("地址").forward(request, response);

那咱們如何進行客戶端跳轉呢?咱們繼續在 HelloController 中編寫:

@RequestMapping("/hello") public ModelAndView handleRequest(javax.servlet.http.HttpServletRequest httpServletRequest, javax.servlet.http.HttpServletResponse httpServletResponse) throws Exception { ModelAndView mav = new ModelAndView("index"); mav.addObject("message", "Hello Spring MVC"); return mav; } @RequestMapping("/jump") public ModelAndView jump() { ModelAndView mav = new ModelAndView("redirect:/hello"); return mav; } 

咱們使用 redirect:/hello 就表示咱們要跳轉到 /hello 這個路徑,咱們重啓服務器,在地址欄中輸入:localhost/jump ,會自動跳轉到 /hello 路徑下:

 
 

也能夠這樣用:

@RequestMapping("/jump") public String jump() { return "redirect: ./hello"; } 

文件上傳

咱們先來回顧一下傳統的文件上傳和下載:這裏

咱們再來看一下在 Spring MVC 中如何實現文件的上傳和下載

  • 注意: 須要先導入 commons-io-1.3.2.jarcommons-fileupload-1.2.1.jar 兩個包

第一步:配置上傳解析器

在 dispatcher-servlet.xml 中新增一句:

<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver"/> 

開啓對上傳功能的支持

第二步:編寫 JSP

文件名爲 upload.jsp,仍建立在【page】下:

<%@ page contentType="text/html;charset=UTF-8" language="java" %> <html> <head> <title>測試文件上傳</title> </head> <body> <form action="/upload" method="post" enctype="multipart/form-data"> <input type="file" name="picture"> <input type="submit" value="上 傳"> </form> </body> </html> 

第三步:編寫控制器

在 Package【controller】下新建【UploadController】類:

package controller; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.multipart.MultipartFile; import org.springframework.web.servlet.ModelAndView; @Controller public class UploadController { @RequestMapping("/upload") public void upload(@RequestParam("picture") MultipartFile picture) throws Exception { System.out.println(picture.getOriginalFilename()); } @RequestMapping("/test2") public ModelAndView upload() { return new ModelAndView("upload"); } } 

第四步:測試

在瀏覽器地址欄中輸入:localhost/test2 ,選擇文件點擊上傳,測試成功:

 
 

參考資料:

歡迎轉載,轉載請註明出處!轉載自@我沒有三顆心臟

相關文章
相關標籤/搜索