1. 概述
本教程將介紹使用開源框架 Jersey 進行 Bean 驗證。
正如我們在之前的文章中所看到,Jersey 是一個用於開發 RESTful Web 服務的開源框架。有關 Jersey 的更多信息,請參閲我們關於使用 Jersey 和 Spring 創建 API 的介紹。
2. 在 Jersey 中使用 Bean 驗證
驗證是指檢查某些數據是否符合預定義的約束。這在大多數應用程序中是一個非常常見的用例。
Java Bean 驗證 框架(JSR-380)已成為 Java 中處理此類操作的默認標準。為了回顧 Java Bean 驗證的基礎知識,請參考我們的上一篇教程。
Jersey 包含一個擴展模塊以支持 Bean 驗證。要使用此功能,我們首先需要在我們的應用程序中進行配置。在下一部分中,我們將看到如何配置我們的應用程序。
3. 應用設置
現在,讓我們基於《Jersey MVC 支持》文章中提供的簡單 Fruit API 示例進行擴展。
3.1. Maven 依賴
首先,讓我們將 Bean Validation 依賴添加到我們的 pom.xml 中:
<dependency>
<groupId>org.glassfish.jersey.ext</groupId>
<artifactId>jersey-bean-validation</artifactId>
<version>3.1.1</version>
</dependency>我們可以從 Maven Central 獲取最新版本:Maven Central。
3.2. 服務器配置
在 Jersey 中,我們通常會在自定義資源配置類中註冊所需的擴展功能。
然而,對於 Bean 驗證擴展,無需進行此項註冊。 幸運的是,這確實是 Jersey 框架自動註冊的少數擴展功能之一。
最後,為了將驗證錯誤發送到客户端,我們將向自定義資源配置添加一個服務器屬性:
public ViewApplicationConfig() {
packages("com.baeldung.jersey.server");
property(ServerProperties.BV_SEND_ERROR_IN_RESPONSE, true);
}
4. 驗證 JAX-RS 資源方法
本節將解釋兩種使用約束註解驗證輸入參數的不同方法:
- 使用內置的 Bean Validation API 約束
- 創建自定義約束和驗證器
4.1. 使用內置約束標註
讓我們首先看一下內置約束標註:
@POST
@Path("/create")
@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
public void createFruit(
@NotNull(message = "Fruit name must not be null") @FormParam("name") String name,
@NotNull(message = "Fruit colour must not be null") @FormParam("colour") String colour) {
Fruit fruit = new Fruit(name, colour);
SimpleStorageService.storeFruit(fruit);
}
在本示例中,我們使用兩個表單參數 Fruit(水果)和 name(名稱)和 colour(顏色)創建了一個新的 Fruit 對象。我們使用了 @NotNull 註解,該註解已包含在 Bean Validation API 中。
這為我們的表單參數施加了一個簡單的非空約束。如果其中一個參數為 null,則註解中聲明的消息將被返回。
當然,我們將通過一個單元測試來演示這一點:
@Test
public void givenCreateFruit_whenFormContainsNullParam_thenResponseCodeIsBadRequest() {
Form form = new Form();
form.param("name", "apple");
form.param("colour", null);
Response response = target("fruit/create").request(MediaType.APPLICATION_FORM_URLENCODED)
.post(Entity.form(form));
assertEquals("Http Response should be 400 ", 400, response.getStatus());
assertThat(response.readEntity(String.class), containsString("Fruit colour must not be null"));
}
在上述示例中,我們使用 JerseyTest 支持類來測試我們的水果資源。我們發送一個帶有空 colour 的 POST 請求,並檢查響應是否包含預期的消息。
有關內置驗證約束的列表,請參閲 文檔。
4.2. 定義自定義約束標註
有時我們需要施加更復雜的約束。 我們可以通過定義自己的自定義標註來實現。
利用我們簡單的 Fruit API 示例,讓我們假設我們需要驗證所有水果都有有效的序列號:
@PUT
@Path("/update")
@Consumes("application/x-www-form-urlencoded")
public void updateFruit(@SerialNumber @FormParam("serial") String serial) {
//...
}
在此示例中,參數 serial 必須滿足 @SerialNumber 定義的約束,我們將在下面定義它。
我們首先定義約束標註:
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = { SerialNumber.Validator.class })
public @interface SerialNumber {
String message()
default "Fruit serial number is not valid";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
接下來,我們將定義驗證器類 SerialNumber.Validator:
public class Validator implements ConstraintValidator<SerialNumber, String> {
@Override
public void initialize(SerialNumber serial) {
}
@Override
public boolean isValid(String serial,
ConstraintValidatorContext constraintValidatorContext) {
String serialNumRegex = "^\\d{3}-\\d{3}-\\d{4}$";
return Pattern.matches(serialNumRegex, serial);
}
}
關鍵在於 Validator 類必須實現 ConstraintValidator,其中 T 是我們想要驗證的值的類型,在本例中是 String。
最後,我們將在 isValid 方法中實現自定義驗證邏輯。
5. 資源驗證
此外,Bean Validation API 也允許我們使用 @Valid 註解來驗證對象。
在下一部分,我們將解釋使用該註解驗證資源類兩種不同的方法:
- 首先,請求資源驗證
- 其次,響應資源驗證
讓我們先添加 @Min 註解到我們的 Fruit 對象中:
@XmlRootElement
public class Fruit {
@Min(value = 10, message = "Fruit weight must be 10 or greater")
private Integer weight;
//...
}
5.1. 請求資源驗證
首先,我們將使用 @Valid 在我們的 FruitResource 類中啓用驗證:
@POST
@Path("/create")
@Consumes("application/json")
public void createFruit(@Valid Fruit fruit) {
SimpleStorageService.storeFruit(fruit);
}
在上面的示例中,如果我們嘗試創建一個重量小於 10 的水果,將會收到驗證錯誤。
5.2. 響應資源驗證
同樣,在下一個示例中,我們將看到如何驗證響應資源:
@GET
@Valid
@Produces("application/json")
@Path("/search/{name}")
public Fruit findFruitByName(@PathParam("name") String name) {
return SimpleStorageService.findByName(name);
}請注意,我們使用相同的 @Valid 註解。但這次我們將其用於資源方法級別,以確保響應有效。
6. 自定義異常處理程序
在這一部分,我們將簡要探討如何創建自定義異常處理程序。 這在我們需要違反特定約束時返回自定義響應時非常有用。
讓我們首先定義我們的 FruitExceptionMapper:
public class FruitExceptionMapper implements ExceptionMapper<ConstraintViolationException> {
@Override
public Response toResponse(ConstraintViolationException exception) {
return Response.status(Response.Status.BAD_REQUEST)
.entity(prepareMessage(exception))
.type("text/plain")
.build();
}
private String prepareMessage(ConstraintViolationException exception) {
StringBuilder message = new StringBuilder();
for (ConstraintViolation<?> cv : exception.getConstraintViolations()) {
message.append(cv.getPropertyPath() + " " + cv.getMessage() + "\n");
}
return message.toString();
}
}首先,我們定義了一個自定義異常映射提供者。為此,我們使用 ConstraintViolationException 實現 ExceptionMapper 接口。
因此,當此異常被拋出時,我們的自定義異常映射器實例的toResponse 方法將被調用。
此外,在這個簡單的示例中,我們遍歷所有違反的情況,並將每個屬性和消息附加到響應中發送。
接下來,為了使用我們的自定義異常映射器,我們需要註冊提供者:
@Override
protected Application configure() {
ViewApplicationConfig config = new ViewApplicationConfig();
config.register(FruitExceptionMapper.class);
return config;
}最後,我們添加一個端點,返回一個無效的 Fruit 以展示異常處理機制:
@GET
@Produces(MediaType.TEXT_HTML)
@Path("/exception")
@Valid
public Fruit exception() {
Fruit fruit = new Fruit();
fruit.setName("a");
fruit.setColour("b");
return fruit;
}
7. 結論
總而言之,在本教程中,我們探討了 Jersey Bean Validation API 擴展。
首先,我們介紹了 Bean Validation API 如何在 Jersey 中使用。 此外,我們還查看了如何配置一個示例 Web 應用程序。
最後,我們研究了使用 Jersey 進行驗證的多種方法,以及如何編寫自定義異常處理程序。