記錄一次全局異常告警@ExceptionHandler和HandlerExceptionResolver的問(wèn)題
最近有同事說(shuō)之前寫(xiě)的全局異常告警,如果有@Valid的注解,在接入新寫(xiě)的插件告警后,返回信息不打印了。全局異常是基于@ExceptionHandler的全局異常類(lèi),主要是Servlet MVC的ModelAndView返回的錯(cuò)誤信息的捕獲。代碼如下:
/** * @author xxx */ @RestControllerAdvice public class DefaultGlobalExceptionHandler { private static final Logger log = LoggerFactory.getLogger(DefaultGlobalExceptionHandler.class); /** * @param ex * @return 響應(yīng)對(duì)象 * @description 基礎(chǔ)異常 */ @ExceptionHandler(value = {BaseException.class}) public Result baseException(BaseException ex) { log.error("baseException:{}", ex); return Result.fail(ex.getErrorCode(), ex.getErrorMsg()); } @ExceptionHandler(value = {Exception.class}) public Result exception(Exception ex) { log.error("Exception:{}", ex); return Result.fail(); } @ExceptionHandler(value = {MaxUploadSizeExceededException.class}) public Result exception(MaxUploadSizeExceededException ex) { log.error("RuntimeException:{}", ex); return Result.error(CommonResultStatus.COMMON_ERROR_UPLOAD_SIZE_EXCEED.getCode(),"上傳文件大小超出限制!"); } @ExceptionHandler(value = {RuntimeException.class}) public Result exception(RuntimeException ex) { log.error("RuntimeException:{}", ex); return Result.fail(); } /** * 處理Get請(qǐng)求中 使用@Valid 驗(yàn)證路徑中請(qǐng)求實(shí)體校驗(yàn)失敗后拋出的異常 * * @param ex * @return com.gwm.marketing.common.result.Result * @author xxx * @date 15:01 2021/6/11 */ @ExceptionHandler(BindException.class) @ResponseStatus(HttpStatus.BAD_REQUEST) public Result validExceptionHandler(BindException ex) { String message = ex.getBindingResult().getAllErrors().stream().map(DefaultMessageSourceResolvable::getDefaultMessage).collect(Collectors.joining(",")); return Result.error(CommonResultStatus.COMMON_INVALID_PARAM.getCode(),CommonResultStatus.COMMON_INVALID_PARAM.getMessage()+":["+message+"]"); } /** * 處理請(qǐng)求參數(shù)格式錯(cuò)誤 @RequestParam上validate失敗后拋出的異常 * * @param ex * @return * @author * @date 15:15 2021/6/11 */ @ExceptionHandler(ConstraintViolationException.class) @ResponseStatus(HttpStatus.BAD_REQUEST) public Result ConstraintViolationExceptionHandler(ConstraintViolationException ex) { String message = ex.getConstraintViolations().stream().map(ConstraintViolation::getMessage).collect(Collectors.joining(",")); return Result.error(CommonResultStatus.COMMON_INVALID_PARAM.getCode(),CommonResultStatus.COMMON_INVALID_PARAM.getMessage()+":["+message+"]"); } /** * 處理請(qǐng)求參數(shù)格式錯(cuò)誤 @RequestBody上validate失敗后拋出的異常 * * @param ex * @return com.gwm.marketing.common.result.Result * @author xxx * @date 15:16 2021/6/11 */ @ExceptionHandler(MethodArgumentNotValidException.class) @ResponseStatus(HttpStatus.BAD_REQUEST) public Result MethodArgumentNotValidExceptionHandler(MethodArgumentNotValidException ex) { String message = ex.getBindingResult().getAllErrors().stream().map(DefaultMessageSourceResolvable::getDefaultMessage).collect(Collectors.joining(",")); return Result.error(CommonResultStatus.COMMON_INVALID_PARAM.getCode(),CommonResultStatus.COMMON_INVALID_PARAM.getMessage()+":["+message+"]"); } @ExceptionHandler(value = {Throwable.class}) @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) public Result throable() { return Result.fail(); } }
加入的插件告警是基于@WebMvcConfigurer,里面重寫(xiě)了對(duì)于異常的處理,重寫(xiě)了HandlerExceptionResolver,如果有異常信息,通過(guò)釘釘告警的方式實(shí)時(shí)通知。加了這么一個(gè)功能。同事給我說(shuō)了之后,我本地使用postman發(fā)現(xiàn)本地是能復(fù)現(xiàn)的,于是開(kāi)始一點(diǎn)點(diǎn)的調(diào)試追蹤。
首先是重寫(xiě)WebMvcConfigurer
package com.gwm.marketing.filter; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.databind.ObjectMapper; import com.gwm.marketing.filter.exception.DingdingHandleException; import com.gwm.marketing.filter.intercepter.*; import okhttp3.Interceptor; import okhttp3.Request; import okhttp3.Response; import org.jetbrains.annotations.NotNull; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.format.FormatterRegistry; import org.springframework.http.converter.*; import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; import org.springframework.validation.MessageCodesResolver; import org.springframework.validation.Validator; import org.springframework.web.method.support.HandlerMethodArgumentResolver; import org.springframework.web.method.support.HandlerMethodReturnValueHandler; import org.springframework.web.servlet.DispatcherServlet; import org.springframework.web.servlet.HandlerExceptionResolver; import org.springframework.web.servlet.config.annotation.*; import java.io.IOException; import java.util.List; import java.util.Optional; /** * @Author fanht * @Description 攔截器參數(shù)校驗(yàn) * @Date 2021/11/12 2:05 下午 * @Version 1.0 */ @Configuration public class OraDingdingConfigurer implements WebMvcConfigurer, Interceptor { /** * 攔截器參數(shù)校驗(yàn) * * @param interceptorRegistry */ @Override public void addInterceptors(InterceptorRegistry interceptorRegistry) { //注意攔截器的順序 interceptorRegistry.addInterceptor(new CharacterValidInterceptors()); interceptorRegistry.addInterceptor(new HttpParamVerifyInterceptor()); interceptorRegistry.addInterceptor(new HttpRequestStatisticsInterceptor()); } @Override public void configurePathMatch(PathMatchConfigurer pathMatchConfigurer) { } @Override public void configureContentNegotiation(ContentNegotiationConfigurer contentNegotiationConfigurer) { } @Override public void configureAsyncSupport(AsyncSupportConfigurer asyncSupportConfigurer) { } @Override public void configureDefaultServletHandling(DefaultServletHandlerConfigurer defaultServletHandlerConfigurer) { } @Override public void addFormatters(FormatterRegistry formatterRegistry) { } @Override public void addResourceHandlers(ResourceHandlerRegistry resourceHandlerRegistry) { } @Override public void addCorsMappings(CorsRegistry corsRegistry) { } @Override public void addViewControllers(ViewControllerRegistry viewControllerRegistry) { } @Override public void configureViewResolvers(ViewResolverRegistry viewResolverRegistry) { } @Override public void addArgumentResolvers(List<HandlerMethodArgumentResolver> list) { } @Override public void addReturnValueHandlers(List<HandlerMethodReturnValueHandler> list) { } @Override public void configureMessageConverters(List<HttpMessageConverter<?>> list) { } @Override public void extendMessageConverters(List<HttpMessageConverter<?>> list) { //新版本中的 Optional<HttpMessageConverter<?>> optional = list.stream() .filter(o -> o instanceof MappingJackson2HttpMessageConverter) .findFirst(); if (optional.isPresent()) { MappingJackson2HttpMessageConverter converter = (MappingJackson2HttpMessageConverter) optional.get(); ObjectMapper mapper = converter.getObjectMapper(); mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL); } } @Override public void configureHandlerExceptionResolvers(List<HandlerExceptionResolver> list) { } @Override public void extendHandlerExceptionResolvers(List<HandlerExceptionResolver> list) { //todo 添加全局異常,因?yàn)镠andlerInterceptor不能顯示異常 之前是寫(xiě)到了configureHandlerExceptionResolvers 里面,使用哪個(gè)會(huì)和全局異常DefaultGlobalExceptionHandler有先后順序問(wèn)題 list.add(0,new DingdingHandleException()); } @Override public Validator getValidator() { return null; } @Override public MessageCodesResolver getMessageCodesResolver() { return null; } @NotNull @Override public Response intercept(@NotNull Chain chain) throws IOException { Request request = chain.request(); return chain.proceed(request); } @Bean public DispatcherServlet dispatcherServlet(){ return new OraDispatcherServlet(); } }
主要是里面的configureHandlerExceptionResolvers(List<HandlerExceptionResolver> list)
原來(lái)我是直接實(shí)現(xiàn)configureHandlerExceptionResolvers,在
@Override public void configureHandlerExceptionResolvers(List<HandlerExceptionResolver> list) { list.add(new DingdingHandleException()); }
DingdingHandleException()代碼如下

Controller類(lèi):

看到前面有一個(gè)@Valid的注解,AuthModelParam的實(shí)體類(lèi):

但是每次調(diào)試發(fā)現(xiàn),哪個(gè)全局異常確實(shí)是走到了,但是在走到下一步的ModelAndView的解析器時(shí)候,發(fā)現(xiàn)是有問(wèn)題的。使用postman看到的結(jié)果是這樣的:

實(shí)際上,如果沒(méi)有這個(gè)自定義的DingdingHandleException,我得到的應(yīng)該是這樣的:

為什么呢? 在我的全局異常處打印斷點(diǎn),發(fā)現(xiàn)確實(shí)是走到了斷點(diǎn)處。

繼續(xù)斷點(diǎn):發(fā)現(xiàn)在選擇選擇器時(shí)候仍然是對(duì)的:


浙公網(wǎng)安備 33010602011771號(hào)