每天⽤SpringBoot,还不懂RESTfulAPI返回统⼀数据格式是
怎么实现的?
上⼀篇⽂章 说明了 RESTful API 统⼀返回数据格式问题,这是请求⼀切正常的情形,这篇⽂章将说明如何统⼀处理异常,以及其背后的实现原理,⽼套路,先实现,后说明原理,有了上⼀篇⽂章的铺底,相信,理解这篇⽂章就驾轻就熟了
实现
新建业务异常
新建 BusinessException.class 类表⽰业务异常,注意这是⼀个 Runtime 异常
@Data
@AllArgsConstructor
public final class BusinessException extends RuntimeException {
private String errorCode;
private String errorMsg;
}
添加统⼀异常处理静态⽅法
在 CommonResult 类中添加静态⽅法 errorResult ⽤于接收异常码和异常消息:
public static <T> CommonResult<T> errorResult(String errorCode, String errorMsg){
CommonResult<T> commonResult = new CommonResult<>();
commonResult.status = -1;
return commonResult;
}
配置
同样要⽤到 @RestControllerAdvice 注解,将统⼀异常添加到配置中:
@RestControllerAdvice("ample.unifiedreturn.api")
static class UnifiedExceptionHandler{
@ExceptionHandler(BusinessException.class)
public CommonResult<Void> handleBusinessException(BusinessException be){
ErrorCode(), be.getErrorMsg());
}
}
三部搞定,到这⾥⽆论是 Controller 还是 Service 中,只要抛出 BusinessException, 我们都会返回给前端⼀个统⼀数据格式
测试
将 UserController 中的⽅法进⾏改造,直接抛出异常:
@GetMapping("/{id}")
public UserVo getUserById(@PathVariable Long id){
throw new BusinessException("1001", "根据ID查询⽤户异常");
}
在 Service 中抛出异常:
@Service
public class UserServiceImpl implements UserService {
/**
* 根据⽤户ID查询⽤户
*
* @param id
* @return
*/
@Override
public UserVo getUserById(Long id) {
throw new BusinessException("1001", "根据ID查询⽤户异常");
}
}
运⾏是得到同样的结果,所以我们尽可能的抛出异常吧 (作为⼀个程序猿这种⼼理很可拍)
解剖实现过程
解剖这个过程是相当纠结的,为了更好的说(yin)明(wei)问(wo)题(lan),我要说重中之重了,真⼼希望
看该⽂章的童鞋⾃⼰去案发现场发现线索 还是在 WebMvcConfigurationSupport 类中实例化了 HandlerExceptionResolver Bean
@Bean
public HandlerExceptionResolver handlerExceptionResolver() {
List<HandlerExceptionResolver> exceptionResolvers = new ArrayList<>();
configureHandlerExceptionResolvers(exceptionResolvers);
if (exceptionResolvers.isEmpty()) {
addDefaultHandlerExceptionResolvers(exceptionResolvers);
}
extendHandlerExceptionResolvers(exceptionResolvers);
HandlerExceptionResolverComposite composite = new HandlerExceptionResolverComposite();
composite.setOrder(0);
composite.setExceptionResolvers(exceptionResolvers);
return composite;
}
和上⼀篇⽂章⼀⽑⼀样的套路,ExceptionHandlerExceptionResolver 实现了 InitializingBean 接⼝,重写了 afterPropertiesSet ⽅法:
@Override
public void afterPropertiesSet() {
// Do this first, it may add ResponseBodyAdvice beans
initExceptionHandlerAdviceCache();
...
}springboot是啥
private void initExceptionHandlerAdviceCache() {
if (getApplicationContext() == null) {
return;
}
List<ControllerAdviceBean> adviceBeans = ControllerAdviceBean.findAnnotatedBeans(getApplicationContext()); AnnotationAwareOrderComparator.sort(adviceBeans);
for (ControllerAdviceBean adviceBean : adviceBeans) {
Class<?> beanType = BeanType();
if (beanType == null) {
throw new IllegalStateException("Unresolvable type for ControllerAdviceBean: " + adviceBean);
}
// 重点看这个构造⽅法
ExceptionHandlerMethodResolver resolver = new ExceptionHandlerMethodResolver(beanType);
if (resolver.hasExceptionMappings()) {
}
if (ResponseBodyAdvice.class.isAssignableFrom(beanType)) {
}
}
}
重点看上⾯我⽤注释标记的构造⽅法,代码很好懂,仔细看看吧
/**
* A constructor that finds {@link ExceptionHandler} methods in the given type.
* @param handlerType the type to introspect
*/
public ExceptionHandlerMethodResolver(Class<?> handlerType) {
for (Method method : MethodIntrospector.selectMethods(handlerType, EXCEPTION_HANDLER_METHODS)) {
for (Class<? extends Throwable> exceptionType : detectExceptionMappings(method)) {
addExceptionMapping(exceptionType, method);
}
}
}
/**
* Extract exception mappings from the {@code @ExceptionHandler} annotation first,
* and then as a fallback from the method signature itself.
*/
@SuppressWarnings("unchecked")
private List<Class<? extends Throwable>> detectExceptionMappings(Method method) {
List<Class<? extends Throwable>> result = new ArrayList<>();
detectAnnotationExceptionMappings(method, result);
if (result.isEmpty()) {
for (Class<?> paramType : ParameterTypes()) {
if (Throwable.class.isAssignableFrom(paramType)) {
result.add((Class<? extends Throwable>) paramType);
}
}
}
if (result.isEmpty()) {
throw new IllegalStateException("No exception types mapped to " + method);
}
return result;
}
private void detectAnnotationExceptionMappings(Method method, List<Class<? extends Throwable>> result) {
ExceptionHandler ann = AnnotatedElementUtils.findMergedAnnotation(method, ExceptionHandler.class);
Assert.state(ann != null, "No ExceptionHandler annotation");
result.addAll(Arrays.asList(ann.value()));
}
到这⾥,我们⽤ @RestControllerAdvice 和 @ExceptionHandler 注解就会被 Spring 扫描到上下⽂,供我们使⽤让我们回到你最熟悉的调⽤的⼊⼝ DispatcherServlet 类的 doDispatch ⽅法:
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
...
try {
ModelAndView mv = null;
Exception dispatchException = null;
try {
...
// 当请求发⽣异常,该⽅法会通过 catch 捕获异常
mv = ha.handle(processedRequest, response, Handler());
...
}
catch (Exception ex) {
dispatchException = ex;
}
catch (Throwable err) {
// As of 4.3, we're processing Errors thrown from handler methods as well,
// making them available for @ExceptionHandler methods and other scenarios.
dispatchException = new NestedServletException("Handler dispatch failed", err);
}
// 调⽤该⽅法分析捕获的异常
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
}
...
}
接下来,我们来看 processDispatchResult ⽅法,这⾥只要展⽰调⽤栈你就会眼前⼀亮了:
总结
上⼀篇⽂章的返回统⼀数据格式是基础,当异常情况发⽣时,只不过需要将异常信息提取出来。⽂章的好多地⽅设计⽅式不可取,⽐如我们最好将异常封装在⼀个 Enum 类,通过 enum 对象抛出异常等,如果你⽤到这些,去完善你的设计⽅案吧
回复 「demo」,打开链接,查看⽂件夹 「unifiedreturn」下内容,获取完整代码
灵魂追问
1. 这两篇⽂章,你学到了哪些设计模式?
2. 你能熟练的使⽤反射吗?当看源码是会看到很多反射的应⽤
3. 你了解 Spring CGLIB 吗?它的⼯作原理是什么?
提⾼效率⼯具
JSON-Viewer
JSON-Viewer 是 Chrome 浏览器的插件,⽤于快速解析及格式化 json 内容,在 Chrome omnibox(多功能输⼊框)输⼊json-viewer + TAB ,将 json 内容拷贝进去,然后输⼊回车键,将看到结构清晰的 json 数据,同时可以⾃定义主题
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论