[英]Spring MVC Validation: Set specific error message for each validation check
[英]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.