SpringBoot接口如何統(tǒng)一異常處理
為什么要優(yōu)雅的處理異常
如果我們不統(tǒng)一的處理異常,經(jīng)常會(huì)在controller層有大量的異常處理的代碼, 比如:
@Slf4j
@Api(value = "User Interfaces", tags = "User Interfaces")
@RestController
@RequestMapping("/user")
public class UserController {
/**
* http://localhost:8080/user/add .
*
* @param userParam user param
* @return user
*/
@ApiOperation("Add User")
@ApiImplicitParam(name = "userParam", type = "body", dataTypeClass = UserParam.class, required = true)
@PostMapping("add")
public ResponseEntity<String> add(@Valid @RequestBody UserParam userParam) {
// 每個(gè)接口充斥著大量的異常處理
try {
// do something
} catch(Exception e) {
return ResponseEntity.fail("error");
}
return ResponseEntity.ok("success");
}
}那怎么實(shí)現(xiàn)統(tǒng)一的異常處理,特別是結(jié)合參數(shù)校驗(yàn)等封裝?
實(shí)現(xiàn)案例
簡單展示通過@ControllerAdvice進(jìn)行統(tǒng)一異常處理。
@ControllerAdvice異常統(tǒng)一處理
對(duì)于400參數(shù)錯(cuò)誤異常
/**
* Global exception handler.
*/
@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {
/**
* exception handler for bad request.
*
* @param e
* exception
* @return ResponseResult
*/
@ResponseBody
@ResponseStatus(code = HttpStatus.BAD_REQUEST)
@ExceptionHandler(value = { BindException.class, ValidationException.class, MethodArgumentNotValidException.class })
public ResponseResult<ExceptionData> handleParameterVerificationException(@NonNull Exception e) {
ExceptionData.ExceptionDataBuilder exceptionDataBuilder = ExceptionData.builder();
log.warn("Exception: {}", e.getMessage());
if (e instanceof BindException) {
BindingResult bindingResult = ((MethodArgumentNotValidException) e).getBindingResult();
bindingResult.getAllErrors().stream().map(DefaultMessageSourceResolvable::getDefaultMessage)
.forEach(exceptionDataBuilder::error);
} else if (e instanceof ConstraintViolationException) {
if (e.getMessage() != null) {
exceptionDataBuilder.error(e.getMessage());
}
} else {
exceptionDataBuilder.error("invalid parameter");
}
return ResponseResultEntity.fail(exceptionDataBuilder.build(), "invalid parameter");
}
}
對(duì)于自定義異常
/**
* handle business exception.
*
* @param businessException
* business exception
* @return ResponseResult
*/
@ResponseBody
@ExceptionHandler(BusinessException.class)
public ResponseResult<BusinessException> processBusinessException(BusinessException businessException) {
log.error(businessException.getLocalizedMessage(), businessException);
// 這里可以屏蔽掉后臺(tái)的異常棧信息,直接返回"business error"
return ResponseResultEntity.fail(businessException, businessException.getLocalizedMessage());
}對(duì)于其它異常
/**
* handle other exception.
*
* @param exception
* exception
* @return ResponseResult
*/
@ResponseBody
@ExceptionHandler(Exception.class)
public ResponseResult<Exception> processException(Exception exception) {
log.error(exception.getLocalizedMessage(), exception);
// 這里可以屏蔽掉后臺(tái)的異常棧信息,直接返回"server error"
return ResponseResultEntity.fail(exception, exception.getLocalizedMessage());
}Controller接口
(接口中無需處理異常)
@Slf4j
@Api(value = "User Interfaces", tags = "User Interfaces")
@RestController
@RequestMapping("/user")
public class UserController {
/**
* http://localhost:8080/user/add .
*
* @param userParam user param
* @return user
*/
@ApiOperation("Add User")
@ApiImplicitParam(name = "userParam", type = "body", dataTypeClass = UserParam.class, required = true)
@PostMapping("add")
public ResponseEntity<UserParam> add(@Valid @RequestBody UserParam userParam) {
return ResponseEntity.ok(userParam);
}
}運(yùn)行測試
這里用postman測試下:

進(jìn)一步理解
我們再通過一些問題來幫助你更深入理解
@ControllerAdvice還可以怎么用?
除了通過@ExceptionHandler注解用于全局異常的處理之外,@ControllerAdvice還有兩個(gè)用法:
- @InitBinder注解
用于請(qǐng)求中注冊自定義參數(shù)的解析,從而達(dá)到自定義請(qǐng)求參數(shù)格式的目的;
比如,在@ControllerAdvice注解的類中添加如下方法,來統(tǒng)一處理日期格式的格式化
@InitBinder
public void handleInitBinder(WebDataBinder dataBinder){
dataBinder.registerCustomEditor(Date.class,
new CustomDateEditor(new SimpleDateFormat("yyyy-MM-dd"), false));
}Controller中傳入?yún)?shù)(string類型)自動(dòng)轉(zhuǎn)化為Date類型
@GetMapping("testDate")
public Date processApi(Date date) {
return date;
}- @ModelAttribute注解
用來預(yù)設(shè)全局參數(shù),比如最典型的使用Spring Security時(shí)將添加當(dāng)前登錄的用戶信息(UserDetails)作為參數(shù)。
@ModelAttribute("currentUser")
public UserDetails modelAttribute() {
return (UserDetails) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
}所有controller類中requestMapping方法都可以直接獲取并使用currentUser
@PostMapping("saveSomething")
public ResponseEntity<String> saveSomeObj(@ModelAttribute("currentUser") UserDetails operator) {
// 保存操作,并設(shè)置當(dāng)前操作人員的ID(從UserDetails中獲得)
return ResponseEntity.success("ok");
}@ControllerAdvice是如何起作用的(原理)?
DispatcherServlet中onRefresh方法是初始化ApplicationContext后的回調(diào)方法,它會(huì)調(diào)用initStrategies方法,主要更新一些servlet需要使用的對(duì)象,包括國際化處理,requestMapping,視圖解析等等。
/**
* This implementation calls {@link #initStrategies}.
*/
@Override
protected void onRefresh(ApplicationContext context) {
initStrategies(context);
}
/**
* Initialize the strategy objects that this servlet uses.
* <p>May be overridden in subclasses in order to initialize further strategy objects.
*/
protected void initStrategies(ApplicationContext context) {
initMultipartResolver(context); // 文件上傳
initLocaleResolver(context); // i18n國際化
initThemeResolver(context); // 主題
initHandlerMappings(context); // requestMapping
initHandlerAdapters(context); // adapters
initHandlerExceptionResolvers(context); // 異常處理
initRequestToViewNameTranslator(context);
initViewResolvers(context);
initFlashMapManager(context);
}從上述代碼看,如果要提供@ControllerAdvice提供的三種注解功能,從設(shè)計(jì)和實(shí)現(xiàn)的角度肯定是實(shí)現(xiàn)的代碼需要放在initStrategies方法中。
- @ModelAttribute和@InitBinder處理
具體來看,如果你是設(shè)計(jì)者,很顯然容易想到:對(duì)于@ModelAttribute提供的參數(shù)預(yù)置和@InitBinder注解提供的預(yù)處理方法應(yīng)該是放在一個(gè)方法中的,因?yàn)樗鼈兌际窃谶M(jìn)入requestMapping方法前做的操作。
如下方法是獲取所有的HandlerAdapter,無非就是從BeanFactory中獲取
private void initHandlerAdapters(ApplicationContext context) {
this.handlerAdapters = null;
if (this.detectAllHandlerAdapters) {
// Find all HandlerAdapters in the ApplicationContext, including ancestor contexts.
Map<String, HandlerAdapter> matchingBeans =
BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerAdapter.class, true, false);
if (!matchingBeans.isEmpty()) {
this.handlerAdapters = new ArrayList<>(matchingBeans.values());
// We keep HandlerAdapters in sorted order.
AnnotationAwareOrderComparator.sort(this.handlerAdapters);
}
}
else {
try {
HandlerAdapter ha = context.getBean(HANDLER_ADAPTER_BEAN_NAME, HandlerAdapter.class);
this.handlerAdapters = Collections.singletonList(ha);
}
catch (NoSuchBeanDefinitionException ex) {
// Ignore, we'll add a default HandlerAdapter later.
}
}
// Ensure we have at least some HandlerAdapters, by registering
// default HandlerAdapters if no other adapters are found.
if (this.handlerAdapters == null) {
this.handlerAdapters = getDefaultStrategies(context, HandlerAdapter.class);
if (logger.isTraceEnabled()) {
logger.trace("No HandlerAdapters declared for servlet '" + getServletName() +
"': using default strategies from DispatcherServlet.properties");
}
}
}我們要處理的是requestMapping的handlerResolver,作為設(shè)計(jì)者,就很容易出如下的結(jié)構(gòu)

在RequestMappingHandlerAdapter中的afterPropertiesSet去處理advice
@Override
public void afterPropertiesSet() {
// Do this first, it may add ResponseBody advice beans
initControllerAdviceCache();
if (this.argumentResolvers == null) {
List<HandlerMethodArgumentResolver> resolvers = getDefaultArgumentResolvers();
this.argumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers);
}
if (this.initBinderArgumentResolvers == null) {
List<HandlerMethodArgumentResolver> resolvers = getDefaultInitBinderArgumentResolvers();
this.initBinderArgumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers);
}
if (this.returnValueHandlers == null) {
List<HandlerMethodReturnValueHandler> handlers = getDefaultReturnValueHandlers();
this.returnValueHandlers = new HandlerMethodReturnValueHandlerComposite().addHandlers(handlers);
}
}
private void initControllerAdviceCache() {
if (getApplicationContext() == null) {
return;
}
List<ControllerAdviceBean> adviceBeans = ControllerAdviceBean.findAnnotatedBeans(getApplicationContext());
List<Object> requestResponseBodyAdviceBeans = new ArrayList<>();
for (ControllerAdviceBean adviceBean : adviceBeans) {
Class<?> beanType = adviceBean.getBeanType();
if (beanType == null) {
throw new IllegalStateException("Unresolvable type for ControllerAdviceBean: " + adviceBean);
}
// 緩存所有modelAttribute注解方法
Set<Method> attrMethods = MethodIntrospector.selectMethods(beanType, MODEL_ATTRIBUTE_METHODS);
if (!attrMethods.isEmpty()) {
this.modelAttributeAdviceCache.put(adviceBean, attrMethods);
}
// 緩存所有initBinder注解方法
Set<Method> binderMethods = MethodIntrospector.selectMethods(beanType, INIT_BINDER_METHODS);
if (!binderMethods.isEmpty()) {
this.initBinderAdviceCache.put(adviceBean, binderMethods);
}
if (RequestBodyAdvice.class.isAssignableFrom(beanType) || ResponseBodyAdvice.class.isAssignableFrom(beanType)) {
requestResponseBodyAdviceBeans.add(adviceBean);
}
}
if (!requestResponseBodyAdviceBeans.isEmpty()) {
this.requestResponseBodyAdvice.addAll(0, requestResponseBodyAdviceBeans);
}
}- @ExceptionHandler處理
@ExceptionHandler顯然是在上述initHandlerExceptionResolvers(context)方法中。
同樣的,從BeanFactory中獲取HandlerExceptionResolver
/**
* Initialize the HandlerExceptionResolver used by this class.
* <p>If no bean is defined with the given name in the BeanFactory for this namespace,
* we default to no exception resolver.
*/
private void initHandlerExceptionResolvers(ApplicationContext context) {
this.handlerExceptionResolvers = null;
if (this.detectAllHandlerExceptionResolvers) {
// Find all HandlerExceptionResolvers in the ApplicationContext, including ancestor contexts.
Map<String, HandlerExceptionResolver> matchingBeans = BeanFactoryUtils
.beansOfTypeIncludingAncestors(context, HandlerExceptionResolver.class, true, false);
if (!matchingBeans.isEmpty()) {
this.handlerExceptionResolvers = new ArrayList<>(matchingBeans.values());
// We keep HandlerExceptionResolvers in sorted order.
AnnotationAwareOrderComparator.sort(this.handlerExceptionResolvers);
}
}
else {
try {
HandlerExceptionResolver her =
context.getBean(HANDLER_EXCEPTION_RESOLVER_BEAN_NAME, HandlerExceptionResolver.class);
this.handlerExceptionResolvers = Collections.singletonList(her);
}
catch (NoSuchBeanDefinitionException ex) {
// Ignore, no HandlerExceptionResolver is fine too.
}
}
// Ensure we have at least some HandlerExceptionResolvers, by registering
// default HandlerExceptionResolvers if no other resolvers are found.
if (this.handlerExceptionResolvers == null) {
this.handlerExceptionResolvers = getDefaultStrategies(context, HandlerExceptionResolver.class);
if (logger.isTraceEnabled()) {
logger.trace("No HandlerExceptionResolvers declared in servlet '" + getServletName() +
"': using default strategies from DispatcherServlet.properties");
}
}
}我們很容易找到ExceptionHandlerExceptionResolver

同樣的在afterPropertiesSet去處理advice
@Override
public void afterPropertiesSet() {
// Do this first, it may add ResponseBodyAdvice beans
initExceptionHandlerAdviceCache();
if (this.argumentResolvers == null) {
List<HandlerMethodArgumentResolver> resolvers = getDefaultArgumentResolvers();
this.argumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers);
}
if (this.returnValueHandlers == null) {
List<HandlerMethodReturnValueHandler> handlers = getDefaultReturnValueHandlers();
this.returnValueHandlers = new HandlerMethodReturnValueHandlerComposite().addHandlers(handlers);
}
}
private void initExceptionHandlerAdviceCache() {
if (getApplicationContext() == null) {
return;
}
List<ControllerAdviceBean> adviceBeans = ControllerAdviceBean.findAnnotatedBeans(getApplicationContext());
for (ControllerAdviceBean adviceBean : adviceBeans) {
Class<?> beanType = adviceBean.getBeanType();
if (beanType == null) {
throw new IllegalStateException("Unresolvable type for ControllerAdviceBean: " + adviceBean);
}
ExceptionHandlerMethodResolver resolver = new ExceptionHandlerMethodResolver(beanType);
if (resolver.hasExceptionMappings()) {
this.exceptionHandlerAdviceCache.put(adviceBean, resolver);
}
if (ResponseBodyAdvice.class.isAssignableFrom(beanType)) {
this.responseBodyAdvice.add(adviceBean);
}
}
}到此這篇關(guān)于SpringBoot接口如何統(tǒng)一異常處理的文章就介紹到這了,更多相關(guān)SpringBoot接口 異常處理內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Intellij IDEA 2019 最新亂碼問題及解決必殺技(必看篇)
大家在使用Intellij IDEA 的時(shí)候會(huì)經(jīng)常遇到各種亂碼問題,今天小編給大家分享一些關(guān)于Intellij IDEA 2019 最新亂碼問題及解決必殺技,感興趣的朋友跟隨小編一起看看吧2020-04-04
spring cloud gateway如何獲取請(qǐng)求的真實(shí)地址
這篇文章主要介紹了spring cloud gateway如何獲取請(qǐng)求的真實(shí)地址問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-05-05
關(guān)于SpringBoot大文件RestTemplate下載解決方案
這篇文章主要介紹了SpringBoot大文件RestTemplate下載解決方案,最近結(jié)合網(wǎng)上案例及自己總結(jié),寫了一個(gè)分片下載tuling/fileServer項(xiàng)目,需要的朋友可以參考下2021-10-10
Java編程中使用XFire框架調(diào)用WebService程序接口
這篇文章主要介紹了Java編程中使用XFire調(diào)用WebService程序接口的方法,WebService是一種跨編程語言和跨操作系統(tǒng)平臺(tái)的遠(yuǎn)程調(diào)用技術(shù),需要的朋友可以參考下2015-12-12
詳解基于spring多數(shù)據(jù)源動(dòng)態(tài)調(diào)用及其事務(wù)處理
本篇文章主要介紹了基于spring多數(shù)據(jù)源動(dòng)態(tài)調(diào)用及其事務(wù)處理 ,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-06-06

