mirror of
https://gitee.com/wangkang/udf.git
synced 2026-11-02 03:33:42 +00:00
更新博客
This commit is contained in:
@@ -24,6 +24,8 @@
|
||||
|
||||
## 项目博客 (点击跳转)
|
||||
|
||||
- [spring boot / cloud (十三) 404错误处理进阶](https://my.oschina.net/wangkang80/blog/1523308)
|
||||
|
||||
- [spring boot / cloud (十二) 异常统一处理进阶](https://my.oschina.net/wangkang80/blog/1519189)
|
||||
|
||||
- [spring boot / cloud (十一) 回归-博客配套项目开源](https://my.oschina.net/wangkang80/blog/1517938)
|
||||
|
||||
@@ -0,0 +1,192 @@
|
||||
# spring boot / cloud (十三) 404错误处理进阶
|
||||
|
||||
## **前言**
|
||||
|
||||
在上一篇文章中介绍了[spring boot 官方文档](http://docs.spring.io/spring-boot/docs/1.5.6.RELEASE/reference/htmlsingle/#boot-features-error-handling)推荐的异常处理方式.承接上一篇文章,我们来了一下如何更好的处理404错误.
|
||||
|
||||
在[spring boot / cloud (二) 规范响应格式以及统一异常处理](https://my.oschina.net/wangkang80/blog/908919)这篇文章的最后跟大家提到了如下的配置
|
||||
|
||||
``` java
|
||||
spring.mvc.throw-exception-if-no-handler-found=true
|
||||
spring.resources.add-mappings=false
|
||||
```
|
||||
|
||||
这两行配置的主要意思是告诉spring,如果你请求的地址,没有找到对应的handler,则抛出异常,默认配置是fase,同时也要关掉静态资源的映射,不然并不会起作用.
|
||||
|
||||
所以我们知道了,在spring中,404并不是是一个异常,而是一个错误,所走的处理方式也是不一样的.
|
||||
|
||||
## **场景分析**
|
||||
|
||||
按照以上的配置,我们可以轻易的将404错误也变成一个异常,然后走统一的异常处理方式,但是这样做是有代价的,就是也会关闭掉spring boot的经验资源mapping功能.
|
||||
|
||||
(大家可查阅[spring boot官方文档](https://docs.spring.io/spring-boot/docs/1.5.6.RELEASE/reference/htmlsingle/#boot-features-spring-mvc-static-content),来了解如何处理静态资源)
|
||||
|
||||
显然,这样做不太友好,因为不可避免的会有在后端服务中包加入一些静态界面情况.所以说,这里提到一个原则
|
||||
|
||||
>不管是封装也好,增强也好,不能丢失原有框架原本支持的特性.
|
||||
|
||||
那么.如果去掉上面两行的配置,大家运行项目,请求一个不而存在的地址,错误信息会变成如下情况:
|
||||
|
||||
``` java
|
||||
{
|
||||
"timestamp": 1503626878668,
|
||||
"status": 404,
|
||||
"error": "Not Found",
|
||||
"message": "No message available",
|
||||
"path": "/a/a/a"
|
||||
}
|
||||
```
|
||||
|
||||
而我们希望的错误输出格式是这样的:
|
||||
|
||||
``` java
|
||||
{
|
||||
"id": "e7593f09-2898-478e-a3bd-0280b93ac77f",
|
||||
"code": "404",
|
||||
"message": "Not Found",
|
||||
"result": null,
|
||||
"error": {
|
||||
"date": 1503626920897,
|
||||
"type": "......",
|
||||
"message": ".........",
|
||||
"stackTrace": null,
|
||||
"child": null
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
如何调整呢?
|
||||
|
||||
## **源码解读**
|
||||
|
||||
在spring中,专门处理error的类是**BasicErrorController**,如下是这个类的两个核心方法,
|
||||
|
||||
一个方法处理html请求的返回,会默认返回一个错误页面
|
||||
|
||||
另外一个则是处理json请求的,会返回json格式的错误信息,我们看到的默认输出结果,就是这个方法生成的
|
||||
|
||||
``` java
|
||||
@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 ? new ModelAndView("error", model) : modelAndView);
|
||||
}
|
||||
|
||||
@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<Map<String, Object>>(body, status);
|
||||
}
|
||||
```
|
||||
|
||||
我们现在知道了我们要关注的地方,但是我们如何改造它呢?
|
||||
|
||||
细心的spring早就想到了可能存在的客制化需求,所以已经为我们留好了口子.
|
||||
|
||||
在源文件里**ErrorMvcAutoConfiguration**是用来配置**BasicErrorController**的,如下是核心方法,
|
||||
|
||||
这个方法上面标记了@ConditionalOnMissingBean,也就是说我们只需要实现一个ErrorController接口,注入到上下文中就可以了
|
||||
|
||||
>@ConditionalOnMissingBean的意思 : 当上下文中存在某一个bean,则不初始化当前被标记的bean
|
||||
|
||||
``` java
|
||||
@Bean
|
||||
@ConditionalOnMissingBean(value = ErrorController.class, search = SearchStrategy.CURRENT)
|
||||
public BasicErrorController basicErrorController(ErrorAttributes errorAttributes) {
|
||||
return new BasicErrorController(errorAttributes, this.serverProperties.getError(),
|
||||
this.errorViewResolvers);
|
||||
}
|
||||
```
|
||||
|
||||
知道了方法,下面就开干吧!
|
||||
|
||||
## **实现**
|
||||
|
||||
创建一个类ExceptionController继承至AbstractErrorController(此基类实现了ErrorController)
|
||||
|
||||
``` java
|
||||
@ApiIgnore
|
||||
@Controller
|
||||
@RequestMapping("${server.error.path:${error.path:/error}}")
|
||||
@Slf4j
|
||||
public class ExceptionController extends AbstractErrorController {
|
||||
....省略
|
||||
}
|
||||
```
|
||||
|
||||
编写如下两个方法,其实就是跟默认实现的方法一样,只是内容可以定制
|
||||
|
||||
然后类中其他的方法,就按照默认的实现照抄就行了.
|
||||
|
||||
``` java
|
||||
@RequestMapping(produces = "text/html")
|
||||
public ModelAndView errorHtml(HttpServletRequest request, HttpServletResponse response) {
|
||||
HttpStatus status = getStatus(request);
|
||||
Map<String, Object> model = getErrorAttributes(request, isIncludeStackTrace(request, MediaType.TEXT_HTML));
|
||||
RestResponse<String> restResponse = this.getRestResponse(request, status, model);
|
||||
model.put("restResponse", restResponse);
|
||||
model.put(KEY_EXCEPTION, restResponse.getError().getType());
|
||||
model.put(KEY_MESSAGE, restResponse.getError().getMessage());
|
||||
response.setStatus(status.value());
|
||||
ModelAndView modelAndView = resolveErrorView(request, response, status, model);
|
||||
return (modelAndView == null ? new ModelAndView("error", model) : modelAndView);
|
||||
}
|
||||
|
||||
@RequestMapping
|
||||
@ResponseBody
|
||||
public ResponseEntity<RestResponse<String>> error(HttpServletRequest request) {
|
||||
Map<String, Object> body = getErrorAttributes(request, isIncludeStackTrace(request, MediaType.ALL));
|
||||
HttpStatus status = getStatus(request);
|
||||
//这里构建自己的输出格式,详细代码就不贴出,如有需要可以到代码仓库查看
|
||||
return new ResponseEntity<>(getRestResponse(request, status, body), status);
|
||||
}
|
||||
```
|
||||
|
||||
然后在创建ExceptionControllerConfig类,注意上面一堆注解照抄默认实现即可
|
||||
|
||||
``` java
|
||||
@Configuration
|
||||
@ConditionalOnWebApplication
|
||||
@ConditionalOnClass({Servlet.class, DispatcherServlet.class})
|
||||
@AutoConfigureBefore(WebMvcAutoConfiguration.class)
|
||||
@EnableConfigurationProperties(ResourceProperties.class)
|
||||
public class ExceptionControllerConfig {
|
||||
....省略
|
||||
}
|
||||
```
|
||||
|
||||
创建exceptionController的bean,然后到这里,我们就已经替换了spring boot默认的错误处理controller,后续的错误处理都会走ExceptionController类
|
||||
|
||||
``` java
|
||||
@Bean
|
||||
public ExceptionController exceptionController(ErrorAttributes errorAttributes) {
|
||||
return new ExceptionController(errorAttributes, this.serverProperties.getError(), this.errorViewResolvers);
|
||||
}
|
||||
```
|
||||
|
||||
## **结束**
|
||||
|
||||
在上面我们优化了404错误的处理流程,替换掉了原来不是很健壮的处理方式,使得代码更加灵活,也更加合理了.
|
||||
|
||||
在下一篇文章中,我会介绍一下服务间接口调用的认证和鉴权的思考和设计.
|
||||
|
||||
## **代码仓库** (博客配套代码)
|
||||
|
||||
- [udf-starter : 基础项目,脚手架,框架](https://gitee.com/wangkang/udf)
|
||||
|
||||
- [udf-sample : 集成样例](https://gitee.com/wangkang/udf-sample)
|
||||
|
||||
---------
|
||||
|
||||
想获得最快更新,请关注公众号
|
||||
|
||||

|
||||
|
||||
Reference in New Issue
Block a user