ControllerAdvice通用異常處理


通用異常處理

在web層的方法中如果出現異常,SpringMVC會自動幫我們處理,並向前端返回500狀態碼以及錯誤信息。但是這樣的錯誤信息是不合理的,我們應該自行處理異常,讓用戶看到一個相對友好的頁面。

如何處理統一異常

我們在學習Spring的時候,了解過AOP的概念,利用AOP可以幫助我們處理全局異常。但是切面切點這些的配置比較繁瑣,SpringMVC為我們提供了簡單的異常處理的方法。

案例

項目代碼:

package com.leyou.web;

import com.leyou.pojo.Goods;
import com.leyou.service.GoodsService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * @Author: rayfoo@qq.com
 * @Date: 2020/7/6 8:41 下午
 * @Description:
 */
@RestController("goods")
public class GoodsController {

    @Autowired
    private GoodsService goodsService;

    @PostMapping
    public ResponseEntity<Goods> saveGoods(Goods goods){
        //校驗價格是否為空
        if(goods.getPrice() == null){
            throw new RuntimeException("商品價格不能為空");
        }
        Goods goods1 = goodsService.saveGoods(goods);
        return ResponseEntity.status(HttpStatus.CREATED).body(goods1);
    }

}

異常處理:

我們要使用之前需要先導入SpringMVC的jar包

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
          	<version>x.x</version>
        </dependency>

創建異常處理類:

package com.leyou.common.advice;

import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;

/**
 * @Author: rayfoo@qq.com
 * @Date: 2020/7/7 11:06 上午
 * @Description:
 */
@ControllerAdvice
public class CommonExceptionHandler {

    @ExceptionHandler(RuntimeException.class)
    public ResponseEntity<String> exceptionHandler(RuntimeException e){
        return new ResponseEntity<String>(e.getMessage(), HttpStatus.BAD_REQUEST);
    }
		//還可以聲明其他類型異常的處理。。。
}

要注意的是:異常處理類必須要被Spring掃描。

此時不同於傳統的異常只能返回500、錯誤信息含糊不清、響應體體為空。我們的統一異常處理的返回內容會更加清晰。使用postman查詢接口后,返回響應體:商品價格不能為空

優化

此時雖然能實現統一異常處理,但是狀態碼仍然存在硬編碼問題(同一類異常返回的都是同一個狀態碼),下面我們就來優化一下上面的案例:

創建異常信息枚舉

枚舉中存放了若干個該類型的實例,並且枚舉是無法實例化的,因為其構造默認是private修飾的,所以可以粗淺的理解為枚舉就是存放果然當前類個實例的類。

  • 枚舉的實例聲明可以簡寫為構造加參數的形式PRICE_COUNT_BE_NULL(400,"價格不能為空"),多個枚舉實例之間以逗號間隔,最后一個實例以分號結束。

  • 枚舉的實例必須在屬性之前聲明。

package com.leyou.common.enums;


import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;

/**
 * @Author: rayfoo@qq.com
 * @Date: 2020/7/7 11:28 上午
 * @Description: 錯誤異常信息枚舉
 */
@Getter
@NoArgsConstructor
@AllArgsConstructor
public enum  ExceptionEnums {

    PRICE_COUNT_BE_NULL(400,"價格不能為空"),
    PRICE_NAME_BE_NULL(400,"價格名稱能為空"),
    PRICE_ID_BE_NULL(400,"價格不能為空"),
    PAGE_NOT_FOUND(404,"頁面未找到")
    ;
    private Integer code;
    private String msg;
}

自定義異常

由於java提供的異常類無法封裝狀態碼信息,我們需要自定義一個異常,用於異常處理。

package com.leyou.common.exception;

import com.leyou.common.enums.ExceptionEnums;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;

/**
 * @Author: rayfoo@qq.com
 * @Date: 2020/7/7 11:37 上午
 * @Description:
 */
@Getter
@NoArgsConstructor
@AllArgsConstructor
public class MyRuntimeExcpetion extends RuntimeException{

    private ExceptionEnums exceptionEnums;

}

使用自定義異常優化異常處理類

package com.leyou.common.advice;

import com.leyou.common.enums.ExceptionEnums;
import com.leyou.common.exception.MyRuntimeExcpetion;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;

/**
 * @Author: rayfoo@qq.com
 * @Date: 2020/7/7 11:06 上午
 * @Description: 通用異常處理
 */
@ControllerAdvice
public class CommonExceptionHandler {

    @ExceptionHandler(MyRuntimeExcpetion.class)
  	@ResponseBody
    public ResponseEntity<String> exceptionHandler(MyRuntimeExcpetion ex){
        //獲取枚舉
        ExceptionEnums em = ex.getExceptionEnums();
        //返回異常信息
        return ResponseEntity.status(em.getCode()).body(em.getMsg());
    }

}

業務代碼優化

package com.leyou.web;

import com.leyou.common.enums.ExceptionEnums;
import com.leyou.common.exception.MyRuntimeExcpetion;
import com.leyou.pojo.Goods;
import com.leyou.service.GoodsService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * @Author: rayfoo@qq.com
 * @Date: 2020/7/6 8:41 下午
 * @Description:
 */
@RestController("goods")
public class GoodsController {

    @Autowired
    private GoodsService goodsService;

    @PostMapping
    public ResponseEntity<Goods> saveGoods(Goods goods){
        //校驗價格是否為空
        if(goods.getPrice() == null){
            throw new MyRuntimeExcpetion(ExceptionEnums.PRICE_COUNT_BE_NULL);
        }
        Goods goods1 = goodsService.saveGoods(goods);
        return ResponseEntity.status(HttpStatus.CREATED).body(goods1);
    }

}

此時已經可以完成狀態碼和異常信息的自定義了,但是此時對異常的信息還是不滿意,只能返回簡單的字符串提示。我們繼續優化!

封裝異常信息類

package com.leyou.common.vo;

import com.leyou.common.enums.ExceptionEnums;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

/**
 * @Author: rayfoo@qq.com
 * @Date: 2020/7/7 11:58 上午
 * @Description: 異常信息類
 */
@Data
public class ExceptionResult {

    /**
     * 狀態碼
     */
    private Integer status;

    /**
     * 異常信息
     */
    private String message;

    /**
     * 時間戳
     */
    private Long timestamp;

    public ExceptionResult(ExceptionEnums em){
        this.status = em.getCode();
        this.message = em.getMsg();
        this.timestamp = System.currentTimeMillis();
    }

}

再次優化通用異常處理

package com.leyou.common.advice;

import com.leyou.common.enums.ExceptionEnums;
import com.leyou.common.exception.MyRuntimeExcpetion;
import com.leyou.common.vo.ExceptionResult;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;

/**
 * @Author: rayfoo@qq.com
 * @Date: 2020/7/7 11:06 上午
 * @Description: 通用異常處理
 */
@ControllerAdvice
public class CommonExceptionHandler {

    @ExceptionHandler(MyRuntimeExcpetion.class)
  	@ResponseBody
    public ResponseEntity<ExceptionResult> exceptionHandler(MyRuntimeExcpetion ex){
        //獲取枚舉
        ExceptionEnums em = ex.getExceptionEnums();
        //返回異常信息
        return ResponseEntity.status(em.getCode()).body(new ExceptionResult(em));
    }

}

除外ControllerAdvice注解還有如下兩個作用

全局數據綁定

全局數據綁定功能可以用來做一些初始化的數據操作,我們可以將一些公共的數據定義在添加了 @ControllerAdvice 注解的類中,這樣,在每一個 Controller 的接口中,就都能夠訪問導致這些數據。

使用步驟,首先定義全局數據,如下:

@ControllerAdvice
public class MyGlobalExceptionHandler {
    @ModelAttribute(name = "md")
    public Map<String,Object> mydata() {
        HashMap<String, Object> map = new HashMap<>();
        map.put("age", 99);
        map.put("gender", "男");
        return map;
    }
}

使用 @ModelAttribute 注解標記該方法的返回數據是一個全局數據,默認情況下,這個全局數據的 key 就是返回的變量名,value 就是方法返回值,當然開發者可以通過 @ModelAttribute 注解的 name 屬性去重新指定 key。

定義完成后,在任何一個Controller 的接口中,都可以獲取到這里定義的數據:

@RestController
public class HelloController {
    @GetMapping("/hello")
    public String hello(Model model) {
        Map<String, Object> map = model.asMap();
        System.out.println(map);
        int i = 1 / 0;
        return "hello controller advice";
    }
}

全局數據預處理

考慮我有兩個實體類,Book 和 Author,分別定義如下:

public class Book {
    private String name;
    private Long price;
    //getter/setter
}
public class Author {
    private String name;
    private Integer age;
    //getter/setter
}

此時,如果我定義一個數據添加接口,如下:

@PostMapping("/book")
public void addBook(Book book, Author author) {
    System.out.println(book);
    System.out.println(author);
}

這個時候,添加操作就會有問題,因為兩個實體類都有一個 name 屬性,從前端傳遞時 ,無法區分。此時,通過 @ControllerAdvice 的全局數據預處理可以解決這個問題

解決步驟如下:

1.給接口中的變量取別名

@PostMapping("/book")
public void addBook(@ModelAttribute("b") Book book, @ModelAttribute("a") Author author) {
    System.out.println(book);
    System.out.println(author);
}

2.進行請求數據預處理
在 @ControllerAdvice 標記的類中添加如下代碼:

@InitBinder("b")
public void b(WebDataBinder binder) {
    binder.setFieldDefaultPrefix("b.");
}
@InitBinder("a")
public void a(WebDataBinder binder) {
    binder.setFieldDefaultPrefix("a.");
}

@InitBinder("b") 注解表示該方法用來處理和Book和相關的參數,在方法中,給參數添加一個 b 前綴,即請求參數要有b前綴.

3.發送請求

請求發送時,通過給不同對象的參數添加不同的前綴,可以實現參數的區分.

補充

1、AOP中拋出的自定義異常需要繼承RuntimeException.否則無法被全局異常處理監測

2、攔截器中的異常也可以被全局異常處理,處理到

RestControllerAdvice

此注解類似於@RestController和Controller的關系,即自動轉換json

參考文章:[https://www.cnblogs.com/lenve/p/10748453.html](


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM