SpringBoot异常处理机制及源码解析
启动⼀个springboot 项⽬后,访问⼀个不存在的页⾯,浏览器则产⽣⼀个错误的⽩页,⽽使⽤客户端⼯具返回的是⼀个json格式的数据。
常见的400/500错误也是如此。
为什么同⼀个地址,不同的客户端访问会产⽣不同的响应呢?
The BasicErrorController can be used as a base class for a custom ErrorController.
官⽅⽂档中有这么⼀句话。在SpringBoot 中是BasicErrorController 是⼀个⾃定义ErrorController的基类来是实现错误的。
@Controller
@RequestMapping("${path:${error.path:/error}}")
public class BasicErrorController extends AbstractErrorController {
该类是⼀个控制器,系统中出现的所有异常全部进到/error这个url下,及该控制器中,在看看它是如何处理的。⾥⾯有两个处理的⽅法:
@RequestMapping(produces = MediaType.TEXT_HTML_VALUE)
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
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);
}
⼀个返回页⾯,⼀个返回json数据。最关键的地⽅在@RequestMapping(produces = MediaType.TEXT_HTML_VALUE) 这⾥,produces源码说明:
/**
* The producible media types of the mapped request, narrowing the primary mapping.
* <p>The format is a single media type or a sequence of media types,
* with a request only mapped if the {@code Accept} matches one of these media types.
* Examples:
* <pre class="code">
* produces = "text/plain"
* produces = {"text/plain", "application/*"}
* produces = MediaType.APPLICATION_JSON_UTF8_VALUE
* </pre>
* <p>It affects the actual content type written, for example to produce a JSON response
* with UTF-8 encoding, {@link org.springframework.http.MediaType#APPLICATION_JSON_UTF8_VALUE} should be used.
* <p>Expressions can be negated by using the "!" operator, as in "!text/plain", which matches
* all requests with a {@code Accept} other than "text/plain".
* <p><b>Supported at the type level as well as at the method level!</b>
* When used at the type level, all method-level mappings override
* this produces restriction.
* @see org.springframework.http.MediaType
*/
String[]produces()default{};
要求请求request中的Accept符合⼀种MediaType才会执⾏,也就是当request中Accept 值为
/**
* A String equivalent of {@link MediaType#TEXT_HTML}.
*/
public static final String TEXT_HTML_VALUE ="text/html";
才会执⾏errorHtml()⽅法,否则执⾏error()⽅法。那我们来看看两种请求的请求头:
所以浏览器访问时返回的是页⾯,客户端访问时返回的是json。
⾃定义错误页⾯返回
在error⽂件夹下的创建对应的http 状态码页⾯即可返回对应的错误页⾯。
为什么呢?它是怎么识别出来的呢?继续看源码:
在errorHtml()⽅法中页⾯返回是调⽤resolveErrorView(request, response, status, model) 来获取到页⾯视图。该⽅法调⽤的是⽗
类AbstractErrorController中的⽅法,⽽该⽅法实际的执⾏是ErrorViewResolver接⼝下resolveErrorView()⽅法。该接⼝只有⼀个实现DefaultErrorViewResolver,来看看该类中的实现
@Override
public ModelAndView resolveErrorView(HttpServletRequest request, HttpStatus status,
Map<String, Object> model){
ModelAndView modelAndView =resolve(String.valueOf(status.value()), model);
if(modelAndView == null && ainsKey(status.series())){
modelAndView =resolve((status.series()), model);
}
return modelAndView;
}
private ModelAndView resolve(String viewName, Map<String, Object> model){
String errorViewName ="error/"+ viewName;
TemplateAvailabilityProvider provider =plateAvailabilityProviders
.getProvider(errorViewName,this.applicationContext);
if(provider != null){
return new ModelAndView(errorViewName, model);
}
return resolveResource(errorViewName, model);
}
在 resolve()中erroViewName 该属性就是实际要返回的视图的url。前缀为error/⽽viewName为状态码,所以⾃定义的404.html等页⾯必须要在error包下才能被正确识别出来。
了解到这些我们即可⾃⼰来实现项⽬中⾃定义全局异常处理,浏览器返回错误页⾯,客户端返回json格式数据。
全局异常处理
slf4j.Slf4j;
import org.springframework.boot.ErrorAttributes;
404页面网站源码import org.springframework.boot.ErrorController;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.stereotype.Controller;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.t.request.ServletWebRequest;
import org.t.request.WebRequest;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* 全局异常处理类
* @author peter
* date: 2019-04-29 18:42
**/
@Controller
@RequestMapping("/error")
@Slf4j
public class GlobalErrorHandlerController implements ErrorController {
private ErrorAttributes errorAttributes;
//注⼊ErrorAttributes
public GlobalErrorHandlerController(ErrorAttributes errorAttributes){
}
@Override
public String getErrorPath(){
return"error";
}
/
/浏览器的处理
@RequestMapping(produces = MediaType.TEXT_HTML_VALUE)
public String errorHtml(HttpServletRequest request,
HttpServletResponse response){
HttpStatus status =getStatus(request);
log.warn("errorHtml错误信息:",getRequestErrorMsg(request),getErrorAttributes(request));
switch(status){
case NOT_FOUND:
return"404";
case BAD_REQUEST:
return"400";
case INTERNAL_SERVER_ERROR:
return"500";
default:
return"404";
}
}
//rest请求的处理
@RequestMapping
public ResponseEntity<?>error(HttpServletRequest request){
HttpStatus status =getStatus(request);
log.warn("error错误信息:",getRequestErrorMsg(request),getErrorAttributes(request));
return ResponseEntity.ok(new GeneralMessage(status.value(), StringUtils.isEmpty(getRequestErrorMsg(request))?getErrorAttributes(request):get RequestErrorMsg(request)));
}
private Object getRequestErrorMsg(HttpServletRequest request){
Attribute("message");
}
/**
* 获取request状态码
* @param request
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论