知識庫 / REST RSS 訂閱

Jersey異常處理

REST
HongKong
15
03:40 AM · Dec 06 ,2025

1. 引言

本教程將介紹使用 Jersey 處理異常的不同方法,Jersey 是一個 JAX-RS 實現。

Jersey 提供了許多機制來處理異常,您可以選擇並組合使用。處理 REST 異常是構建更好 API 的重要步驟。 在我們的用例中,我們將構建一個股票購買 API,並觀察每個步驟之間的影響。

2. 場景搭建

我們的最小配置包括創建倉庫、創建幾個 Bean 以及定義一些端點。它從我們的資源配置開始。在那裏,我們將使用 @ApplicationPath 定義我們的起始 URL 以及我們的端點包:

@ApplicationPath("/exception-handling/*")
public class ExceptionHandlingConfig extends ResourceConfig {
    public ExceptionHandlingConfig() {
        packages("com.baeldung.jersey.exceptionhandling.rest");
    }
}

2.1. Bean 實例

我們需要兩個 Bean 實例:<em style="font-style: italic;">Stock</em>(股票)和 <em style="font-style: italic;">Wallet</em>(錢包),以便我們能夠保存 <em style="font-style: italic;">Stock</em> 實例並進行購買。 針對我們的 <em style="font-style: italic;">Stock</em> 實例,我們只需要一個 <em style="font-style: italic;">price</em> 屬性,以輔助驗證。 更重要的是,我們的 <em style="font-style: italic;">Wallet</em> 類將包含驗證方法,以幫助構建我們的場景。

public class Wallet {
    private String id;
    private Double balance = 0.0;

    // getters and setters

    public Double addBalance(Double amount) {
        return balance += amount;
    }

    public boolean hasFunds(Double amount) {
        return (balance - amount) >= 0;
    }
}

2.2. 端點

同樣,我們的API將包含兩個端點。這些端點將定義標準方法,用於保存和檢索我們的Bean:

@Path("/stocks")
public class StocksResource {
    // POST and GET methods
}
@Path("/wallets")
public class WalletsResource {
    // POST and GET methods
}

例如,我們來看一下在 StocksResource 中的 GET 方法:

@GET
@Path("/{ticker}")
@Produces(MediaType.APPLICATION_JSON)
public Response get(@PathParam("ticker") String id) {
    Optional<Stock> stock = stocksRepository.findById(id);
    stock.orElseThrow(() -> new IllegalArgumentException("ticker"));

    return Response.ok(stock.get())
      .build();
}

在我們的 GET 方法中,我們拋出了第一個異常。 稍後我們會處理它,以便觀察其影響。

3. 異常處理時發生的情況?

當未處理的異常發生時,我們可能會暴露應用程序內部的敏感信息。如果我們嘗試從 StocksResource 中使用不存在的 Stock 進行 GET 請求,我們會得到如下類似的頁面:

該頁面顯示了應用程序服務器和版本信息,這可能會幫助潛在攻擊者利用漏洞。此外,還包含有關我們的類名和行號的信息,這也可能幫助攻擊者。 最重要的是,大部分信息對 API 用户來説毫無用處,並且給用户留下了糟糕的印象。

為了幫助控制異常響應,JAX-RS 提供了 ExceptionMapperWebApplicationException 類。 讓我們看看它們是如何工作的。

4. 自定義異常與 WebApplicationException

使用 WebApplicationException,我們可以創建自定義異常。 這種特殊的 RuntimeException 允許我們定義響應狀態和實體。 我們將首先創建一個 InvalidTradeException,該異常設置了消息和狀態:

public class InvalidTradeException extends WebApplicationException {
    public InvalidTradeException() {
        super("invalid trade operation", Response.Status.NOT_ACCEPTABLE);
    }
}

值得一提的是,JAX-RS 定義了 <em >WebApplicationException</em> 的子類用於常見的 HTTP 狀態碼。這些包括有用的異常,如 <em >NotAllowedException</em><em >BadRequestException</em> 等。`但是,當我們想要返回更復雜的錯誤消息時,我們可以返回一個 JSON 響應。

4.1. JSON 異常

我們可以創建簡單的 Java 類並將它們包含在我們的 Response 中。 在我們的示例中,我們有一個 subject 屬性,我們將使用它來包裝上下文數據:

public class RestErrorResponse {
    private Object subject;
    private String message;

    // getters and setters
}

由於此異常並非設計用於操縱,因此我們無需關注 的主題類型</em subject>。

4.2. 充分利用自定義異常

為了瞭解如何使用自定義異常,讓我們定義一個用於購買 股票 的方法:

@POST
@Path("/{wallet}/buy/{ticker}")
@Produces(MediaType.APPLICATION_JSON)
public Response postBuyStock(
  @PathParam("wallet") String walletId, @PathParam("ticker") String id) {
    Optional<Stock> stock = stocksRepository.findById(id);
    stock.orElseThrow(InvalidTradeException::new);

    Optional<Wallet> w = walletsRepository.findById(walletId);
    w.orElseThrow(InvalidTradeException::new);

    Wallet wallet = w.get();
    Double price = stock.get()
      .getPrice();

    if (!wallet.hasFunds(price)) {
        RestErrorResponse response = new RestErrorResponse();
        response.setSubject(wallet);
        response.setMessage("insufficient balance");
        throw new WebApplicationException(Response.status(Status.NOT_ACCEPTABLE)
          .entity(response)
          .build());
    }

    wallet.addBalance(-price);
    walletsRepository.save(wallet);

    return Response.ok(wallet)
      .build();
}

本方法中,我們使用迄今為止創建的一切。 如果股票或錢包不存在,我們拋出 InvalidTradeException 並且,如果資金不足,我們將構建一個 RestErrorResponse ,其中包含我們的 Wallet ,並將其作為 WebApplicationException 拋出。

4.3. 用例示例

首先,讓我們創建一個 股票

$ curl 'http://localhost:8080/jersey/exception-handling/stocks' -H 'Content-Type: application/json' -d '{
    "id": "STOCK",
    "price": 51.57
}'

{"id": "STOCK", "price": 51.57}

然後購買它需要一個 錢包

$ curl 'http://localhost:8080/jersey/exception-handling/wallets' -H 'Content-Type: application/json' -d '{
    "id": "WALLET",
    "balance": 100.0
}'

{"balance": 100.0, "id": "WALLET"}

之後,我們將使用我們的錢包購買股票

$ curl -X POST 'http://localhost:8080/jersey/exception-handling/wallets/WALLET/buy/STOCK'

{"balance": 48.43, "id": "WALLET"}

我們將在響應中獲取更新後的餘額。如果嘗試再次購買,我們將收到詳細的 RestErrorResponse

{
    "message": "insufficient balance",
    "subject": {
        "balance": 48.43,
        "id": "WALLET"
    }
}

5. 未處理異常與 <em ExceptionMapper</em>>

為了明確説明,僅僅拋出 <em WebApplicationException</em>> 並無法阻止默認錯誤頁的顯示。我們需要為我們的 <em Response</em>> 指定一個實體,而 <em InvalidTradeException</em>> 並不適用。 儘管我們盡力處理所有情況,但未處理的異常仍然可能發生。 因此,最好先處理這些異常。 藉助 <em ExceptionMapper</em>>,我們可以定義針對特定異常類型的處理點,並在提交 <em Response</em>> 之前對其進行修改。

public class ServerExceptionMapper implements ExceptionMapper<WebApplicationException> {
    @Override
    public Response toResponse(WebApplicationException exception) {
        String message = exception.getMessage();
        Response response = exception.getResponse();
        Status status = response.getStatusInfo().toEnum();

        return Response.status(status)
          .entity(status + ": " + message)
          .type(MediaType.TEXT_PLAIN)
          .build();
    }
}

例如,我們只是將異常信息傳遞到我們的 Response 中,這將顯示我們返回的內容。隨後,我們可以進一步檢查狀態碼,在構建 Response 之前:

switch (status) {
    case METHOD_NOT_ALLOWED:
        message = "HTTP METHOD NOT ALLOWED";
        break;
    case INTERNAL_SERVER_ERROR:
        message = "internal validation - " + exception;
        break;
    default:
        message = "[unhandled response code] " + exception;
}

5.1. 處理特定異常

如果經常拋出特定的 Exception,我們可以為它創建一個 ExceptionMapper在我們的端點中,我們使用 IllegalArgumentException 進行簡單的驗證,因此我們先為它創建一個映射器。 這一次,使用 JSON 響應:

public class IllegalArgumentExceptionMapper
  implements ExceptionMapper<IllegalArgumentException> {
    @Override
    public Response toResponse(IllegalArgumentException exception) {
        return Response.status(Response.Status.EXPECTATION_FAILED)
          .entity(build(exception.getMessage()))
          .type(MediaType.APPLICATION_JSON)
          .build();
    }

    private RestErrorResponse build(String message) {
        RestErrorResponse response = new RestErrorResponse();
        response.setMessage("an illegal argument was provided: " + message);
        return response;
    }
}

現在,每當應用程序中出現未處理的 IllegalArgumentException 時,我們的 IllegalArgumentExceptionMapper 將會處理它。

5.2. 配置

為了激活我們的異常映射器,我們需要返回到我們的 Jersey 資源配置並註冊它們:

public ExceptionHandlingConfig() {
    // packages ...
    register(IllegalArgumentExceptionMapper.class);
    register(ServerExceptionMapper.class);
}

這足以消除默認錯誤頁面。然後,根據拋出的異常類型,Jersey 會使用我們的異常映射器,當未處理的異常發生時。例如,嘗試獲取不存在的 Stock 時,IllegalArgumentExceptionMapper 將被使用:

$ curl 'http://localhost:8080/jersey/exception-handling/stocks/NONEXISTENT'

{"message": "an illegal argument was provided: ticker"}

同樣,對於其他未處理的異常,更廣泛的 ServerExceptionMapper 將被使用。例如,當我們使用錯誤的 HTTP 方法時:

$ curl -X POST 'http://localhost:8080/jersey/exception-handling/stocks/STOCK'

Method Not Allowed: HTTP 405 Method Not Allowed

6. 結論

在本文中,我們探討了使用 Jersey 處理異常的多種方法。此外,我們還了解了其重要性以及如何進行配置。隨後,我們構建了一個簡單的場景,以便應用這些方法。最終,我們擁有一個更友好、更安全的 API。

user avatar
0 位用戶收藏了這個故事!
收藏

發佈 評論

Some HTML is okay.