Tuesday, January 19, 2016

Создаем RESTful веб-сервис с помощью Jersey. Обработка исключений

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

Рассмотрим как это сделать.

Возьмем за основу приложение, созданное в посте "Создаем RESTful веб-сервис с помощью Jersey".

Создадим класс ErrorDto с полями code и message для хранения информации о возникшей ошибке при работе приложения.
Добавим аннотацию @XmlRootElement для преобразования объектов ErrorDto в JSON-формат в процессе работы приложения:
package com.jersey.tutorial.dto;

import javax.xml.bind.annotation.XmlRootElement;

@XmlRootElement
public class ErrorDto {

    private int code;
    private String message;

    public ErrorDto() {

    }

    public ErrorDto(int code, String message) {
        this.code = code;
        this.message = message;
    }

    public int getCode() {
        return code;
    }

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

    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }
}

Создадим класс исключения, которое будет выбрасываться, если цитата не найдена:
package com.jersey.tutorial.exception;

public class QuoteNotFoundException extends Exception {

    public QuoteNotFoundException(String message) {
        super(message);
    }
}

Для обработки исключений создадим класс QuotesExceptionMapper, реализующий интерфейс ExceptionMapper.
Добавим классу аннотацию @Provider, чтобы он использовался в качестве реализации интерфейса ExceptionMapper при работе приложения.

В методе toResponse() реализуем логику обработки исключений:
  • если выбрасывается исключение QuoteNotFoundException, то ответ сервера должен возвращаться со статусом 404 Not Found.
    При других исключениях ответ должен иметь статус 400 Bad Request
  • код статуса и сообщение об ошибке хранятся в объекте ErrorDto, который возвращается клиенту веб-сервиса при помощи объекта Response
package com.jersey.tutorial.exception;

import com.jersey.tutorial.dto.ErrorDto;

import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.ext.ExceptionMapper;
import javax.ws.rs.ext.Provider;

@Provider
public class QuotesExceptionMapper implements ExceptionMapper<Exception> {

    public Response toResponse(Exception exception) {
        Response.Status status;
        String message;

        if (exception instanceof QuoteNotFoundException) {
            status = Response.Status.NOT_FOUND;
            message = exception.getMessage();
        } else {
            status = Response.Status.BAD_REQUEST;
            message = "Bad request";
        }

        ErrorDto errorDto = new ErrorDto(status.getStatusCode(), message);
        return Response.status(status).entity(errorDto).type(MediaType.APPLICATION_JSON_TYPE).build();
    }
}

Изменим методы getQuote(), updateQuote() и deleteQuote() класса QuotesService так, чтобы они выбрасывали исключение QuoteNotFoundException, если цитата не найдена в HashMap:
package com.jersey.tutorial.service;

import com.jersey.tutorial.dto.QuoteDto;
import com.jersey.tutorial.exception.QuoteNotFoundException;

import java.util.Collections;
import java.util.HashMap;
import java.util.Map;

public class QuotesService {

    private static final QuotesService INSTANCE = new QuotesService();
    private static final Map<Integer, QuoteDto> QUOTES = Collections.synchronizedMap(new HashMap<Integer, QuoteDto>());

    private Integer id = 0;

    public static QuotesService getInstance() {
        return INSTANCE;
    }

    public QuoteDto getQuote(Integer id) throws QuoteNotFoundException {
        QuoteDto quoteDto = QUOTES.get(id);

        if (quoteDto == null) {
            throw new QuoteNotFoundException(String.format("Quote with id=%d not found. Cannot retrieve it", id));
        }

        return quoteDto;
    }

    public void addQuote(QuoteDto quote) {
        synchronized(QUOTES) {
            id++;
            QUOTES.put(id, quote);
        }
    }

    public void updateQuote(Integer id, QuoteDto updatedQuoteDto) throws QuoteNotFoundException {
        synchronized(QUOTES) {
            QuoteDto quoteDto = QUOTES.get(id);

            if (quoteDto == null) {
                throw new QuoteNotFoundException(String.format("Quote with id=%d not found. Cannot update it", id));
            } else {
                QUOTES.put(id, updatedQuoteDto);
            }
        }
    }

    public void deleteQuote(Integer id) throws QuoteNotFoundException {
        synchronized(QUOTES) {
            QuoteDto quoteDto = QUOTES.get(id);

            if (quoteDto == null) {
                throw new QuoteNotFoundException(String.format("Quote with id=%d not found. Cannot delete it", id));
            } else {
                QUOTES.remove(id);
            }
        }
    }
}

Изменим сигнатуру методов getQuote(), updateQuote() и deleteQuote() класса QuotesResource, указав, что они могут выбрасывать QuoteNotFoundException:
package com.jersey.tutorial.ws;

...

@Path("/quotes")
public class QuotesResource {

    ...
    
    @GET
    @Produces(MediaType.APPLICATION_JSON)
    @Path("/{id}")
    public Response getQuote(@PathParam("id") Integer id) throws QuoteNotFoundException {
        ...        
    }

    ...    

    @PUT
    @Consumes(MediaType.APPLICATION_JSON)
    @Path("/{id}")
    public Response updateQuote(@PathParam("id") Integer id, QuoteDto quoteDto) throws QuoteNotFoundException {
        ...        
    }

    @DELETE
    @Path("/{id}")
    public Response deleteQuote(@PathParam("id") Integer id) throws QuoteNotFoundException {
        ...
    }
}

Соберем проект, выполнив команду "mvn clean install":
[INFO] Scanning for projects...
[INFO]                                                                         
[INFO] ------------------------------------------------------------------------
[INFO] Building jersey-exceptions-tutorial 1.0-SNAPSHOT
[INFO] ------------------------------------------------------------------------
[INFO] 
[INFO] --- maven-clean-plugin:2.4.1:clean (default-clean) @ jersey-exceptions-tutorial ---
[INFO] Deleting C:\Blog\jersey-exceptions-tutorial\target
[INFO] 
[INFO] --- maven-resources-plugin:2.5:resources (default-resources) @ jersey-exceptions-tutorial ---
[debug] execute contextualize
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] Copying 0 resource
[INFO] 
[INFO] --- maven-compiler-plugin:2.3.2:compile (default-compile) @ jersey-exceptions-tutorial ---
[INFO] Compiling 7 source files to C:\Blog\jersey-exceptions-tutorial\target\classes
[INFO] 
[INFO] --- maven-resources-plugin:2.5:testResources (default-testResources) @ jersey-exceptions-tutorial ---
[debug] execute contextualize
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] skip non existing resourceDirectory C:\Blog\jersey-exceptions-tutorial\src\test\resources
[INFO] 
[INFO] --- maven-compiler-plugin:2.3.2:testCompile (default-testCompile) @ jersey-exceptions-tutorial ---
[INFO] Nothing to compile - all classes are up to date
[INFO] 
[INFO] --- maven-surefire-plugin:2.19.1:test (default-test) @ jersey-exceptions-tutorial ---
[INFO] Tests are skipped.
[INFO] 
[INFO] --- maven-war-plugin:2.6:war (default-war) @ jersey-exceptions-tutorial ---
[INFO] Packaging webapp
[INFO] Assembling webapp [jersey-exceptions-tutorial] in [C:\Blog\jersey-exceptions-tutorial\target\tutorial]
[INFO] Processing war project
[INFO] Webapp assembled in [466 msecs]
[INFO] Building war: C:\Blog\jersey-exceptions-tutorial\target\tutorial.war
[INFO] 
[INFO] --- maven-install-plugin:2.3.1:install (default-install) @ jersey-exceptions-tutorial ---
[INFO] Installing C:\Blog\jersey-exceptions-tutorial\target\tutorial.war to C:\Users\alex\.m2\repository\jersey-exceptions-tutorial\jersey-exceptions-tutorial\1.0-SNAPSHOT\jersey-exceptions-tutorial-1.0-SNAPSHOT.war
[INFO] Installing C:\Blog\jersey-exceptions-tutorial\pom.xml to C:\Users\alex\.m2\repository\jersey-exceptions-tutorial\jersey-exceptions-tutorial\1.0-SNAPSHOT\jersey-exceptions-tutorial-1.0-SNAPSHOT.pom
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 2.973s
[INFO] Finished at: Tue Jan 19 12:14:19 EET 2016
[INFO] Final Memory: 17M/220M
[INFO] ------------------------------------------------------------------------

Скопируем полученный war-файл в директорию Tomcat'а webapps, запустим сервер и проверим его работу с помощью REST-клиента, например Postman.

Попробуем получить несуществующую цитату:




















Приложение прислало ответ со статусом "404 Not Found" и сообщением, описывающим проблему.
Попробуем изменить и удалить несуществующую цитату:





















Аналогичный результат.
Теперь добьемся появления другого исключения (не QuoteNotFoundException) - попробуем получить ресурс с неправильным URI:




















Приложение ожидаемо прислало ответ со другим статусом - "400 Bad Request".

Структура проекта:































Исходный код созданного приложения доступен по ссылке https://subversion.assembla.com/svn/jersey-exceptions-tutorial/trunk/.

Рекомендуемые посты:

No comments:

Post a Comment