簡體   English   中英

Spring驗證,如何讓PropertyEditor生成特定的錯誤消息

[英]Spring validation, how to have PropertyEditor generate specific error message

我正在使用Spring進行表單輸入和驗證。 表單控制器的命令包含正在編輯的模型。 某些模型的屬性是自定義類型。 例如,Person的社會安全號碼是自定義SSN類型。

public class Person {
    public String getName() {...}
    public void setName(String name) {...}
    public SSN getSocialSecurtyNumber() {...}
    public void setSocialSecurtyNumber(SSN ssn) {...}
}

並在Spring表單編輯命令中包裝Person:

public class EditPersonCommand {
    public Person getPerson() {...}
    public void setPerson(Person person) {...}
}

由於Spring不知道如何將文本轉換為SSN,因此我使用表單控制器的binder注冊了一個客戶編輯器:

public class EditPersonController extends SimpleFormController {
    protected void initBinder(HttpServletRequest req, ServletRequestDataBinder binder) {
        super.initBinder(req, binder);
        binder.registerCustomEditor(SSN.class, "person.ssn", new SsnEditor());
    }
}

和SsnEditor只是一個自定義java.beans.PropertyEditor ,可以將文本轉換為SSN對象:

public class SsnEditor extends PropertyEditorSupport {
    public String getAsText() {...} // converts SSN to text
    public void setAsText(String str) {
        // converts text to SSN
        // throws IllegalArgumentException for invalid text
    }
}

如果setAsText遇到無效且無法轉換為SSN的文本,則會拋出IllegalArgumentException (根據PropertyEditor setAsText的規范)。 我遇到的問題是文本到對象的轉換(通過PropertyEditor.setAsText() )發生我的Spring驗證器被調用之前。 setAsText拋出IllegalArgumentException ,Spring只顯示errors.properties定義的一般錯誤消息。 我想要的是一個特定的錯誤消息,取決於輸入的SSN無效的確切原因。 PropertyEditor.setAsText()將確定原因。 我已經嘗試在IllegalArgumentException的文本中嵌入錯誤原因文本,但Spring只是將其視為一般錯誤。

這個問題有方法解決嗎? 重復一遍,我想要的是PropertyEditor生成的特定錯誤消息,以表示Spring表單上的錯誤消息。 我能想到的唯一選擇是將SSN作為文本存儲在命令中,並在驗證器中執行驗證。 SSN對象轉換的文本將在表單的onSubmit 這是不太理想的,因為我的表單(和模型)具有許多屬性,我不希望創建和維護一個將每個模型屬性作為文本字段的命令。

以上只是一個例子,我的實際代碼不是Person / SSN,所以沒有必要回復“為什么不將SSN存儲為文本......”

您正嘗試在活頁夾中進行驗證。 這不是活頁夾的目的。 綁定器應該將請求參數綁定到您的后備對象,僅此而已。 屬性編輯器將字符串轉換為對象,反之亦然 - 它不是為了做其他事情而設計的。

換句話說,你需要考慮關注點的分離 - 你試圖將功能強加到一個對象中,而這個對象除了將字符串轉換為對象外,從不打算做任何事情,反之亦然。

您可以考慮將SSN對象分解為多個易於綁定的可驗證字段(String對象,日期等基本對象)。 這樣,您可以在綁定后使用驗證程序來驗證SSN是否正確,或者您可以直接設置錯誤。 使用屬性編輯器,拋出IllegalArgumentException,Spring將其轉換為類型不匹配錯誤,因為它就是這樣 - 字符串與預期的類型不匹配。 就是這樣。 另一方面,驗證器可以做到這一點。 只要填充了SSN實例,就可以使用spring綁定標記綁定到嵌套字段 - 必須首先使用new()初始化它。 例如:

<spring:bind path="ssn.firstNestedField">...</spring:bind>

但是,如果你真的想堅持這條路徑,那么讓你的屬性編輯器保留一個錯誤列表 - 如果要拋出IllegalArgumentException,將它添加到列表然后拋出IllegalArgumentException(如果需要,捕獲並重新拋出)。 因為您可以在與綁定相同的線程中構造屬性編輯器,所以如果您只是覆蓋屬性編輯器的默認行為,它將是線程安全的 - 您需要找到它用來進行綁定的鈎子,並覆蓋它 - 執行相同的屬性編輯器您現在正在進行注冊(除了使用相同的方法,以便您可以保留對編輯器的引用),然后在綁定結束時,如果您提供公共訪問器,則可以通過從編輯器中檢索列表來注冊錯誤。 檢索到列表后,您可以對其進行處理並相應地添加錯誤。

如上所述:

我想要的是PropertyEditor生成特定錯誤消息,表示Spring表單上的錯誤消息

在幕后,Spring MVC使用BindingErrorProcessor策略處理丟失的字段錯誤,並將PropertyAccessException轉換為FieldError 因此,如果要覆蓋默認的Spring MVC BindingErrorProcessor策略,則必須根據以下內容提供BindingErrorProcessor策略:

public class CustomBindingErrorProcessor implements DefaultBindingErrorProcessor {

    public void processMissingFieldError(String missingField, BindException errors) {
        super.processMissingFieldError(missingField, errors);
    }

    public void processPropertyAccessException(PropertyAccessException accessException, BindException errors) {
        if(accessException.getCause() instanceof IllegalArgumentException)
            errors.rejectValue(accessException.getPropertyChangeEvent().getPropertyName(), "<SOME_SPECIFIC_CODE_IF_YOU_WANT>", accessException.getCause().getMessage());
        else
            defaultSpringBindingErrorProcessor.processPropertyAccessException(accessException, errors);
    }

}

為了測試,讓我們做以下幾點

protected void initBinder(HttpServletRequest request, ServletRequestDataBinder binder) {
    binder.registerCustomEditor(SSN.class, new PropertyEditorSupport() {

        public String getAsText() {
            if(getValue() == null)
                return null;

            return ((SSN) getValue()).toString();
        }

        public void setAsText(String value) throws IllegalArgumentException {
            if(StringUtils.isBlank(value))
                return;

            boolean somethingGoesWrong = true;
            if(somethingGoesWrong)
                throw new IllegalArgumentException("Something goes wrong!");
        }

    });
}

現在我們的Test類

public class PersonControllerTest {

    private PersonController personController;
    private MockHttpServletRequest request;

    @BeforeMethod
    public void setUp() {
        personController = new PersonController();
        personController.setCommandName("command");
        personController.setCommandClass(Person.class);
        personController.setBindingErrorProcessor(new CustomBindingErrorProcessor());

        request = new MockHttpServletRequest();
        request.setMethod("POST");
        request.addParameter("ssn", "somethingGoesWrong");
    }

    @Test
    public void done() {
        ModelAndView mav = personController.handleRequest(request, new MockHttpServletResponse());

        BindingResult bindingResult = (BindingResult) mav.getModel().get(BindingResult.MODEL_KEY_PREFIX + "command");

        FieldError fieldError = bindingResult.getFieldError("ssn");

        Assert.assertEquals(fieldError.getMessage(), "Something goes wrong!");
    }

}

問候,

作為@Arthur Ronald回答的后續內容,這就是我最終實現這一點的方式:

在控制器上:

setBindingErrorProcessor(new CustomBindingErrorProcessor());

然后綁定錯誤處理器類:

public class CustomBindingErrorProcessor extends DefaultBindingErrorProcessor {

    public void processPropertyAccessException(PropertyAccessException accessException, 
                                               BindingResult bindingResult) {

        if(accessException.getCause() instanceof IllegalArgumentException){

            String fieldName = accessException.getPropertyChangeEvent().getPropertyName();
            String exceptionError = accessException.getCause().getMessage();

            FieldError fieldError = new FieldError(fieldName,
                                                   "BINDING_ERROR", 
                                                   fieldName + ": " + exceptionError);

            bindingResult.addError(fieldError);
        }else{
            super.processPropertyAccessException(accessException, bindingResult);
        }

    }

}        

因此處理器方法的簽名在此版本上采用BindingResult而不是BindException。

我相信你可以嘗試將它放在你的消息來源中:

typeMismatch.person.ssn =錯誤的SSN格式

這聽起來類似於我在使用NumberFormatExceptions時遇到的問題,如果在表單中輸入了String,則無法綁定整數屬性的值。 表單上的錯誤消息是該異常的通用消息。

解決方案是將我自己的消息資源包添加到我的應用程序上下文中,並為該屬性上的類型不匹配添加我自己的錯誤消息。 也許您可以對特定字段上的IllegalArgumentExceptions執行類似的操作。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM