知識庫 / REST RSS 訂閱

本地化驗證消息在 RESTful API 中

REST,Spring Web
HongKong
6
03:35 AM · Dec 06 ,2025

1. 概述

我們經常面臨設計應用程序,需要在多語言環境中傳遞本地化消息的任務。在這種情況下,將消息傳遞給用户的選擇語言是一種常見的做法。

當收到客户端對 REST Web 服務的請求時,我們必須確保傳入的客户端請求在處理之前滿足預定義的驗證規則。驗證旨在保持數據完整性和增強系統安全性。服務負責提供信息性消息,指示請求驗證失敗時發生的情況。

在本教程中,我們將探討在 REST Web 服務中實現本地化驗證消息的實現。

2. 關鍵步驟

我們的旅程從使用資源包作為存儲本地化消息的倉庫開始。然後,我們將資源包與 Spring Boot 集成,從而能夠在我們的應用程序中檢索本地化消息。

接下來,我們將創建一個包含請求驗證的 Web 服務,這展示了在請求驗證錯誤發生時如何利用本地化消息。

最後,我們將探索不同類型的本地化消息自定義選項。這些選項包括覆蓋默認驗證消息、定義自己的資源包以提供自定義驗證消息,以及創建自定義驗證註解以實現動態消息生成。

通過這些步驟,我們將深入理解如何在多語言應用程序中提供精確且語言特定的反饋。

3. Maven 依賴

在開始之前,我們先添加 Spring Boot Starter WebSpring Boot Starter Validation 依賴,用於 Web 開發和 Java Bean 驗證,添加到 <pom.xml> 中:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-validation</artifactId>
</dependency>

最新版本的這些內容可以在 Maven Central 上找到。

4. 本地化消息存儲

在 Java 應用開發中,屬性文件通常用作國際化應用中本地化消息的存儲庫。 採用這種方法被認為是約定俗成的做法。 這些文件通常被稱為資源包。

這些文件是包含鍵值對的純文本文檔。 鍵作為消息檢索的標識符,而關聯的值則包含相應語言中的本地化消息。

在本教程中,我們將創建兩個屬性文件。

CustomValidationMessages.properties 是我們的默認屬性文件,文件名不包含任何區域設置名稱。 當客户端指定不支持的區域設置時,應用程序始終回退到其默認語言:

field.personalEmail=Personal Email
validation.notEmpty={field} cannot be empty
validation.email.notEmpty=Email cannot be empty

我們希望創建一個額外的中文語言屬性文件,即 CustomValidationMessages_zh.properties。應用程序在客户端指定 zh 或諸如 zh-tw 之類的 locale 時,會切換到中文。

field.personalEmail=個人電郵
validation.notEmpty={field}不能是空白
validation.email.notEmpty=電郵不能留空

必須確保所有屬性文件都使用 UTF-8 編碼。這在處理包含非拉丁字符(如中文、日語和韓語)的消息時尤其重要。 這種保證將確保我們能夠準確顯示所有消息,而無需擔心數據損壞的風險。

5. 本地化消息檢索

Spring Boot 通過 <em data-renderer-mark="true">MessageSource</em> 接口簡化了本地化消息檢索。它從應用程序的資源包中解析消息,並允許我們無需額外努力獲取不同區域的語言信息。

必須在 Spring Boot 中配置 <em data-renderer-mark="true">MessageSource</em> 的提供者,才能使用它。在本教程中,我們將 <em data-renderer-mark="true">ReloadableResourceBundleMessageSource</em> 作為實現方式。

它能夠重新加載消息屬性文件,而無需重啓服務器。這在應用程序初始開發階段非常有用,此時我們希望在不重新部署整個應用程序的情況下看到消息更改。

必須將默認編碼與我們用於屬性文件的 UTF-8 編碼對齊:

@Configuration
public class MessageConfig {

    @Bean
    public MessageSource messageSource() {
        ReloadableResourceBundleMessageSource messageSource = new ReloadableResourceBundleMessageSource();
        messageSource.setBasename("classpath:CustomValidationMessages");
        messageSource.setDefaultEncoding("UTF-8");
        return messageSource;
    }
}

6. Bean 驗證

在驗證過程中,使用一個名為 User 的數據傳輸對象 (DTO),其中包含一個 email 字段。我們將使用 Java Bean 驗證來驗證此 DTO 類。 email 字段使用 @NotEmpty 註解,以確保它不是一個空字符串。 此註解是標準的 Java Bean 驗證註解:

public class User {
	
    @NotEmpty
    private String email;

    // getters and setters
}

7. RESTful 服務

在本節中,我們將創建一個 RESTful 服務,名為 <em data-renderer-mark="true">UserService</em>,該服務負責通過 PUT 方法根據請求體更新特定用户的信息。

@RestController
public class UserService {

    @PutMapping(value = "/user", produces = MediaType.APPLICATION_JSON_VALUE)
    public ResponseEntity<UpdateUserResponse> updateUser(
      @RequestBody @Valid User user,
      BindingResult bindingResult) {

        if (bindingResult.hasFieldErrors()) {

            List<InputFieldError> fieldErrorList = bindingResult.getFieldErrors().stream()
              .map(error -> new InputFieldError(error.getField(), error.getDefaultMessage()))
              .collect(Collectors.toList());

            UpdateUserResponse updateResponse = new UpdateUserResponse(fieldErrorList);
            return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(updateResponse);
        }
        else {
            // Update logic...
            return ResponseEntity.status(HttpStatus.OK).build();
        }
    }

}

7.1. 地區選擇

使用 Accept-Language HTTP 標頭來定義客户端的語言偏好是很常見的做法。

我們可以通過在 Spring Boot 中使用 The code snippet you provided is incomplete. It appears to be a placeholder for a large block of code that likely involves a complex algorithm or data structure. Without the rest of the code, it's impossible to determine its specific functionality.

However, the presence of MCP suggests that this code is likely related to Machine Learning, particularly Convolutional Neural Networks (CNNs). The MCP could stand for "Multi-Channel Processing" or "Multi-Channel Pooling," which are common techniques used in CNNs to handle multiple input channels (e.g., RGB images).

Here's a breakdown of why this is a likely interpretation and what the code might be doing:

  • CNNs and Channels: CNNs are designed to process data with multiple channels. For images, these channels typically represent the red, green, and blue color components.
  • Multi-Channel Processing: This refers to the process of applying convolutional filters to each of these channels independently.
  • Multi-Channel Pooling: Pooling layers (like Max Pooling or Average Pooling) are used to reduce the spatial dimensions of the feature maps, making the network more robust to variations in the input.

To help you further, please provide the rest of the code snippet. With the complete code, I can give you a much more accurate explanation of its purpose.

Example (Illustrative - Not the full code):

import tensorflow as tf

# Assume this is part of a CNN
def my_cnn_layer(input_tensor, filters, kernel_size, strides, padding):
  # Perform convolution with the specified filters, kernel size, strides, and padding
  conv = tf.keras.layers.Conv2D(filters=filters, kernel_size=kernel_size, strides=strides, padding=padding)(input_tensor)
  return conv

# Example usage:
# input_tensor = tf.random.normal((1, 28, 28, 1))  # Example input (1 image, 28x28 pixels, 1 channel)
# output_tensor = my_cnn_layer(input_tensor, 32, (3, 3), (1, 1), 'same')

In this example, my_cnn_layer would be a function that performs a 3x3 convolution with 32 filters, using strides of 1, and padding 'same'. The MCP would be related to the processing of these multiple channels.

7.2. 驗證

我們在 updateUser(…) 方法中為 User DTO 添加了 @Valid 註解。這表明當 REST Web 服務被調用時,Java Bean 驗證會校驗該對象。驗證在幕後進行。我們將通過 BindingResult 對象來檢查驗證結果。 當存在任何字段錯誤(通過 bindingResult.hasFieldErrors(), 確定),Spring Boot 會根據當前區域設置為我們獲取本地化的錯誤消息,並將消息封裝到字段錯誤實例中。 我們將迭代 BindingResult 中的每個字段錯誤,並將它們收集到一個響應對象中,然後將響應發送回客户端。

7.3. 響應對象

如果驗證失敗,服務將返回一個 UpdateResponse 對象,其中包含以指定語言表示的驗證錯誤消息:

public class UpdateResponse {

    private List<InputFieldError> fieldErrors;

    // getter and setter
}

<em >InputFieldError</em> 是一個佔位符類,用於存儲包含錯誤信息和錯誤消息的字段:

public class InputFieldError {

    private String field;
    private String message;

    // getter and setter
}

8. 驗證消息類型

讓我們向 REST 服務 user</em/> 發起一個更新請求,並使用以下請求體:

{
    "email": ""
}

提醒您,User 對象必須包含一個非空郵箱。因此,我們預期此請求會觸發驗證錯誤。

8.1. 標準消息

如果請求中未提供任何語言信息,我們將收到以下典型響應,其中包含英文消息:

{
    "fieldErrors": [
        {
            "field": "email",
            "message": "must not be empty"
        }
    ]
}

現在,讓我們使用以下 <em accept-language HTTP 標頭髮起另一個請求:

accept-lanaguage: zh-tw

該服務會解釋我們希望使用中文。它會從相應的資源包中檢索消息。您將看到包含中文驗證消息的以下響應:

{
    "fieldErrors": [
        {
            "field": "email",
            "message": "不得是空的"
        }
    ]
}

這些是 Java Bean Validation 提供的標準驗證消息。我們可以從 Hibernate 驗證器中找到一份詳盡的驗證消息列表,它作為默認驗證實現。

然而,我們之前看到的這些消息看起來不夠友好。我們可能希望將驗證消息修改為提供更清晰的説明。讓我們修改標準消息。

8.2. 覆蓋的消息

我們可以覆蓋 Java Bean 驗證實現中定義的默認消息。 只需要定義一個基名是 ValidationMessages.properties 的屬性文件:

jakarta.validation.constraints.NotEmpty.message=The field cannot be empty

使用相同的基名,我們將為中文創建另一個屬性文件 ValidationMessages_zh.properties

jakarta.validation.constraints.NotEmpty.message=本欄不能留空

再次調用相同的服務時,響應消息將被我們定義的那個消息所替換。

{
    "fieldErrors": [
        {
            "field": "email",
            "message": "The field cannot be empty"
        }
    ]
}

儘管已覆蓋消息,但驗證消息仍然顯得過於通用,消息本身並不能表明哪個字段出錯。我們接下來將包含字段名稱在錯誤消息中。

8.3. 定製化消息

在本場景中,我們將深入探討如何定製驗證消息。我們先前已在 CustomValidationMessages 資源包中定義了所有定製化消息。

然後,我們將使用新的消息 {validation.email.notEmpty} 應用到 User DTO 的驗證註解中: 這裏的花括號表示消息是一個屬性鍵,它與資源包內的相應消息相關聯。

public class User {
	
    @NotEmpty(message = "{validation.email.notEmpty}")
    private String email;

    // getter and setter
}

我們將在向服務發起請求時看到以下消息:

{
    "fieldErrors": [
        {
            "field": "email",
            "message": "Email cannot be empty"
        }
    ]
}

8.4. 間接消息

我們通過在消息中包含字段名,顯著改進了消息。然而,在處理大量字段時,可能會出現挑戰。假設我們有 30 個字段,並且每個字段需要三種不同的驗證類型。這將導致每個本地化資源包內產生 90 條驗證消息。

我們可以利用消息間接操作來解決這個問題。 間接消息基於佔位符,在呈現給用户之前,這些佔位符會被動態替換為實際值。 在我們之前提到的場景中,這種方法將驗證消息數量減少到 33 條,包含 30 個字段名和 3 種唯一驗證消息。

Java Bean Validation 不支持帶有自定義佔位符的驗證消息。但是,我們可以定義包含額外屬性的自定義驗證。

現在,我們將 User 註解為一個新的自定義註解 @FieldNotEmpty。 基於現有的 message 屬性,我們將引入一個新的屬性 field 來指示字段名:

public class User {

    @FieldNotEmpty(message = "{validation.notEmpty}", field = "{field.personalEmail}")
    private String email;

    // getter and setter
}

現在,讓我們定義 @FieldNotEmpty 屬性,包含兩個屬性:

@Documented
@Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE})
@Retention(RUNTIME)
@Constraint(validatedBy = {FieldNotEmptyValidator.class})
public @interface FieldNotEmpty {

    String message() default "{validation.notEmpty}";

    String field() default "Field";

    Class<?>[] groups() default {};

    Class<? extends Payload>[] payload() default {};

}

@FieldNotEmpty 作為一個約束條件,使用 FieldNotEmptyValidator 作為驗證器實現:

public class FieldNotEmptyValidator implements ConstraintValidator<FieldNotEmpty, Object> {

    private String message;
    private String field;

    @Override
    public boolean isValid(Object value, ConstraintValidatorContext context) {
        return (value != null && !value.toString().trim().isEmpty());
    }

}

isValid(…) 方法執行驗證邏輯,並僅確定 value 是否為空。如果 value 為空,則從請求上下文中檢索與當前區域設置對應的 fieldmessage 屬性。 message 屬性會被插值以形成完整的消息。

執行過程中,我們觀察到以下結果:

{
    "fieldErrors": [
        {
            "field": "email",
            "message": "{field.personalEmail} cannot be empty"
        }
    ]
}

message 屬性及其對應的佔位符已成功檢索。但是,我們期望{field.personalEmail} 被實際值替換。

8.5. 自定義 MessageInterpolator

問題在於默認的 MessageInterpolator 僅會翻譯佔位符一次。我們需要重新對消息進行插值,以用本地化消息替換後續的佔位符。在這種情況下,我們需要定義一個自定義的消息插值器來替換默認的插值器:

public class RecursiveLocaleContextMessageInterpolator extends AbstractMessageInterpolator {

    private static final Pattern PATTERN_PLACEHOLDER = Pattern.compile("\\{([^}]+)\\}");

    private final MessageInterpolator interpolator;

    public RecursiveLocaleContextMessageInterpolator(ResourceBundleMessageInterpolator interpolator) {
        this.interpolator = interpolator;
    }

    @Override
    public String interpolate(MessageInterpolator.Context context, Locale locale, String message) {
        int level = 0;
        while (containsPlaceholder(message) && (level++ < 2)) {
            message = this.interpolator.interpolate(message, context, locale);
        }
        return message;
    }

    private boolean containsPlaceholder(String code) {
        Matcher matcher = PATTERN_PLACEHOLDER.matcher(code);
        return matcher.find();
    }

}

RecursiveLocaleContextMessageInterpolator 只是一個裝飾器。它會在檢測到消息包含任何花括號佔位符時,重新應用對 MessageInterpolator 的插值。

我們已完成實現,現在是時候配置 Spring Boot 以包含它。我們將向 MessageConfig 添加兩個提供程序方法:

@Bean
public MessageInterpolator getMessageInterpolator(MessageSource messageSource) {
    MessageSourceResourceBundleLocator resourceBundleLocator = new MessageSourceResourceBundleLocator(messageSource);
    ResourceBundleMessageInterpolator messageInterpolator = new ResourceBundleMessageInterpolator(resourceBundleLocator);
    return new RecursiveLocaleContextMessageInterpolator(messageInterpolator);
}

@Bean
public LocalValidatorFactoryBean getValidator(MessageInterpolator messageInterpolator) {
    LocalValidatorFactoryBean bean = new LocalValidatorFactoryBean();
    bean.setMessageInterpolator(messageInterpolator);
    return bean;
}

getMessageInterpolator(…)方法返回我們自己的實現。該實現封裝了 ResourceBundleMessageInterpolator,它是 Spring Boot 中的默認 MessageInterpolatorgetValidator() 用於註冊驗證器,以便在我們的 Web 服務中使用我們自定義的 MessageInterpolator

現在,我們已經準備就緒,讓我們再次測試一下。我們將會得到包含佔位符被替換為本地化消息的完整插值消息:

{
    "fieldErrors": [
        {
            "field": "email",
            "message": "Personal Email cannot be empty"
        }
    ]
}

9. 結論

在本文中,我們深入探討了在多語言應用程序中交付本地化消息的過程。

我們首先制定了完整實施的關鍵步驟的框架,從使用屬性文件作為消息存儲庫並將其編碼為 UTF-8 開始。 Spring Boot 集成簡化了基於客户端區域設置偏好的消息檢索。 Java Bean 驗證,以及自定義註解和消息插值,允許針對特定語言定製的錯誤響應。

通過將這些技術結合使用,我們能夠為 REST Web 服務提供本地化的驗證響應。

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

發佈 評論

Some HTML is okay.