17

再会!

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

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

4

8 回答 8

5

您需要使用CDI InterceptorAspectJ等 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
    }
}

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

于 2019-01-19T11:20:00.607 回答
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
}
于 2019-01-17T08:14:38.613 回答
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
}
于 2013-07-09T01:41:17.197 回答
2

你能解释一下为什么需要这个吗?我是出于好奇而问的,因为我从来没有觉得这是必需的,原因如下:

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

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

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

  2. 异常继承。例子:

如果您的系统中有 aUserDoesNotExistException并且您想要更具体,例如管理在某些流程中退休的用户的系统,您始终可以创建更具体的异常:

class UserRetiredException extends UserDoesNotExistException {...}

显然,spring 可以支持这两种情况:ExceptionMapper无论如何,您都可以访问异常,因此您可以执行以下操作:

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

在第二种情况下,对于不同类型的异常,您只有不同的异常映射器。

您还可以考虑@ControllerAdvice使用注释来重用代码或其他东西。

于 2019-01-17T08:07:44.793 回答
1

您可以根据您希望如何处理它们,从其他方法抛出的常见异常中派生子异常。

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

定义一个@ControllerAdvice捕获ParentException并定义委托方法中的特定行为的类。

@ControllerAdvice
public class ParentExceptionHandler {

    @ExceptionHandler(ParentException.class)
    public ResponseEntity<Object> handleParentException(ParentException pe) {
        if (pe instanceof ChildAException) {
            return handleChildAException((ChildAException) pe);
        } else if (...) {
            ...
        } else {
            // handle parent exception
        }
    }

    private ResponseEntity<Object> handleChildAException(ChildAException cae) {
        // handle child A exception
    }
}
于 2019-01-22T06:27:10.767 回答
0

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

于 2019-08-23T22:15:33.743 回答
0

我试过 try{}catch(Exception ex){} 并没有发现异常。但是异常处理程序可以很好地处理它。

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

你应该做的是——在应用层打开事务。

控制器只是将 xml/json 映射到传入的 RequestDto 对象。然后调用 Service 来处理业务逻辑。 服务(或其方法)应由@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在方法级别,它会简化很多像你这样的情况。

于 2020-04-22T10:52:05.987 回答
0

我刚遇到和你一样的问题。所以我针对这种情况检查了spring源代码。似乎spring会在@Controller类中搜索任何@ExceptionHandler首先注释的方法,如果没有匹配,那么它将继续搜索所有注释的类@ControllerAdvice。所以你可以使用下面的策略:

  • MyController用一个@ExceptionHandler方法:
@RestController
public class MyController {
  @RequestMapping("/foo")
  public String foo() {
    throw new IllegalArgumentException();
  }

  @ExceptionHandler(IllegalArgumentException.class)
  public ResponseEntity<String> handle(IllegalArgumentException ex) {
    return new ResponseEntity<>("Specific handler", HttpStatus.BAD_REQUEST);
  }
}
  • AnotherController没有任何方法注释@ExceptionHandler
@RestController
public class AnotherController {
  @RequestMapping("/bar")
  public String bar() {
    throw new IllegalArgumentException();
  }
}
  • 一个全局@ControllerAdvice类:
@ControllerAdvice
public class GlobalExceptionHandler {
  @ExceptionHandler(IllegalArgumentException.class)
  public ResponseEntity<String> handle(IllegalArgumentException ex) {
    return new ResponseEntity<>("Global handler", HttpStatus.BAD_REQUEST);
  }
}

那么如果你访问 http://ip:port/foo,你会在访问 http://ip:port/bar 时得到 400 状态码和特定处理程序,以及 400 状态码和全局处理程序。

于 2021-10-09T08:46:58.753 回答