Springvalidation,如何让PropertyEditor生成特定的错误消息

我正在使用Spring进行表单输入和validation。 表单控制器的命令包含正在编辑的模型。 某些模型的属性是自定义类型。 例如,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() )发生我的Springvalidation器被调用之前。 当setAsText抛出IllegalArgumentException ,Spring只显示errors.properties定义的一般错误消息。 我想要的是一个特定的错误消息,取决于输入的SSN无效的确切原因。 PropertyEditor.setAsText()将确定原因。 我已经尝试在IllegalArgumentException的文本中嵌入错误原因文本,但Spring只是将其视为一般错误。

有针对这个的解决方法吗? 重复一遍,我想要的是PropertyEditor生成的特定错误消息,以表示Spring表单上的错误消息。 我能想到的唯一选择是将SSN作为文本存储在命令中并在validation器中执行validation。 SSN对象转换的文本将在表单的onSubmit 。 这是不太理想的,因为我的表单(和模型)具有许多属性,我不希望创建和维护一个将每个模型属性作为文本字段的命令。

以上只是一个例子,我的实际代码不是Person / SSN,所以没有必要回复“为什么不将SSN存储为文本……”

您正尝试在活页夹中进行validation。 这不是活页夹的目的。 绑定器应该将请求参数绑定到您的后备对象,仅此而已。 属性编辑器将字符串转换为对象,反之亦然 – 它不是为了做其他事情而设计的。

换句话说,你需要考虑关注点的分离 – 你试图将function强加到一个对象中,除了将一个字符串转换为一个对象,反之亦然。

您可以考虑将SSN对象分解为多个易于绑定的可validation字段(String对象,日期等基本对象)。 这样,您可以在绑定后使用validation程序来validationSSN是否正确,或者您可以直接设置错误。 使用属性编辑器,抛出IllegalArgumentException,Spring将其转换为类型不匹配错误,因为它就是这样 – 字符串与预期的类型不匹配。 就是这样。 另一方面,validation器可以做到这一点。 只要填充了SSN实例,就可以使用spring绑定标记绑定到嵌套字段 – 必须首先使用new()初始化它。 例如:

 ... 

但是,如果你真的想坚持这条路径,那么让你的属性编辑器保留一个错误列表 – 如果要抛出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(), "", 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()); 

然后绑定error handling器类:

 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。

这听起来类似于我在使用NumberFormatExceptions时遇到的问题,如果在表单中输入了String,则无法绑定整数属性的值。 表单上的错误消息是该exception的通用消息。

解决方案是将我自己的消息资源包添加到我的应用程序上下文中,并为该属性上的类型不匹配添加我自己的错误消息。 也许您可以对特定字段上的IllegalArgumentExceptions执行类似的操作。

我相信你可以尝试将它放在你的消息来源中:

typeMismatch.person.ssn =错误的SSN格式