Spring進階之@ControllerAdvice與統一異常處理
Spring原始碼中有關@ControllerAdvice
的註解如下:
Specialization of {@link Component @Component} for classes that declare {@link ExceptionHandler @ExceptionHandler}, {@link InitBinder @InitBinder}, or {@link ModelAttribute @ModelAttribute} methods to be shared across multiple {@code @Controller} classes.
理解:
@ControllerAdvice
是一個特殊的@Component
,用於標識一個類,這個類中被以下三種註解標識的方法:@ExceptionHandler
,@InitBinder
,@ModelAttribute
,將作用於所有的@Controller
類的介面上。
那麼,這個三個註解分別是什麼意思,起到什麼作用呢?
@InitBinder
Annotation that identifies methods which initialize the {@link org.springframework.web.bind.WebDataBinder} which will be used for populating command and form object arguments of annotated handler methods. Such init-binder methods support all arguments that {@link RequestMapping} supports, except for command/form objects and corresponding validation result objects. Init-binder methods must not have a return value; they are usually declared as {@code void}.
作用:註冊屬性編輯器,對HTTP請求引數進行處理,再繫結到對應的介面,比如格式化的時間轉換等。應用於單個@Controller類的方法上時,僅對該類裡的介面有效。與@ControllerAdvice組合使用可全域性生效。
示例:
@ControllerAdvice public class ActionAdvice { @InitBinder public void handleException(WebDataBinder binder) { binder.addCustomFormatter(new DateFormatter("yyyy-MM-dd HH:mm:ss")); } } 複製程式碼
@ExceptionHandler
作用:統一異常處理,也可以指定要處理的異常型別
示例:
@ControllerAdvice public class ActionAdvice { @ExceptionHandler(Exception.class) @ResponseBody @ResponseStatus(HttpStatus.OK) public Map handleException(Exception ex) { Map<String, Object> map = new HashMap<>(); map.put("code", 400); map.put("msg", ex.toString()); return map; } } 複製程式碼
@ModelAttribute
作用:繫結資料
示例:
@ControllerAdvice public class ActionAdvice { @ModelAttribute public void handleException(Model model) { model.addAttribute("user", "zfh"); } } 複製程式碼
在介面中獲取前面繫結的引數:
@RestController public class BasicController { @GetMapping(value = "index") public Map index(@ModelAttribute("user") String user) { //... } } 複製程式碼
完整示例程式碼:
import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.format.datetime.DateFormatter; import org.springframework.http.HttpStatus; import org.springframework.ui.Model; import org.springframework.validation.Validator; import org.springframework.web.bind.WebDataBinder; import org.springframework.web.bind.annotation.*; import java.util.HashMap; import java.util.Map; /** * 統一異常處理 * @author zfh * @version 1.0 * @since 2019/1/4 15:23 */ @ControllerAdvice public class ControllerExceptionHandler { private Logger logger = LoggerFactory.getLogger(ControllerExceptionHandler.class); @InitBinder public void initMyBinder(WebDataBinder binder) { // 新增對日期的統一處理 //binder.addCustomFormatter(new DateFormatter("yyyy-MM-dd")); binder.addCustomFormatter(new DateFormatter("yyyy-MM-dd HH:mm:ss")); // 新增表單驗證 //binder.addValidators(); } @ModelAttribute public void addMyAttribute(Model model) { model.addAttribute("user", "zfh"); // 在@RequestMapping的介面中使用@ModelAttribute("name") Object name獲取 } @ExceptionHandler(value = Exception.class) @ResponseStatus(HttpStatus.OK) @ResponseBody // 如果使用了@RestControllerAdvice,這裡就不需要@ResponseBody了 public Map handler(Exception ex) { logger.error("統一異常處理", ex); Map<String, Object> map = new HashMap<>(); map.put("code", 400); map.put("msg", ex); return map; } } 複製程式碼
測試介面:
@RestController public class TestAction { @GetMapping(value = "testAdvice") public JsonResult testAdvice(@ModelAttribute("user") String user, Date date) throws Exception { System.out.println("user: " + user); System.out.println("date: " + date); throw new Exception("直接丟擲異常"); } } 複製程式碼
高階應用--格式化時間轉Date
使用@ControllerAdvice
+@InitBinder
,可將http請求引數中的時間自動轉換成Date型別。
@InitBinder public void initBinder(WebDataBinder binder) { GenericConversionService genericConversionService = (GenericConversionService) binder.getConversionService(); if (genericConversionService != null) { genericConversionService.addConverter(new DateConverter()); } } 複製程式碼
自定義的時間型別轉換器:
import org.springframework.core.convert.converter.Converter; import org.springframework.util.StringUtils; import java.text.SimpleDateFormat; import java.util.Date; /** * 日期轉換類 * 將標準日期、標準日期時間、時間戳轉換成Date型別 */ public class DateConverter implements Converter<String, Date> { private static final String dateFormat = "yyyy-MM-dd HH:mm:ss"; private static final String shortDateFormat = "yyyy-MM-dd"; private static final String timeStampFormat = "^\\d+$"; @Override public Date convert(String value) { if(StringUtils.isEmpty(value)) { return null; } value = value.trim(); try { if (value.contains("-")) { SimpleDateFormat formatter; if (value.contains(":")) { formatter = new SimpleDateFormat(dateFormat); } else { formatter = new SimpleDateFormat(shortDateFormat); } return formatter.parse(value); } else if (value.matches(timeStampFormat)) { Long lDate = new Long(value); return new Date(lDate); } } catch (Exception e) { throw new RuntimeException(String.format("parser %s to Date fail", value)); } throw new RuntimeException(String.format("parser %s to Date fail", value)); } } 複製程式碼
擴充套件:
@RestControllerAdvice
=@ControllerAdvice
+@ResponseBody
參考: