SpringMVC從認識到細化了解

首發日期:2018-11-01前端


SpringMVC的介紹


介紹:

  • SpringMVC是一個Web層的MVC框架
  • SpringMVC是基於servlet來處理web請求的,全部的請求都是先通過核心servlet再轉交給對應的控制器。
  • 它與spring同屬一家產品,能夠很好地進行整合。


執行流程

  • 請求處理:
    • 當請求過來時,先要通過核心的servlet--DispatcherServlet,DispatcherServlet把請求交給處理器映射器(HandlerMapping)解析,HandlerMapping查找與請求對應的處理器信息(Handler),處理器須要處理器適配器(HandlerAdapter)做爲運行環境,處理器適配器建立出須要的處理器,處理器處理完業務後,返回模型和視圖給處理器適配器,處理器適配器再傳遞給DispatcherServlet。DispatcherServlet將視圖信息傳遞給視圖解析器(ViewResolver),若是是邏輯視圖,視圖解析器會進行解析,而後把模型渲染到視圖中,而後跳轉視圖。若是不是邏輯視圖,則不會進行處理,而是直接經過視圖渲染數據模型。


與strut2的對比

  • SpringMVC是基於servlet的;struts2是基於filter的。
  • springMVC攔截針對方法,它的每個請求都與方法對應;struts2攔截針對控制器,每個請求先對應到控制器,再對應到方法。
  • springMVC參數經過方法傳入;參數經過類成員變量傳入。


【SpringMVC的配置有註解式配置的,也有XML配置的,因爲如今廣泛使用註解式的開發,因此這篇博文也主要講解註解式。】java



基本運行環境搭建


1.創建web工程,導入依賴包:web

  • 非Spring的包:
    • jstl標籤:jstl-1.2.jar,standard.jar【若是你的頁面須要jstl標籤庫,就加上它,否則就省去,使用它主要是用來獲取SpringMVC傳遞給頁面的數據】
    • 日誌接口:commons-logging-1.2.jar
  • Spring的包:
    • 因爲涉及核心容器,因此須要核心容器包
      • spring-beans-4.3.4.RELEASE.jar
      • spring-context-4.3.4.RELEASE.jar
      • spring-core-4.3.4.RELEASE.jar
      • spring-expression-4.3.4.RELEASE.jar
    • 須要一些AOP基礎特性:spring-aop-4.3.4.RELEASE.jar
    • 須要ContextLoaderListener:spring-web-4.3.4.RELEASE.jar
    • 須要mvc:spring-webmvc-4.3.4.RELEASE.jar
  • 【若是你會maven,也能夠嘗試使用maven搭建環境。】

【這裏只是一個基礎的包,僅僅實現簡單的springmvc功能,支持什麼切面編程之類的包都沒有。爲何說是最基礎的包,有個老哥測試過了:https://blog.csdn.net/frankcheng5143/article/details/50512340】spring


2.配置web.xml,聲明springmvc核心servlet。express

<servlet>
        <servlet-name>springmvc</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
          <param-name>contextConfigLocation</param-name>
          <!--springmvc.xml是咱們建立的springmvc核心配置文件 -->
          <param-value>classpath:springmvc.xml</param-value>
        </init-param>
  </servlet>
  <servlet-mapping>
        <servlet-name>springmvc</servlet-name>
        <url-pattern>*.action</url-pattern>
  </servlet-mapping>



基礎示例



【有註解式配置的,也有XML配置的,因爲如今廣泛使用註解式的開發,因此這篇博文也主要講解註解式。】編程

1.下載springjson

2.建立web工程導入依賴包:數組

3.在web.xml中配置前端控制器,同時要指定springmvc配置文件的位置spring-mvc

<!-- 配置前端控制器 -->
  <servlet>
    <servlet-name>springmvc</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <!-- 設置springmvc的配置文件名稱 -->
    <init-param>
      <param-name>contextConfigLocation</param-name>
      <param-value>classpath:springmvc.xml</param-value>
    </init-param>
  </servlet>
  <!-- Spring依賴核心servlet來分發請求,須要配置攔截路徑來將請求先交給servlet -->
<servlet-mapping>
    <servlet-name>springmvc</servlet-name>
    <url-pattern>*.action</url-pattern>
</servlet-mapping>


【這裏要提一下:SpringMVC也須要Spring IOC容器,但若是Spring沒有初始化IOC容器,SpringMVC也會嘗試去初始化IOC;若是你的功能不涉及Spring,那麼你能夠不初始化IOC,若是你的功能涉及到Spring,那麼你應該在web.xml中加上下面的代碼來提早初始化】

<!-- 利用監聽器來初始化Spring工廠 -->
<listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
  <!-- 配置參數,告訴核心過濾器讀取哪一個文件來建立工廠 -->
  <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath:applicationContext.xml</param-value>
  </context-param>

4.建立HelloController:HelloController用於處理請求

package work.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;
//先用註解的方式聲明是一個bean,方便springmvc管理。
@Controller
public class HelloController{
    //使用註解把/MySpringMVC01/hello.action請求與下面的方法對應起來
  @RequestMapping("hello")
    public ModelAndView hello() {
        System.out.println("僞裝在處理業務");
        //返回結果,由視圖解析器解析成視圖
        return new ModelAndView("/WEB-INF/jsp/index.jsp");
    }
}


5.在springmvc配置文件中配置組件掃描,這樣纔可以把控制器上使用註解標明的請求與控制器的映射關係告訴springmvc【這個包掃描須要context的xsd】【注意,配置文件的講解將會在很後面講,但事實上內容很少,在尚未講以前,你均可以使用下面的配置文件來作練習】:

<?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: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/mvc 
        http://www.springframework.org/schema/mvc/spring-mvc.xsd">
     <!--掃描控制器所在的包,這樣纔可以識別註解 -->
    <context:component-scan base-package="work.controller" />
    
    <mvc:annotation-driven />
        
</beans>

6.測試訪問:http://localhost:8080/MySpringMVC01/hello.action


上面的示例演示了請求是如何交給SpringMVC處理以及如何返回視圖的。這已經演示了「請求發起-請求處理-請求返回」的步驟了。



控制器的編寫


控制器建立方式:

  • 使用@Controller來註解類
  • 在配置文件中開啓包掃描

1.使用@Controller來註解類:

package work.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;

@Controller
public class HelloController{
    //RequestMapping負責將請求與處理方法對應起來
    @RequestMapping("/hello.action")
    public ModelAndView hello() {
        System.out.println("僞裝在處理業務");
        //返回結果,由視圖解析器解析成視圖
        return new ModelAndView("/WEB-INF/jsp/index.jsp");
    }
}

2.在配置文件中開啓包掃描:

<?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">
    <!-- 開啓組件掃描,使得spring可以識別出注解  -->
    <context:component-scan base-package="work.controller" />
</beans>


請求映射問題:

  • spring要知道請求與控制器的映射纔可以把提交的請求交給對應的控制器處理

    • 使用註解@RequestMapping(請求路徑)來指定處理請求的方法【要求開啓了包掃描,使得springmvc可以識別註解】
      • 給註解的value屬性賦值時不須要指定參數名,請求路徑默認就是value的值。除了value還有一些其餘參數,這會單獨列成一個知識點來說。
      • 三種請求路徑填寫方式的區別:
        • @RequestMapping("/hello.action")表明僅匹配/hello.action
        • @RequestMapping("hello")表明可匹配/hello.action或者/hello.*.action或者/hello/*.action
        • @RequestMapping("/hello")表明可匹配/hello.action或者/hello.*.action或者/hello/*.action
@Controller
      public class HelloController{
        //如下三種方式都會把/MySpringMVC01/hello.action請求與下面的方法對應起來
      //    @RequestMapping("/hello")
      //    @RequestMapping("hello")
        @RequestMapping("/hello.action")
        public ModelAndView hello() {
            System.out.println("僞裝在處理業務");
            //返回結果,由視圖解析器解析成視圖
            return new ModelAndView("/WEB-INF/jsp/index.jsp");
        }
      }


獲取請求提交的參數


經過域對象(request,response,session)獲取:

  • 在方法中添加數據域形參,經過數據域形參來獲取參數。例如:public ModelAndView save(HttpServletRequest request,HttpServletResponse response,HttpSession session),而後利用數據域對象來獲取數據:String id = request.getParameter("id");【在形參中包含的數據域對象,springmvc會幫咱們封裝到形參中】
@RequestMapping("login1")
        public ModelAndView login1(HttpServletRequest request) {
            System.out.println(request.getParameter("id"));
            return new ModelAndView("/WEB-INF/jsp/index.jsp");
        }


同名參數自動封裝:

  • 在方法上增長參數同名形參。好比,頁面中有一個name="id"的輸入框提交了數據,那麼能夠在方法中定義一個與這個輸入框提交的數值數據類型相同的、形參名與name的屬性一致的形參。public ModelAndView save(Integer id)【注意,定義了同名形參,但頁面沒有提交對應的表單項,這時候封裝的值爲null,因此若是形參的類型沒法存儲null則會報錯】

    • 頁面表單的編寫:

      <form action="login2.action" method="post">
          <input type="text" name="id" >
          <input type="submit">
      </form>
    • 控制器方法的編寫:

      @RequestMapping("login2")
          public ModelAndView login2(Integer id) {//頁面表單項名稱爲id
              System.out.println(id);
              return new ModelAndView("/WEB-INF/jsp/index.jsp");
          }
    • 同名參數自動封裝也支持POJO類中同名屬性,若是把一個POJO類對象做爲形參,當提交的表單項與POJO類對象的屬性同名時,也會把數據封裝到POJO類對象中,但要注意表單提交的name屬性必需與pojo的屬性名稱一致。表單的name要是 pojo的屬性名,若是屬性是一個對象,那麼使用 pojo的內嵌對象變量名.內嵌對象的屬性名

    • 頁面表單的編寫:

      <form action="login3.action" method="post">
          <!-- 普通屬性直接使用屬性名 -->
          <input type="text" name="id" >
          <input type="text" name="name" >
          <!-- 內嵌對象的屬性,用內嵌對象變量名.內嵌對象的屬性名 -->
          <input type="text" name="account.money" >
          <input type="submit">
      </form>
    • 控制器方法的編寫:

      @RequestMapping("login3")
          public ModelAndView login3(User user) {
              System.out.println(user.getId()+"..."+user.getName()+"..."+
              user.getAccount().getMoney());
              return new ModelAndView("/WEB-INF/jsp/index.jsp");
          }


手動映射封裝:

  • 若是形參名字與表單項名字不一樣,能夠利用註解@RequestParam來強制把表單項提交的值賦值給形參。
    • @RequestParam:定義參數綁定規則,解決參數名與形參名不一致問題。這時候參數默認不能爲空(由@RequestParam的required屬性限制,required默認爲true)。

    • //  public ModelAndView login4(@RequestParam("id") Integer uid) {//這時候必需要提交上來
          public ModelAndView login4(@RequestParam(value="id",required=false) Integer uid) {
          //把提交的表單項名爲id的值存儲到形參uid中
              System.out.println(uid);
              return new ModelAndView("/WEB-INF/jsp/index.jsp");
          }


數組、集合類型參數綁定

  • 數組類型綁定:對於多選框類型的表單項提交,因爲表單項名都同樣,一般都會使用數組來存儲,只要形參名與表單項名一致,並且類型是數組類型的,那麼提交的多個同名表單數據就會存儲到數組中。
    • 頁面的編寫:

      <form action="login5.action" method="post">
        <input type="checkbox" name="courses" value="Chinese" >語文
        <input type="checkbox" name="courses" value="English" >英語
        <input type="checkbox" name="courses" value="Math" >數學
        <input type="submit">
      </form>
    • 控制器方法的編寫:

      @RequestMapping("login5")
        public ModelAndView login5(String[] courses) {
            for (String course : courses) {
                System.out.println(course);
            }
            return new ModelAndView("/WEB-INF/jsp/index.jsp");
        }
    • 數組類型的數據封裝也是能夠封裝到POJO類對象中的,只要遵循形參命名規則便可。

  • List:
    • 而對於非同名的多選框,但又須要出現屢次時(相似批量修改多個表單項的狀況),一般使用List搭配POJO類來存儲。【注意這裏List封裝須要把List放到一個POJO類中(或者使用json方式來傳遞),它不可以直接在形參中使用同名形參封裝

    • 頁面的編寫:

      <form action="login6.action" method="post">
        1:<input type="text" name="accounts[0].money">
        2:<input type="text" name="accounts[1].money">
        3:<input type="text" name="accounts[2].money">
        <input type="submit">
      </form>
    • 控制器方法的編寫:

      @RequestMapping("login6")
        public ModelAndView login6(MContainer mcontainer) {
            //MContainer類沒什麼特別意義,它裏面有List<Account> accounts。僅做演示
            for (Account account : mcontainer.getAccounts()) {
                System.out.println(account);
            }
            return new ModelAndView("/WEB-INF/jsp/index.jsp");
        }


使用URL傳遞參數:

  • 還能夠從URL中獲取參數,這是一種Restful風格的獲取參數方式。
  • 在@RequestMapping的value中使用{}來包裹參數,而後在方法的形參中使用@PathVariable來指定形參獲取的是URL中的參數。
@RequestMapping("shop/{product}/{id}")
        public ModelAndView shopOperation(@PathVariable("product") String product,@PathVariable("id")Integer id) {
            System.out.println("product:"+product+"  id:"+id);
            return new ModelAndView("/WEB-INF/jsp/index.jsp");
        }
    //<而後訪問`http://localhost:8080/項目名/shop/food/1000.action`,就能夠得出product爲food和id的值爲1000。


傳遞參數給視圖

  • 利用request傳遞數據:在方法中添加形參HttpServletRequest request,而後在方法中調用request的API便可。

    • @RequestMapping("paramreturn1")
          public ModelAndView paramreturn1(HttpServletRequest request) {
              //把數據存到request域中
              request.setAttribute("name", "huluwa");
              return new ModelAndView("/WEB-INF/jsp/showparam.jsp");
          }


  • 經過ModelAndView傳遞數據:在代碼中new ModelAndView()而後調用addObject方法把參數添加進去(數據會存儲到request域中,在視圖中能夠用EL表達式${參數名}獲取)。ModelAndView還能夠做爲視圖返回結果,調用setName方法來設置返回的視圖。

    • @RequestMapping("paramreturn2")
          public ModelAndView paramreturn2() {
              ModelAndView mav = new ModelAndView();
              String name=new String("Robert");
              mav.addObject("name", name);
              mav.setViewName("/WEB-INF/jsp/showparam.jsp");
              return mav;
          }


  • 經過Model 傳遞數據:在方法中定義一個Model類對象的形參,利用Model類對象的addAttribute方法把參數傳遞給視圖。(數據會存儲到request域中)。

    • @RequestMapping("paramreturn3")
          public String paramreturn3(Model model) {
              String name=new String("Robert");
              model.addAttribute("name", name);
              return "/WEB-INF/jsp/showparam.jsp";
          }


  • 經過ModelMap傳遞數據:在方法中定義一個ModelMap類對象的形參,利用ModelMap類對象的addAttribute方法把參數傳遞給視圖。【ModelMap是Model的實現類】

    • @RequestMapping("paramreturn4")
          public String paramreturn3(ModelMap modelmap) {
              String name=new String("Robert");
              modelmap.addAttribute("name", name);
              return "/WEB-INF/jsp/showparam.jsp";
          }


那麼,視圖怎麼獲取返回的參數呢?

能夠經過jsp標籤、jstl標籤、el表達式來獲取。數據都存儲在request域中,可使用以下的代碼來獲取。

<c:forEach items="${itemList }" var="item">
    <tr>
            <td>${item.name }</td>
            <td>${item.price }</td>
            <td>${item.detail }</td>
    </tr>
</c:forEach>


@RequestMapping註解

  • 在前面介紹了使用@RequestMapping註解來定義請求與方法的映射關係,下面具體講一下@RequestMapping註解的使用。
  • @RequestMapping的value的值還能夠是一個數組,表明控制器的方法映射給多個請求,使得多個請求路徑都交給同一個方法來處理。

    • @RequestMapping(value= {"mapping1","mapping2"})
          public ModelAndView mapping1() {
              System.out.println("你訪問了mapping1");
              return new ModelAndView("/WEB-INF/jsp/index.jsp");//隨意跳個頁面,關鍵是上面的打印結果
          }


  • @RequestMapping註解除了能夠修飾方法,也能夠修飾類,修飾類的時候,至關於給方法下的@RequestMapping配置的請求路徑都加了一個父級目錄。

    • @Controller
      @RequestMapping("map")
      public class MappingTest {
          @RequestMapping(value= {"mapping1","mapping2"})
          public ModelAndView mapping1() {
              System.out.println("你訪問了mapping1");
              return new ModelAndView("/WEB-INF/jsp/index.jsp");//隨意跳個頁面,關鍵是上面的打印結果
          }
          //如今調用方法要訪問http://localhost:8080/MySpringMVC01/map/mapping1.action
      }


  • @RequestMapping還能夠限定請求的方式,某些方法可能僅僅想限定POST方法來請求,那麼可使用method參數來設置。若是有多種容許的請求方法,使用數組括起來。

    • //只容許post方式請求
          @RequestMapping(value="mapping3",method=RequestMethod.POST)
          public ModelAndView mapping3() {
              System.out.println("你訪問了mapping3");
              return new ModelAndView("/WEB-INF/jsp/index.jsp");//隨意跳個頁面,關鍵是上面的打印結果
          }


返回視圖

  • 能夠經過返回一個ModelAndView對象來返回視圖:

    • 手動設置視圖名稱:mav.setViewName("/WEB-INF/jsp/itemList.jsp");

    • 構造函數傳入視圖名稱:ModelAndView mav=new ModelAndView("/WEB-INF/jsp/index.jsp");

    • @Controller
      public class ViewTest {
          @RequestMapping(value="mapping3")
          public ModelAndView view1() {
              System.out.println("你訪問了mapping3");
              /*方式一,手動設置視圖名稱
              ModelAndView mav=new ModelAndView();
              mav.setViewName("/WEB-INF/jsp/index.jsp");
              return mav;
              */
              /*方式二,構造函數傳入視圖名稱
              ModelAndView mav=new ModelAndView("/WEB-INF/jsp/index.jsp");
              return mav;
              */
              return new ModelAndView("/WEB-INF/jsp/index.jsp");
          }
      }
  • 經過返回一個字符串來返回視圖,字符串要求是視圖名稱字符串。

    • @RequestMapping(value="view2")
          public String view2() {
              System.out.println("你訪問了view2");
              return "/WEB-INF/jsp/index.jsp";
          }


  • 返回void,利用request和response來進行跳轉視圖:【request和response跳轉視圖是不通過視圖解析器的】

    • 經過request轉發:

    • @RequestMapping(value="view3")
          public void view3(HttpServletRequest request,HttpServletResponse response) throws ServletException, IOException {
              System.out.println("你訪問了view3");
              request.getRequestDispatcher("/WEB-INF/jsp/index.jsp").forward(request, response);
              return;
          }
  • 經過response重定向:

  • @RequestMapping(value="view4")
      public void view4(HttpServletResponse response) throws ServletException, IOException {
          System.out.println("你訪問了view4");
          response.sendRedirect("/MySpringMVC01/login.jsp");//要注意路徑區別
          return;
      }


  • 返回以forward或redirect帶頭的字符串:

    • redirect:後面跟着的路徑能夠省去項目名,其餘都和response.sendRedirect()差很少;forward:後面跟着的路徑與在request.getRequestDispatcher()中填的差很少。【redirect:和forward:也能夠用在ModelAndView的返回視圖中】

    • @RequestMapping(value="view5")
          public String view5()  {
              System.out.println("你訪問了view5");
      //      return "forward:/WEB-INF/jsp/index.jsp";
              return "redirect:/login.jsp";//這裏能夠省去項目名
          }


  • 跳轉到控制器:【上面的示例中返回的視圖都是普通資源文件,有時候咱們會須要跳到某個控制器上】
    • 事實上,上面的幾種方式均可以用來跳轉到控制器上
      • return "forward:hello.action";
      • return "redirect:hello.action";
      • return "hello.action";
      • request.getRequestDispatcher("hello.action").forward(request, response);
      • response.sendRedirect("hello.action")
      • return new ModelAndView("hello.action");


題外話:重定向時的數據存儲問題

按之前的來講,當咱們使用重定向時,像request數據域中的數據,在新的視圖中是獲取不了的。

咱們既想實現跳轉,又想保留數據,能夠利用redirect:和ModelAndView,咱們在ModelAndView中綁定數據,並在視圖名稱前加上redirect:便可,這樣ModelAndView中的數據仍然可以獲取。



字符編碼問題

  • 字符編碼影響了數據傳遞,因此這裏提一下。
  • 在servlet中,咱們一般都須要處理請求數據和返回結果的字符編碼問題,咱們須要確保post和get方式提交的數據的可以被請求方法正確識別。而在springMVC中,它自帶了編碼處理手段。


get提交亂碼問題:

  • 因爲get提交的數據會拼接在url中,這時候解決方案主要有兩種:
    • 解決方案:編寫過濾器,把get方式提交的數據解碼成服務端能識別的數據。【若是你學過servlet,相信這個你會的】


post提交亂碼問題:

  • 在servlet中,post提交數據亂碼問題一般會使用過濾器來處理。而SpringMVC內置了一個用於處理post數據編碼問題的過濾器。這個過濾器的名字是CharacterEncodingFilter,咱們能夠在web.xml中配置它.
<filter>
        <filter-name>encoding-filter</filter-name>
        <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
        <!-- 下面的參數是編碼成什麼格式,請求的數據默認是ISO-8859-1,下面的是目標字符集 -->
        <!-- 效果是request.setCharacterEncoding(this.encoding); -->
        <init-param>
            <param-name>encoding</param-name>
            <param-value>UTF-8</param-value>
        </init-param>
    </filter>
    <filter-mapping>
        <filter-name>encoding-filter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>


向頁面返回數據亂碼問題:

  • 向頁面返回數據時有時候會發生亂碼問題,若是你使用response來返回數據,你可能須要在獲取getWriter以前,先執行下面的代碼。
response.setHeader("Content-type", "text/html;charset=UTF-8");
response.setCharacterEncoding("utf-8");


題外話:返回視圖的時候,有些jsp文件的編碼不是utf-8,並且也沒有在代碼中顯式地設置字符編碼,爲何仍是能正確編碼?

由於Spring MVC的視圖解析器幫咱們解析了jsp文件的編碼格式,並幫咱們在http響應信息中自動設置了響應的字符編碼格式。



springMVC.xml的配置

與applicationContext.xml的區別:springMVC.xml主要是針對SpringMVC,因此它的配置一般都是關於SpringMVC的配置,而一些例如屬性注入、事務的配置都交給Spring。


開啓註解掃描

只有開啓了註解掃描,SpringMVC才能識別@Controller和@RequestMapping這些註解。

<context:component-scan base-package="work.controller" />


配置註解驅動

<mvc:annotation-driven />

上面這個操做會自動註冊RequestMappingHandlerMapping與RequestMappingHandlerAdapter兩個Bean,這兩個Bean是處理器映射器和處理器適配器,若是不配置,默認狀況下的使用的是舊版的處理器映射器和處理器適配器。新版的Bean增長了很多功能,包含數據綁定支持,@NumberFormatannotation支持,@DateTimeFormat支持,@Valid支持讀寫XML的支持(JAXB)和讀寫JSON的支持(默認Jackson)等功能


配置視圖解析器

控制器返回結果給核心servlet,核心servlet把結果交給視圖解析器來進行解析。

配置視圖屬性

視圖解析器負責解析視圖,咱們能夠給視圖解析器配置一些屬性,例如前綴和後綴,若是加了後綴.jsp,那麼控制器返回的結果就會加上.jsp再進行解析。【好比加了後綴.jsp,若是返回success,那麼解析結果應該是success.jsp】

<!-- 配置視圖解析器 -->
    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <!-- 配置視圖響應的前綴 -->
        <property name="prefix" value="/WEB-INF/jsp/" />
        <!-- 配置視圖響應的後綴 -->
        <property name="suffix" value=".jsp" />
    </bean>

配置了以後:以返回字符串方式的視圖跳轉爲例,return "success"解析出的視圖結果將是/WEB-INF/jsp/success.jsp


補充

  • springMVC.xml中還能夠配置文件上傳,不過這個內容涉及控制器,因此單獨做爲一個知識點在後面講。
  • springMVC.xml中還能夠配置全局異常處理器,這涉及異常處理,因此單獨做爲一個知識點在後面講。
  • springMVC.xml能夠配置的內容不少,有興趣的能夠自查。



攔截器

  • 攔截器是用來攔截請求處處理器的,例如像用戶權限驗證就須要攔截器。
  • 攔截器能夠設置幾個方法:preHandle、postHandle、afterCompletion


使用:

自定義攔截器

1.定義一個攔截器類實現HandlerInterceptor接口中的三個方法。

  • preHandle:發生在進入業務邏輯前,能夠進行登陸攔截,權限驗證。
  • postHandle: 觸發在處理器的邏輯完成後,返回視圖以前,能夠用來設置頁面共有參數。
  • afterCompletion:發生在處理器執行完成以後,能夠用來處理異常、釋放資源、記錄日誌。
  • 【有多個攔截器的狀況下,後續的攔截器就算不放行,前面已經放行的攔截器的after仍是會執行】
public class LoginInterceptor implements HandlerInterceptor {
    @Override
    public void afterCompletion(HttpServletRequest arg0, HttpServletResponse arg1, Object arg2, Exception arg3)
            throws Exception {
            //注意這個方法有Exception形參
        System.out.println("afterCompletion執行了");
    }

    @Override
    public void postHandle(HttpServletRequest arg0, HttpServletResponse arg1, Object arg2, ModelAndView arg3)
            throws Exception {
            //注意這個方法有ModelAndView形參
        System.out.println("postHandle執行了");
    }

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object arg2) throws Exception {
        
        if(request.getSession().getAttribute("loginUser")!=null) {
            //返回true放行,返回false不放行
            return true;
        }
        response.setHeader("Content-type", "text/html;charset=UTF-8");
        response.setCharacterEncoding("utf-8");
        response.getWriter().println("你沒有權限,請登陸");
        return false;
    }
}

2.配置攔截器
定義了攔截器以後,咱們還須要把咱們定義的攔截器告訴springmvc。能夠在springmvc.xml中配置攔截器(須要注意須要引入mvc的xsd)

<!-- 開始攔截器定義,interceptors下能夠定義多個攔截器 -->
    <mvc:interceptors>
        <!-- 定義一個攔截器 -->
        <mvc:interceptor>
            <!-- path是要攔截的請求,/**表明攔截全部請求(包括二級以上目錄),/*表明攔截全部一級目錄請求 -->
            <mvc:mapping path="/**"/>
            <!-- bean的class裏面填攔截器的全限定名 -->
            <bean class="work.interceptor.LoginInterceptor"></bean>
        </mvc:interceptor>
    </mvc:interceptors>

有多個攔截器時,攔截器的執行按照配置順序。


攔截排除

用於配置不進行攔截的請求。例如用戶有多個操做,不但願他在沒有登陸的狀況進行操做,但應該容許他發起登陸與註冊請求,那麼登陸和註冊就不該該被攔截器攔截,這時候就應該使用攔截排除。

<!-- 登陸攔截器 -->
    <mvc:interceptors>
        <mvc:interceptor>
            <mvc:mapping path="/**"/>
            <!-- 配置不攔截請求的地址,path裏面是不攔截的請求 -->
            <mvc:exclude-mapping path="/user/*"/>
            <bean class="work.interceptor.LoginInterceptor"></bean>
        </mvc:interceptor>
    </mvc:interceptors>



json數據交互


  • springmvc還支持轉換json數據。
  • 若是你在springmvc.xml中配置了<mvc:annotation-driven />,那麼你可使用@ResponseBody和@RequestBody註解來與視圖進行JSON數據交互。


依賴包

【下面的依賴包是從SpringMVC4纔開始的,舊版本的話就不是下面三個】

  • jackson-annotations-2.5.0.jar
  • jackson-core-2.5.0.jar
  • jackson-databind-2.5.0.jar


使用

  • 註解@ResponseBody能夠把向視圖返回的結果對象轉成json格式的字符串響應給用戶(經過HttpMessageConverter接口轉換爲指定格式的數據),鍵名是對象的屬性名;
  • @RequestBody能夠用於接收用戶傳入json格式的字符串並轉成對象(經過HttpMessageConverter接口轉換爲指定格式的數據),若是json格式的字符串中鍵名與對象的屬性名一致,那麼就能封裝上數據。
@RequestMapping("/getCategory.action")
    @ResponseBody
    public Category getCategory(@RequestBody Category category) {
        System.out.println(category);
        return category;
    }

使用postman測試的結果:



全局異常處理


在控制器中有時候可能也會發生異常,發生異常的時候,若是不進行處理,異常會顯示到頁面上。因此咱們一般都須要在控制器中進行異常處理,但下面講到的SpringMVC支持的全局異常處理器能夠攔截控制器拋出的全部異常,也就是說能夠在全局異常處理器中統一處理異常。


定義全局異常處理器:

若是控制器沒有處理異常,那麼能夠由全局異常處理器處理異常。

1.實現接口HandlerExceptionResolver並實現resolveException方法,resolveException是用來處理異常的,返回結果是一個ModelAndView,這表明了處理完異常了能夠跳轉到一個視圖中顯示異常。

public class CategoryException implements HandlerExceptionResolver {

    @Override
    public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object hanlder,
            Exception e) {
        System.out.println(e.getStackTrace());
        String message="抱歉,系統發生了錯誤";
        ModelAndView mav = new ModelAndView();
        mav.addObject("message", message);
        mav.setViewName("/error.jsp");
        return mav;
    }

}

2.配置異常處理器

只須要把全局異常處理器配置成一個bean便可,因爲繼承了andlerExceptionResolver,SpringMVC很清楚它是什麼東西。

<bean class="work.exception.CategoryException"></bean>


自定義異常

  • 有了一個全局的異常處理器,有時候控制器內部的異常處理機制的做用就沒那麼大了(爲何這麼說,對於那些不可處理的異常,咱們不少時候都是選擇返回指定的異常信息給用戶,而如今有了全局異常處理器,咱們可讓全局異常處理器返回指定的異常信息給用戶。)
  • 那麼,控制器怎麼給全局異常處理器指定異常信息呢?能夠經過拋出攜帶異常信息的自定義異常給全局異常處理器。

1.首先,建立自定義異常類,繼承Exception,要求要有帶錯誤信息的構造方法(這樣就能構造自定義的異常信息了。)另外,最好有異常信息的getter和setter方法,這樣全局異常處理器就能夠經過getter來獲取異常信息了,否則採用默認的異常構造方式的話還要利用e.getMessage()來獲取異常。

public class MyException extends Exception {
    private String msg;//這是自定義的異常信息
    public MyException() {
        super();
    }
    public MyException(String msg) {
        super();
        this.msg = msg;
    }
    public String getMsg() {
        return msg;
    }
    public void setMsg(String msg) {
        this.msg = msg;
    }

}

2.在控制器中拋出自定義異常。

@RequestMapping("/save.action")
    public ModelAndView save(Category category) throws MyException {
        System.out.println(category);
        categoryService.save(category);
        if(true) {//這裏假設發生了異常
            throw new MyException("保存商品失敗!");
        }
        return new ModelAndView("findAll.action");
    }

3.修改全局處理器代碼,使得可以獲取自定義異常信息。

public class CategoryException implements HandlerExceptionResolver {

    @Override
    public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object hanlder,
            Exception e) {
        String message="抱歉,系統發生了錯誤";
        //獲取拋出的自定義異常信息
        if(e instanceof MyException) {
            message=((MyException)e).getMsg();;
        }
        
        ModelAndView mav = new ModelAndView();
        
        mav.addObject("message", message);
        mav.setViewName("/error.jsp");
        return mav;
    }

}



上傳文件

在開發中,或許須要上傳文件,SpringMVC也提供了很方便的上傳文件功能。


使用

1.首先導入依賴包:

  • commons.fileupload-1.2.2.jar
  • commons-io-2.4.jar

2.在springmvc.xml中配置多媒體解析器

<!-- 配置多媒體處理器 -->
    <!-- 下面的id必須是multipartResolver -->
    <bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
        <!-- 裏面能夠配置一系列的值,用於配置上傳文件的限制 -->
    </bean>

3.編寫測試頁面:
【要加上enctype="multipart/form-data",但注意這並不會影響其餘數據的封裝,由於有了多媒體處理器。】

<form class="form-inline" action="save.action" method="post" enctype="multipart/form-data">
            <!-- 省去其餘內容 -->
                <div class="form-group">
                      <label for="pimage">商品圖片</label>
                        <input type="file" class="form-control" id="pimage" placeholder="商品圖片" name="uploadFile">
                </div>
                <div class="form-group">
                            <button type="submit" class="btn btn-default">提交</button>
                        </div>
</form>

4.編寫文件上傳代碼:在形參中添加一個參數:MultipartFile uploadfile【若是是MultipartFile類的,那麼上傳的數據會自動封裝到對象中】【要求MultipartFile形參的名字與上傳項的name相同,否則須要@RequestParam強制對應】

  • 常見方法:
    • uploadfile.transferTo(File類對象):把上傳的文件寫入本地磁盤
    • uploadfile.getOriginalFilename():獲取上傳的文件名
@RequestMapping("save.action")
    public ModelAndView save(Product product,MultipartFile uploadFile) throws Exception {
        System.out.println(product);
        String name = UUID.randomUUID().toString(); //隨機獲取文件名,避免重複
        
        String oldName = uploadFile.getOriginalFilename();//獲取原文件名
        System.out.println(oldName);
        String extName = oldName.substring(oldName.lastIndexOf("."));//獲取擴展名
        
        File file=new File("D:\\upload\\"+name+extName);//文件存儲的路徑
        uploadFile.transferTo(file);//存儲文件到指定位置
        product.setPimage(name+extName);
        productService.save(product);
        ModelAndView mav= new ModelAndView();
        mav.setViewName("findAll.action");
        return mav;
    }



寫在最後:


這裏沒寫,準備後期有空寫的內容:

  • 表單驗證
  • 類型轉換與格式化
  • JConfig式配置。
  • Restful
相關文章
相關標籤/搜索