Статьи

Обработка исключений в Spring RESTful Web Service

1. Введение

Возможно, мы сталкивались с некоторыми из нескольких способов обработки исключений в приложении веб-службы RESTful в Spring. В этой статье мы попытаемся изучить лучший подход, который мы можем использовать для достижения эффективной обработки исключений.

2. Постановка проблемы

Давайте создадим простое приложение, которое будет идентифицировать имя сотрудника в REST URI. Если имя сотрудника, указанное в запросе, является числовым, пусть наше приложение сгенерирует специальное исключение, которое мы будем обрабатывать с помощью обработчиков исключений , и, соответственно, вернем ответ JSON клиенту. Ответом об успешном выполнении будет JSON с подробностями сотрудника, а ответом об ошибке будет JSON с ошибкой с errorCode и соответствующим сообщением об ошибке.

3. Реализация

Давайте сначала проверим, как выглядят записи нашего файла pom и web.xml

pom.xml

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<!-- Spring dependencies -->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-core</artifactId>
    <version>4.2.1.RELEASE</version>
</dependency>
  
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-web</artifactId>
    <version>4.2.1.RELEASE</version>
</dependency>
  
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-webmvc</artifactId>
    <version>4.2.1.RELEASE</version>
</dependency>
  
<!-- Jackson JSON Processor -->
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.4.1</version>
</dependency>

web.xml

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
<?xml version="1.0" encoding="ISO-8859-1"?>
    xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
    id="WebApp_ID" version="2.5">
  
    <display-name>RESTWithSpringMVCException</display-name>
  
    <servlet>
        <servlet-name>mvc-dispatcher</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>
  
    <servlet-mapping>
        <servlet-name>mvc-dispatcher</servlet-name>
        <url-pattern>/*</url-pattern>
    </servlet-mapping>
  
</web-app>

Давайте теперь проверим контекст веб-приложения .

MVC-диспетчерская-servlet.xml

Теперь пришло время создать классы сущностей, один для Employee, а другой для ErrorResponse , который должен быть возвращен как JSON в случае любого исключения в любом из слоев в нашем приложении.

Employee.java

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
package com.jcombat.bean;
  
public class Employee {
    private String empId;
    private String name;
     
    public String getEmpId() {
        return empId;
    }
    public void setEmpId(String empId) {
        this.empId = empId;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
}

ErrorResponse.java

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
package com.jcombat.bean;
  
public class ErrorResponse {
    private int errorCode;
    private String message;
     
    public int getErrorCode() {
        return errorCode;
    }
    public void setErrorCode(int errorCode) {
        this.errorCode = errorCode;
    }
    public String getMessage() {
        return message;
    }
    public void setMessage(String message) {
        this.message = message;
    }
}

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

EmployeeException.java

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
package com.jcombat.exception;
  
public class EmployeeException extends Exception {
    private static final long serialVersionUID = 1L;
    private String errorMessage;
  
    public String getErrorMessage() {
        return errorMessage;
    }
    public EmployeeException(String errorMessage) {
        super(errorMessage);
        this.errorMessage = errorMessage;
    }
    public EmployeeException() {
        super();
    }
}

Spring предоставляет нам аннотацию @ExceptionHandler для специальной обработки определенного или распространенного типа исключений в контроллере.

Наиболее важной частью здесь является написание контроллера покоя для нашего приложения.

DemoController.java

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
package com.jcombat.controller;
  
import org.apache.commons.lang3.StringUtils;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
  
import com.jcombat.bean.Employee;
import com.jcombat.bean.ErrorResponse;
import com.jcombat.exception.EmployeeException;
  
@RestController
public class EmployeeController {
  
    @RequestMapping(value = "/{firstName}", method = RequestMethod.GET)
    public ResponseEntity<Employee> showMessage(
            @PathVariable("firstName") String firstName,
            @RequestParam(value = "empId", required = false, defaultValue = "00000") final String empId) throws EmployeeException {
  
        Employee employee = new Employee();
        employee.setEmpId(empId);
        employee.setFirstName(firstName);
  
        if (StringUtils.isNumeric(firstName)) {
            throw new EmployeeException("Invalid employee name requested");
        }
  
        return new ResponseEntity<Employee>(employee, HttpStatus.OK);
    }
  
    @ExceptionHandler(EmployeeException.class)
    public ResponseEntity<ErrorResponse> exceptionHandler(Exception ex) {
        ErrorResponse error = new ErrorResponse();
        error.setErrorCode(HttpStatus.PRECONDITION_FAILED.value());
        error.setMessage(ex.getMessage());
        return new ResponseEntity<ErrorResponse>(error, HttpStatus.OK);
    }
}

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

Но что, если NullPointerException генерируется из ниоткуда. Чтобы быть в безопасности, у нас должен быть общий обработчик исключений в нашем приложении, который обрабатывает все другие типы исключений, такие как IOException , NullPointerException и так далее. Для этого Spring представил версию @ControllerAdvice в версии 3.2, где в нашем приложении можно создать класс Controller Advice, который будет способен обрабатывать все глобальные сценарии исключений.

Класс, аннотированный @ControllerAdvice, будет зарегистрирован как глобальный обработчик исключений .

Давайте создадим один для нашего приложения.

ExceptionControllerAdvice.java

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
package com.jcombat.controller;
  
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 com.jcombat.bean.ErrorResponse;
  
@ControllerAdvice
public class ExceptionControllerAdvice {
  
    @ExceptionHandler(Exception.class)
    public ResponseEntity<ErrorResponse> exceptionHandler(Exception ex) {
        ErrorResponse error = new ErrorResponse();
        error.setErrorCode(HttpStatus.INTERNAL_SERVER_ERROR.value());
        error.setMessage("Please contact your administrator");
        return new ResponseEntity<ErrorResponse>(error, HttpStatus.OK);
    }
}

Это означает, что если мы когда-либо получим непредвиденное исключение в нашем приложении, отличное от пользовательского исключения, будет подготовлен общий объект ошибки с общим кодом ошибки и сообщением об ошибке, которое будет возвращено как ответ JSON об ошибке.

В версии Spring более ранней, чем 3.2, лучшей альтернативой было бы создание с одним базовым контроллером с расширением всех отдельных контроллеров вместо @ControllerAdvice .

Здесь есть что отметить. Ошибка ответа JSON не может быть возвращена в Spring 3.0.x с ResponseEntity из-за отсутствия поддержки, предоставляемой Spring 3.0.x. Альтернативой для этого было бы использование BeanNameViewResolver с ModelAndView в качестве возвращаемого типа. Вскоре мы придумаем пример приложения по этому вопросу.

4. Запуск приложения

Время запустить приложение, которое мы создали.

Убедитесь, что мы опубликовали приложение на сервере и запустили его.

Теперь нажмите на указанный ниже URI в браузере — http: // localhost: 8080 / RESTWithSpringMVCException / Ramesh? EmpId = 1234

Успех-2

Давайте посмотрим, как выглядит ответ об ошибке. Обратите внимание, что мы добавили блок IF в EmployeeController , который проверяет, является ли переменная пути для имени сотрудника числовой. Если он числовой, наше приложение выдает исключение EmployeeException . Давайте ударим ниже URI —

отказ

Если нам нужно добавить электронную почту в качестве одной из переменных пути, лучше пройти через приложение-службу RESTful, которое мы создали в предыдущем уроке .

5. Загрузите исходный код

Ссылка: Обработка исключений весной RESTful Web Service от нашего партнера JCG Абхиманью Прасада в блоге jCombat .