Правильный способ обработки исключений в Spring Boot - PullRequest
0 голосов
/ 28 мая 2018

Я читал документы Spring и обнаружил, что создание подкласса из ResponseEntityExceptionHandler было хорошим способом обработки исключений.Однако я попытался обработать исключения другим способом, так как мне нужно отличить BusinessExceptions от TechnicalExceptions.

Создан bean-компонент с именем BusinessFault, который инкапсулирует детали исключения:

BusinessFault.java

import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonInclude.Include;
import com.fasterxml.jackson.annotation.JsonProperty;

@JsonInclude(value = Include.NON_NULL)
public class BusinessFault {

    @JsonProperty(value = "category")
    private final String CATEGORY = "Business Failure";
    protected String type;
    protected String code;
    protected String reason;
    protected String description;
    protected String instruction;

    public BusinessFault(String type, String code, String reason) {
        this.type = type;
        this.code = code;
        this.reason = reason;
    }

    public BusinessFault(String type, String code, String reason, String description, String instruction) {
        this.type = type;
        this.code = code;
        this.reason = reason;
        this.description = description;
        this.instruction = instruction;
    }

    public String getType() {
        return type;
    }

    public void setType(String type) {
        this.type = type;
    }

    public String getCode() {
        return code;
    }

    public void setCode(String code) {
        this.code = code;
    }

    public String getReason() {
        return reason;
    }

    public void setReason(String reason) {
        this.reason = reason;
    }

    public String getDescription() {
        return description;
    }

    public void setDescription(String description) {
        this.description = description;
    }

    public String getInstruction() {
        return instruction;
    }

    public void setInstruction(String instruction) {
        this.instruction = instruction;
    }

    public String getCATEGORY() {
        return CATEGORY;
    }
}

Создан класс BusinessException,которые выполняют работу, создавая BusinessFault bean-компоненты через детали, переданные его конструктором:

BusinessException.java

import com.rest.restwebservices.exception.fault.BusinessFault;

public abstract class BusinessException extends RuntimeException {

    private BusinessFault businessFault;

    public BusinessException(String type, String code, String reason) {
        this.businessFault = new BusinessFault(type, code, reason);
    }

    public BusinessException(String type, String code, String reason, String description, String instruction) {
        this.businessFault = new BusinessFault(type, code, reason, description, instruction);
    }

    public BusinessException(BusinessFault businessFault) {
        this.businessFault = businessFault;
    }

    public BusinessFault getBusinessFault() {
        return businessFault;
    }

    public void setBusinessFault(BusinessFault businessFault) {
        this.businessFault = businessFault;
    }
}

Создан определенный класс UserNotFoundException, который расширяется от BusinessException class:

UserNotFoundException.java

import com.rest.restwebservices.exception.fault.BusinessFault;
import com.rest.restwebservices.exception.map.ExceptionMap;

public class UserNotFoundException extends BusinessException {

    public UserNotFoundException(BusinessFault businessFault) {
        super(businessFault);
    }

    public UserNotFoundException(String reason) {
        super(ExceptionMap.USERNOTFOUND.getType(), ExceptionMap.USERNOTFOUND.getCode(), reason);
    }

    public UserNotFoundException(String reason, String description, String instruction) {
        super(ExceptionMap.USERNOTFOUND.getType(), ExceptionMap.USERNOTFOUND.getCode(), reason, description,
                instruction);
    }
}

Создано BusinessExceptionHandler, но вместо того, чтобы быть подклассом ResponseEntityExceptionHandler,он имеет только аннотацию @ControllerAdvice и метод, который обрабатывает все выброшенные BusinessExceptions:

BusinessExceptionHandler.java

import javax.servlet.http.HttpServletRequest;

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

import com.rest.restwebservices.controller.UserController;
import com.rest.restwebservices.exception.BusinessException;
import com.rest.restwebservices.exception.fault.BusinessFault;

@ControllerAdvice(basePackageClasses = UserController.class)
public class BusinessExceptionHandler {

    @ExceptionHandler(BusinessException.class)
    @ResponseBody
    public ResponseEntity<BusinessFault> genericHandler(HttpServletRequest request, BusinessException ex) {
        return new ResponseEntity<BusinessFault>(ex.getBusinessFault(), HttpStatus.OK);
    }
}

Служебный уровень может выдавать UserNotFoundException:

@Service
public class UserService {

    @Autowired
    private UserRepository userRepository;

    public User findById(Long id) {
        User user = userRepository.findOne(id);
        if (user == null)
            throw new UserNotFoundException("The ID " + id + " doesn't behave to any user!");

        return user;
    }
}

Работает нормально.Но мне было интересно, если это плохая практика обработки исключений?

Ответы [ 2 ]

0 голосов
/ 28 мая 2018

У меня небольшая проблема с обработкой исключений.В принципе, абсолютно нормально ловить runtime exceptions, обрабатывать их и отправлять их клиенту, который, вероятно, использует ваш сервис REST и получает ответ об ошибке в виде объекта JSON.Если вам удастся рассказать ему, что он сделал не так и что он может с этим поделать, прекрасно!Конечно, это добавит к нему некоторую сложность, но, вероятно, с этим API работать легко и удобно.

Но подумайте и о бэкэнд-разработчиках, которые работают с вашим кодом.Особенно метод public User findById(Long id) в вашем UserService неясен.Причина этого в том, что вы сделали свой BusinessException, в частности, UserNotFoundException неконтролируемым .

Если я присоединился к вашей (бэкэнд) команде и мне пришлось написать какое-то делоИспользуя эту службу, я был бы совершенно уверен, чего ожидать от этого метода: я передаю идентификатор пользователя и возвращаю объект User, если он был найден, или null, если нет.Вот почему я написал бы такой код

User user = userService.findById("42A");
if (user == null) {
  // create a User or return an error or null or whatever
} else {
  // proceed
}

Однако я бы никогда не узнал, что первое условие никогда не будет верным, поскольку вы никогда не вернете null.Откуда мне знать, что я должен был поймать исключение?

Компилятор говорит мне перехватить его? Нет, поскольку он не проверен.

Я бы посмотрел ваш исходный код? Черт, нет!Ваш случай чрезвычайно прост.Это UserNotFoundException может быть поднято в другом методе в другом классе среди сотен строк кода.Иногда я все равно не мог заглянуть внутрь, так как UserService - это просто скомпилированный класс в зависимости.

Читал ли я JavaDoc? Хахаха.Скажем, 50% времени я бы не стал, а остальные 50% вы забыли документировать.

Итак, разработчик должен дождаться, пока его код будет использован (либо клиентом, либо в модульных тестах), чтобы убедиться, что он работает не так, как он задумал, и вынудить его переделать то, что он до сих пор кодировал.,И если весь ваш API спроектирован таким образом, что непроверенные исключения появляются из ниоткуда, это может быть очень и очень раздражающим, стоит времени и денег и так легко избежать, на самом деле.

0 голосов
/ 28 мая 2018

Я использую аналогичный способ обработки исключений.Но в моем случае разные обработчики управляются в соответствии с состоянием ошибки (например, пользователь существует, пользователь не может быть зарегистрирован из-за некоторого неудовлетворенного состояния и т. Д.).

Вы также можете добавить свое универсальное исключение BusinessException для некоторых особых случаев.Надеюсь, это поможет вам чувствовать себя лучше.

import javax.servlet.http.HttpServletRequest;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;

import com.rest.restwebservices.controller.UserController;
import com.rest.restwebservices.exception.ResourceNotFoundException;
import com.rest.restwebservices.exception.PreconditionFailedException;
import com.rest.restwebservices.exception.ResourceAlreadyExistsException;
import com.rest.restwebservices.exception.fault.BusinessFault;

@ControllerAdvice(basePackageClasses = UserController.class)
public class BusinessExceptionHandler {

    @ExceptionHandler(ResourceNotFoundException.class)
    @ResponseBody
    public ResponseEntity<BusinessFault> genericHandler(HttpServletRequest request, ResourceNotFoundException ex) {
        return new ResponseEntity<BusinessFault>(ex.getBusinessFault(), HttpStatus.NOT_FOUND);
    }

    @ExceptionHandler(PreconditionFailedException.class)
    @ResponseBody
    public ResponseEntity<BusinessFault> genericHandler(HttpServletRequest request, PreconditionFailedExceptionex) {
        return new ResponseEntity<BusinessFault>(ex.getBusinessFault(), HttpStatus.PRECONDITION_FAILED);
    }

    @ExceptionHandler(ResourceAlreadyExistsException.class)
    @ResponseBody
    public ResponseEntity<BusinessFault> genericHandler(HttpServletRequest request, ResourceAlreadyExistsException) {
        return new ResponseEntity<BusinessFault>(ex.getBusinessFault(), HttpStatus.CONFLICT);
    }
}
...