我们先来看一下Springboot的默认效果
浏览器访问
客户端访问
划重点!!!
但是绝大部分公司的代码,都是没做自适应处理的,很大一部分原因在于,你在网上搜索Springboot全局异常处理,都是搜索到这么一段代码!
@ControllerAdvice
publicclass MyControllerAdvice {
@ResponseBody
@ExceptionHandler(value = Exception.class)
public ResponseEntity errorHandler(Exception ex) {
// 处理异常
}
}
强烈建议先用自己常用的搜索引擎搜索一遍,然后再看一下自己公司代码,看看是不是类似这么一段代码再往下看。
当然很多同学可能会说,我们就已经和客户端约定很好了,只会有json,不会有返回html的场景。所以,不做这个适应,其实也是没问题的。但是如果你是做基础架构的同学,这个功能你是必须要做的,因为你对接的是整个公司的业务部门,Springboot能做,你做类似的基础组件,如果功能比Springboot还差,你让业务方的同学怎么想?
当然,对于绝大部分同学来说,不做问题也不大。
但是这样你会错过一个很好的学习机会。什么学习机会?因为很多同学平时总说,面试造火箭,工作中遇到不懂的问题百度或者谷歌一下就好了。然而,这个问题,你就没这么好搜索到。也就是说,绝大部分人都是面向搜索引擎编程,当遇到搜索引擎无法解决的问题的时候,就是体现你价值的时候,好好珍惜。
做不做这个功能我觉得不重要,这个宝贵的锻炼解决问题能力的机会是真的很难得,毕竟,确实大部分功能是真的简单搜索,或者肥朝交流群问问就能解决。
自适应原理
很多同学说,既然搜索不到,那果断一波源码走起。但是,Springboot源码这么多,请问哪里入手?这个才是重点!这个时候,我们可以官方文档走一波。
27.1.9 Error Handling
Spring Boot provides an /error mapping by default that handles all errors in a sensible way, and it is registered as a ‘global’ error page in the servlet container. For machine clients it will produce a JSON response with details of the error, the HTTP status and the exception message. For browser clients there is a ‘whitelabel’ error view that renders the same data in HTML format (to customize it just add a View that resolves to ‘error’). To replace the default behaviour completely you can implement ErrorController and register a bean definition of that type, or simply add a bean of type ErrorAttributes to use the existing mechanism but replace the contents.
一些同学说,英文看不懂?我挑5个重点单词给你,都是小学单词,只要小学能毕业,我认为都能看懂。
clients JSON browser HTML ErrorController
肥朝小声逼逼:这里特别强调,并不是说看官方文档是最优解决问题方案。还是那句话,老司机都是看菜吃饭的,解决问题的套路有很多,时间有限,我就不把所有套路一个一个列出来(其实是怕套路全部告诉你们了,你们就取关了!),直入主题就行。
从文档和小学的英文单词我们把目标锁定在了ErrorController,给大家看一下关键代码
@Controller
@RequestMapping("${server.error.path:${error.path:/error}}")
publicclassBasicErrorControllerextendsAbstractErrorController {
@RequestMapping(produces ="text/html")
publicModelAndView errorHtml(HttpServletRequest request,
HttpServletResponse response) {
HttpStatus status = getStatus(request);
Map 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 :newModelAndView("error", model);
}
@RequestMapping
@ResponseBody
publicResponseEntity> error(HttpServletRequest request) {
Map body = getErrorAttributes(request,
isIncludeStackTrace(request, MediaType.ALL));
HttpStatus status = getStatus(request);
returnnewResponseEntity>(body, status);
}
}
从这里我们大致可以猜测出,要做一个自适应的全局异常处理,理论上是要这么写的。
@ControllerAdvice
publicclassMyExceptionHandler{
@ExceptionHandler(Exception.class)
publicStringhandleExceptionHtml(Exceptione,HttpServletRequesthttpServletRequest){
// 这里做一些你自己的处理,比如
httpServletRequest.setAttribute("欢迎关注微信公众号","肥朝");
return "forward:/error";
}
}
果然调试一波,发现果真如此。当然具体怎么自定义这个错误界面之类的,网上一搜就有,所以这些不是肥朝的重点。那么这个自适应全局异常似乎美滋滋了?
遇到问题
我们知道了这个自适应的全局异常处理的原理,也很容易想到怎么弄出bug。比如,你在拦截器出现了异常的话。
@Configuration
publicclassMyMvcConfigextendsWebMvcConfigurerAdapter{
@Override
publicvoidaddInterceptors(InterceptorRegistry registry){
registry.addInterceptor(newHandlerInterceptor() {
@Override
publicbooleanpreHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o)throwsException{
thrownewRuntimeException("这里假装抛出一个肥朝异常");
//return true;
}
@Override
public void postHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, ModelAndView modelAndView) throws Exception {
}
@Override
public void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) throws Exception {
}
});
}
}
那么就会出现,StackOverflowError。
因为拦截器出现异常,会掉进你的全局异常处理,然后你的全局异常处理,又进行forward,又进入了拦截器,然后一直循环。
那么怎么解决这个问题呢?我们见招拆招,这个时候,我要演示常见的几种不优雅,但是平时大家都容易做的写法。
将配置写死
registry.addInterceptor(newHandlerInterceptor() {
@Override
public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o) throws Exception {
thrownewRuntimeException("这里假装抛出一个肥朝异常");
//returntrue;
}
@Override
public void postHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, ModelAndView modelAndView) throws Exception {
}
@Override
public void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) throws Exception {
}
}).excludePathPatterns("/error");
我们从
@RequestMapping("${server.error.path:${error.path:/error}}")
这里得知,这个error.path是可以配置的,很多同学图快,excludePathPatterns处写死了/error,这样一直用默认的自然没问题,一旦人家配置了error.path,就出问题了。
潜规则
这个潜规则的问题,是绝大部分同学写代码中最常见的问题。你想一下,你的自适应全局异常是解决了,但是,带来的影响却是,每一个拦截器都要加上excludePathPatterns这么一个配置。对于使用者来说,这个必须加上某个配置,就是一种潜规则,而且,对于新来的同事而言,他根本不知道这种潜规则,一旦潜规则的代码多了,后续很难维护。
网友评论