1. 程式人生 > 實用技巧 >springmvc筆記 狂神說

springmvc筆記 狂神說

SpringMVC

1.回顧JavaWeb-MVC

  1. View: 負責顯示資料。eg: jsp
  2. Model層:負責實現業務,訪問資料庫 eg: service dao
  3. Controller層:eg: servlet
    1. 從View層取得表單資料
    2. 把表單資料提供給Model層
    3. 獲取Model層返回的資料,並帶著資料請求轉發或者重定向到新的頁面

回顧servlet

  1. 編寫前端資料

    1. index.jsp

      <body>
      <form action="${pageContext.request.contextPath}/hello" method="get">
        <label>
          <input type="text" name="method" value="add"/>
        </label>
        <input type="submit">
      </form>
      </body>
      
    2. hello.jsp

      <body>
       ${msg}
      </body>
      
  2. 編寫servlet

    public class HelloServlet extends HttpServlet {
        @Override
        protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
            //獲取前端資料
            String method = req.getParameter("method");
            //進行資料處理,通常交給service和dao層去完成
            
            
            
            
            
            if (method.equals("add")){
                req.getSession().setAttribute("msg","執行了add方法");
            }
            if (method.equals("delete")){
                req.getSession().setAttribute("msg","執行了delete方法");
            }
            //請求轉發到其他頁面
            req.getRequestDispatcher("/WEB-INF/jsp/hello.jsp").forward(req,resp);
        }
    
        @Override
        protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
            doGet(req, resp);
        }
    }
    
  3. 在web配置檔案中註冊servlet

  4. 啟動測試

2.hello-SpringMVC

2.1 什麼是springMVC

Spring MVC是Spring Framework的一部分,是基於Java實現MVC的輕量級Web框架。

檢視官方文件:https://docs.spring.io/spring/docs/5.2.0.RELEASE/spring-framework-reference/web.html#spring-web

我們為什麼要學習SpringMVC呢?

Spring MVC的特點:

  1. 輕量級,簡單易學
  2. 高效 , 基於請求響應的MVC框架
  3. 與Spring相容性好,無縫結合
  4. 約定優於配置
  5. 功能強大:RESTful、資料驗證、格式化、本地化、主題等
  6. 簡潔靈活

Spring的web框架圍繞DispatcherServlet [ 排程Servlet ] 設計。

DispatcherServlet的作用是將請求分發到不同的處理器。從Spring 2.5開始,使用Java 5或者以上版本的使用者可以採用基於註解形式進行開發,十分簡潔;正因為SpringMVC好 , 簡單 , 便捷 , 易學 , 天生和Spring無縫整合(使用SpringIoC和Aop) , 使用約定優於配置 . 能夠進行簡單的junit測試 . 支援Restful風格 .異常處理 , 本地化 , 國際化 , 資料驗證 , 型別轉換 , 攔截器 等等......所以我們要學習 。

最重要的一點還是用的人多 , 使用的公司多.

2.2 中心控制器

Spring的web框架圍繞DispatcherServlet設計。DispatcherServlet的作用是將請求分發到不同的處理器。從Spring 2.5開始,使用Java 5或者以上版本的使用者可以採用基於註解的controller宣告方式。

Spring MVC框架像許多其他MVC框架一樣, 以請求為驅動 , 圍繞一箇中心Servlet分派請求及提供其他功能DispatcherServlet是一個實際的Servlet (它繼承自HttpServlet 基類)

SpringMVC的原理如下圖所示:

​ 當發起請求時被前置的控制器攔截到請求,根據請求引數生成代理請求,找到請求對應的實際控制器,控制器處理請求,建立資料模型,訪問資料庫,將模型響應給中心控制器,控制器使用模型與檢視渲染檢視結果,將結果返回給中心控制器,再將結果返回給請求者。

2.3 springMVC 的第一個程式

  1. 在web.xml中配置dispacterServelet

    <!--1.註冊DispatcherServlet-->
    <servlet>
        <servlet-name>springmvc</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <!--關聯一個springmvc的配置檔案:【servlet-name】-servlet.xml-->
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>classpath:springmvc-servlet.xml</param-value>
        </init-param>
        <!--啟動級別-1-->
        <load-on-startup>1</load-on-startup>
    </servlet>
    
    <!--/ 匹配所有的請求;(不包括.jsp)-->
    <!--/* 匹配所有的請求;(包括.jsp)-->
    <servlet-mapping>
        <servlet-name>springmvc</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>
    
  2. 編寫springmvc-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 class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping"/>
        <!--處理器介面卡-->
        <bean class="org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter"/>
        <!--檢視解析器:DispatcherServlet給他的ModelAndView-->
        <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver" id="InternalResourceViewResolver">
            <!--字首-->
            <property name="prefix" value="/WEB-INF/jsp/"/>
            <!--字尾-->
            <property name="suffix" value=".jsp"/>
        </bean>
        <!--註冊handler-->
        <bean id="/hello" class="com.iandf.controller.HelloController"/>
    </beans>
    
  3. 編寫Controller

    public class HelloController implements Controller {
        public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
            //建立一個模型檢視
            ModelAndView mv = new ModelAndView();
            //封裝物件
            mv.addObject("msg", "HelloMVC");
            //封裝要跳轉的檢視
            mv.setViewName("hello");//  /WEB-INF/jsp/hello.jsp 由InternalResourceViewResolver獲取完整路徑
            return mv;
        }
    }
    

原生開發的步驟為

  1. 編寫servlet
  2. 在web.xml檔案中註冊servlet

springMVC開發步驟

  1. 在web.xml檔案中配置DispactherServlet
  2. 編寫springmvc-servlet.xml 配置對映器和介面卡和檢視直譯器
  3. 編寫Controller
  4. 註冊編寫完成的Controller.在對映器為BeanNameUrlHandlerMapping的情況下Controller的id屬性相當於servlet-mapping的url-pattern屬性,根據這個id找到對應的Controller

2.4 分析以上程式的執行流程

  1. DispatcherServlet表示前置控制器,是整個SpringMVC的控制中心。使用者發出請求,DispatcherServlet接收請求並攔截請求。

    我們假設請求的url為 : http://localhost:8080/SpringMVC/hello

    如上url拆分成三部分:

    1. http://localhost:8080伺服器域名
    2. SpringMVC部署在伺服器上的web站點
    3. hello表示控制器
  2. HandlerMapping為處理器對映。DispatcherServlet呼叫HandlerMapping,HandlerMapping根據請求url查詢Handler。

  3. HandlerExecution表示具體的Handler,其主要作用是根據url查詢控制器,如上url被查詢控制器為:hello。

  4. HandlerExecution將解析後的資訊傳遞給DispatcherServlet,如解析控制器對映等。

  5. HandlerAdapter表示處理器介面卡,其按照特定的規則去執行Handler。

  6. Handler讓具體的Controller執行。

  7. Controller將具體的執行資訊返回給HandlerAdapter,如ModelAndView。

  8. HandlerAdapter將檢視邏輯名或模型傳遞給DispatcherServlet。

  9. DispatcherServlet呼叫檢視解析器(ViewResolver)來解析HandlerAdapter傳遞的邏輯檢視名。

  10. 檢視解析器將解析的邏輯檢視名傳給DispatcherServlet。

  11. DispatcherServlet根據檢視解析器解析的檢視結果,呼叫具體的檢視。

  12. 最終檢視呈現給使用者。

SpringMVC的執行原理圖如下圖所示:

最終需要我們做的只有兩步

  1. 處理業務,得到資料
  2. 封裝資料和指明要轉發或者重定向的檢視

2.5 使用註解實現上面的例子

  1. 配置DispatcherServlet

    <?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">
        <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-servlet.xml</param-value>
            </init-param>
            <!-- 啟動順序,數字越小,啟動越早 -->
            <load-on-startup>1</load-on-startup>
        </servlet>
        <servlet-mapping>
            <servlet-name>springmvc</servlet-name>
            <url-pattern>/</url-pattern>
        </servlet-mapping>
    </web-app>
    
  2. 編寫springmvc-servlet.xml

    1. 開啟註解支援
    2. 自動掃描指定包下的註解
    3. 忽略包下的靜態檔案
    4. 註冊資源檢視解析器
    5. mvc:default-servlet-handler的作用
      1. springmvc會把所有的請求都當做對後臺控制器的請求,當我們對靜態資源進行訪問的時候(.html .jpg等)也會認為是對控制器的訪問,導致請求不到報404錯誤
      2. 使用<mvc:default-servlet-handler/>標籤。在 WEB 容器啟動的時候會在上下文中定義一個 DefaultServletHttpRequestHandler,它會對DispatcherServlet的請求進行處理,如果該請求已經作了對映,那麼會接著交給後臺對應的處理程式,如果沒有作對映,就交給 WEB 應用伺服器預設的 Servlet 處理,從而找到對應的靜態資源,只有再找不到資源時才會報錯。
    <?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
           https://www.springframework.org/schema/context/spring-context.xsd
           http://www.springframework.org/schema/mvc
           https://www.springframework.org/schema/mvc/spring-mvc.xsd">
    
    	<!--自動掃描指定包下的註解-->
        <context:component-scan base-package="com.iandf.controller"/>
        <!--讓spring不處理靜態資源-->
        <mvc:default-servlet-handler/>
        <!--配置註解驅動,自動配置處理對映器和處理處理器介面卡-->
        <!--
        支援mvc註解驅動
        在spring中一般採用@RequestMapping註解來完成對映關係
        要想使@RequestMapping註解生效
        必須向上下文中註冊DefaultAnnotationHandlerMapping
        和一個AnnotationMethodHandlerAdapter例項
        這兩個例項分別在類級別和方法級別處理。
        而annotation-driven配置幫助我們自動完成上述兩個例項的注入。
     -->
        <mvc:annotation-driven/>
        <!--配置檢視解析器-->
        <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver" id="internalResourceViewResolver">
            <property name="prefix" value="/WEB-INF/jsp/"/>
            <property name="suffix" value=".jsp"/>
        </bean>
    
    </beans>
    
  3. 編寫Controller

    //@Controller註解的類會自動新增到Spring上下文中
    @Controller
    public class HelloController {
        @RequestMapping("/hello")
        //對映訪問路徑
        public String hello(Model model){
            //Spring MVC會自動例項化一個Model物件用於向檢視中傳值
            model.addAttribute("msg","hello_springmvc_annotation");
            //返回檢視位置
            return "hello";
        }
    }
    

3. Controller

3.1 什麼是Controller

控制器作為應用程式邏輯的處理入口,它會負責去呼叫你已經實現的一些服務。通常,一個控制器會接收並解析使用者的請求,然後把它轉換成一個模型交給檢視,由檢視渲染出頁面最終呈現給使用者。

在註解配置成的Controller類中可以實現多個方法,意味著一個類中可以實現多個請求的業務處理。

3.2 Controller介面

Controller是一個介面,在org.springframework.web.servlet.mvc包下,介面中只有一個方法;

//實現該介面的類獲得控制器功能
public interface Controller {
   //處理請求且返回一個模型與檢視物件
   ModelAndView handleRequest(HttpServletRequest var1, HttpServletResponse var2) throws Exception;
}
  1. 可以使類實現controller介面,這是比較老的方式,這種方式實現的Controller只能包含一種方法,如果要處理多個請求就只能註冊多個Controller
  2. 可以使用註解的方式實現Controller, 可以發現,我們的兩個請求都可以指向一個檢視,但是頁面結果的結果是不一樣的,從這裡可以看出檢視是被複用的,而控制器與檢視之間是弱偶合關係。
  3. 使用註解實現Controller可以在一個Controller中處理多個請求

4. Resultful分格

4.1 Resultful風格簡介

概念

Restful就是一個資源定位及資源操作的風格。不是標準也不是協議,只是一種風格。基於這個風格設計的軟體可以更簡潔,更有層次,更易於實現快取等機制。

功能

資源:網際網路所有的事物都可以被抽象為資源

資源操作:使用POST、DELETE、PUT、GET,使用不同方法對資源進行操作。

分別對應 新增、 刪除、修改、查詢。

傳統方式操作資源 :通過不同的引數來實現不同的效果!方法單一,post 和 get

http://127.0.0.1/item/queryItem.action?id=1 查詢,GET

http://127.0.0.1/item/saveItem.action 新增,POST

http://127.0.0.1/item/updateItem.action 更新,POST

http://127.0.0.1/item/deleteItem.action?id=1 刪除,GET或POST

使用RESTful操作資源 :可以通過不同的請求方式來實現不同的效果!如下:請求地址一樣,但是功能可以不同!

http://127.0.0.1/item/1 查詢,GET

http://127.0.0.1/item 新增,POST

http://127.0.0.1/item 更新,PUT

http://127.0.0.1/item/1 刪除,DELETE

4.2 測試案例

4.2.1 使用傳統的URL進行請求

  1. 搭建環境

  2. 編寫Controller

    @Controller
    public class ResultFulController {
        @RequestMapping("/add")
        public String add(int a, int b, Model model){
            int result = a+b;
            model.addAttribute("msg",result);
            return "test";
        }
    }
    
  3. 測試

    在位址列輸入http://localhost:8080/add?a=1&b=2會得到結果。其引數名必須和int a, int b一樣否則會報錯,估計是後臺使用HttpServletRequest根據引數名去取值的,所以請求的時候必須一致。

4.2.2 使用ResultFul分格實現上面的案例

  1. 在Controller新增一個方法,接收resultful分格的請求

    @RequestMapping("/addByResultFul/{a}/{b}")
    public String addByResultFul(@PathVariable int a,@PathVariable int b, Model model){
        int result = a+b;
        model.addAttribute("msg",result);
        return "test";
    }
    
    1. 使用@PathVariable將方法引數的值繫結在URL的模板變數上
  2. 測試

    在位址列輸入http://localhost:8080/addByResultFul/1/2會自動的讓ab根據根據模板變數/addByResultFul/{a}/{b}拿到值

使用resultful分格的好處

  1. 是請求的路徑更加簡潔

  2. 獲取引數更加方便,框架會自動進行型別轉換

  3. 通過路徑對請求的引數進行約束,如果請求時引數錯誤,不會報轉換異常,而是報400,表示伺服器無法處理當前請求

提出問題:如果使用一個路徑,兩個不同的方法,引數不一樣,伺服器會自動識別引數得到結果嗎?

  1. 新增方法

    @RequestMapping("/addByResultFul/{a}/{b}")
    public String addByResultFul1(@PathVariable int a,@PathVariable String b, Model model){
        String result = a+b;
        model.addAttribute("msg",result);
        return "test";
    }
    
  2. 測試

    http://localhost:8080/addByResultFul/1/a報500錯誤Ambiguous mapping

結果:是不能的,請求路徑一樣,由於引數型別的不同伺服器是無法自動識別型別去處理業務的

4.2.3 使用method引數指定請求的型別

  1. 修改方法

    @RequestMapping(value = "/addByResultFul/{a}/{b}",method = RequestMethod.POST)
    public String addByResultFul(@PathVariable int a,@PathVariable int b, Model model){
        int result = a+b;
        model.addAttribute("msg",result);
        return "test";
    }
    
  2. 測試

    http://localhost:8080/addByResultFul/1/2報405方法不允許錯誤

Spring MVC 的 @RequestMapping 註解能夠處理 HTTP 請求的方法, 比如 GET, PUT, POST, DELETE 以及 PATCH。

所有的位址列請求預設都會是 HTTP GET 型別的。

方法級別的註解變體有如下幾個:組合註解

@GetMapping
@PostMapping
@PutMapping
@DeleteMapping
@PatchMapping

@GetMapping 是一個組合註解,平時使用的會比較多!

它所扮演的是 @RequestMapping(method =RequestMethod.GET) 的一個快捷方式。

5. 通過SpringMVC來實現轉發和重定向

重定向 , 不需要檢視解析器 , 本質就是重新請求一個新地方嘛 , 所以注意路徑問題.

可以重定向到另外一個請求實現 .

@Controller
public class ResultSpringMVC2 {
   @RequestMapping("/rsm2/t1")
   public String test1(){
       //轉發
       return "test";
  }

   @RequestMapping("/rsm2/t2")
   public String test2(){
       //重定向
       return "redirect:/index.jsp";
       //return "redirect:hello.do"; //hello.do為另一個請求/
  }

}

6. 獲取前端資料、將資料顯示到前端

6.1 獲取基本資料型別

6.1.1 URL中型別名字和方法中引數名一樣

提交資料 : http://localhost:8080/FundamentalTyp?name=iandf

處理方法 :

@RequestMapping("/FundamentalType")
    public String getFundamentalTypeData(String name, Model model){
        model.addAttribute("msg",name);
        return "test";
    }

6.1.2 URL中型別名字和方法中引數名不一樣

提交資料 : http://localhost:8080/FundamentalTyp?username=iandf

處理方法:

@RequestMapping("/FundamentalType")
public String getFundamentalTypeData(@RequestParam("username") String name, Model model){
    model.addAttribute("msg",name);
    return "test";
}

使用@RequestParam("xxx") 起別名,只要加了註解請求的URL上就要攜帶這個資料,如果不攜帶就會報400錯誤, Required String parameter 'username' is not present

6.2 獲取引用資料型別

要求提交的表單域和物件的屬性名一致 , 引數使用物件即可

  1. 實體類

    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    public class User {
        private String name;
        private int id;
    }
    
  2. 提交資料 : http://localhost:8080/referenceType?name=iandf&id=1

  3. 在Controller裡面實現這個請求

    @RequestMapping("/referenceType")
    public String getReferenceTypeData(User user, Model model){
        System.out.println(user);
        model.addAttribute("msg",user);
        return "test";
    }
    

提出問題?

如果方法中有引數名和類中欄位名一樣,會怎麼樣?

提交資料 : http://localhost:8080/referenceType?name=iandf&id=1

@RequestMapping("/referenceType")
public String getReferenceTypeData(User user, Model model,String name){
    System.out.println(user);
    System.out.println("name -> " + name);
    model.addAttribute("msg",user);
    return "test";
}

結果:會共享表單提交的資料

如果提交的資料還有id沒有name,會怎麼樣

結果:name屬性會為NULL。如果沒有id,會為基本資料型別的預設值,這裡id是int所以會為0

6.3 資料顯示到前端

6.3.1 通過ModelAndView

public class ControllerTest1 implements Controller {

   public ModelAndView handleRequest(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws Exception {
       //返回一個模型檢視物件
       ModelAndView mv = new ModelAndView();
       mv.addObject("msg","ControllerTest1");
       mv.setViewName("test");
       return mv;
  }
}

6.3.2 通過ModelMap

@RequestMapping("/hello")
public String hello(@RequestParam("username") String name, ModelMap model){
   //封裝要顯示到檢視中的資料
   //相當於req.setAttribute("name",name);
   model.addAttribute("name",name);
   System.out.println(name);
   return "hello";
}

6.3.3 通過Model

@RequestMapping("/hello")
public String hello(@RequestParam("username") String name, Model model){
   //封裝要顯示到檢視中的資料
   //相當於req.setAttribute("name",name);
   model.addAttribute("msg",name);
   System.out.println(name);
   return "test";
}

6.3.4 三種方式的對比

就對於新手而言簡單來說使用區別就是:

Model 只有寥寥幾個方法只適合用於儲存資料,簡化了新手對於Model物件的操作和理解;

ModelMap 繼承了 LinkedMap ,除了實現了自身的一些方法,同樣的繼承 LinkedMap 的方法和特性;

ModelAndView 可以在儲存資料的同時,可以進行設定返回的邏輯檢視,進行控制展示層的跳轉。

當然更多的以後開發考慮的更多的是效能和優化,就不能單單僅限於此的瞭解。

7. 亂碼問題

測試步驟:

1、我們可以在首頁編寫一個提交的表單

<form action="/e/t" method="post">
 <input type="text" name="name">
 <input type="submit">
</form>

2、後臺編寫對應的處理類

@Controller
public class Encoding {
   @RequestMapping("/e/t")
   public String test(Model model,String name){
       model.addAttribute("msg",name); //獲取表單提交的值
       return "test"; //跳轉到test頁面顯示輸入的值
  }
}

3、輸入中文測試,發現亂碼

不得不說,亂碼問題是在我們開發中十分常見的問題,也是讓我們程式猿比較頭大的問題!以前亂碼問題通過過濾器解決 , 而SpringMVC給我們提供了一個過濾器 , 可以在web.xml中配置。

<filter>
   <filter-name>encoding</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>encoding</filter-name>
   <url-pattern>/*</url-pattern>
</filter-mapping>

8. json

8.1 什麼是JSON?

  • JSON(JavaScript Object Notation, JS 物件標記) 是一種輕量級的資料交換格式,目前使用特別廣泛。
  • 採用完全獨立於程式語言的文字格式來儲存和表示資料。
  • 簡潔和清晰的層次結構使得 JSON 成為理想的資料交換語言。
  • 易於人閱讀和編寫,同時也易於機器解析和生成,並有效地提升網路傳輸效率。

在 JavaScript 語言中,一切都是物件。因此,任何JavaScript 支援的型別都可以通過 JSON 來表示,例如字串、數字、物件、陣列等。看看他的要求和語法格式:

  • 物件表示為鍵值對,資料由逗號分隔
  • 花括號儲存物件
  • 方括號儲存陣列

JSON 鍵值對是用來儲存 JavaScript 物件的一種方式,和 JavaScript 物件的寫法也大同小異,鍵/值對組合中的鍵名寫在前面並用雙引號 "" 包裹,使用冒號 : 分隔,然後緊接著值:

{"name": "QinJiang"}
{"age": "3"}
{"sex": "男"}

很多人搞不清楚 JSON 和 JavaScript 物件的關係,甚至連誰是誰都不清楚。其實,可以這麼理解:

JSON 是 JavaScript 物件的字串表示法,它使用文字表示一個 JS 物件的資訊,本質是一個字串。

var obj = {a: 'Hello', b: 'World'}; //這是一個物件,注意鍵名也是可以使用引號包裹的
var json = '{"a": "Hello", "b": "World"}'; //這是一個 JSON 字串,本質是一個字串

8.2 程式碼測試

程式碼測試

1、新建一個module ,springmvc-05-json , 新增web的支援

2、在web目錄下新建一個 json-1.html , 編寫測試內容

<!DOCTYPE html>
<html lang="en">
<head>
   <meta charset="UTF-8">
   <title>JSON_秦疆</title>
</head>
<body>

<script type="text/javascript">
   //編寫一個js的物件
   var user = {
       name:"秦疆",
       age:3,
       sex:"男"
  };
   //將js物件轉換成json字串
   var str = JSON.stringify(user);
   console.log(str);
   
   //將json字串轉換為js物件
   var user2 = JSON.parse(str);
   console.log(user2.age,user2.name,user2.sex);

</script>

</body>
</html>

3、在IDEA中使用瀏覽器開啟,檢視控制檯輸出!

8.3 在controller中返回JSON資料

Jackson應該是目前比較好的json解析工具了。

當然工具不止這一個,比如還有阿里巴巴的 fastjson 等等。

  1. 我們這裡使用Jackson,使用它需要匯入它的jar包;

    <!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-core -->
    <dependency>
       <groupId>com.fasterxml.jackson.core</groupId>
       <artifactId>jackson-databind</artifactId>
       <version>2.9.8</version>
    </dependency>
    
  2. 配置web.xml和springmvc-servlet.xml

  3. 編寫Controller

    @Controller
    public class JsonController {
        @ResponseBody
        @RequestMapping("/json")
        public String testObject(){
            User user = new User("黃鶴",1);
            ObjectMapper mapper = new ObjectMapper();
            String json = null;
            try {
                json = mapper.writeValueAsString(user);
            } catch (JsonProcessingException e) {
                e.printStackTrace();
            }
            return json;
        }
    }
    
  4. 測試發現亂碼

8.3.1 json亂碼問題的解決

通過requestMapping的produces屬性來解決亂碼問題

//produces:指定響應體返回型別和編碼
@RequestMapping(value = "/json1",produces = "application/json;charset=utf-8")

但是專案中使用到JSON的地方很多,所以每個前面加一行很繁瑣,這裡有一種統一的解決辦法。

在springmvc-servlet.xml中配置message-converters

<mvc:annotation-driven>
   <mvc:message-converters register-defaults="true">
       <bean class="org.springframework.http.converter.StringHttpMessageConverter">
           <constructor-arg value="UTF-8"/>
       </bean>
       <bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter">
           <property name="objectMapper">
               <bean class="org.springframework.http.converter.json.Jackson2ObjectMapperFactoryBean">
                   <property name="failOnEmptyBeans" value="false"/>
               </bean>
           </property>
       </bean>
   </mvc:message-converters>
</mvc:annotation-driven>

返回json字串統一解決

在類上直接使用 @RestController ,這樣子,裡面所有的方法都只會返回 json 字串了,不用再每一個都新增@ResponseBody !我們在前後端分離開發中,一般都使用 @RestController ,十分便捷!

@RestController
public class JsonController {
    @RequestMapping(value = "/json")
    public String testObject(){
        User user = new User("黃鶴",1);
        ObjectMapper mapper = new ObjectMapper();
        String json = null;
        try {
            json = mapper.writeValueAsString(user);
        } catch (JsonProcessingException e) {
            e.printStackTrace();
        }
        return json;
    }
}

8.3.2 測試集合輸出

@RequestMapping(value = "/json1")
public String testList() {
    List<User> users = new ArrayList<User>();
    User user = new User("黃鶴", 1);
    users.add(user);
    user = new User("黃鶴", 2);
    users.add(user);
    user = new User("黃鶴", 3);
    users.add(user);
    user = new User("黃鶴", 4);
    users.add(user);

    ObjectMapper mapper = new ObjectMapper();
    String json = null;
    try {
        json = mapper.writeValueAsString(users);
    } catch (JsonProcessingException e) {
        e.printStackTrace();
    }
    return json;
}

測試結果

8.3.3 測試時間輸出

@RequestMapping(value = "/json2")
public String testDate() {
    Date date = new Date();
    SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    ObjectMapper mapper = new ObjectMapper();
    String json = null;
    try {
        json = mapper.writeValueAsString(format.format(date));
    } catch (JsonProcessingException e) {
        e.printStackTrace();
    }
    return json;
}

測試結果

也可以使用jackson自帶的屬性來設定時間格式,預設格式是時間戳是1970年1月1日到當前日期的毫秒數!

ObjectMapper mapper = new ObjectMapper();
//不使用時間差的方式
mapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
//自定義日期格式物件
SimpleDateFormat sdf = new SimpleDateFormat(dateFormat);
//設定日期格式
mapper.setDateFormat(sdf);

8.3.4 抽取成工具類

前面三個方法中有大量重複的程式碼,我們可以抽取成一個工具類。減少冗餘的程式碼

public class JsonUtil {

    public static String getJson(Object obj){
        return getJson(obj,"yyyy-MM-dd HH:mm:ss");
    }

    public static String getJson(Object obj,String dateFormat){
        String json = "";
        ObjectMapper mapper = new ObjectMapper();
        mapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
        SimpleDateFormat sdf = new SimpleDateFormat(dateFormat);
        mapper.setDateFormat(sdf);
        try {
            json = mapper.writeValueAsString(obj);
        } catch (JsonProcessingException e) {
            e.printStackTrace();
        }
        return json;
    }
}

那上面的程式碼簡化為

@RequestMapping(value = "/json2")
public String testDate() {
    Date date = new Date();
    return JsonUtil.getJson(date);
}

9. 攔截器

9.1 interceptor簡介

SpringMVC的處理器攔截器類似於Servlet開發中的過濾器Filter,用於對處理器進行預處理和後處理。開發者可以自己定義一些攔截器來實現特定的功能。

過濾器與攔截器的區別:攔截器是AOP思想的具體應用。

過濾器

  • servlet規範中的一部分,任何java web工程都可以使用
  • 在url-pattern中配置了/*之後,可以對所有要訪問的資源進行攔截

攔截器

  • 攔截器是SpringMVC框架自己的,只有使用了SpringMVC框架的工程才能使用
  • 攔截器只會攔截訪問的控制器方法, 如果訪問的是jsp/html/css/image/js是不會進行攔截的

9.2 自定義攔截器

想要自定義攔截器,必須實現 HandlerInterceptor 介面。

public class MyInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("攔截前");
        //必須寫true,寫true會執行下一個攔截器,寫false會卡在這個攔截器上
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        System.out.println("攔截後");
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        System.out.println("處理完成後,清理");
    }
}

在applicationContext.xml中配置攔截器

<!--配置攔截器-->
<mvc:interceptors>
    <mvc:interceptor>
        <!--攔截所有請求-->
        <mvc:mapping path="/**"/>
        <bean class="com.iandf.interceptor.MyInterceptor"/>
    </mvc:interceptor>
</mvc:interceptors>

Controller測試

@RestController
public class InterceptorController {
    @RequestMapping("/i1")
    public String test(){
        System.out.println("執行請求");
        return "hello";
    }
}

開啟IDEA,請求controller測試結果為

9.3 使用攔截器驗證使用者是否登入

實現思路:

  1. 由於攔截器只會攔截通過控制器的方法,所以訪問主頁和登入頁面都走控制器
  2. 定義一個登入攔截器,如果是登入的請求或者session中已經攜帶相關資訊則放行否則進行轉發

程式碼實現

  1. index.jsp

    <body>
    <a href="${pageContext.request.contextPath}/user/goLogin">進入登入頁面</a>
    <br>
    <a href="${pageContext.request.contextPath}/user/goMain">進入首頁</a>
    </body>
    
  2. login.jsp

    <body>
    <form action="${pageContext.request.contextPath}/user/login" method="get">
      使用者名稱:<input type="text" name="userName" />
      密碼:<input type="text" name="password" />
      <input type="submit">
    </form>
    </body>
    
  3. main.jsp

    <body>
        <h1>首頁</h1>
        ${userName}
        <a href="${pageContext.request.contextPath}/user/goOut">登出</a>
    </body>
    
  4. loginController

    @RequestMapping("/user")
    @Controller
    public class LoginController {
    
        @RequestMapping("/goLogin")
        public String goLogin(){
            return "login";
        }
    
        @RequestMapping("/goMain")
        public String goMain(){
            return "main";
        }
    
        @RequestMapping("/login")
        public String login(HttpSession session,String userName, String password, Model Model){
            System.out.println("userName -> "+userName);
            System.out.println("password -> "+password);
            session.setAttribute("UserInfo",userName);
            Model.addAttribute("userName",userName);
            return "main";
        }
    
        @RequestMapping("/goOut")
        public String goOut(HttpSession session){
            session.removeAttribute("UserInfo");
            return "login";
        }
    }
    
  5. 攔截器

    //攔截所有user/xxx/xxx的請求,並判斷使用者是否登入此頁面
    public class LoginInterceptor implements HandlerInterceptor {
        @Override
        public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
            //如果是/user/login 或者是user/goLogin就放行
            if (request.getRequestURL().toString().contains("login")){
                return true;
            }
            //如果session中已經有了userInfo資訊也放行
            if (request.getSession().getAttribute("UserInfo")!= null){
                return true;
            }
    
            //如果不是上面兩種情況就轉發到登入頁面
            request.getRequestDispatcher(request.getContextPath()+"/WEB-INF/jsp/login.jsp").forward(request,response);
            return false;
        }
    }
    
  6. 註冊攔截器

    <mvc:interceptor>
        <mvc:mapping path="/user/**"/>
        <bean class="com.iandf.interceptor.LoginInterceptor"/>
    </mvc:interceptor>
    

測試結果無誤

10. 檔案的上傳和下載

10.1 檔案上傳

準備工作

檔案上傳是專案開發中最常見的功能之一 ,springMVC 可以很好的支援檔案上傳,但是SpringMVC上下文中預設沒有裝配MultipartResolver,因此預設情況下其不能處理檔案上傳工作。如果想使用Spring的檔案上傳功能,則需要在上下文中配置MultipartResolver,且id必須為

前端表單要求:為了能上傳檔案,必須將表單的method設定為POST,並將enctype設定為multipart/form-data。只有在這樣的情況下,瀏覽器才會把使用者選擇的檔案以二進位制資料傳送給伺服器;

對錶單中的 enctype 屬性做個詳細的說明:

  • application/x-www=form-urlencoded:預設方式,只處理表單域中的 value 屬性值,採用這種編碼方式的表單會將表單域中的值處理成 URL 編碼方式。
  • multipart/form-data:這種編碼方式會以二進位制流的方式來處理表單資料,這種編碼方式會把檔案域指定檔案的內容也封裝到請求引數中,不會對字元編碼。
  • text/plain:除了把空格轉換為 "+" 號外,其他字元都不做編碼處理,這種方式適用直接通過表單傳送郵件。
<form action="" enctype="multipart/form-data" method="post">
   <input type="file" name="file"/>
   <input type="submit">
</form>

一旦設定了enctype為multipart/form-data,瀏覽器即會採用二進位制流的方式來處理表單資料,而對於檔案上傳的處理則涉及在伺服器端解析原始的HTTP響應。在2003年,Apache Software Foundation釋出了開源的Commons FileUpload元件,其很快成為Servlet/JSP程式設計師上傳檔案的最佳選擇。

  • Servlet3.0規範已經提供方法來處理檔案上傳,但這種上傳需要在Servlet中完成。
  • 而Spring MVC則提供了更簡單的封裝。
  • Spring MVC為檔案上傳提供了直接的支援,這種支援是用即插即用的MultipartResolver實現的。
  • Spring MVC使用Apache Commons FileUpload技術實現了一個MultipartResolver實現類:
  • CommonsMultipartResolver。因此,SpringMVC的檔案上傳還需要依賴Apache Commons FileUpload的元件。

檔案上傳

1、匯入檔案上傳的jar包,commons-fileupload , Maven會自動幫我們匯入他的依賴包 commons-io包;

<!--檔案上傳-->
<dependency>
   <groupId>commons-fileupload</groupId>
   <artifactId>commons-fileupload</artifactId>
   <version>1.3.3</version>
</dependency>
<!--servlet-api匯入高版本的-->
<dependency>
   <groupId>javax.servlet</groupId>
   <artifactId>javax.servlet-api</artifactId>
   <version>4.0.1</version>
</dependency>

2、配置bean:multipartResolver

注意!!!這個bena的id必須為:multipartResolver , 否則上傳檔案會報400的錯誤!

<!--檔案上傳配置-->
<bean id="multipartResolver"  class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
   <!-- 請求的編碼格式,必須和jSP的pageEncoding屬性一致,以便正確讀取表單的內容,預設為ISO-8859-1 -->
   <property name="defaultEncoding" value="utf-8"/>
   <!-- 上傳檔案大小上限,單位為位元組(10485760=10M) -->
   <property name="maxUploadSize" value="10485760"/>
   <property name="maxInMemorySize" value="40960"/>
</bean>

CommonsMultipartFile 的 常用方法:

  • String getOriginalFilename():獲取上傳檔案的原名

  • InputStream getInputStream():獲取檔案流

  • void transferTo(File dest):將上傳檔案儲存到一個目錄檔案中

程式碼測試

<form action="/upload" enctype="multipart/form-data" method="post">
 <input type="file" name="file"/>
 <input type="submit" value="upload">
</form>

Controller: 有兩種方式實現檔案上傳,第二種其實是對第一種方式的封裝

@RestController
public class FileController {
    //@RequestParam("file") 將name=file控制元件得到的檔案封裝成CommonsMultipartFile 物件
    //批量上傳CommonsMultipartFile則為陣列即可
    @RequestMapping(path = "/upload",method = RequestMethod.POST)
    public String fileUpload(@RequestParam("file") CommonsMultipartFile file , HttpServletRequest request) throws IOException {
        System.out.println("fileUpload");
        //獲取檔名 : file.getOriginalFilename();
        String uploadFileName = file.getOriginalFilename();
        //如果檔名為空,直接回到首頁!
        if ("".equals(uploadFileName)){
            return "redirect:/index.jsp";
        }
        System.out.println("上傳檔名 : "+uploadFileName);

        //上傳路徑儲存設定
        String path = request.getServletContext().getRealPath("/upload");
        //如果路徑不存在,建立一個
        File realPath = new File(path);
        if (!realPath.exists()){
            realPath.mkdir();
        }
        System.out.println("上傳檔案儲存地址:"+realPath);

        InputStream is = file.getInputStream(); //檔案輸入流
        OutputStream os = new FileOutputStream(new File(realPath,uploadFileName)); //檔案輸出流

        //讀取寫出
        int len=0;
        byte[] buffer = new byte[1024];
        while ((len=is.read(buffer))!=-1){
            os.write(buffer,0,len);
            os.flush();
        }
        os.close();
        is.close();
        return "redirect:/index.jsp";
    }

    /*
     * 採用file.Transto 來儲存上傳的檔案,檔名和型別如果相同則會覆蓋原來的檔案
     */
    @RequestMapping("/upload2")
    public String  fileUpload2(@RequestParam("file") CommonsMultipartFile file, HttpServletRequest request) throws IOException {

        //上傳路徑儲存設定
        String path = request.getServletContext().getRealPath("/upload");
        File realPath = new File(path);
        if (!realPath.exists()){
            realPath.mkdir();
        }
        //上傳檔案地址
        System.out.println("上傳檔案儲存地址:"+realPath);

        //通過CommonsMultipartFile的方法直接寫檔案(注意這個時候)
        file.transferTo(new File(realPath +"/"+ file.getOriginalFilename()));

        return "redirect:/index.jsp";
    }
}

測試程式碼無誤

10.2 檔案下載

檔案下載步驟:

1、設定 response 響應頭

2、讀取檔案 -- InputStream

3、寫出檔案 -- OutputStream

4、執行操作

5、關閉流 (先開後關)

程式碼實現

@RequestMapping(value="/download")
public String downloads(HttpServletResponse response , HttpServletRequest request) throws Exception{
    //要下載的圖片地址
    String  path = request.getServletContext().getRealPath("/upload");
    String  fileName = "基礎語法.jpg";

    //1、設定response 響應頭
    response.reset(); //設定頁面不快取,清空buffer
    response.setCharacterEncoding("UTF-8"); //字元編碼
    response.setContentType("multipart/form-data"); //二進位制傳輸資料
    //設定響應頭
    response.setHeader("Content-Disposition",
            "attachment;fileName="+ URLEncoder.encode(fileName, "UTF-8"));

    File file = new File(path,fileName);
    //2、 讀取檔案--輸入流
    InputStream input=new FileInputStream(file);
    //3、 寫出檔案--輸出流
    OutputStream out = response.getOutputStream();

    byte[] buff =new byte[1024];
    int index=0;
    //4、執行 寫出操作
    while((index= input.read(buff))!= -1){
        out.write(buff, 0, index);
        out.flush();
    }
    out.close();
    input.close();
    return null;
}

程式碼測試成功