SpringBoot全局异常处理(Controller、interceptor、filter)

1、Controller层的全局异常处理

通过对Controller添加注释@ControllerAdvice可以实现Controller层的全局异常处理

统一的拦截异常处理类AppExceptionHandler

@Slf4j
@RestControllerAdvice
public class AppExceptionHandler {
    @Autowired
    protected MessageSource messageSource;

    @ExceptionHandler({UserLoginException.class})
    @ResponseStatus(HttpStatus.FORBIDDEN)
    public Result handleUserLoginException(final UserLoginException userLoginException) {
        log.error("LiveAppExceptionHandler->UserLoginException={}", userLoginException);
        return Result.error(userLoginException.getCode(), userLoginException.getMessage());
    }
    
    @ExceptionHandler({UrlException.class})
    @ResponseStatus(HttpStatus.OK)
    public Result handleChanceException(final UrlException urlException) {
        log.error("【UrlException】:", UrlException);
        return Result.error(urlException.getCode(), urlException.getMessage());
    }

    @ExceptionHandler({Exception.class})
    @ResponseStatus(HttpStatus.OK)
    public Result handleException(final Exception exception) {
        log.error("【exception】:", exception);
        return Result.error(AdminErrorCodeEnum.A0001);
    }

}

Result 统一封装的返回类

@Data
public class Result<T> {
    private int code;
    private T data;
    private String msg;


    public static <T> Result<T> success(T response) {
        Result<T> result = new Result<>();
        result.setCode(0);
        result.setData(response);
        return result;
    }

    public static Result error(int code, String message) {
        Result result = new Result();
        result.setCode(code);
        result.setMsg(message);
        return result;
    }

    public static Result error(AppErrorCodeEnum errorCodeEnum) {
        Result result = new Result();
        result.setCode(errorCodeEnum.getCode());
        result.setMsg(errorCodeEnum.getMessage());
        return result;
    }

    public static Result error(AppErrorCodeEnum errorCodeEnum, MessageSource messageSource, Object... params) {
        Result jsonResult = new Result();
        jsonResult.setCode(errorCodeEnum.getCode());
        jsonResult.setMsg(messageSource.getMessage(String.valueOf(errorCodeEnum.getCode()), params, errorCodeEnum.getMessage(), Locale.SIMPLIFIED_CHINESE));
        return jsonResult;
    }
}

2、Interceptor异常统一处理

Interceptor是基于SpringMVC框架的,有时候我们需要拦截器中进行逻辑处理时(比如:tonken登陆验证)需要抛出异常,那么这里抛出的异常是可以被@ControllerAdvice注释的异常处理类捕获的。

InterceptorConfig 配置类,将具体的拦截器类注册到InterceptorConfig 中

@Configuration
public class InterceptorConfig implements WebMvcConfigurer {

    @Autowired
    SessionInterceptor userLoginInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        InterceptorRegistration interceptorRegistration = registry.addInterceptor(userLoginInterceptor);
        interceptorRegistration.addPathPatterns("/**");
        interceptorRegistration.excludePathPatterns(
                "/login/**"
        );
}

实际的业务拦截器类SessionInterceptor

@Slf4j
@Component
public class SessionInterceptor implements HandlerInterceptor {

    @Autowired
    RedisTemplate redisTemplate;

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
        String token = request.getHeader("token");
        if (StringUtils.isEmpty(token)){
            log.error("传入token参数值为空={},请先登录",token);
            throw new UserLoginException(AppErrorCodeEnum.A0003.getCode(),AppErrorCodeEnum.A0003.getMessage() );
        }
        //从redis中获取token
        LiveTokenDTO liveTokenDTO = (LiveTokenDTO) redisTemplate.opsForValue().get(token);
        if(ObjectUtil.isNotEmpty(liveTokenDTO)){  
            return true;
        } else {
            log.error("SessionInterceptor->userToken用户会话解析为空");
            throw new UserLoginException(AppErrorCodeEnum.A0003.getCode(),AppErrorCodeEnum.A0003.getMessage() );
        }
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
    }

异常类

@data
public class UserLoginException extends RuntimeException{
    int code;
    String message;

}

枚举类

@Getter
public enum AppErrorCodeEnum {

    A0000(10000, "参数校验失败"),
    A0001(10001, "系统繁忙,请稍后再试"),
    A0002(10002, "登录失败"),
    A0003(10003, "token失效,请重新登录");
    private int code;

    private String message;

    private String subCode;

    private String subMessage;

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

    public void setSubCodeAndSubMessage(String subCode, String subMessage) {
        setSubCode(subCode);
        setSubMessage(subMessage);
    }

    private void setSubCode(String subCode) {
        this.subCode = subCode;
    }

    private void setSubMessage(String subMessage) {
        this.subMessage = subMessage;
    }
}

3、Filter异常统一处理

过滤器Filter和基于Servlet框架编写的,抛出的异常是不可以被@ControllerAdvice注释的异常处理类捕获的,常见的异常处理的方法有两种:
(1)通过请求转发到特定的异常处理controller中进行处理

定义一个filter异常处理的Controller

@Slf4j
@RestController
@RequestMapping("/filter")
public class FilterExceptionController {

    @RequestMapping("exception")
    public void handleException() {
        throw new UrlException(AppErrorCodeEnum.A0003.getCode(), AppErrorCodeEnum.A0003.getMessage());

    }
}

请求url路径过滤器

@Slf4j
@Component
public class RequestUrlFilter implements Filter {

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {

        String path = ((HttpServletRequest) servletRequest).getRequestURI();
        if (!path.contains("/app")) {
//            throw new UrlException(401, "filter内部抛出异常");//AppExceptionHandler 全局异常捕获不到
//转发给filter异常处理的Controller,controller中直接抛出异常,AppExceptionHandler全局异常就可已捕获到了       servletRequest.getRequestDispatcher("/filter/exception").forward(servletRequest,servletResponse);
        }
        filterChain.doFilter(servletRequest, servletResponse);
    }

}

(2)、SpringBoot 提供了 ErrorController 这个接口能力;其内置了一个 BasicErrorController 对异常进行统一的处理,当在 Controller 发生异常的时候会自动把请求 forward 到 /error 这个请求 path 下(/error 是 SpringBoot 提供的一个默认的mapping)。BasicErrorController 提供两种返回错误:1、页面返回;2、json 返回。

@RestController
public class UrlErrorController extends BasicErrorController {

    public UrlErrorController() {
        super(new DefaultErrorAttributes(), new ErrorProperties());
    }

    @Override
    @RequestMapping(produces = {MediaType.APPLICATION_JSON_VALUE})
    public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {
        Map<String, Object> body = getErrorAttributes(request, isIncludeStackTrace(request, MediaType.ALL));
        HttpStatus status = getStatus(request);
        return new ResponseEntity((String) body.get("message"), status);
    }
}

将filter异常直接抛出

@Slf4j
@Component
public class RequestUrlFilter implements Filter {

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {

        String path = ((HttpServletRequest) servletRequest).getRequestURI();
        if (!path.contains("/app")) {
			throw new UrlException(401, "filter内部抛出异常");
        }
        filterChain.doFilter(servletRequest, servletResponse);
    }

}

返回:java.lang.RuntimeException: UrlException(code=401, message=filter内部抛出异常)

你可能感兴趣的:(杂谈,spring,boot,java,spring)