springboot下的异常拦截,通常可以通过在每个节点引入一个exceptionHandler来实现,使用@ControllerAdvice在类上来声明这是一个controller层的异常捕获器,使用@ExceptionHandler在方法上声明捕获什么类型的异常,两者搭配使用已经可以解决大部分的异常问题.
/**
* @author liucw
* 全局捕获异常类,只要作用在@RequestMapping上,所有的异常都会被捕获
*/
@Slf4j
@ResponseBody
@ControllerAdvice
public class GlobalExceptionHanlder {
/* @Autowired
private LogClient logClient;*/
/**
* 手工调用数据验证不通过
*/
@ExceptionHandler(value = ValidException.class)
public Result handleValidException(ValidException e) {
Result errorResult = new Result();
errorResult.setCode(ResultCodeEnum.EROR_DATA);
errorResult.setMsg(e.getMessage());
return errorResult;
}
}
如果想要捕获service层的异常,通常需要额外自定义一个异常类,然后在@ExceptionHandler捕获自定义的异常.
public class BusinessException extends RuntimeException {
private Integer code;
private String message;
public BusinessException(ResultCodeEnum errorCode) {
super(errorCode.getMsg());
this.code = errorCode.getCode();
this.message=errorCode.getMsg();
}
}
然后拦截的时候使用@ExceptionHandler(BusinessException.class)即可.
实际项目中经常会有在filter抛出的异常,例如我们项目在gateway的OncePerRequestFilter中做权限校验时抛出了很多自定义的异常,这些异常时无法被全局异常捕捉到的.通常来说,在springboot项目中如果想要拦截filter的异常,只需让filter继承zuulfilter类,然后声明他的filterType类型是error,那么他就能捕获所有继承zuulFilter并且filtertaye类别不是error的所有filter抛出的异常.由此可以看出,他只能拦截继承zuulfilter的抛出去的异常,并不能拦截通用的filter的异常.
所幸springboot本身提供了一个全局异常拦截器AbstractErrorController,我们可以仿造他写一个.
/*
* Copyright 2012-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.autoconfigure.web.servlet.error;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.boot.autoconfigure.web.ErrorProperties;
import org.springframework.boot.autoconfigure.web.ErrorProperties.IncludeStacktrace;
import org.springframework.boot.web.servlet.error.ErrorAttributes;
import org.springframework.boot.web.servlet.server.AbstractServletWebServerFactory;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.util.Assert;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.servlet.ModelAndView;
/**
* Basic global error {@link Controller}, rendering {@link ErrorAttributes}. More specific
* errors can be handled either using Spring MVC abstractions (e.g.
* {@code @ExceptionHandler}) or by adding servlet
* {@link AbstractServletWebServerFactory#setErrorPages server error pages}.
*
* @author Dave Syer
* @author Phillip Webb
* @author Michael Stummvoll
* @author Stephane Nicoll
* @see ErrorAttributes
* @see ErrorProperties
*/
@Controller
@RequestMapping("${server.error.path:${error.path:/error}}")
public class BasicErrorController extends AbstractErrorController {
private final ErrorProperties errorProperties;
/**
* Create a new {@link BasicErrorController} instance.
* @param errorAttributes the error attributes
* @param errorProperties configuration properties
*/
public BasicErrorController(ErrorAttributes errorAttributes,
ErrorProperties errorProperties) {
this(errorAttributes, errorProperties, Collections.emptyList());
}
/**
* Create a new {@link BasicErrorController} instance.
* @param errorAttributes the error attributes
* @param errorProperties configuration properties
* @param errorViewResolvers error view resolvers
*/
public BasicErrorController(ErrorAttributes errorAttributes,
ErrorProperties errorProperties, List<ErrorViewResolver> errorViewResolvers) {
super(errorAttributes, errorViewResolvers);
Assert.notNull(errorProperties, "ErrorProperties must not be null");
this.errorProperties = errorProperties;
}
@Override
public String getErrorPath() {
return this.errorProperties.getPath();
}
@RequestMapping(produces = "text/html")
public ModelAndView errorHtml(HttpServletRequest request,
HttpServletResponse response) {
HttpStatus status = getStatus(request);
Map<String, Object> model = Collections.unmodifiableMap(getErrorAttributes(
request, isIncludeStackTrace(request, MediaType.TEXT_HTML)));
response.setStatus(status.value());
ModelAndView modelAndView = resolveErrorView(request, response, status, model);
return (modelAndView != null) ? modelAndView : new ModelAndView("error", model);
}
@RequestMapping
@ResponseBody
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<>(body, status);
}
/**
* Determine if the stacktrace attribute should be included.
* @param request the source request
* @param produces the media type produced (or {@code MediaType.ALL})
* @return if the stacktrace attribute should be included
*/
protected boolean isIncludeStackTrace(HttpServletRequest request,
MediaType produces) {
IncludeStacktrace include = getErrorProperties().getIncludeStacktrace();
if (include == IncludeStacktrace.ALWAYS) {
return true;
}
if (include == IncludeStacktrace.ON_TRACE_PARAM) {
return getTraceParameter(request);
}
return false;
}
/**
* Provide access to the error properties.
* @return the error properties
*/
protected ErrorProperties getErrorProperties() {
return this.errorProperties;
}
}
这是一个实现类AbstractErrorController的异常捕获类,我们可以仿造他写一个
package com.gpm.gateway.controller;
import com.gpm.common.exception.BusinessException;
import com.gpm.common.exception.OauthException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.web.ErrorProperties;
import org.springframework.boot.autoconfigure.web.ServerProperties;
import org.springframework.boot.autoconfigure.web.servlet.error.AbstractErrorController;
import org.springframework.boot.web.servlet.error.DefaultErrorAttributes;
import org.springframework.boot.web.servlet.error.ErrorAttributes;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.context.request.ServletWebRequest;
import org.springframework.web.context.request.WebRequest;
import javax.servlet.http.HttpServletRequest;
import java.util.Map;
/**
* 用于拦截filter的异常,然后再次抛出给全局异常捕获
* @ClassName:GlobalExceptionController
* @Description:TODO
* @Author:linda
* @Date:2019-7-9
*/
@Controller
@RequestMapping("${server.error.path:${error.path:/error}}")
@Slf4j
public class GlobalExceptionController extends AbstractErrorController {
private final ErrorProperties errorProperties;
private final ErrorAttributes errorAttributes;
private final static String EXCEPTION_NAME ="exception";
private final static String OAUTH_EXCEPTION_NAME ="OauthException";
private final static String BUSINESS_EXCEPTION_NAME ="BusinessException";
private final static String ILLEGAL_ARGUMENT_EXCEPTION_NAME ="IllegalArgumentException";
public GlobalExceptionController(ErrorAttributes errorAttributes, ServerProperties serverProperties) {
super(errorAttributes);
this.errorProperties = serverProperties.getError();
this.errorAttributes = new DefaultErrorAttributes(true);
}
@Override
public String getErrorPath() {
return errorProperties.getPath();
}
@RequestMapping
@ResponseBody
public void error(HttpServletRequest request) throws Exception {
Map<String, Object> errorMap = getErrorAttributes(request, isIncludeStackTrace(request, MediaType.APPLICATION_JSON));
Object exception = errorMap.get(EXCEPTION_NAME);
String errorMessage = errorMap.get("message").toString();
if(exception==null){
throw new BusinessException(errorMessage);
}else{
String exceptionName = exception.toString();
// String exceptionString = exceptionName.substring(exception.toString().lastIndexOf("."), exceptionName.length() - 1);
throw new Exception();
// int i = exception.toString().lastIndexOf(".");
if(exceptionName.contains(OAUTH_EXCEPTION_NAME )){
throw new OauthException(errorMessage);
}else if(exceptionName.contains(BUSINESS_EXCEPTION_NAME)){
throw new BusinessException(errorMessage);
}else if(exceptionName.contains(ILLEGAL_ARGUMENT_EXCEPTION_NAME)){
throw new IllegalArgumentException(errorMessage);
}else{
throw new Exception(errorMessage);
}
}
}
@Override
protected Map<String, Object> getErrorAttributes(HttpServletRequest request,
boolean includeStackTrace) {
WebRequest webRequest = new ServletWebRequest(request);
return this.errorAttributes.getErrorAttributes(webRequest, includeStackTrace);
}
private boolean isIncludeStackTrace(HttpServletRequest request,
MediaType produces) {
ErrorProperties.IncludeStacktrace include = getErrorProperties().getIncludeStacktrace();
if (include == ErrorProperties.IncludeStacktrace.ALWAYS) {
return true;
}
if (include == ErrorProperties.IncludeStacktrace.ON_TRACE_PARAM) {
return getTraceParameter(request);
}
return false;
}
private ErrorProperties getErrorProperties() {
return this.errorProperties;
}
}
有个细节和BasicErrorController有所差别,就是构造器的注入上,
public GlobalExceptionController(ErrorAttributes errorAttributes, ServerProperties serverProperties) {
super(errorAttributes);
this.errorProperties = serverProperties.getError();
this.errorAttributes = new DefaultErrorAttributes(true);
}
我们自己注入的可以看到有这么一句this.errorAttributes = new DefaultErrorAttributes(true).我特意注入的是DefaultErr
orAttributes而不是errorAttributes,原因就在于在AbstractErrorController的
protected Map<String, Object> getErrorAttributes(HttpServletRequest request,
boolean includeStackTrace) {
WebRequest webRequest = new ServletWebRequest(request);
return this.errorAttributes.getErrorAttributes(webRequest, includeStackTrace);
}
方法里面调用了DefaultErrorAttributes的
@Override
public Map<String, Object> getErrorAttributes(WebRequest webRequest,
boolean includeStackTrace) {
Map<String, Object> errorAttributes = new LinkedHashMap<>();
errorAttributes.put("timestamp", new Date());
addStatus(errorAttributes, webRequest);
addErrorDetails(errorAttributes, webRequest, includeStackTrace);
addPath(errorAttributes, webRequest);
return errorAttributes;
}
这个方法里的
private void addErrorDetails(Map<String, Object> errorAttributes,
WebRequest webRequest, boolean includeStackTrace) {
Throwable error = getError(webRequest);
if (error != null) {
while (error instanceof ServletException && error.getCause() != null) {
error = ((ServletException) error).getCause();
}
if (this.includeException) {
errorAttributes.put("exception", error.getClass().getName());
}
addErrorMessage(errorAttributes, error);
if (includeStackTrace) {
addStackTrace(errorAttributes, error);
}
}
Object message = getAttribute(webRequest, "javax.servlet.error.message");
if ((!StringUtils.isEmpty(message) || errorAttributes.get("message") == null)
&& !(error instanceof BindingResult)) {
errorAttributes.put("message",
StringUtils.isEmpty(message) ? "No message available" : message);
}
}
判断了是否要把exception的信息放入进map.判断的条件就是这个this.includeException,而这个是一开始
/**
* Create a new {@link DefaultErrorAttributes} instance.
* @param includeException whether to include the "exception" attribute
*/
public DefaultErrorAttributes(boolean includeException) {
this.includeException = includeException;
}
构造注入的,所以我们才采用DefaultErrorAttributes作为我们自定义的GlobalExceptionController 的属性注入.没错费了一大圈,其实就是为了获得返回的这个map信息中包含了exception信息,这个exception可以获取抛出的异常类别,否则map中是没有这个属性的,不利于我们自己去实现异常的逻辑判断.此时我们再手动抛出异常,全局异常拦截器就可以拦截了.其实还有一个方案,就是直接在这里进行全局异常的拦截,不需要全局异常拦截器了.