Spring MVC 中的异常处理程序

发布于 2024-11-24 22:59:09 字数 130 浏览 2 评论 0原文

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

I want to create an exception handler which will intercept all controllers in my project. Is that possible to do? Looks like I have to put a handler method in each controller. Thanks for your help. I have a spring controller that sends Json response. So if an exception happens I want to send an error response which can be controlled from one place.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

鲜血染红嫁衣 2024-12-01 22:59:09

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

请参阅章节 16.11处理异常

除了使用之外还有一些方法@ExceptionHandler (参见gouki 的回答

  • 你可以实现一个 HandlerExceptionResolver (使用 servlet 而不是 portlet 包) - 这是某种全局 @ExceptionHandler
  • 如果您没有异常的特定逻辑,而只有特定视图,那么您可以使用 SimpleMappingExceptionResolver,它至少是 HandlerExceptionResolver 的实现,您可以在其中指定异常名称模式以及引发异常时显示的视图 (jsp)。例如:

    <预置><代码>
    <属性名称=“异常映射”>
    <道具>dataAccessFailure;resourceNotFound;accessDenied;




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);`.

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

ExceptionHandlerExceptionResolver 使用 handler (HandlerMethod) 获取 Controller 类并扫描它以查找使用 @ExceptionHandler 注解的方法。如果此方法之一与异常 (ex) 匹配,则调用此方法以处理异常。 (否则返回 null 以表明此异常解析器不承担任何责任)。

第一个想法是实现一个自己的 HandlerExceptionResolver,其行为类似于 ExceptionHandlerExceptionResolver,但不应在控制器类中搜索 @ExceptionHandler,而是应该在一颗特殊的豆子中寻找它们。缺点是,必须(复制(或子类 ExceptionHandlerExceptionResolver )并且必须)手动配置所有好的消息转换器、参数解析器和返回值处理程序(真正的配置并且仅< code>ExceptionHandlerExceptionResolver 由 spring 自动完成)。因此,我提出了另一个想法:

实现一个简单的 HandlerExceptionResolver,将异常“转发”到(已配置的)ExceptionHandlerExceptionResolver,但使用修改后的处理程序code> 指向包含全局异常处理程序的 bean(我称它们为全局异常处理程序,因为它们为所有控制器执行工作)。

这是实现: 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 >

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 的异常处理程序所以一个简单的 就足够了。

(I found a way to implement it in Spring 3.1, this is described in the second part of this answer)

See chapter 16.11 Handling exceptions of Spring Reference

There are some more ways than using @ExceptionHandler (see gouki's answer)

  • You could implement a HandlerExceptionResolver (use the servlet not the portlet package) - that is some kind of global @ExceptionHandler
  • If you do not have a specific logic for the exception, but only specifc view then you could use the SimpleMappingExceptionResolver, which is at least an implementation of the HandlerExceptionResolver where you can specify an Exception name pattern and the view (jsp) which is shown when the exception is thrown. For example:

    <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>
    

In Spring 3.2+ one can annotate a class with @ControllerAdvice, all @ExceptionHandler methods in this class work in a global way.


In Spring 3.1 there is no @ControllerAdvice. But with a little hack one could have a similar feature.

The key is the understanding of the way @ExceptionHandler works. In Spring 3.1 there is a class ExceptionHandlerExceptionResolver. This class implements (with help of its superclasses) the interface HandlerExceptionResolver and is responsible invoking the @ExceptionHandler methods.

The HandlerExceptionResolver interface has only one Method:

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

When the request was handled by a Spring 3.x Controller Method, then this method (represented by org.springframework.web.method.HandlerMethod) is the handler parameter.

The ExceptionHandlerExceptionResolver uses the handler (HandlerMethod) to obtain the Controller class and scan it for methods annotated with @ExceptionHandler. If one of this methods matches the exception (ex) then this methods get invoked in order to handle the exception. (else null get returned in order to signal that this exception resolver feels no responsible).

The first idea would be to implement an own HandlerExceptionResolver that behaves like ExceptionHandlerExceptionResolver, but instead of search for @ExceptionHandler in the controller class, it should search for them in one special bean. The drawback would be, that one has to (copy (or subclass ExceptionHandlerExceptionResolver) and must) configure all nice message converters, argument resolvers and return value handlers by hand (the configuration of the real one and only ExceptionHandlerExceptionResolver is done by spring automatically). So I came up with another idea:

Implement a simple HandlerExceptionResolver that "forwards" the exception to THE (already configured) ExceptionHandlerExceptionResolver, BUT with an modified handler which points to the bean that contains the global Exception handlers (I call them global, because they do the work for all controllers).

And this is the implementation: 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);
        }            
    }
}

The global Handler has to implement this interface (in order to get found and to implement the fakeHanderMethod used for the handler

public interface GlobalMethodExceptionResolverContainer {
    void fakeHanderMethod();
}

And example for an global Handler:

@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;
    }
}

BTW: You do not need to register the GlobalMethodHandlerExeptionResolver because spring automatically register all beans that implements HandlerExceptionResolver for exception resolvers. So a simple <bean class="GlobalMethodHandlerExeptionResolver"/> is enough.

北音执念 2024-12-01 22:59:09

从 Spring 3.2 开始,您可以使用 @ControllerAdvice 注释。
您可以声明 @ExceptionHandler 方法
在这种情况下,它处理来自所有控制器的@RequestMapping方法的异常。

@ControllerAdvice
public class MyGlobalExceptionHandler {

    @ExceptionHandler(value=IOException.class)
    public @ResponseBody String iOExceptionHandler(Exception ex){
        //
        //
    }

    // other exception handler methods
    // ...

}

Since Spring 3.2 you can use @ControllerAdvice annotation.
You can declare an @ExceptionHandler method within an @ControllerAdvice class
in which case it handles exceptions from @RequestMapping methods from all controllers.

@ControllerAdvice
public class MyGlobalExceptionHandler {

    @ExceptionHandler(value=IOException.class)
    public @ResponseBody String iOExceptionHandler(Exception ex){
        //
        //
    }

    // other exception handler methods
    // ...

}
花期渐远 2024-12-01 22:59:09

您可以在其中定义异常处理程序的抽象类。然后让你的控制器继承它。

An abstract class where you define the exception handlers will do. And then make your controllers inherit it.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文