2020 年 11 月 24 日

IT Skills 波林

Polin WEI – 資訊工作者的技術手札

Spring boot Exception Handling For REST

3 min read
RestResponseEntityExceptionHandler

Spring boot Exception Handling For REST 使用 @ControllerAdvice annotation

 

對於 axios 作一個統一錯誤回饋的設定: errorHandle

import Vue from 'vue';
import router from '../router'
import axios from 'axios'
import i18n from '../i18n'

const errorHandle = async (status, msg) => {
  switch (status) {
    case 400:
      console.log("400")
      Vue.prototype.$toast.add({severity:'error', summary: i18n.t('HttpStatus.400'), detail: msg.error, life: 3000});
      break;
    case 401:
      console.log("401")
      sessionStorage.clear
      await Vue.prototype.$toast.add({severity:'error', summary: i18n.t('HttpStatus.401'), detail: msg.error , life: 3000});      
      setTimeout(() => {
        router.push("/Login");
      }, 4000);                         
            
      break;
    case 403:
      console.log("403")
      await Vue.prototype.$toast.add({severity:'error', summary: i18n.t('HttpStatus.403'), detail: msg.error , life: 3000});
      setTimeout(() => {
        router.push("/Login");
      }, 4000);
      break;
    case 409:      
      console.log("409")
      await Vue.prototype.$toast.add({severity:'error', summary: i18n.t('HttpStatus.409'), detail: msg.message , life: 3000});
      break;
    case 500:      
      console.log("500")
      await Vue.prototype.$toast.add({severity:'error', summary: i18n.t('HttpStatus.500'), detail: msg.message , life: 3000});
      break;
    default:
      console.log(status)
      await Vue.prototype.$toast.add({severity:'error', summary: msg , detail: msg.message , life: 3000});
      break;
  }
}

 

在 axios 對於 response 的 interceptors ,將定義好的 errorHandle 放入,這樣 axios 就設定好了

// doing something with the response
axios.interceptors.response.use(
  (response) => {    
    return response;
  },
  (error) => {
    const {response} = error;
    if (response) {      
      // 成功發出請求且收到 response, 但有 error
      errorHandle(response.status, response.data);
      return Promise.reject(error);
    }
  }
);

 

建立一個 class: RestResponseEntityExceptionHandler ,因為是針對 REST 的錯誤來作處理,所以要 extends ResponseEntityExceptionHandler,在 Spring boot 使用 @ControllerAdvice 來捕捉錯誤,並利用 @ExceptionHandler 針對 Exception.class 進行捕捉。這裡是對 Conflict (資料重覆) 時作特別處理。

註: @ControllerAdvice是在Spring 3.2新增的annotation,可以用來攔截並處理應用程式中全部Controller所拋出的Exception例外錯誤。其也是@Component,所以會被Spring scan為Bean。

@ControllerAdvice
public class RestResponseEntityExceptionHandler extends ResponseEntityExceptionHandler {
 @Autowired
 ResourceBundleMessageSource messageSource;
 
 @ExceptionHandler(Exception.class)
 protected ResponseEntity<Object> handleConflict(RuntimeException ex, WebRequest request) {
  String bodyOfResponse = "資料重覆";
  return handleExceptionInternal(ex, bodyOfResponse, new HttpHeaders(), HttpStatus.CONFLICT, request);
 }
 
}

RestResponseEntityExceptionHandler

在RestController中,當發生 Conflict (資料重覆)  ,印出錯誤訊息後e.printStackTrace(); ,就會到 RestResponseEntityExceptionHandler.class 中的 handleConflict() 了

@PostMapping("role/save")
public ResponseEntity<?> saveRole(@RequestBody @Valid Authority role, HttpServletRequest request) {
 Map<String,Object> responseJson = new HashMap<String,Object>();
 Map<String,Object> responseError = new HashMap<String,Object>();   

 Authority newEntity = new Authority();
 try {
  boolean isSystem = Objects.isNull( role.getIsSystem() ) ? false : role.getIsSystem();
  boolean isEnabled = Objects.isNull( role.getEnabled() ) ? false : role.getEnabled();
  role.setEnabled(isEnabled);
  role.setIsSystem(isSystem);
  role.setCreateDate(sysService.getNowTimestamp());
  role.setCreateUser(userService.getCurrentUser().getId());
  newEntity = roleRepo.save(role);
  responseJson.put("role", newEntity);          
 
 } catch (Exception e) {        
  e.printStackTrace();
 }
 
 if (responseError.isEmpty()) {         
  return ResponseEntity
    .ok()
    .contentType(MediaType.APPLICATION_JSON)
    .body(responseJson);
 } else {
  responseJson.put("error", responseError);
  return ResponseEntity
    .badRequest()                   
    .contentType(MediaType.APPLICATION_JSON)
    .body(responseJson);
 }
}

 

上面的作法, 仍是用 try...catch 到  Exception 才到 RestResponseEntityExceptionHandler ,這仍不是我們想要的結果,應該是在 controller 裡寫正常的商業交易,發生問題時統一到 RestResponseEntityExceptionHandler 。因此,改寫一下作法如下:

/**
 * 統一異常處理
 * @author polin.wei
 *
 */
@ControllerAdvice
public class RestResponseEntityExceptionHandler extends ResponseEntityExceptionHandler {
 private final Logger logger = LoggerFactory.getLogger(this.getClass());
 
 @Autowired
 ResourceBundleMessageSource messageSource;
 /**
  * 新增/修改 時發生資料重覆的異常處理
  * @param ex
  * @param request
  * @return
  */
 @ExceptionHandler(DataIntegrityViolationException.class)
 protected ResponseEntity<Object> handleConflict(RuntimeException ex, WebRequest request) {
  String bodyOfResponse = messageSource.getMessage("page.text.curd.conflict", null, request.getLocale());
  if (logger.isDebugEnabled()) {
   ex.printStackTrace();
  }
  return handleExceptionInternal(ex, bodyOfResponse, new HttpHeaders(), HttpStatus.CONFLICT, request);
 }
 
 @ExceptionHandler(NullPointerException.class)
 protected ResponseEntity<?> handleNullPointerException(RuntimeException ex, WebRequest request) {
  String bodyOfResponse = messageSource.getMessage("page.msg.nullPointerException", null, request.getLocale());
  if (logger.isDebugEnabled()) {
   ex.printStackTrace();
  }
  return handleExceptionInternal(ex, bodyOfResponse, new HttpHeaders(), HttpStatus.BAD_REQUEST, request);
 }
 /**
  * 其它的異常處理
  * @param ex
  * @param request
  * @return
  */
 @ExceptionHandler(Exception.class)
 protected ResponseEntity<Object> handleException(RuntimeException ex, WebRequest request) {
  String bodyOfResponse = messageSource.getMessage("主機作業異常, 請連繫資訊單位", null, request.getLocale());
  if (logger.isDebugEnabled()) {
   ex.printStackTrace();
  }
  return handleExceptionInternal(ex, bodyOfResponse, new HttpHeaders(), HttpStatus.INTERNAL_SERVER_ERROR , request);
  
 }
 
}

 

這時候, controller 就可以不用再寫 try...catch

@PostMapping("role/save")
public ResponseEntity<?> saveRole(@RequestBody @Valid Authority role, HttpServletRequest request, Locale locale) {
 responseJson = new HashMap<String, Object>();
 responseError = new HashMap<String, Object>();

 Authority newEntity = new Authority();

 boolean isSystem = Objects.isNull(role.getIsSystem()) ? false : role.getIsSystem();
 boolean isEnabled = Objects.isNull(role.getEnabled()) ? false : role.getEnabled();
 role.setEnabled(isEnabled);
 role.setIsSystem(isSystem);
 role.setCreateDate(sysService.getNowTimestamp());
 role.setCreateUser(userService.getCurrentUser().getId());
 newEntity = roleRepo.save(role);
 responseJson.put("role", newEntity);
 responseJson.put("message", messageSource.getMessage(super.CRUD_SUCCESS, null, locale));

 return super.doResponse();
}

 

參考:
Error Handling for REST with Spring

Spring Boot @ControllerAdvice用法

Copyright © All rights reserved. | Newsphere by AF themes.