Spring:RestController和Controller的异常处理程序

时间:2017-04-10 14:20:12

标签: java spring rest spring-mvc exception-handling

在春天,您可以设置"全球"异常处理程序通过@ControllerAdvice和@ExceptionHandler注释。我试图利用这种机制来拥有两个全局异常处理程序:

  • RestControllerExceptionHandler - 对于使用@RestController注释的任何控制器,应将错误响应作为json返回
  • ControllerExceptionHandler - 应该在屏幕上为任何其他控制器打印错误消息(使用@Controller注释)

问题在于,当我声明这两个异常处理程序时,spring总是使用ControllerExceptionHandler而不是RestControllerExceptionHandler来处理异常。

如何使这项工作? 顺便说一句:我尝试使用@Order注释,但这似乎不起作用。

以下是我的异常处理程序:

// should handle all exception for classes annotated with         
@ControllerAdvice(annotations = RestController.class)
public class RestControllerExceptionHandler {


  @ExceptionHandler(Exception.class)
  public ResponseEntity<ErrorResponse> handleUnexpectedException(Exception e) {

    // below object should be serialized to json
    ErrorResponse errorResponse = new ErrorResponse("asdasd"); 

    return new ResponseEntity<ErrorResponse>(errorResponse, HttpStatus.INTERNAL_SERVER_ERROR);
  }

// should handle exceptions for all the other controllers
@ControllerAdvice(annotations = Controller.class)
public class ControllerExceptionHandler {


  @ExceptionHandler(Exception.class)
  public ResponseEntity<String> handleUnexpectedException(Exception e) {
    return new ResponseEntity<String>("Unexpected exception, HttpStatus.INTERNAL_SERVER_ERROR);
  }


}
}

当我删除ControllerExceptionHandler而不是RestControllerExceptionHandler被春天正确调用时(仅适用于使用@RestController注释的类)....但是当我添加ControllerExceptionHandler时通过ControllerExceptionHandler。为什么?

5 个答案:

答案 0 :(得分:8)

发生了这种情况,因为@RestController注释本身也是一个@Controller,所以Spring正在考虑@ annotation = Controller.class的@ControllerAdvice。

你可以尝试另一个method来定义@ControllerAdvice将有效的控制器子集,所以这里有一些解决方案:

解决方案1 ​​

  1. 创建一个新的注释@NotRestController:

    @Target({ElementType.TYPE})
    @Retention(RetentionPolicy.RUNTIME)
        public @interface NotRestController {
    }
    
  2. 使用@Controller和@NotRestController标记不是@RestController的控制器:

    @Controller
    @NotRestController
    @RequestMapping("/controller")
    public class SampleController {
    }
    
  3. NotRestController.class上使用ControllerExceptionHandler

    @ControllerAdvice(annotations = NotRestController.class)
    public class ControllerExceptionHandler {
    
  4. 我已经使用Solution 1创建了一个示例项目并在Github上共享:

    https://github.com/brunocleite/springexceptionhandlertest

    解决方案2

    1. 将@RestController类移动到包foo.bar中,将@Controller类移到另一个包foo.nuk中。

    2. 使用basePackages的{​​{1}}属性代替@ControllerAdvice

      annotations
    3. 祝你好运!

答案 1 :(得分:8)

经过深入调查后,字母顺序似乎很重要:/。

当我将RestControllerExceptionHandler重命名为ARestControllerExceptionHandler(按字母顺序排在ControllerExceptionHandler之前)时,所有工作都按预期进行! ARestControllerExceptionHandler正确处理来自RestControllers的异常,ControllerExceptionHandler处理来自其他控制器的异常。

我在春天为此创建了一个错误:https://jira.spring.io/browse/SPR-15432

---编辑:

我收到了SPR-15432的答案,建议可以使用@Orderorg.springframework.core.annotation.Order)注释或通过实现Ordered接口解决此案例。

这对我来说不起作用,但似乎我导入了错误的@Order注释。(来自log4j2而不是spring)。修好之后就行了。修正版如下:

// should handle all exception for classes annotated with         
@ControllerAdvice(annotations = RestController.class)
@Order(1) // NOTE: order 1 here
public class RestControllerExceptionHandler {


  @ExceptionHandler(Exception.class)
  public ResponseEntity<ErrorResponse> handleUnexpectedException(Exception e) {

    // below object should be serialized to json
    ErrorResponse errorResponse = new ErrorResponse("asdasd"); 

    return new ResponseEntity<ErrorResponse>(errorResponse, HttpStatus.INTERNAL_SERVER_ERROR);
  }
}
// should handle exceptions for all the other controllers
@ControllerAdvice(annotations = Controller.class)
@Order(2)  // NOTE: order 2 here
public class ControllerExceptionHandler {


  @ExceptionHandler(Exception.class)
  public ResponseEntity<String> handleUnexpectedException(Exception e) {
    return new ResponseEntity<String>("Unexpected exception, HttpStatus.INTERNAL_SERVER_ERROR);
  }
}

答案 2 :(得分:1)

要进一步构建@ bruno-leite的解决方案1:

您可以定义一个元注释,因此每次插入2时只需要1个注释。

import org.springframework.core.annotation.AliasFor;
import org.springframework.stereotype.Controller;

import java.lang.annotation.*;

/**
 * Alias annotation for @Controller so that @ControllerAdvice instances can
 * target the web controllers separately from the rest controllers.
 */
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Controller
public @interface WebController {
    @AliasFor(
            annotation = Controller.class
    )
    String value() default "";
}

在网络控制器上使用它:

@WebController
@RequestMapping("/customers")
public class CustomerController {

最后定义一个@ControllerAdvice,仅将Web控制器定位为:

@ControllerAdvice(annotations = WebController.class)
public class GlobalControllerAdvice {

    @ModelAttribute("displayName")
    public String getDisplayName(@AuthenticationPrincipal KeycloakAuthenticationToken principal) {
        if (principal != null) {
            String givenName = principal.getAccount().getKeycloakSecurityContext().getToken().getGivenName();
            String familyName = principal.getAccount().getKeycloakSecurityContext().getToken().getFamilyName();
            return givenName + " " + familyName;
        } else {
            return null;
        }
    }
}

(此示例确保我的Thymeleaf模板始终具有displayName属性,但也可以轻松地针对异常处理程序进行调整)

答案 3 :(得分:0)

这是我的解决方案。无需创建其他注释。

  1. 创建2个基本控制器。
@Controller
public class BaseController

@Controller
public class BaseRestController
  1. 用选定的基本控制器扩展每个控制器。
@Controller
public class UserController extends BaseController

@RestController
public class UserRestController extends BaseRestController
  1. 创建2个全局控制器,并且每个控制器仅包含扩展基类。
@ControllerAdvice(assignableTypes = BaseController.class)
public class GlobalController

@ControllerAdvice(assignableTypes = BaseRestController.class)
public class GlobalRestController

答案 4 :(得分:-3)

使用命令修复错误:

@Order(1)
@ControllerAdvice(annotations = RestController.class)
public class ErrorRestControllerAdvice {
}

@Order(2)
@ControllerAdvice(annotations = Controller.class)
public class ErrorControllerAdvice {
}