Spring MVC中的异常处理程序


问题内容

我想创建一个异常处理程序,它将拦截我项目中的所有控制器。那有可能吗?看来我必须在每个控制器中放置一个处理程序方法。谢谢你的帮助。我有一个发送Json响应的弹簧控制器。因此,如果发生异常,我想发送一个可以从一个地方控制的错误响应。


问题答案:

(我在Spring 3.1中找到了实现它的方法,此答案的第二部分对此进行了描述)

请参见第16.11处理 Spring
Reference的异常

  • 您可以实现HandlerExceptionResolver使用servlet而不是portlet包 )-这是某种全局@ExceptionHandler
  • 如果您没有针对异常的特定逻辑,而只是特定视图,则可以使用SimpleMappingExceptionResolver,它至少是的实现HandlerExceptionResolver,您可以在其中指定异常名称模式和视图(jsp),当引发异常。例如:
        <bean
       class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver"
       p:defaultErrorView="uncaughtException">
       <property name="exceptionMappings">
           <props>
               <prop key=".DataAccessException">dataAccessFailure</prop>
               <prop key=".TypeMismatchException">resourceNotFound</prop>
               <prop key=".AccessDeniedException">accessDenied</prop>
            </props>
        </property>
     </bean>

Spring 3.2+中,
可以使用注释类@ControllerAdvice,该类中的所有@ExceptionHandler方法都以全局方式工作。


Spring 3.1 中没有@ControllerAdvice。但是只要稍加修改,就可以具有类似的功能。

关键是对工作方式的理解@ExceptionHandler。在Spring
3.1中有一个类ExceptionHandlerExceptionResolver。此类(在其超类的帮助下)实现接口HandlerExceptionResolver并负责调用@ExceptionHandler方法。

HandlerExceptionResolver接口只有一种方法:

    ModelAndView resolveException(HttpServletRequest request,
                                  HttpServletResponse response,
                                  Object handler,
                                  Exception ex);`.

当请求由Spring3.x控制器方法处理时,该方法(由表示org.springframework.web.method.HandlerMethod)就是handler参数。

ExceptionHandlerExceptionResolver用途handlerHandlerMethod),以获得控制器类和扫描它用于与所注解的方法@ExceptionHandler。如果其中一个方法与例外(ex)相符,则会调用此方法以处理该例外。(否则null返回此信号是为了表示此异常解析器不承担任何责任)。

第一个想法是实现一个HandlerExceptionResolver行为类似于的自己ExceptionHandlerExceptionResolver,但@ExceptionHandler与其在控制器类中进行搜索,不如在控制器类中进行搜索。缺点是,ExceptionHandlerExceptionResolver必须手动配置(复制(或子类),并且必须)所有不错的消息转换器,参数解析器和返回值处理程序(真正的配置,并且仅ExceptionHandlerExceptionResolver由spring自动完成)。所以我想到了另一个想法:

实现一个简单的HandlerExceptionResolver“将”异常“转发”到THE(已配置)ExceptionHandlerExceptionResolver,但将其修改后handler指向包含全局Exception处理程序的Bean(我称它们为Global,因为它们对所有控制器都起作用)。

这是实现: GlobalMethodHandlerExeptionResolver

    import java.util.List;
    import java.util.concurrent.ConcurrentHashMap;
    import java.util.concurrent.ConcurrentMap;

    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;

    import org.springframework.beans.factory.NoSuchBeanDefinitionException;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.core.Ordered;
    import org.springframework.util.StringUtils;
    import org.springframework.web.method.HandlerMethod;
    import org.springframework.web.servlet.HandlerExceptionResolver;
    import org.springframework.web.servlet.ModelAndView;
    import org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver;


    public class GlobalMethodHandlerExeptionResolver
                 implements HandlerExceptionResolver, Ordered {

        @Override
        public int getOrder() {
            return -1; //
        }

        private ExceptionHandlerExceptionResolver realExceptionResolver;

        private List<GlobalMethodExceptionResolverContainer> containers;

        @Autowired
        public GlobalMethodHandlerExeptionResolver(
                ExceptionHandlerExceptionResolver realExceptionResolver,
                List<GlobalMethodExceptionResolverContainer> containers) {
            this.realExceptionResolver = realExceptionResolver;
            this.containers = containers;
        }

        @Override
        public ModelAndView resolveException(HttpServletRequest request,
                                             HttpServletResponse response,
                                             Object handler,
                                             Exception ex) {              
            for (GlobalMethodExceptionResolverContainer container : this.containers) {    
                ModelAndView result = this.realExceptionResolver.resolveException(
                        request,
                        response,
                        handlerMethodPointingGlobalExceptionContainerBean(container),
                        ex);
                if (result != null)
                    return result;
            }
            // we feel not responsible
            return null;
        }


        protected HandlerMethod handlerMethodPointingGlobalExceptionContainerBean(
                                   GlobalMethodExceptionResolverContainer container) {
            try {
                return new HandlerMethod(container,
                                         GlobalMethodExceptionResolverContainer.class.
                                              getMethod("fakeHanderMethod"));            
            } catch (NoSuchMethodException | SecurityException e) {
                throw new RuntimeException(e);
            }            
        }
    }

全局处理程序必须实现此接口(以便找到并实现fakeHanderMethod用于handler

    public interface GlobalMethodExceptionResolverContainer {
        void fakeHanderMethod();
    }

全局处理程序的示例:

    @Component
    public class JsonGlobalExceptionResolver
                 implements GlobalMethodExceptionResolverContainer {

        @Override
        public void fakeHanderMethod() {
        }


        @ExceptionHandler(MethodArgumentNotValidException.class)
        @ResponseStatus(HttpStatus.BAD_REQUEST)
        @ResponseBody
        public ValidationErrorDto handleMethodArgumentNotValidException(
                    MethodArgumentNotValidException validationException,
                    Locale locale) {

             ...
             /* map validationException.getBindingResult().getFieldErrors()
              * to ValidationErrorDto (custom class) */
             return validationErrorDto;
        }
    }

顺便说一句:您不需要注册,GlobalMethodHandlerExeptionResolver因为spring会自动注册所有HandlerExceptionResolver为异常解析器实现的bean
。如此简单<bean class="GlobalMethodHandlerExeptionResolver"/>就足够了。