@controlleradvice注解类未捕获服务层中抛出的异常

cwxwcias  于 2021-07-24  发布在  Java
关注(0)|答案(1)|浏览(474)

我正试图在我的spring boot应用程序中集中错误处理。目前我只处理一个潜在的异常(nosuchelementexception),这是控制器建议:

import java.util.NoSuchElementException;

import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;

@ControllerAdvice
public class ExceptionController {

    @ExceptionHandler(NoSuchElementException.class)
    @ResponseStatus(HttpStatus.NOT_FOUND)
    public DispatchError dispatchNotFound(NoSuchElementException exception) {
        System.out.println("asdasdasd");
        return new DispatchError(exception.getMessage());
    }
}

下面是抛出异常的服务:

import java.util.List;

import com.deliveryman.deliverymanapi.model.entities.Dispatch;
import com.deliveryman.deliverymanapi.model.repositories.DispatchRepository;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class DaoService {

    @Autowired
    DispatchRepository dispatchRepo;

    public Dispatch findByShipmentNumber(long shipmentNumber) {
        return dispatchRepo.findById(shipmentNumber).orElseThrow();
    }

    public List<Dispatch> findByUser(String user, String status) {
        if(status == null) {
            return dispatchRepo.findByOriginator(user).orElseThrow();
        } else {
            return dispatchRepo.findByOriginatorAndStatus(user, status).orElseThrow();
        }
    }

    public Dispatch createDispatch(Dispatch dispatch) { //TODO parameter null check exception
        return dispatchRepo.save(dispatch);
    }

}

问题是,一旦我发送了对不存在的资源的请求,所显示的json消息就是spring的默认消息。它应该是我自定义的json错误消息(dispatcherror)。
现在,可以通过向异常处理程序方法添加@responsebody来解决这个问题,但问题是我使用了我的一个旧代码作为引用,它在没有@responsebody注解的情况下可以正常工作。
有人能解释一下为什么会这样吗?

pexxcrt2

pexxcrt21#

或者用 @ResponseBody ```
@ControllerAdvice
@ResponseBody
public class ExceptionController {
...

或替换 `@ControllerAdvice` 与 `@RestControllerAdvice` .
在我的电脑上测试和验证,来源于你的控制器建议。
来源 `@RestControllerAdvice` ```
@ControllerAdvice
@ResponseBody
public @interface RestControllerAdvice {
  ...

因此, @RestControllerAdvice 是的缩写

@ControllerAdvice
@ResponseBody

来源单据 @ResponseBody 指示方法返回值的注解应绑定到web响应主体。支持带注解的处理程序方法。
替代使用 @ControllerAdvice 仅限于:

@ControllerAdvice
public class ExceptionHandlerAdvice {

    @ExceptionHandler(NoSuchElementException.class)
    public ResponseEntity<DispatchError> dispatchNotFound(NoSuchElementException exception) {
        return new ResponseEntity<>(new DispatchError(exception.getMessage()), HttpStatus.NOT_FOUND);
    }
}

我对你的旧应用程序有个理论。根据您问题的建议和下面的错误处理程序,我可以创建一个 DispatchError 示例看起来是由advice返回的(advice被执行),但实际上是由error controller返回的。

package no.mycompany.myapp.error;

import lombok.RequiredArgsConstructor;
import org.springframework.boot.web.error.ErrorAttributeOptions;
import org.springframework.boot.web.servlet.error.ErrorAttributes;
import org.springframework.boot.web.servlet.error.ErrorController;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.request.WebRequest;

@RestController
@RequiredArgsConstructor
public class ErrorHandler implements ErrorController {

    private static final String ERROR_PATH = "/error";
    private final ErrorAttributes errorAttributes;

    @RequestMapping(ERROR_PATH)
    DispatchError handleError(WebRequest webRequest) {
        var attrs = errorAttributes.getErrorAttributes(webRequest, ErrorAttributeOptions.of(ErrorAttributeOptions.Include.MESSAGE));
        return new DispatchError((String) attrs.get("message"));
    }

    @Override
    public String getErrorPath() {
        return ERROR_PATH;
    }
}

实施 ErrorController 进入类路径,替换spring的 BasicErrorController .
加固时 @RestControllerAdvice ,错误控制器不再对有效 NoSuchElementException .
在大多数情况下 ErrorController 处理所有错误的实现,结合针对更复杂异常(如 MethodArgumentNotValidException ,应该足够了。这将需要像这样的一般错误数据

package no.mycompany.myapp.error;

import com.fasterxml.jackson.annotation.JsonInclude;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.util.Date;
import java.util.Map;

@Data
@NoArgsConstructor
@JsonInclude(JsonInclude.Include.NON_NULL)
public class ApiError {

    private long timestamp = new Date().getTime();
    private int status;
    private String message;
    private String url;
    private Map<String, String> validationErrors;

    public ApiError(int status, String message, String url) {
        this.status = status;
        this.message = message;
        this.url = url;
    }

    public ApiError(int status, String message, String url, Map<String, String> validationErrors) {
        this(status, message, url);
        this.validationErrors = validationErrors;
    }
}

为了 ErrorHandler 以上,更换 handleError 用这个

@RequestMapping(ERROR_PATH)
    ApiError handleError(WebRequest webRequest) {
        var attrs = errorAttributes.getErrorAttributes(webRequest, ErrorAttributeOptions.of(ErrorAttributeOptions.Include.MESSAGE));
        return new ApiError(
                (Integer) attrs.get("status"),
                (String) attrs.get("message"), // consider using predefined message(s) here
                (String) attrs.get("path"));
    }

带验证异常处理的建议

package no.mycompany.myapp.error;

import org.springframework.http.HttpStatus;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;

import javax.servlet.http.HttpServletRequest;
import java.util.stream.Collectors;

@RestControllerAdvice
public class ExceptionHandlerAdvice {

    private static final String ERROR_MSG = "validation error";

    @ExceptionHandler(MethodArgumentNotValidException.class)
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    ApiError handleValidationException(MethodArgumentNotValidException exception, HttpServletRequest request) {

        return new ApiError(
                HttpStatus.BAD_REQUEST.value(),
                ERROR_MSG,
                request.getServletPath(),
                exception.getBindingResult().getFieldErrors().stream()
                    .collect(Collectors.toMap(
                            FieldError::getField,
                            FieldError::getDefaultMessage,
                            // mergeFunction handling multiple errors for a field
                            (firstMessage, secondMessage) -> firstMessage)));
    }
}

application.yml中的相关配置

server:
  error:
    include-message: always
    include-binding-errors: always

使用application.properties时

server.error.include-message=always
server.error.include-binding-errors=always

当使用springdatajpa时,请考虑使用以下设置来关闭第二次验证。

spring:
  jpa:
    properties:
      javax:
        persistence:
          validation:
            mode: none

有关spring中异常处理的更多信息:
https://spring.io/blog/2013/11/01/exception-handling-in-spring-mvc (2018年4月修订)https://www.baeldung.com/exception-handling-for-rest-with-spring (2020年12月31日)

相关问题