Spring MVC:ツールException Handler Exception Resolaver

77410 ワード

概要Spring MVCは、実装されたHandlerExceptionResolverを内蔵しており、HandlerのタイプがHandlerMethodの中で異常が発生した場合、つまり@Controllerのコンポーネントクラスの@RequestMappingの方法における異常である.このような異常がExceptionHandlerExceptionResolverに解析されると、まず異常が発生したコントローラ方法の中に適切な@ExceptionHanlder方法があるかどうかを確認し、@ControllerAdvice類の中に適切な@ExceptionHanlder方法があるかどうかを確認します.この方法は異常な処理過程に対して、コントローラ方法のユーザー要求に対する処理と似ています.Spring MVC:コントローラ方法の処理要求のプロセス分析-0.概説では、結果は主に2つの大きな分岐があります.
  • は、要求を完全に処理し、その後、処理要求を継続する必要がない.
  • ExceptionHandlerExceptionResolverオブジェクトに戻り、その後、オブジェクトに基づいてビュー解析とレンダリングを継続する必要がある.ModelAndViewオブジェクトが空のオブジェクト(ModelAndView#isEmptyに戻る)であることは、使用者がデフォルトの解析メカニズムを使用することを示す.
  • trueの使用位置:
        // DispatcherServlet     
    	@Nullable
    	protected ModelAndView processHandlerException(HttpServletRequest request, 
    			HttpServletResponse response,
    			@Nullable Object handler, Exception ex) throws Exception {
    
    		// Success and error responses may use different content types
    		request.removeAttribute(HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE);
    
    		// Check registered HandlerExceptionResolvers...
    		ModelAndView exMv = null;
    		if (this.handlerExceptionResolvers != null) { 
               //       this.handlerExceptionResolvers       
               // ExceptionHandlerExceptionResolver   
               //   this.handlerExceptionResolvers    DispatcherServlet            
               // HandlerExceptionResolver    bean   ,    bean             
               //               
    			for (HandlerExceptionResolver resolver : this.handlerExceptionResolvers) {
    				exMv = resolver.resolveException(request, response, handler, ex);
    				if (exMv != null) {
    					break;
    				}
    			}
    		}
    		//       
    		// ...
    	}
    
    ソースコード
    ソースコードバージョン:ExceptionHandlerExceptionResolver
    package org.springframework.web.servlet.mvc.method.annotation;
    
    //    import  
    
    /**
     * An {@link AbstractHandlerMethodExceptionResolver} that resolves exceptions
     * through {@code @ExceptionHandler} methods.
     *
     * 

    Support for custom argument and return value types can be added via * {@link #setCustomArgumentResolvers} and {@link #setCustomReturnValueHandlers}. * Or alternatively to re-configure all argument and return value types use * {@link #setArgumentResolvers} and {@link #setReturnValueHandlers(List)}. * * @author Rossen Stoyanchev * @author Juergen Hoeller * @since 3.1 */

    public class ExceptionHandlerExceptionResolver extends AbstractHandlerMethodExceptionResolver implements ApplicationContextAware, InitializingBean { @Nullable private List<HandlerMethodArgumentResolver> customArgumentResolvers; @Nullable private HandlerMethodArgumentResolverComposite argumentResolvers; @Nullable private List<HandlerMethodReturnValueHandler> customReturnValueHandlers; @Nullable private HandlerMethodReturnValueHandlerComposite returnValueHandlers; // HttpMessageConverter // HandlerMethodReturnValueHandler private List<HttpMessageConverter<?>> messageConverters; private ContentNegotiationManager contentNegotiationManager = new ContentNegotiationManager(); // Advice , , private final List<Object> responseBodyAdvice = new ArrayList<>(); @Nullable private ApplicationContext applicationContext; // @ExceptionHandler private final Map<Class<?>, ExceptionHandlerMethodResolver> exceptionHandlerCache = new ConcurrentHashMap<>(64); // @ControllerAdvice bean @ExceptionHandler private final Map<ControllerAdviceBean, ExceptionHandlerMethodResolver> exceptionHandlerAdviceCache = new LinkedHashMap<>(); public ExceptionHandlerExceptionResolver() { // this.messageConverters, : // 1. ByteArrayHttpMessageConverter // 2. StringHttpMessageConverter, ISO8859-1 , // 3. SourceHttpMessageConverter , DOMSource/SAXSource/StAXSource/StreamSource // 4. AllEncompassingFormHttpMessageConverter ,UTF-8, multipart , // FormHttpMessageConverter , XML/JSON StringHttpMessageConverter stringHttpMessageConverter = new StringHttpMessageConverter(); stringHttpMessageConverter.setWriteAcceptCharset(false); // see SPR-7316 this.messageConverters = new ArrayList<>(); this.messageConverters.add(new ByteArrayHttpMessageConverter()); this.messageConverters.add(stringHttpMessageConverter); try { this.messageConverters.add(new SourceHttpMessageConverter<>()); } catch (Error err) { // Ignore when no TransformerFactory implementation is available } this.messageConverters.add(new AllEncompassingFormHttpMessageConverter()); } /** * Provide resolvers for custom argument types. Custom resolvers are ordered * after built-in ones. To override the built-in support for argument * resolution use {@link #setArgumentResolvers} instead. */ public void setCustomArgumentResolvers(@Nullable List<HandlerMethodArgumentResolver> argumentResolvers) { this.customArgumentResolvers= argumentResolvers; } /** * Return the custom argument resolvers, or {@code null}. */ @Nullable public List<HandlerMethodArgumentResolver> getCustomArgumentResolvers() { return this.customArgumentResolvers; } /** * Configure the complete list of supported argument types thus overriding * the resolvers that would otherwise be configured by default. */ public void setArgumentResolvers( @Nullable List<HandlerMethodArgumentResolver> argumentResolvers) { if (argumentResolvers == null) { this.argumentResolvers = null; } else { this.argumentResolvers = new HandlerMethodArgumentResolverComposite(); this.argumentResolvers.addResolvers(argumentResolvers); } } /** * Return the configured argument resolvers, or possibly {@code null} if * not initialized yet via {@link #afterPropertiesSet()}. */ @Nullable public HandlerMethodArgumentResolverComposite getArgumentResolvers() { return this.argumentResolvers; } /** * Provide handlers for custom return value types. Custom handlers are * ordered after built-in ones. To override the built-in support for * return value handling use {@link #setReturnValueHandlers}. */ public void setCustomReturnValueHandlers( @Nullable List<HandlerMethodReturnValueHandler> returnValueHandlers) { this.customReturnValueHandlers = returnValueHandlers; } /** * Return the custom return value handlers, or {@code null}. */ @Nullable public List<HandlerMethodReturnValueHandler> getCustomReturnValueHandlers() { return this.customReturnValueHandlers; } /** * Configure the complete list of supported return value types thus * overriding handlers that would otherwise be configured by default. */ public void setReturnValueHandlers( @Nullable List<HandlerMethodReturnValueHandler> returnValueHandlers) { if (returnValueHandlers == null) { this.returnValueHandlers = null; } else { this.returnValueHandlers = new HandlerMethodReturnValueHandlerComposite(); this.returnValueHandlers.addHandlers(returnValueHandlers); } } /** * Return the configured handlers, or possibly {@code null} if not * initialized yet via {@link #afterPropertiesSet()}. */ @Nullable public HandlerMethodReturnValueHandlerComposite getReturnValueHandlers() { return this.returnValueHandlers; } /** * Set the message body converters to use. *

    These converters are used to convert from and to HTTP requests and responses. */

    public void setMessageConverters(List<HttpMessageConverter<?>> messageConverters) { this.messageConverters = messageConverters; } /** * Return the configured message body converters. */ public List<HttpMessageConverter<?>> getMessageConverters() { return this.messageConverters; } /** * Set the {@link ContentNegotiationManager} to use to determine requested media types. * If not set, the default constructor is used. */ public void setContentNegotiationManager(ContentNegotiationManager contentNegotiationManager) { this.contentNegotiationManager = contentNegotiationManager; } /** * Return the configured {@link ContentNegotiationManager}. */ public ContentNegotiationManager getContentNegotiationManager() { return this.contentNegotiationManager; } /** * Add one or more components to be invoked after the execution of a controller * method annotated with {@code @ResponseBody} or returning {@code ResponseEntity} * but before the body is written to the response with the selected * {@code HttpMessageConverter}. */ public void setResponseBodyAdvice(@Nullable List<ResponseBodyAdvice<?>> responseBodyAdvice) { this.responseBodyAdvice.clear(); if (responseBodyAdvice != null) { this.responseBodyAdvice.addAll(responseBodyAdvice); } } @Override public void setApplicationContext(@Nullable ApplicationContext applicationContext) { this.applicationContext = applicationContext; } @Nullable public ApplicationContext getApplicationContext() { return this.applicationContext; } // InitializingBean @Override public void afterPropertiesSet() { // Do this first, it may add ResponseBodyAdvice beans initExceptionHandlerAdviceCache(); if (this.argumentResolvers == null) { List<HandlerMethodArgumentResolver> resolvers = getDefaultArgumentResolvers(); this.argumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers); } if (this.returnValueHandlers == null) { List<HandlerMethodReturnValueHandler> handlers = getDefaultReturnValueHandlers(); this.returnValueHandlers = new HandlerMethodReturnValueHandlerComposite().addHandlers(handlers); } } private void initExceptionHandlerAdviceCache() { if (getApplicationContext() == null) { return; } // @ControllerAdvice bean List<ControllerAdviceBean> adviceBeans = ControllerAdviceBean.findAnnotatedBeans(getApplicationContext()); AnnotationAwareOrderComparator.sort(adviceBeans); for (ControllerAdviceBean adviceBean : adviceBeans) { Class<?> beanType = adviceBean.getBeanType(); if (beanType == null) { throw new IllegalStateException( "Unresolvable type for ControllerAdviceBean: " + adviceBean); } // ExceptionHandlerMethodResolver , , // @ExceptionHandler , ExceptionHandlerMethodResolver resolver = new ExceptionHandlerMethodResolver(beanType); if (resolver.hasExceptionMappings()) { // resolver beanType, @ExceptionHandler , // this.exceptionHandlerAdviceCache this.exceptionHandlerAdviceCache.put(adviceBean, resolver); } if (ResponseBodyAdvice.class.isAssignableFrom(beanType)) { // @ControllerAdvice bean ResponseBodyAdvice , // this.responseBodyAdvice this.responseBodyAdvice.add(adviceBean); } } if (logger.isDebugEnabled()) { int handlerSize = this.exceptionHandlerAdviceCache.size(); int adviceSize = this.responseBodyAdvice.size(); if (handlerSize == 0 && adviceSize == 0) { logger.debug("ControllerAdvice beans: none"); } else { logger.debug("ControllerAdvice beans: " + handlerSize + " @ExceptionHandler, " + adviceSize + " ResponseBodyAdvice"); } } } /** * Return an unmodifiable Map with the {@link ControllerAdvice @ControllerAdvice} * beans discovered in the ApplicationContext. The returned map will be empty if * the method is invoked before the bean has been initialized via * {@link #afterPropertiesSet()}. */ public Map<ControllerAdviceBean, ExceptionHandlerMethodResolver> getExceptionHandlerAdviceCache() { return Collections.unmodifiableMap(this.exceptionHandlerAdviceCache); } /** * HandlerMethodArgumentResolver , * * Return the list of argument resolvers to use including built-in resolvers * and custom resolvers provided via {@link #setCustomArgumentResolvers}. */ protected List<HandlerMethodArgumentResolver> getDefaultArgumentResolvers() { List<HandlerMethodArgumentResolver> resolvers = new ArrayList<>(); // Annotation-based argument resolution // , resolvers.add(new SessionAttributeMethodArgumentResolver()); resolvers.add(new RequestAttributeMethodArgumentResolver()); // Type-based argument resolution // , resolvers.add(new ServletRequestMethodArgumentResolver()); resolvers.add(new ServletResponseMethodArgumentResolver()); resolvers.add(new RedirectAttributesMethodArgumentResolver()); resolvers.add(new ModelMethodProcessor()); // Custom arguments // , if (getCustomArgumentResolvers() != null) { resolvers.addAll(getCustomArgumentResolvers()); } return resolvers; } /** * HandlerMethodReturnValueHandler * * Return the list of return value handlers to use including built-in and * custom handlers provided via {@link #setReturnValueHandlers}. */ protected List<HandlerMethodReturnValueHandler> getDefaultReturnValueHandlers() { List<HandlerMethodReturnValueHandler> handlers = new ArrayList<>(); // Single-purpose return value types handlers.add(new ModelAndViewMethodReturnValueHandler()); handlers.add(new ModelMethodProcessor()); handlers.add(new ViewMethodReturnValueHandler()); handlers.add(new HttpEntityMethodProcessor( getMessageConverters(), this.contentNegotiationManager, this.responseBodyAdvice)); // Annotation-based return value types handlers.add(new ModelAttributeMethodProcessor(false)); handlers.add(new RequestResponseBodyMethodProcessor( getMessageConverters(), this.contentNegotiationManager, this.responseBodyAdvice)); // Multi-purpose return value types handlers.add(new ViewNameMethodReturnValueHandler()); handlers.add(new MapMethodProcessor()); // Custom return value types if (getCustomReturnValueHandlers() != null) { handlers.addAll(getCustomReturnValueHandlers()); } // Catch-all // handlers.add(new ModelAttributeMethodProcessor(true)); return handlers; } /** * Find an {@code @ExceptionHandler} method and invoke it to handle the raised exception. */ @Override @Nullable protected ModelAndView doResolveHandlerMethodException(HttpServletRequest request, HttpServletResponse response, @Nullable HandlerMethod handlerMethod, Exception exception) { // exception , @ControllerAdvice // @ExceptionHandler , , // , , null, ServletInvocableHandlerMethod exceptionHandlerMethod = getExceptionHandlerMethod( handlerMethod, exception); if (exceptionHandlerMethod == null) { return null; } // exception , // exceptionHandlerMethod , : // 1. : , // 2. mavContainer // 3. if (this.argumentResolvers != null) { exceptionHandlerMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers); } if (this.returnValueHandlers != null) { exceptionHandlerMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers); } ServletWebRequest webRequest = new ServletWebRequest(request, response); ModelAndViewContainer mavContainer = new ModelAndViewContainer(); // try { if (logger.isDebugEnabled()) { logger.debug("Using @ExceptionHandler " + exceptionHandlerMethod); } Throwable cause = exception.getCause(); if (cause != null) { // Expose cause as provided argument as well exceptionHandlerMethod.invokeAndHandle(webRequest, mavContainer, exception, cause, handlerMethod); } else { // Otherwise, just the given exception as-is exceptionHandlerMethod.invokeAndHandle(webRequest, mavContainer, exception, handlerMethod); } } catch (Throwable invocationEx) { // Any other than the original exception is unintended here, // probably an accident (e.g. failed assertion or the like). if (invocationEx != exception && logger.isWarnEnabled()) { logger.warn("Failure in @ExceptionHandler " + exceptionHandlerMethod, invocationEx); } // Continue with default processing of the original exception... // , null, // , , exception, // invocationEx return null; } if (mavContainer.isRequestHandled()) { // , ModelAndView() , // return new ModelAndView(); } else { // , , ModelAndView, // ModelMap model = mavContainer.getModel(); HttpStatus status = mavContainer.getStatus(); ModelAndView mav = new ModelAndView(mavContainer.getViewName(), model, status); mav.setViewName(mavContainer.getViewName()); if (!mavContainer.isViewReference()) { mav.setView((View) mavContainer.getView()); } if (model instanceof RedirectAttributes) { Map<String, ?> flashAttributes = ((RedirectAttributes) model).getFlashAttributes(); RequestContextUtils.getOutputFlashMap(request).putAll(flashAttributes); } return mav; } } /** * Find an {@code @ExceptionHandler} method for the given exception. The default * implementation searches methods in the class hierarchy of the controller first * and if not found, it continues searching for additional {@code @ExceptionHandler} * methods assuming some {@linkplain ControllerAdvice @ControllerAdvice} * Spring-managed beans were detected. * @param handlerMethod the method where the exception was raised (may be {@code null}) * @param exception the raised exception * @return a method to handle the exception, or {@code null} if none */ @Nullable protected ServletInvocableHandlerMethod getExceptionHandlerMethod( @Nullable HandlerMethod handlerMethod, Exception exception) { Class<?> handlerType = null; if (handlerMethod != null) { // Local exception handler methods on the controller class itself. // To be invoked through the proxy, even in case of an interface-based proxy. // @ExceptionHander, // @ControllerAdvice , // ExceptionHandlerMethodResolver handlerType = handlerMethod.getBeanType(); ExceptionHandlerMethodResolver resolver = this.exceptionHandlerCache.get(handlerType); if (resolver == null) { resolver = new ExceptionHandlerMethodResolver(handlerType); this.exceptionHandlerCache.put(handlerType, resolver); } // exception , // ServletInvocableHandlerMethod, 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()); } } // exception , // @ControllerAdvice exception // , ServletInvocableHandlerMethod // , for (Map.Entry<ControllerAdviceBean, ExceptionHandlerMethodResolver> entry : this.exceptionHandlerAdviceCache.entrySet()) { ControllerAdviceBean advice = entry.getKey(); if (advice.isApplicableToBeanType(handlerType)) { ExceptionHandlerMethodResolver resolver = entry.getValue(); Method method = resolver.resolveMethod(exception); if (method != null) { return new ServletInvocableHandlerMethod(advice.resolveBean(), method); } } } // exception @ControllerAdvice // , null, : // , HandlerExceptionResolver ? return null; } }
    参考文献
  • Spring MVCコンセプトモデル–インターフェースHandlerException Resolover
  • Spring MVC:ツールDefault Handler Exception Resolover
  • Spring MVC:ツールResonseStatus Exception Resolover
  • Spring MVC:ツールSimpleMappingException Resolover
  • Spring MVC:WebMvcConfigrationSupportで定義されているHandlerException Resoliverコンポーネント
  • How Spring Boot Initializes the Spring MVC Appplication Controt