SpringMVC(2)- 非同步呼叫、非同步請求-跨域訪問、攔截器、異常處理、實用技術

Huonly發表於2020-12-08

1 非同步呼叫

1.1 傳送非同步請求(回顧)

<a href="javascript:void(0);" id="testAjax">訪問controller</a>
<script type="text/javascript" src="/js/jquery-3.3.1.min.js"></script>
<script type="text/javascript">
    $(function(){
    $("#testAjax").click(function(){ //為id="testAjax"的元件繫結點選事件
        $.ajax({ //傳送非同步呼叫
            type:"POST", //請求方式: POST請求
            url:"ajaxController", //請求引數(也就是請求內容)
            data:'ajax message', //請求引數(也就是請求內容)
            dataType:"text", //響應正文型別
            contentType:"application/text", //請求正文的MIME型別
        });
    });
});
</script>

1.2 接受非同步請求引數

 名稱: @RequestBody
 型別: 形參註解
 位置:處理器類中的方法形參前方
 作用:將非同步提交資料組織成標準請求引數格式,並賦值給形參
 範例:

@RequestMapping("/ajaxController")
public String ajaxController(@RequestBody String message){
    System.out.println(message);
    return "page.jsp";
}  
  • 註解新增到Pojo引數前方時,封裝的非同步提交資料按照Pojo的屬性格式進行關係對映
  • 註解新增到集合引數前方時,封裝的非同步提交資料按照集合的儲存結構進行關係對映
@RequestMapping("/ajaxPojoToController")
//如果處理引數是POJO,且頁面傳送的請求資料格式與POJO中的屬性對應,@RequestBody註解可以自動對映對應請求資料到POJO中
//注意:POJO中的屬性如果請求資料中沒有,屬性值為null,POJO中沒有的屬性如果請求資料中有,不進行對映
public String  ajaxPojoToController(@RequestBody User user){
    System.out.println("controller pojo :"+user);
    return "page.jsp";
}

@RequestMapping("/ajaxListToController")
//如果處理引數是List集合且封裝了POJO,且頁面傳送的資料是JSON格式的物件陣列,資料將自動對映到集合引數中
public String  ajaxListToController(@RequestBody List<User> userList){
    System.out.println("controller list :"+userList);
    return "page.jsp";
}

1.3 非同步請求接受響應資料

  • 方法返回值為Pojo時,自動封裝資料成json物件資料
@RequestMapping("/ajaxReturnJson")
@ResponseBody
public User ajaxReturnJson(){
    System.out.println("controller return json pojo...");
    User user = new User();
    user.setName("Jockme");
    user.setAge(40);
    return user;
}  
  • 方法返回值為List時,自動封裝資料成json物件陣列資料
@RequestMapping("/ajaxReturnJsonList")
@ResponseBody
//基於jackon技術,使用@ResponseBody註解可以將返回的儲存POJO物件的集合轉成json陣列格式資料
public List ajaxReturnJsonList(){
    System.out.println("controller return json list...");
    User user1 = new User();
    user1.setName("Tom");
    user1.setAge(3);

    User user2 = new User();
    user2.setName("Jerry");
    user2.setAge(5);

    ArrayList al = new ArrayList();
    al.add(user1);
    al.add(user2);

    return al;
}

2 非同步請求-跨域訪問

2.1 跨域訪問介紹

  • 當通過域名A下的操作訪問域名B下的資源時,稱為跨域訪問
  • 跨域訪問時,會出現無法訪問的現象

在這裡插入圖片描述

2.2 跨域環境搭建

  • 為當前主機新增備用域名
    • 修改windows安裝目錄中的host檔案
    • 格式: ip 域名
  • 動態重新整理DNS
    • 命令: ipconfig /displaydns
    • 命令: ipconfig /flushdns

2.3 跨域訪問支援

 名稱: @CrossOrigin
 型別: 方法註解 、 類註解
 位置:處理器類中的方法上方 或 類上方
 作用:設定當前處理器方法/處理器類中所有方法支援跨域訪問
 範例:

@RequestMapping("/cross")
@ResponseBody
//使用@CrossOrigin開啟跨域訪問
//標註在處理器方法上方表示該方法支援跨域訪問
//標註在處理器類上方表示該處理器類中的所有處理器方法均支援跨域訪問
@CrossOrigin
public User cross(HttpServletRequest request){
    System.out.println("controller cross..."+request.getRequestURL());
    User user = new User();
    user.setName("Jockme");
    user.setAge(39);
    return user;
}

3 攔截器

3.1 攔截器概念

  • 請求處理過程解析

在這裡插入圖片描述

 攔截器( Interceptor)是一種動態攔截方法呼叫的機制
 作用:
1. 在指定的方法呼叫前後執行預先設定後的的程式碼
2. 阻止原始方法的執行

 核心原理: AOP思想
 攔截器鏈:多個攔截器按照一定的順序,對原始被呼叫功能進行增強

  • 攔截器VS過濾器
     歸屬不同: Filter屬於Servlet技術, Interceptor屬於SpringMVC技術
     攔截內容不同: Filter對所有訪問進行增強, Interceptor僅針對SpringMVC的訪問進行增強

在這裡插入圖片描述

3.2 自定義攔截器開發過程

  • 實現HandlerInterceptor介面

    //自定義攔截器需要實現HandleInterceptor介面
    public class MyInterceptor implements HandlerInterceptor {
        //處理器執行之前執行
        @Override
        public boolean preHandle(HttpServletRequest request,
                                 HttpServletResponse response,
                                 Object handler) throws Exception {
            System.out.println("前置執行----a1");
            //返回值為false將攔截原始處理器的執行
            //如果配置多攔截器,返回值為false將終止當前攔截器後面配置的攔截器的執行
            return true;
        }
    
        //處理器執行之後執行
        @Override
        public void postHandle(HttpServletRequest request,
                               HttpServletResponse response,
                               Object handler,
                               ModelAndView modelAndView) throws Exception {
            System.out.println("後置執行----b1");
        }
    
        //所有攔截器的後置執行全部結束後,執行該操作
        @Override
        public void afterCompletion(HttpServletRequest request,
                                    HttpServletResponse response,
                                    Object handler,
                                    Exception ex) throws Exception {
            System.out.println("完成執行----c1");
        }
    
        //三個方法的執行順序為    preHandle -> postHandle -> afterCompletion
        //如果preHandle返回值為false,三個方法僅執行preHandle
    }
    
  • 配置攔截器

配置攔截器

<mvc:interceptors>
    <mvc:interceptor>
        <mvc:mapping path="/showPage"/>
        <bean class="com.itheima.interceptor.MyInterceptor"/>
    </mvc:interceptor>
</mvc:interceptors>

注意:配置順序為先配置執行位置,後配置執行類

3.3 攔截器執行流程

在這裡插入圖片描述

3.4 攔截器配置與方法引數

3.4.1 前置處理方法

原始方法之前執行

public boolean preHandle(HttpServletRequest request,
                         HttpServletResponse response,
                         Object handler) throws Exception {
    System.out.println("preHandle");
    return true;
}
  • 引數
     request:請求物件
     response:響應物件
     handler:被呼叫的處理器物件,本質上是一個方法物件,對反射中的Method物件進行了再包裝
  • 返回值
     返回值為false,被攔截的處理器將不執行

3.4.2 後置處理方法

原始方法執行後執行,如果原始方法被攔截,則不執行

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

 引數
 modelAndView:如果處理器執行完成具有返回結果,可以讀取到對應資料與頁面資訊,並進行調整

3.4.3 完成處理方法

攔截器最後執行的方法,無論原始方法是否執行

public void afterCompletion(HttpServletRequest request,
                            HttpServletResponse response,
                            Object handler,
                            Exception ex) throws Exception {
    System.out.println("afterCompletion");
}

 引數
 ex:如果處理器執行過程中出現異常物件,可以針對異常情況進行單獨處理

3.5 攔截器配置項

<mvc:interceptors>
    <!--開啟具體的攔截器的使用,可以配置多個-->
    <mvc:interceptor>
        <!--設定攔截器的攔截路徑,支援*通配-->
        <!--/**         表示攔截所有對映-->
        <!--/*          表示攔截所有/開頭的對映-->
        <!--/user/*     表示攔截所有/user/開頭的對映-->
        <!--/user/add*  表示攔截所有/user/開頭,且具體對映名稱以add開頭的對映-->
        <!--/user/*All  表示攔截所有/user/開頭,且具體對映名稱以All結尾的對映-->
        <mvc:mapping path="/*"/>
        <mvc:mapping path="/**"/>
        <mvc:mapping path="/handleRun*"/>
        <!--設定攔截排除的路徑,配置/**或/*,達到快速配置的目的-->
        <mvc:exclude-mapping path="/b*"/>
        <!--指定具體的攔截器類-->
        <bean class="MyInterceptor"/>
    </mvc:interceptor>
</mvc:interceptors>

3.6 多攔截器配置

在這裡插入圖片描述

責任鏈模式
 責任鏈模式是一種行為模式
 特徵:
沿著一條預先設定的任務鏈順序執行,每個節點具有獨立的工作任務
 優勢:
獨立性:只關注當前節點的任務,對其他任務直接放行到下一節點
隔離性:具備鏈式傳遞特徵,無需知曉整體鏈路結構,只需等待請求到達後進行處理即可
靈活性:可以任意修改鏈路結構動態新增或刪減整體鏈路責任
解耦:將動態任務與原始任務解耦
 弊端:
鏈路過長時,處理效率低下
可能存在節點上的迴圈引用現象,造成死迴圈,導致系統崩潰

4 異常處理

4.1 異常處理器

HandlerExceptionResolver介面(異常處理器)

@Component
public class ExceptionResolver implements HandlerExceptionResolver {
    public ModelAndView resolveException(HttpServletRequest request,
                                         HttpServletResponse response,
                                         Object handler,
                                         Exception ex) {
        System.out.println("異常處理器正在執行中");
        ModelAndView modelAndView = new ModelAndView();
        //定義異常現象出現後,反饋給使用者檢視的資訊
        modelAndView.addObject("msg","出錯啦! ");
        //定義異常現象出現後,反饋給使用者檢視的頁面
        modelAndView.setViewName("error.jsp");
        return modelAndView;
    }
}

根據異常的種類不同,進行分門別類的管理,返回不同的資訊

public class ExceptionResolver implements HandlerExceptionResolver {
    @Override
    public ModelAndView resolveException(HttpServletRequest request,
                                         HttpServletResponse response,
                                         Object handler,
                                         Exception ex) {
        System.out.println("my exception is running ...."+ex);
        ModelAndView modelAndView = new ModelAndView();
        if( ex instanceof NullPointerException){
            modelAndView.addObject("msg","空指標異常");
        }else if ( ex instanceof  ArithmeticException){
            modelAndView.addObject("msg","算數運算異常");
        }else{
            modelAndView.addObject("msg","未知的異常");
        }
        modelAndView.setViewName("error.jsp");
        return modelAndView;
    }
}

4.2 註解開發異常處理器

  • 使用註解實現異常分類管理
     名稱: @ControllerAdvice
     型別: 類註解
     位置:異常處理器類上方
     作用:設定當前類為異常處理器類
     範例:
@Component
@ControllerAdvice
public class ExceptionAdvice {
}  
  • 使用註解實現異常分類管理
     名稱: @ExceptionHandler
     型別: 方法註解
     位置:異常處理器類中針對指定異常進行處理的方法上方
     作用:設定指定異常的處理方式
     範例:
     說明:處理器方法可以設定多個
@ExceptionHandler(Exception.class)
@ResponseBody
public String doOtherException(Exception ex){
   return "出錯啦,請聯絡管理員! ";
}  

4.3 異常處理解決方案

  • 異常處理方案
    • 業務異常:
       傳送對應訊息傳遞給使用者,提醒規範操作
    • 系統異常:
       傳送固定訊息傳遞給使用者,安撫使用者
       傳送特定訊息給運維人員,提醒維護
       記錄日誌
    • 其他異常:
       傳送固定訊息傳遞給使用者,安撫使用者
       傳送特定訊息給程式設計人員,提醒維護
       納入預期範圍內
       記錄日誌

4.4 自定義異常

  • 異常定義格式

    //自定義異常繼承RuntimeException,覆蓋父類所有的構造方法
    public class BusinessException extends RuntimeException {
        public BusinessException() {
        }
    
        public BusinessException(String message) {
            super(message);
        }
    
        public BusinessException(String message, Throwable cause) {
            super(message, cause);
        }
    
        public BusinessException(Throwable cause) {
            super(cause);
        }
    
        public BusinessException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
            super(message, cause, enableSuppression, writableStackTrace);
        }
    }
    
  • 異常觸發方式

    if(user.getName().trim().length()<4) {
        throw new BusinessException("使用者名稱長度必須在2-4位之間,請重新輸入! ");
    }
    
  • 通過自定義異常將所有的異常現象進行分類管理,以統一的格式對外呈現異常訊息

5 實用技術

5.1 檔案上傳下載

  • 上傳檔案過程分析

在這裡插入圖片描述

  • MultipartResolver介面

  • MultipartResolver介面定義了檔案上傳過程中的相關操作,並對通用性操作進行了封裝

  • MultipartResolver介面底層實現類CommonsMultipartResovler

  • CommonsMultipartResovler並未自主實現檔案上傳下載對應的功能,而是呼叫了apache的檔案上傳下載元件

<dependency>
    <groupId>commons-fileupload</groupId>
    <artifactId>commons-fileupload</artifactId>
    <version>1.4</version>
</dependency>
  • 檔案上傳下載實現

    • 頁面表單
    <form action="/fileupload" method="post" enctype="multipart/form-data">
        上傳LOGO: <input type="file" name="file"/><br/>
        <input type="submit" value="上傳"/>
    </form>
    
    • SpringMVC配置
    <bean id="multipartResolver"
          class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
    </bean>
    
    • 控制器
    @RequestMapping(value = "/fileupload")
    public void fileupload(MultipartFile file){
        file.transferTo(new File("file.png"));
    }
    

5.2 檔案上傳注意事項

  1. 檔案命名問題, 獲取上傳檔名,並解析檔名與副檔名
  2. 檔名過長問題
  3. 檔案儲存路徑
  4. 重名問題
@RequestMapping(value = "/fileupload")
//引數中定義MultipartFile引數,用於接收頁面提交的type=file型別的表單,要求表單名稱與引數名相同
public String fileupload(MultipartFile file,MultipartFile file1,MultipartFile file2, HttpServletRequest request) throws IOException {
    System.out.println("file upload is running ..."+file);
    //        MultipartFile引數中封裝了上傳的檔案的相關資訊
    //        System.out.println(file.getSize());
    //        System.out.println(file.getBytes().length);
    //        System.out.println(file.getContentType());
    //        System.out.println(file.getName());
    //        System.out.println(file.getOriginalFilename());
    //        System.out.println(file.isEmpty());
    //首先判斷是否是空檔案,也就是儲存空間佔用為0的檔案
    if(!file.isEmpty()){
        //如果大小在範圍要求內正常處理,否則丟擲自定義異常告知使用者(未實現)
        //獲取原始上傳的檔名,可以作為當前檔案的真實名稱儲存到資料庫中備用
        String fileName = file.getOriginalFilename();
        //設定儲存的路徑
        String realPath = request.getServletContext().getRealPath("/images");
        //儲存檔案的方法,指定儲存的位置和檔名即可,通常檔名使用隨機生成策略產生,避免檔名衝突問題
        file.transferTo(new File(realPath,file.getOriginalFilename()));
    }
    //測試一次性上傳多個檔案
    if(!file1.isEmpty()){
        String fileName = file1.getOriginalFilename();
        //可以根據需要,對不同種類的檔案做不同的儲存路徑的區分,修改對應的儲存位置即可
        String realPath = request.getServletContext().getRealPath("/images");
        file1.transferTo(new File(realPath,file1.getOriginalFilename()));
    }
    if(!file2.isEmpty()){
        String fileName = file2.getOriginalFilename();
        String realPath = request.getServletContext().getRealPath("/images");
        file2.transferTo(new File(realPath,file2.getOriginalFilename()));
    }
    return "page.jsp";
}

5.4 Restful風格配置

5.4.1 Rest

  • Rest( REpresentational State Transfer) 一種網路資源的訪問風格,定義了網路資源的訪問方式
    • 傳統風格訪問路徑
       http://localhost/user/get?id=1
       http://localhost/deleteUser?id=1
    • Rest風格訪問路徑
       http://localhost/user/1
  • Restful是按照Rest風格訪問網路資源
  • 優點
     隱藏資源的訪問行為,通過地址無法得知做的是何種操作
     書寫簡化

5.4.2 Rest行為約定方式

 GET(查詢) http://localhost/user/1 GET
 POST(儲存) http://localhost/user POST
 PUT(更新) http://localhost/user PUT
 DELETE(刪除) http://localhost/user DELETE
**注意:**上述行為是約定方式,約定不是規範,可以打破,所以稱Rest風格,而不是Rest規範

5.4.3 Restful開發入門

//設定rest風格的控制器
@RestController
//設定公共訪問路徑,配合下方訪問路徑使用
@RequestMapping("/user/")
public class UserController {

    //rest風格訪問路徑完整書寫方式
    @RequestMapping("/user/{id}")
    //使用@PathVariable註解獲取路徑上配置的具名變數,該配置可以使用多次
    public String restLocation(@PathVariable Integer id){
        System.out.println("restful is running ....");
        return "success.jsp";
    }

    //rest風格訪問路徑簡化書寫方式,配合類註解@RequestMapping使用
    @RequestMapping("{id}")
    public String restLocation2(@PathVariable Integer id){
        System.out.println("restful is running ....get:"+id);
        return "success.jsp";
    }

    //接收GET請求配置方式
    @RequestMapping(value = "{id}",method = RequestMethod.GET)
    //接收GET請求簡化配置方式
    @GetMapping("{id}")
    public String get(@PathVariable Integer id){
        System.out.println("restful is running ....get:"+id);
        return "success.jsp";
    }

    //接收POST請求配置方式
    @RequestMapping(value = "{id}",method = RequestMethod.POST)
    //接收POST請求簡化配置方式
    @PostMapping("{id}")
    public String post(@PathVariable Integer id){
        System.out.println("restful is running ....post:"+id);
        return "success.jsp";
    }

    //接收PUT請求簡化配置方式
    @RequestMapping(value = "{id}",method = RequestMethod.PUT)
    //接收PUT請求簡化配置方式
    @PutMapping("{id}")
    public String put(@PathVariable Integer id){
        System.out.println("restful is running ....put:"+id);
        return "success.jsp";
    }

    //接收DELETE請求簡化配置方式
    @RequestMapping(value = "{id}",method = RequestMethod.DELETE)
    //接收DELETE請求簡化配置方式
    @DeleteMapping("{id}")
    public String delete(@PathVariable Integer id){
        System.out.println("restful is running ....delete:"+id);
        return "success.jsp";
    }
}
<!--配置攔截器,解析請求中的引數_method,否則無法發起PUT請求與DELETE請求,配合頁面表單使用-->
<filter>
    <filter-name>HiddenHttpMethodFilter</filter-name>
    <filter-class>org.springframework.web.filter.HiddenHttpMethodFilter</filter-class>
</filter>
<filter-mapping>
    <filter-name>HiddenHttpMethodFilter</filter-name>
    <servlet-name>DispatcherServlet</servlet-name>
</filter-mapping>

 開啟SpringMVC對Restful風格的訪問支援過濾器,即可通過頁面表單提交PUT與DELETE請求
 頁面表單使用隱藏域提交請求型別,引數名稱固定為_method,必須配合提交型別method=post使用

<form action="/user/1" method="post">
    <input type="hidden" name="_method" value="PUT"/>
    <input type="submit"/>
</form>  
  • Restful請求路徑簡化配置方式
@RestController
public class UserController {
    @RequestMapping(value = "/user/{id}",method = RequestMethod.DELETE)
    public String restDelete(@PathVariable String id){
        System.out.println("restful is running ....delete:"+id);
        return "success.jsp";
    }
}  

5.5 postman工具安裝與使用

postman 是 一款可以傳送Restful風格請求的工具,方便開發除錯。首次執行需要聯網註冊

在這裡插入圖片描述

相關文章