Spring проверка, как заставить PropertyEditor генерировать определенное сообщение об ошибке
Я использую Spring для ввода и проверки формы. Команда контроллера формы содержит модель, которая редактируется. Некоторые атрибуты модели являются настраиваемым. Например, номер социального страхования человека является настраиваемым типом SSN.
public class Person {
public String getName() {...}
public void setName(String name) {...}
public SSN getSocialSecurtyNumber() {...}
public void setSocialSecurtyNumber(SSN ssn) {...}
}
и перенос лица в команду редактирования формы Spring:
public class EditPersonCommand {
public Person getPerson() {...}
public void setPerson(Person person) {...}
}
Так как Spring не знает, как преобразовать текст в SSN, я регистрирую редактор клиента с помощью связующего устройства формы:
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
throws IllegalArgumentException
, Spring просто отображает общее сообщение об ошибке, определенное в errors.properties
. То, что я хочу, является конкретным сообщением об ошибке, которое зависит от точной причины, по которой введенный SSN недействителен. PropertyEditor.setAsText()
будет определять причину. Я попытался встроить текст причины ошибки в текст IllegalArgumentException
, но Spring просто рассматривает его как общую ошибку.
Есть ли решение? Повторяю то, что я хочу, это конкретное сообщение об ошибке, сгенерированное PropertyEditor
для отображения сообщения об ошибке в форме Spring. Единственной альтернативой, о которой я могу думать, является сохранение SSN в виде текста в команде и выполнение проверки в валидаторе. Преобразование текста в объект SSN будет иметь вид onSubmit
. Это менее желательно, так как моя форма (и модель) имеет много свойств, и я не хочу создавать и поддерживать команду, которая имеет атрибут каждой модели как текстовое поле.
Вышеприведенный пример - мой фактический код не является Person/SSN, поэтому нет необходимости отвечать "почему бы не хранить SSN как текст..."
Ответы
Ответ 1
Вы пытаетесь выполнить проверку в связующем. Это не связующее назначение. Связывание должно связывать параметры запроса с вашим фоновым объектом, не более того. Редактор свойств преобразует строки в объекты и наоборот - он не предназначен для чего-либо другого.
Другими словами, вам нужно рассмотреть вопрос о разделении проблем - вы пытаетесь использовать функции shoehorn в объекте, который никогда не должен был делать ничего, кроме преобразования строки в объект и наоборот.
Вы можете рассмотреть возможность разбиения вашего объекта SSN на несколько проверяемых полей, которые легко связаны (объекты String, базовые объекты, такие как Dates и т.д.). Таким образом, вы можете использовать валидатор после привязки, чтобы проверить правильность SSN, или вы можете установить ошибку напрямую. С редактором свойств вы бросаете IllegalArgumentException, Spring преобразует его в ошибку несоответствия типа, потому что это то, что это - строка не соответствует ожидаемому типу. Это все, что есть. С другой стороны, валидатор может это сделать. Вы можете использовать тег связывания Spring для привязки к вложенным полям, если экземпляр SSN заполнен - он должен быть сначала инициализирован с помощью new(). Например:
<spring:bind path="ssn.firstNestedField">...</spring:bind>
Если вы действительно хотите сохранить этот путь, сохраните список ошибок в вашем редакторе свойств, если он должен бросить исключение IllegalArgumentException, добавить его в список и затем выбросить исключение IllegalArgumentException (при необходимости поймать и реконструировать), Поскольку вы можете построить свой редактор свойств в том же потоке, что и привязка, он будет потокобезопасным, если вы просто переопределите поведение по умолчанию редактора свойств - вам нужно найти крючок, который он использует для выполнения привязки, и переопределить его - сделать тот же редактор свойств (за исключением того же метода, чтобы вы могли сохранить ссылку на свой редактор), а затем в конце привязки вы можете регистрировать ошибки, получая список из вашего редактора, если вы предоставляете публичный доступ, После того, как список будет восстановлен, вы можете обработать его и соответственно добавить свои ошибки.
Ответ 2
Как сказано:
То, что я хочу, это сообщение об ошибке, сгенерированное 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!");
}
});
}
Теперь наш тестовый класс
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!");
}
}
С уважением,
Ответ 3
В качестве ответа на вопрос @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 в этой версии.
Ответ 4
Это похоже на проблему, с которой я столкнулся с NumberFormatExceptions, когда значение для свойства integer не могло быть связано, если, скажем, строка была введена в форму. Сообщение об ошибке в форме было общим сообщением для этого исключения.
Решение заключалось в том, чтобы добавить мой собственный пакет ресурсов сообщений в контекст моего приложения и добавить собственное сообщение об ошибке для несоответствий типов для этого свойства. Возможно, вы можете сделать что-то подобное для IllegalArgumentExceptions в определенном поле.
Ответ 5
Я думаю, вы могли бы просто попытаться поместить это в свой источник сообщения:
typeMismatch.person.ssn = Неверный формат SSN