开发者

SpringBoot中如何进行全局异常处理方式

开发者 https://www.devze.com 2024-11-06 10:23 出处:网络 作者: houxian1103
目录前言如何实现全局拦截?1.1 自定义异常处理类1.2 定义异常码1.3 自定义异常类1.4 自定义返回类型实际测试异常全局拦截的背后原理?总结前言
目录
  • 前言
  • 如何实现全局拦截?
    • 1.1 自定义异常处理类
    • 1.2 定义异常码
    • 1.3 自定义异常类
    • 1.4 自定义返回类型
  • 实际测试异常
    • 全局拦截的背后原理?
      • 总结

        前言

        在SpringBoot的开发中,为了提高程序运行的鲁棒性,我们经常需要对各种程序异常进行处理,但是如果在每个出异常的地方进行单独处理的话,这会引入大量业务不相关的异常处理代码,增加了程序的耦合,同时未来想改变异常的处理逻辑,也变得比较困难。这篇文章带大家了解一下如何优雅的进行全局异常处理。

        为了实现全局拦截,这里使用到了Spring中提供的两个注解,@RestControllerAdvice和@ExceptionHandler,结合使用可以拦截程序中产生的异常,并且根据不同的异常类型分别处理。下面我会先介绍如何利用这两个注解,优雅的完成全局异常的处理,接着解释这背后的原理。

        如何实现全局拦截?

        1.1 自定义异常处理类

        在下面的例子中,我们继承了ResponseEntityExceptionHandler并使用@RestControllerAdvice注解了这个类,接着结合@ExceptionHandler针对不同的异常类型,来定义不同的异常处理方法。

        这里可以看到我处理的异常是自定义异常,后续我会展开介绍。

        • ResponseEntityExceptionHandler中包装了各种SpringMVC在处理请求时可能抛出的异常的处理,处理结果都是封装成一个ResponseEntity对象。
        • ResponseEntityExceptionHandler是一个抽象类,通常我们需要定义一个用来处理异常的使用@RestControllerAdvice注解标注的异常处理类来继承自ResponseEntityExceptionHandler。
        • ResponseEntityExceptionHandler中为每个异常的处理都单独定义了一个方法,如果默认的处理不能满足你的需求,则可以重写对某个异常的处理。
        @Log4j2  
        @RestControllerAdvice  
        public class GlobalExceptionHandler extends ResponseEntityExceptionHandler {  
        
            /**  
             * 定义要捕获的异常 可以多个 @ExceptionHandler({})     *  
             * @param request  request  
             * @param e        exception  
             * @param response response  
             * @return 响应结果  
             */  
            @ExceptionHandler(AuroraRuntimeException.class)  
            public GenericResponse customExceptionHandler(HttpServletRequest request, final Exception e, HttpServletResponse response) {  
                AuroraRuntimeException exception = (AuroraRuntimeException) e;  
        
               if (exception.getCode() == ResponseCode.USER_INPUT_ERROR) {  
                   response.setStatus(HttpStatus.BAD_REQUEST.value());  
               } else if (exception.getCode() == ResponseCode.FORBIDDEN) {  
                   response.setStatus(HttpStatus.FORBIDDEN.value());  
               } else {  
                   response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());  
               }  
        
                return new GenericResponse(exception.getCode(), null, exception.getMessage());  
            }  
        
            @ExceptionHandler(NotLoginException.class)  
            public GenericResponse tokenExceptionHandler(HttpServletRequest request, final Expythonception e, HttpServletResponse response) {  
                log.error("token exception", e);  
                response.setStatus(HttpStatus.FORBIDDEN.value());  
           http://www.devze.com     return new GenericResponse(ResponseCode.AUTHENTICATION_NEEDED);  
            }  
        
        }

        1.2 定义异常码

        这里定义了常见的几种异常码,主要用在抛出自定义异常时,对不同的情形进行区分。

        @Getter  
        public enum ResponseCode {  
        
            SUCCESS(0, "Success"),  
        
            INTERNAL_ERROR(1, "服务器内部错误"),  
        
            USER_INPUT_ERROR(2, "用户输入错误"),  
        
            AUTHENTICATION_NEEDED(3, "Token过期或无效"),  
        
            FORBIDDEN(4, "禁止访问"),  
        
            TOO_FREQUENT_VISIT(5, "访问太频繁,请休息一会儿");  
        
            private final int code;  
        
            private final String message;  
        
            private final Response.Status status;  
        
            ResponseCode(int code, String message, Response.Status status) {  
                this.code = code;  
                this.message = message;  
         js       this.status = status;  
            }  
        
            ResponseCode(int code, String message) {  
                this(code, message, Response.Status.INTERNAL_SERVER_ERROR);  
            }  
        
        }

        1.3 自定义异常类

        这里我定义了一个AuroraRuntimeException的异常,就是在上面的异常处理函数中,用到的异常。

        每个异常实例会有一个对应的异常码,也就是前面刚定义好的。

        @Getter  
        public class AuroraRuntimeException extends RuntimeException {  
        
            private final ResponseCode code;  
        
            public AuroraRuntimeException() {  
                super(String.format("%s", ResponseCode.INTERNAL_ERROR.getMessage()));  
                this.code = ResponseCode.INTERNAL_ERROR;  
            }  
        
            public AuroraRuntimeException(Throwable e) {  
                super(e);  
                this.code = ResponseCode.INTERNAL_ERROR;  
            }  
        
            public AuroraRuntimeException(String msg) {  
                this(ResponseCode.INTERNAL_ERROR, msg);  
            }  
        
            public AuroraRuntimeException(ResponseCode code) {  
                super(String.format("%s", code.getMessage()));  
                this.code = code;  
            }  
        
            public AuroraRuntimeException(ResponseCode code, String msg) {  
                super(msg);  
                this.code = code;  
            }  
        
        }

        1.4 自定义返回类型

        为了保证各个接口的返回统一,这里专门定义了一个返回类型。

        @Getter  
        @Setter  
        public class GenericResponse<T> {  
        
            private int code;  
        
            private T data;  
        
            private String message;  
        
            public GenericResponse() {};  
        
            public GenericResponse(int code, T data) {  
                this.code = code;  
                this.data = data;  
            }  
        
            public GenericResponse(int code, T data, String message) {  
                this(code, data);  
                this.message = message;  
            }  
        
            public GenericResponse(ResponseCode responseCode) {  
                this.code = responseCode.getCode();  
                this.data = null;  
                this.message = responseCode.getMessage();  
            }  
        
            public GenericResponse(ResponseCode responseCode, T data) {  
                this(responseCode);  
                this.data = data;  
            }  
        
            public GenericResponse(ResponseCode responseCode, T data, String message) {  
                this(responseCode, data);  
                this.message = message;  
            }  
        }

        实际测试异常

        下面的例子中,我们想获取到用户的信息,如果用户的信息不存在,可以直接抛出一个异常,这个异常会被我们上面定义的全局异常处理方法所捕获,然后根据不同的异常编码,完成不同的处理和返回。

        public User getUserInfo(Long userId) {  
            // some logic
        
            User user = daoFactory.getExtendedUserMapper().selectByPrimaryKey(userId);  
            if (user == null) {  
                throw new AuroraRuntimeException(ResponseCode.USER_INPUT_ERROR, "用户id不存在");  
            }
        
            // some logic
            ....
        }

        以上就完成了整个全局异常的处理过程,接下来重点说说为什么@RestControllerAdvice和@ExceptionHandler结合使用可以拦截程序中产生的异常?

        全局拦截的背后原理?

        下面会提到@ControllerAdvice注解,简单地说,@RestControllerAdvice与@ControllerAdvice的区别就和@RestController与@Controller的区别类似,@RestControllerAdvice注解包含了@ControllerAdvice注解和@ResponseBody注解。

        public class DispaandroidtcherServlet extends FrameworkServlet {
            // ......
            protected void initStrategies(ApplicationContext context) {
                initMultipartResolver(context);
                initLocaleResolver(context);
                initThemeResolver(context);
                initHandlerMappings(context);
                initHandlerAdapters(context);
        
                // 重点关注
                initHandlerExceptionResolvers(context);
        
                initRequestToViewNameTranslator(context);
                initViewResolvers(context);
                initFlashMapManager(context);
            }
            // ......
        }

        在initHandlerExceptionResolvers(context)方法中,会取得所有实现了HandlerExceptionResolver接口的bean并保存起来,其中就有一个类型为ExceptionHandlerExceptionResolver的bean,这个bean在应用启动过程中会获取所有被@ControllerAdvice注解标注的bean对象做进一步处理,关键代码在这里:

        public class ExceptionHandlerExceptionResolver extends AbstractHandlerMethodExceptionResolver
                implements ApplicationContextAware, InitializingBean {
            // ......
            private void initExceptionHandlerAdviceCache() {
                // ......
                List<ControllerAdviceBean> adviceBeans = ControllerAdviceBean.findAnnotatedBeans(getApplicationContext());
                AnnotationAwareOrderComparator.sort(adviceBeans);
        
                for (ControllerAdviceBean adviceBean : adviceBeans) {
                    ExceptionHandlerMethodResolver resolver = new ExceptionHandlerMethodResolver(adviceBean.getBeanType());
                    if (resolver.hasExceptionMappings()) {
                        // 找到所有ExceptionHandler标注的方法并保存成一个ExceptionHandlerMethodResolver类型的对象缓存起来
                        this.exceptionHandlerAdviceCache.put(adviceBean, resolver);
                        if (logger.isInfoEnabled()) {
                            logger.info("Detected @ExceptionHandler methods in " + adviceBean);
                        }
                    }
                    // ......
                }
            }
            // ......
        }

        当Controller抛出异常时,DispatcherServlet通过ExceptionHandlerExceptionResolver来解析异常,而ExceptionHandlerExceptionResolver又通过ExceptionHandlerMethodResolver 来解析异常, ExceptionHandlerMethodResolver 最终解析异常找到适用的@ExceptionHandler标注的方法是这里:

        public class ExceptionHandlerMethodResolver {
            // ......
            private Method getMappedMethod(Class<? extends Throwable> exceptionType) {
                List<Class<? extends Throwable>> matches = new ArrayList<Class<? extends Throwable>>();
                // 找到所有适用于Controller抛出异常的处理方法,例如Controller抛出的异常
                // 是AuroraRuntimeException(继承自RuntimeException),那么@ExceptionHandler(AuroraRuntimeException.class)和
                // @ExceptionHandler(Exception.class)标注的方法都适用此异常
                for (Class<? extends Throwable> mappedException : this.mappedMethods.keySet()) {
                    ijavascriptf (mappedException.isAssignableFrom(exceptionType)) {
                        matches.add(mappedException);
                    }
                }
                if (!matches.isEmpty()) {
                /* 这里通过排序找到最适用的方法,排序的规则依据抛出异常相对于声明异常的深度,例如
            Controller抛出的异常是是AuroraRuntimeException(继承自RuntimeException),那么AuroraRuntimeException
            相对于@ExceptionHandler(AuroraRuntimeException.class)声明的AuroraRuntimeException.class其深度是0,
            相对于@ExceptionHandler(Exception.class)声明的Exception.class其深度是2,所以
            @ExceptionHandler(BizException.class)标注的方法会排在前面 */
                    Collections.sort(matches, new ExceptionDepthComparator(exceptionType));
                    return this.mappedMethods.get(matches.get(0));
                }
                else {
                    return null;
                }
            }
            // ......
        }

        整个@RestControllerAdvice处理的流程就是这样,结合@ExceptionHandler就完成了对不同异常的灵活处理。

        总结

        以上为个人经验,希望能给大家一个参考,也希望大家多多支持编程客栈(www.devze.com)。

        0

        精彩评论

        暂无评论...
        验证码 换一张
        取 消

        关注公众号