博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
springboot情操陶冶-web配置(五)
阅读量:4699 次
发布时间:2019-06-09

本文共 10759 字,大约阅读时间需要 35 分钟。

本文讲讲mvc的异常处理机制,方便查阅以及编写合理的异常响应方式

入口例子

很简单,根据之前的文章,我们只需要复写WebMvcConfigurer接口的异常添加方法即可,如下


1.创建简单的异常处理类,本例针对绑定异常

package com.example.demo.web.validation;import com.example.demo.web.model.ResEntity;import com.google.gson.Gson;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.validation.BindException;import org.springframework.validation.ObjectError;import org.springframework.web.servlet.ModelAndView;import org.springframework.web.servlet.handler.AbstractHandlerExceptionResolver;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import java.io.IOException;import java.util.ArrayList;import java.util.HashMap;import java.util.List;import java.util.Map;/** * @author nanco * ------------- * resolve bindexception * ------------- * @create 18/9/9 */public class SimpleExceptionResolver extends AbstractHandlerExceptionResolver {    private static final Logger EXCEPTION_LOG = LoggerFactory.getLogger(SimpleExceptionResolver.class);    private final Map
> errorResultMap = new HashMap<>(2); private final String ERROR_KEY = "error_result"; private Gson gson = new Gson(); @Override protected ModelAndView doResolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) { // only process BindException,unless return null to allow the next handler understanding the exception if (BindException.class.isInstance(ex)) { ResEntity resEntity = new ResEntity(); try { BindException bindException = BindException.class.cast(ex); List
allErrors = bindException.getAllErrors(); List
resMessages = new ArrayList<>(allErrors.size()); allErrors.stream().forEach(error -> { resMessages.add(error.getDefaultMessage()); }); errorResultMap.put(ERROR_KEY, resMessages); resEntity.setData(errorResultMap); response.getOutputStream().write(gson.toJson(resEntity).getBytes()); } catch (IOException e) { EXCEPTION_LOG.error("process BindException fail.", e); } return new ModelAndView(); } return null; }}

2.实现WebMvcConfigurer接口后复写其中的extendHandlerExceptionResolvers()方法

package com.example.demo.web.config;import com.example.demo.web.validation.SimpleExceptionResolver;import org.springframework.context.annotation.Configuration;import org.springframework.http.converter.HttpMessageConverter;import org.springframework.web.servlet.HandlerExceptionResolver;import org.springframework.web.servlet.config.annotation.InterceptorRegistry;import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;import java.util.List;/** * @author nanco * ------------- * color the mvc config * ------------- * @create 2018/9/5 **/@Configurationpublic class BootWebMvcConfigurer implements WebMvcConfigurer {    @Override    public void addInterceptors(InterceptorRegistry registry) {    }    @Override    public void configureMessageConverters(List
> converters) { } @Override public void extendHandlerExceptionResolvers(List
resolvers) { // response first resolvers.add(0, new SimpleExceptionResolver()); }}

上述简单的代码便会对系统抛出的BindException异常进行针对性的处理,从而返回合乎格式的响应体。当然这只是一小部分,笔者可以稍微从源码的角度来分析下spring的异常机制

源码层

查阅过DispatcherServlet源码的都知道,当出现异常的时候,则会尝试调用HandlerExceptionResolver解析器去根据异常进行视图渲染或者直接返回对应的错误信息。笔者按步骤来进行简单分析,从WebMvcConfigurationSupport入手


1.异常解析器注册

@Bean    public HandlerExceptionResolver handlerExceptionResolver() {        List
exceptionResolvers = new ArrayList<>(); // 优先加载用户自定义的异常解析器,也可通过WebMvcConfigurer来复写 configureHandlerExceptionResolvers(exceptionResolvers); // 当用户没有复写上述方法后,采取默认的异常解析器 if (exceptionResolvers.isEmpty()) { addDefaultHandlerExceptionResolvers(exceptionResolvers); } // 扩增异常解析器,可见上文中的例子 extendHandlerExceptionResolvers(exceptionResolvers); HandlerExceptionResolverComposite composite = new HandlerExceptionResolverComposite(); composite.setOrder(0); composite.setExceptionResolvers(exceptionResolvers); return composite; }

2.直接看下spring内置的默认异常解析器吧,参考addDefaultHandlerExceptionResolvers()方法

protected final void addDefaultHandlerExceptionResolvers(List
exceptionResolvers) { // 1.异常的方法处理,跟@RequestMapping注解的方法调用类似 ExceptionHandlerExceptionResolver exceptionHandlerResolver = createExceptionHandlerExceptionResolver(); exceptionHandlerResolver.setContentNegotiationManager(mvcContentNegotiationManager()); exceptionHandlerResolver.setMessageConverters(getMessageConverters()); exceptionHandlerResolver.setCustomArgumentResolvers(getArgumentResolvers()); exceptionHandlerResolver.setCustomReturnValueHandlers(getReturnValueHandlers()); if (jackson2Present) { exceptionHandlerResolver.setResponseBodyAdvice( Collections.singletonList(new JsonViewResponseBodyAdvice())); } if (this.applicationContext != null) { exceptionHandlerResolver.setApplicationContext(this.applicationContext); } exceptionHandlerResolver.afterPropertiesSet(); exceptionResolvers.add(exceptionHandlerResolver); // 2.携带@ResponseStatus注解的解析器 ResponseStatusExceptionResolver responseStatusResolver = new ResponseStatusExceptionResolver(); responseStatusResolver.setMessageSource(this.applicationContext); exceptionResolvers.add(responseStatusResolver); // 3.默认的异常解析器,针对spring的内置异常作下简单的response exceptionResolvers.add(new DefaultHandlerExceptionResolver()); }

笔者主要关注ExceptionHandlerExceptionResolverResponseStatusExceptionResolver解析器,那就分块来简单的讲解把

ExceptionHandlerExceptionResolver

初始化状态的代码就不罗列了,读者直接阅读源码就知道,笔者此处作下初始化的总结

  1. 寻找所有的携带@ControllerAdvice注解的bean,包装成ExceptionHandlerMethodResolver方法解析器,由此来从中挑选出携带@ExceptionHandler注解的方法集合

  2. 对第一条中所得的方法集合,读取其中@ExceptionHandler注解的值(Throwable实现类);无则读取对应方法实现了Throwable异常接口的参数集合。即得出exceptionTypes集合

  3. 对上述的exceptionTypes集合依次与对应的method形成映射,即方便针对指定的异常可以调用相应的方法来返回结果

  4. 对上述满足条件的ControllerAdvice ,结合ExceptionHandlerMethodResolver装入exceptionHandlerAdviceCache属性map中

  5. 封装参数解析器集合与返回值解析器集合,和处理@RequestMapping的操作一样

具体的解析过程,笔者此处点一下,方便与上文对照着看,直接看关键的getExceptionHandlerMethod()方法

protected ServletInvocableHandlerMethod getExceptionHandlerMethod(            @Nullable HandlerMethod handlerMethod, Exception exception) {        Class
handlerType = null; if (handlerMethod != null) { // 获取出现异常类方法的所在类 handlerType = handlerMethod.getBeanType(); // 优先判断如果此类直接返回的是异常类,则尝试寻找解析器 ExceptionHandlerMethodResolver resolver = this.exceptionHandlerCache.get(handlerType); if (resolver == null) { // 查找异常所在类是否有符合的@ExceptionHandler语法方法 resolver = new ExceptionHandlerMethodResolver(handlerType); this.exceptionHandlerCache.put(handlerType, resolver); } // 得到映射的方法 Method method = resolver.resolveMethod(exception); if (method != null) { return new ServletInvocableHandlerMethod(handlerMethod.getBean(), method); } // For advice applicability check below (involving base packages, assignable types // and annotation presence), use target class instead of interface-based proxy. if (Proxy.isProxyClass(handlerType)) { handlerType = AopUtils.getTargetClass(handlerMethod.getBean()); } } // 进入@ControlleAdvice的语法环境了,判断抛异常的所在类,ControllerAdvice是否支持 for (Map.Entry
entry : this.exceptionHandlerAdviceCache.entrySet()) { ControllerAdviceBean advice = entry.getKey(); // 如果@ControllerAdvice注解无任何的属性配置,则默认是支持的 if (advice.isApplicableToBeanType(handlerType)) { ExceptionHandlerMethodResolver resolver = entry.getValue(); Method method = resolver.resolveMethod(exception); if (method != null) { return new ServletInvocableHandlerMethod(advice.resolveBean(), method); } } } return null; }

最终就是根据Exception的类型找寻符合条件的method,然后按照@RequestMapping注解的处理方式得到相应的视图对象供视图解析器去渲染

ResponseStatusExceptionResolver

针对携带@ResponseStatus注解的异常类来返回响应体的,简单的看下代码吧

@Override    @Nullable    protected ModelAndView doResolveException(            HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex) {        try {            // 直接返回的是ResponseStatusException类型的异常则直接处理            if (ex instanceof ResponseStatusException) {                return resolveResponseStatusException((ResponseStatusException) ex, request, response, handler);            }            // 读取异常类上携带的@ResponseStatus注解,有则返回结果            ResponseStatus status = AnnotatedElementUtils.findMergedAnnotation(ex.getClass(), ResponseStatus.class);            if (status != null) {                return resolveResponseStatus(status, request, response, handler, ex);            }            // 递归调用下            if (ex.getCause() instanceof Exception) {                ex = (Exception) ex.getCause();                return doResolveException(request, response, handler, ex);            }        }        catch (Exception resolveEx) {            logger.warn("ResponseStatus handling resulted in exception", resolveEx);        }        // 无符合条件的,直接返回null,调用下一个异常解析器        return null;    }

最终调用的也就是HttpServletResponse#sendError(int statusCode,String reason)方法直接返回结果

DispatcherServlet异常处理逻辑

此处还是贴下重要的代码片段,加深印象,直接查阅processHandlerException()方法

protected ModelAndView processHandlerException(HttpServletRequest request, HttpServletResponse response,            @Nullable Object handler, Exception ex) throws Exception {....if (this.handlerExceptionResolvers != null) {            // 对异常解析器集合进行遍历            for (HandlerExceptionResolver handlerExceptionResolver : this.handlerExceptionResolvers) {                exMv = handlerExceptionResolver.resolveException(request, response, handler, ex);                // ModelAndView对象不为null则直接跳出,否则采取下一个异常解析器                if (exMv != null) {                    break;                }            }        }}....

温馨提示:

  1. 根据上述代码的逻辑可见,用户在自定义相应的异常解析器时,需要注意如果满足解析指定的异常,则最后返回不为null的视图对象(return new ModelAndView()),以免其跑至下一个异常解析器,影响服务执行结果。
  2. 遍历的异常解析器顺序此处提一下,其采取的是简单的ArrayList集合来保持顺序,所以用户如果想自己的异常解析器保持较高的优先级,则可以采取List接口的add(int index, T value)方法添加或者直接实现HandlerExceptionResolver并设置order属性来保持即可

结语

了解异常解析器的加载机制以及运行逻辑,方便我们写出合乎spring逻辑的代码,以此保证代码的整洁性。

转载于:https://www.cnblogs.com/question-sky/p/9729958.html

你可能感兴趣的文章
chrome 开发者工具,查看元素 hover 样式
查看>>
多校题解
查看>>
HackerRank Extra long factorials
查看>>
js和jquery的基本应用
查看>>
Vanilla Masker – 功能强大的输入过滤插件
查看>>
imagesLoaded – 检测网页中的图片是否加载
查看>>
1005 Number Sequence(HDU)
查看>>
Mono For Android离线激活
查看>>
20135302魏静静Linux内核分析第二周学习总结
查看>>
XML文件中<return_code><![CDATA[SUCCESS]]></return_code>中CDATA的用法
查看>>
《重构:改善既有代码的设计》重构的方法整理
查看>>
HBase工程师线上工作经验总结----HBase常见问题及分析
查看>>
FICO_月末关帐
查看>>
获取包含中文字符串的长度、截取包含中文的字符串
查看>>
unity编辑器学习,创建自己的窗口
查看>>
Microsoft Build 2015
查看>>
使用EntityFrameWork 5.0增删查改(&分页,连表)
查看>>
ios block常见的错误(三)——并发编程的block引用
查看>>
Arcgis Server发布的带有透明度的地图服务,调用时不显示透明度问题
查看>>
Android Loader详解(官方文档翻译)
查看>>