浏览器访问出现错误是返回视图层
其他客户端出现错误时候返回定制的json数据
先写一个ExceptionHandler
@ControllerAdvice
public class MyExceptionHandler {
//出现运行时异常就会进入这个方法,将信息放到域中,然后转发到/error
@ExceptionHandler(RunTimeException.class)
public String handleException(Exception e, HttpServletRequest request) {
Map<String, Object> map = new HashMap<>();
//传入我们自己的错误状态码 4xx 5xx
/**
* Integer statusCode = (Integer) request
.getAttribute("javax.servlet.error.status_code");
*/
request.setAttribute("javax.servlet.error.status_coede", 500);
map.put("code", "user.notexist");
map.put("message", "用户出错啦");
request.setAttribute("ext", map);
//转发到/error
return "forward:/error";
}
}
首先先分析下springboot的自动配置
先看ErrorMvcAutoConfiguration
@Configuration
@ConditionalOnWebApplication(type = Type.SERVLET)
@ConditionalOnClass({ Servlet.class, DispatcherServlet.class })
// Load before the main WebMvcAutoConfiguration so that the error View is available
@AutoConfigureBefore(WebMvcAutoConfiguration.class)
@EnableConfigurationProperties({ ServerProperties.class, ResourceProperties.class,
WebMvcProperties.class })
public class ErrorMvcAutoConfiguration {
//这里new了一个BasicErrorController,当出现错误的时候BasicErrorController里面就有对应的方法会被执行
@Bean
@ConditionalOnMissingBean(value = ErrorController.class, search = SearchStrategy.CURRENT)
public BasicErrorController basicErrorController(ErrorAttributes errorAttributes) {
return new BasicErrorController(errorAttributes, this.serverProperties.getError(),
this.errorViewResolvers);
}
}
BasicErrorController
@Controller
//被异常处理器拦截到 return "forward:/error" 就会跳到这个控制器里面来
@RequestMapping("${server.error.path:${error.path:/error}}")
public class BasicErrorController extends AbstractErrorController {
//该类只有两个处理器 处理的都是同一套逻辑
//这个处理器是对浏览器访问处理的
//返回的是ModelAndView
@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);
}
}
//这两个处理器都有个共同的方法getErrorAttributes,将错误的信息都取出来,返回给客户端
getErrorAttributes
@Order(Ordered.HIGHEST_PRECEDENCE)
public class DefaultErrorAttributes
implements ErrorAttributes, HandlerExceptionResolver, Ordered {
//get出了timestamp、status、error、path、message
@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;
}
}
综上,执行顺序是
-
@ExceptionHandler(…)拦截到了错误
1.1.如果处理器的返回值是数据@ResponseBody,则直接将数据返回给客户端。结束。
1.2. 如果处理器的返回的是逻辑页面,如果是 return “forward:/error”;则将数据转发到处理/error请求的controller ----> BasicErrorController
-
BasicErrorController 里面有两个方法,一个是处理浏览器错误的(返回的是视图页面),另一个是处理其他客户端错误的(返回的是json数据)
这两个方法的共同点是都调用了getErrorAttributes方法
-
getErrorAttributes---->这个方法是抽象类AbstractErrorController的
这个方法里面调用了getErrorAttributes -
getErrorAttributes ---->这个方法是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;
}
//这里面并没有把异常处理器中request中放置的数据存进去,故客户端显示错误信息的时候并没有显示我们自己定制的错误,所以如果要显示自己定制的错误,我们需要重写这个方法,从webRequest中getAttribute数据
- 所以要定制自己需要的错误数据,只需要继承DefaultErrorAttributes,重写getErrorAttributes 方法
- 在容器中,DefaultErrorAttributes只能有一个
//类ErrorMvcAutoConfiguration
@Bean
//这里有个条件 如果没有ErrorAttribute才创建,因为DefaultErrorAttributes实现了ErrorAttribute,所以也就是 没有DefaultErrorAttributes才会new一个DefaultErrorAttributes
@ConditionalOnMissingBean(value = ErrorAttribute.class, search = SearchStrategy.CURRENT)
public DefaultErrorAttributes errorAttributes() {
return new DefaultErrorAttributes(
this.serverProperties.getError().isIncludeException());
}
- 故我们实现DefaultErrorAttributes并重写方法getErrorAttributes
@Component
public class MyExceptionAttributes extends DefaultErrorAttributes {
@Override
public Map<String, Object> getErrorAttributes(WebRequest webRequest,
boolean includeStackTrace) {
//调用父类的方法
Map<String, Object> map = super.getErrorAttributes(webRequest, includeStackTrace);
//添加自己的错误信息
Object ext = webRequest.getAttribute("ext", RequestAttributes.SCOPE_REQUEST);
map.put("ext", ext);
//返回错误信息
return map;
}
最后效果
浏览器 返回的是页面
其他客户端 返回的是json数据