Spring MVC:将异常处理程序绑定到特定方法

时间:2013-07-08 09:41:12

标签: java spring-mvc

美好的一天!

我有@Controller。它的一些方法抛出相同的异常,但我想以不同的方式处理这些异常。

有没有办法将@ExceptionHandler绑定到特定方法?

7 个答案:

答案 0 :(得分:4)

您需要使用 CDI拦截器 AspectJ 之类的AOP工具来解决这一跨领域问题。关注是指根据功能划分的系统的一部分。

基本上,这种类型的功能用于处理日志记录,安全性以及错误……这不是您的业务逻辑的一部分……

例如,如果要将应用程序的记录器从log4j更改为sl4j,则需要遍历使用log4j的每个类并进行更改。但是,如果您使用过AOP工具,则只需要转到拦截器类并更改实现即可。即插即用和功能非常强大的工具。

这是使用JavaEE CDI拦截器的代码段

/*
    Creating the interceptor binding
*/
@InterceptorBinding
@Retention(RUNTIME)
@Target({TYPE, METHOD})
public @interface BindException {

}

定义拦截器绑定之后,我们需要定义拦截器绑定实现

/*
    Creating the interceptor implementation
*/
@Interceptor
@BindException
public class ExceptionCDIInterceptor {

    @AroundInvoke
    public Object methodInterceptor(InvocationContext ctx) throws Exception {
        System.out.println("Invoked method " + ctx.getMethod().getName());
        try {
            return ctx.proceed(); // this line will try to execute your method
                                 // and if the method throw the exception it will be caught  
        } catch (Exception ex) {
            // here you can check for your expected exception 
            // code for Exception handler
        }
    }

}

现在我们只需要对我们的方法应用拦截器

/*
    Some Service class where you want to implement the interceptor
*/
@ApplicationScoped
public class Service {

    // adding annotation to thisMethodIsBound method to intercept
    @BindException
    public String thisMethodIsBound(String uid) {
        // codes....

        // if this block throw some exception then it will be handled by try catch block
        // from ExceptionCDIInterceptor
    }
}

您也可以使用AspectJ实现相同的功能。

/*
    Creating the Aspect implementation
*/
@Aspect
public class  ExceptionAspectInterceptor {

    @Around("execution(* com.package.name.SomeService.thisMethodIsBound.*(..))")
    public Object methodInterceptor(ProceedingJoinPoint ctx) throws Throwable {
        System.out.println("Invoked method " + ctx.getSignature().getName());
        try {
            return ctx.proceed(); // this line will try to execute your method
                                 // and if the method throw the exception it will be caught  
        } catch (Exception ex) {
            // here you can check for your expected exception 
            // codes for Exception handler
        }
    }
}

现在我们只需要在应用程序配置中启用AspectJ

/*
    Enable the AspectJ in your application
*/
@Configuration
@EnableAspectJAutoProxy
public class AppConfig {

    @Bean
    public SomeService SomeService() {
        return new SomeService();
    }

}

/*
    Some Service class where you want to implement the Aspect
*/
package com.package.name;
public class SomeService {

    public String thisMethodIsBound(String uid) {
        // codes....

        // if this block throw some exception then it will be handled by try catch block
        // from ExceptionAspectInterceptor
    }
}

我的git repo https://github.com/prameshbhattarai/javaee-exceptionBinding中有使用CDI拦截器的代码示例。

答案 1 :(得分:3)

只是一个选择(显然,这并不理想):您可以将异常包装到您的方法之一中的自定义异常中,然后将其捕获在@ExceptionHandler

void boo() throws WrappingException {
    try {

    } catch (TargetException e) {
        throw new WrappingException(e);
    }
}

然后

@ExceptionHandler(WrappingException.class)
public void handleWrappingException() {
    // handle
}

@ExceptionHandler(TargetException.class)
public void handleTargetException() {
    // handle
}

答案 2 :(得分:2)

我认为您不能为方法指定特定的@ExceptionHandler,但您可以将@ExceptionHandler方法绑定到特定的Exception

因此,如果您想要在另一个方式中处理所有DataIntegrityViolationException单一方式和所有其他例外情况,那么您应该能够通过以下方式实现这一目标:

@ExceptionHandler(DataIntegrityViolationException.class)
public void handleIntegrityViolation() {
    // do stuff for integrity violation here
}

@ExceptionHandler(Exception.class)
public void handleEverythingElse() {
    // do stuff for everything else here
}

答案 3 :(得分:2)

能否请您解释为什么需要这个?我出于好奇而问,因为我从来没有觉得这是必需的,这就是原因:

异常通常表示一个非常具体的“错误”-以非常具体的方式出错了。 基本上,异常代表错误,而不是流程...

弹簧可以开箱即用地支持两个“自由度”:

  1. 异常参数。也许像错误代码之类的东西,可以声明为异常本身的数据字段。

  2. 异常继承。示例:

如果您的系统中有一个UserDoesNotExistException,并且您想更具体一些,例如管理某些流中退休的用户的系统,那么您始终可以创建一个更具体的例外:

class UserRetiredException extends UserDoesNotExistException {...}

显然,spring可以支持这两种情况:在ExceptionMapper中,您仍然可以访问异常,因此您可以执行以下操作:

handleException(SomeExceptionWithErrorCode ex) {
   if(ex.getErrorCode() == "A") {
      // do this
   }
   else if(ex.getErrroCode() == "B") {
      // do that
   }
}

在第二种情况下,您只是针对不同类型的异常使用了不同的异常映射器。

您还可以考虑使用@ControllerAdvice批注来重用代码或其他内容。

答案 4 :(得分:1)

您可以根据要处理它们的方式,从其他方法引发的常见异常中派生子异常。

假设您已将父异常声明为ParentException。派生子类,例如ChildAException extends ParentExceptionChildBException extends ParentException

定义一个捕获@ControllerAdvice的{​​{1}}类,并定义委托方法中的特定行为。

ParentException

答案 5 :(得分:0)

我同意无法映射特定的@ExceptionHandler来仅处理@RestController中的一个特定方法是一个非常理想的功能。

答案 6 :(得分:0)

我尝试了try {} catch(exException ex){},但没有捕获任何异常。但 异常处理程序可以很好地处理它。

由于我们正在谈论休眠异常,因此这些异常通常在事务的提交阶段抛出。这里的问题是,似乎您已在控制器中直接打开事务,这被认为是不好的做法。

您应该做的是-在应用程序层中打开事务。

控制器仅将xml / json映射到传入的RequestDto对象。 然后,您调用服务来处理业务逻辑。 服务(或其方法)应由@Transactional.

注释
@RestController
public class MyController {

    @Autowired // but better to use constructor injection
    private MyService myService;

    public ResponseDto doSomething(RequestDto request) {
        try {
            myService.doSomething(request);
        } catch (DataIntegrityViolationException ex) {
            // process exception
        }
    }
}

@Transactional
class MyService {

    public void doSomething() {
       // do your processing which uses jpa/hibernate under the hood
   }
}

完成此操作后,try catch将开始在控制器级别上按预期方式运行。 但是,我什至会走得更远,因为DatabaseExeption不应真正走到控制器的那一步。另一种选择是在服务内部使用手动交易并在那里尝试捕获。

然后在服务层中将数据库异常转换为更通用的异常,其中包含控制器要处理的所有必要信息。

然后,您应该在控制器中捕获该更通用的异常(MyDatabaseAccessException),并根据表示层的需要进行转换。

===

此处建议的@ControllerAdvice对于跨控制器的全局异常处理非常有用。

@ExceptionHandler不适用于每种方法,除非您希望每个方法都具有控制器。甚至在那之后它也可能与全局@ControllerAdvice冲突。

我不确定为什么spring在方法级别不允许@ExceptionHandler,它将简化许多类似您的情况。