Restfull服务异常统一处理

1.前言

在构建一个服务时,总会抛出各种异常,这时我们就需要进行统一的异常处理,这样就能保证对外有一致的返回。
而且可以避免通过返回值判断函数调用结果这种繁琐的代码。

2.Controller层方法,进行统一异常处理

提供两种不同的方案,如下:

  • 方案1:使用 @ControllerAdvice (或@RestControllerAdvice), @ExceptionHandler注解实现;
  • 方案2: 使用AOP技术实现;

3.使用@ControllerAdvice注解的方式

3.1 创建一个全局异常处理类

如下的代码会对我们项目中的所有Controller的抛出异常进行处理,这里我们引入了自定义异常ServiceException
可以声明要捕获的异常,比如@ExceptionHandler(NoHandlerFoundException.class)
可以指定拦截的包,@RestControllerAdvice(basePackages = "com.demo.springboot_helloword.webrest")

/**
 * Created on 2019/1/15 19:09.
 *
 * @author caogu
 */
@RestControllerAdvice
public class ControllerExceptionHandleAdvice {
    private final static Logger logger = LoggerFactory.getLogger(ControllerExceptionHandleAdvice.class);

    @ExceptionHandler
    public BaseResult handler(HttpServletRequest request, HttpServletResponse response, Exception e) {
        logger.error("Restful Http请求发生异常, Path=" + request.getServletPath(), e);
        if (response.getStatus() == HttpStatus.BAD_REQUEST.value()) {
            logger.info("修改返回状态值,oldStatus={} newStatus={}", response.getStatus(), HttpStatus.BAD_REQUEST.value());
            response.setStatus(HttpStatus.OK.value());
        }

        BaseResult baseResult = new BaseResult();
        if (e instanceof ServiceException) {
            ServiceException exception = (ServiceException) e;
            baseResult.setCode(exception.getCode());
            baseResult.setMessage(exception.getMessage());
        } else {
            baseResult.setCode(PlatformErrorCode.SERVICE_INTERNAL_ERROR.getCode());
            baseResult.setMessage(PlatformErrorCode.SERVICE_INTERNAL_ERROR.getMessage());
        }

        return baseResult;
    }
}

3.2 创建一个自定义异常

一个业务异常,一般需要错误码和错误信息。有助于我们来定位问题和统一对外接口行为。

/**
 * Created on 2019/1/14 11:37.
 *
 * @author caogu
 */
public class ServiceException extends RuntimeException {
    /**
     * 错误码
     */
    private String code;

    /*public ServiceException(String message, String code) {
        super(message);
        this.code = code;
    }*/

    public ServiceException(BusinessErrorCode businessErrorCode) {
        super(businessErrorCode.getMessage());
        this.code = businessErrorCode.getCode();
    }

    public ServiceException(BusinessErrorCode businessErrorCode, Throwable cause) {
        super(businessErrorCode.getMessage(), cause);
        this.code = businessErrorCode.getCode();
    }

    public ServiceException(PlatformErrorCode platformErrorCode) {
        super(platformErrorCode.getMessage());
        this.code = platformErrorCode.getCode();
    }

    public ServiceException(PlatformErrorCode businessErrorCode, Throwable cause) {
        super(businessErrorCode.getMessage(), cause);
        this.code = businessErrorCode.getCode();
    }

    /*public ServiceException(String message, String code, Throwable cause) {
        super(message, cause);
        this.code = code;
    }*/

    public String getCode() {
        return code;
    }

    public void setCode(String code) {
        this.code = code;
    }
}

这时候,我们发现还有一个问题,如果这样写,在代码多起来以后,很难管理这些业务异常和错误码之间的对应关系。所以在优化一下。
把错误码及错误信息,组装成枚举来统一管理。
定义一个业务异常的枚举。

3.3 业务异常枚举

package com.sinosun.starter.enums;

/**
 * Created on 2019/1/14 12:10.
 *
 * @author caogu
 */
public enum PlatformErrorCode {
    SERVICE_INTERNAL_ERROR(1, "服务器内部错误")
    ;
    private static final int BASE_CODE_VALUE = 10000;
    private static final String BASE_CODE_PREFIX = "P";
    private String code;
    private String message;

    PlatformErrorCode(int code, String message) {
        this.code = buildCode(code);
        this.message = message;
    }

    private String buildCode(int code) {
        int codeValue = BASE_CODE_VALUE + code;
        return BASE_CODE_PREFIX + codeValue;
    }

    public String getCode() {
        return code;
    }

    public String getMessage() {
        return message;
    }
}

3.4 结果测试

在代码中抛出一个异常后,统一异常处理就会生效,如下所示:

public StationResult getAllCity(NoneRequest requestBody) {
    throw new ServiceException(PlatformErrorCode.SERVICE_INTERNAL_ERROR);
    //return new StationResult(new StationList(PreloadData.getTrainAllCity()));
}

在这里插入图片描述

4.使用AOP技术

主要思路:通过AOP的@Around拦截所有的Controller方法,在调用处统一处理异常。还可在调用前调用后打印日志等操作。
例如:

package com.sinosun.starter.config;

/**
 * Created on 2019/1/16 8:44.
 *
 * @author caogu
 */
@Component
@Aspect
public class ControllerAspect {
    private static final Logger logger = LoggerFactory.getLogger(ControllerAspect.class);

    @Pointcut("execution(public * com.sinosun.starter.controller..*.*(..))")
    public void allControllerFunction() {
    }


    @Around("allControllerFunction()")
    public Object handleControllerMethod(ProceedingJoinPoint pjp) {
        Stopwatch stopwatch = Stopwatch.createStarted();

        BaseResult baseResult;
        try {
            logger.info("执行Controller方法{}开始,参数:{}", pjp.getSignature().getName(), Arrays.toString(pjp.getArgs()));
            baseResult = (BaseResult) pjp.proceed(pjp.getArgs());
            logger.info("执行Controller方法{}结束,返回值:{}", pjp.getSignature().getName(), baseResult);
            logger.info("执行Controller方法{}耗时:{}(ms).", pjp.getSignature().getName(), stopwatch.stop().elapsed(TimeUnit.MILLISECONDS));
        } catch (Throwable throwable) {
            baseResult = handlerException(pjp, throwable);
        }
        return baseResult;
    }

    private BaseResult handlerException(ProceedingJoinPoint pjp, Throwable e) {
        BaseResult baseResult = new BaseResult();
        logger.error("方法:" + pjp.getSignature().getName() + ", 参数:" + Arrays.toString(pjp.getArgs()) + ",异常:" + e.getMessage() + "}", e);
        if (e instanceof ServiceException) {
            ServiceException exception = (ServiceException) e;
            baseResult.setCode(exception.getCode());
            baseResult.setMessage(exception.getMessage());
        } else {
            baseResult.setCode(PlatformErrorCode.SERVICE_INTERNAL_ERROR.getCode());
            baseResult.setMessage(PlatformErrorCode.SERVICE_INTERNAL_ERROR.getMessage());
        }

        return baseResult;
    }

}

结果:
在这里插入图片描述

5.结论

  • 异常处理推荐使用 @ControllerAdvice (或@RestControllerAdvice), @ExceptionHandler注解方案。
  • 异常处理不推介使用AOP技术,使用 AOP技术作为出入口日志打印,异常处理比较麻烦,对所有的返回值需要手动设置response。
  • 这两者应该结合使用,@ControllerAdvice处理异常,使用AOP统一记录出入口日志。
发布了418 篇原创文章 · 获赞 745 · 访问量 126万+

猜你喜欢

转载自blog.csdn.net/u013467442/article/details/89193430