zoukankan      html  css  js  c++  java
  • springboot情操陶冶-web配置(五)

    本文讲讲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<String, List<String>> 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<ObjectError> allErrors = bindException.getAllErrors();
    
                    List<String> 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
     **/
    @Configuration
    public class BootWebMvcConfigurer implements WebMvcConfigurer {
    
    
        @Override
        public void addInterceptors(InterceptorRegistry registry) {
    
        }
    
        @Override
        public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
    
        }
    
        @Override
        public void extendHandlerExceptionResolvers(List<HandlerExceptionResolver> resolvers) {
            // response first
            resolvers.add(0, new SimpleExceptionResolver());
        }
    }
    
    

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

    源码层

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


    1.异常解析器注册

    	@Bean
    	public HandlerExceptionResolver handlerExceptionResolver() {
    		List<HandlerExceptionResolver> 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<HandlerExceptionResolver> 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<ControllerAdviceBean, ExceptionHandlerMethodResolver> 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逻辑的代码,以此保证代码的整洁性。

  • 相关阅读:
    全球疫情可视化
    ListView(1)
    《浪潮之巅》阅读笔记02
    Intern Day12
    Intern Day11
    Intern Day10
    Intern Day10
    Intern Day10
    PTA1065
    Intern Day10
  • 原文地址:https://www.cnblogs.com/question-sky/p/9729958.html
Copyright © 2011-2022 走看看