通过对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;
}
}
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;
}
}
过滤器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内部抛出异常)