Весенняя проверка, как заставить PropertyEditor генерировать конкретное сообщение об ошибке - PullRequest
13 голосов
/ 28 марта 2009

Я использую Spring для ввода и проверки формы. Команда контроллера формы содержит редактируемую модель. Некоторые атрибуты модели являются пользовательским типом. Например, номер социального страхования лица - это пользовательский тип SSN.

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

и обтекание Person в команде редактирования формы 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 выдает IllegalArgumentException, Spring просто отображает общее сообщение об ошибке, определенное в errors.properties. Что я хочу, это конкретное сообщение об ошибке, которое зависит от точной причины, почему введенный SSN является недействительным. PropertyEditor.setAsText() определит причину. Я пытался встроить текст причины ошибки в текст IllegalArgumentException, но Spring просто воспринимает его как общую ошибку.

Есть ли решение для этого? Повторим, что мне нужно конкретное сообщение об ошибке, сгенерированное PropertyEditor для отображения сообщения об ошибке в форме Spring. Единственная альтернатива, о которой я могу подумать, - это сохранить SSN как текст в команде и выполнить проверку в валидаторе. Преобразование текста в объект SSN будет иметь место в форме onSubmit. Это менее желательно, поскольку моя форма (и модель) имеет много свойств, и мне не нужно создавать и поддерживать команду, в которой каждый атрибут модели представлен в виде текстового поля.

Выше приведен только пример, мой настоящий код не Person / SSN, поэтому нет необходимости отвечать «почему бы не сохранить SSN в виде текста ...»

Ответы [ 5 ]

6 голосов
/ 28 марта 2009

Вы пытаетесь выполнить проверку в связующем. Это не цель связующего. Предполагается, что связыватель связывает параметры запроса с вашим объектом поддержки, не более того. Редактор свойств преобразует строки в объекты и наоборот - он не предназначен для других действий.

Другими словами, вам нужно учитывать разделение интересов - вы пытаетесь включить функциональность в объект, который никогда не предназначался для чего-то большего, чем преобразование строки в объект и наоборот.

Вы можете рассмотреть возможность разбиения вашего объекта SSN на несколько проверяемых полей, которые легко связываются (объекты String, базовые объекты, такие как Dates и т. Д.). Таким образом, вы можете использовать валидатор после привязки, чтобы проверить правильность SSN, или вы можете установить ошибку напрямую. С помощью редактора свойств вы генерируете исключение IllegalArgumentException, Spring преобразует его в ошибку несоответствия типов, потому что это именно то, что есть - строка не соответствует ожидаемому типу. Это все, что есть. Валидатор, с другой стороны, может сделать это. Вы можете использовать тег весеннего связывания для привязки к вложенным полям, если экземпляр SSN заполнен - ​​его сначала нужно инициализировать с помощью new (). Например:

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

Однако, если вы действительно хотите сохранить этот путь, пусть ваш редактор свойств хранит список ошибок - если он выбрасывает исключение IllegalArgumentException, добавьте его в список, а затем выбросьте исключение IllegalArgumentException (при необходимости перехват и повторный выброс) , Поскольку вы можете создать свой редактор свойств в том же потоке, что и привязка, это будет потокобезопасным, если вы просто переопределите поведение редактора свойств по умолчанию - вам нужно найти хук, используемый для связывания, и переопределить его - сделать тот же редактор свойств регистрацию, которую вы выполняете сейчас (за исключением того же метода, чтобы вы могли сохранить ссылку на свой редактор), а затем в конце привязки вы можете регистрировать ошибки, получая список из вашего редактора, если вы предоставляете общедоступный метод доступа , Как только список получен, вы можете обработать его и добавить свои ошибки соответственно.

5 голосов
/ 20 августа 2009

Как сказано:

То, что я хочу, это конкретное сообщение об ошибке, сгенерированное 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!");
    }

}

С уважением,

1 голос
/ 19 января 2016

Как ответ на ответ @ Артура Рональда, вот как я реализовал это:

На контроллере:

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 в этой версии.

0 голосов
/ 23 ноября 2009

Полагаю, вы можете просто попытаться добавить это в свой источник сообщений:

typeMismatch.person.ssn = Неверный формат SSN

0 голосов
/ 28 марта 2009

Это похоже на проблему, возникшую у меня с NumberFormatExceptions, когда значение целочисленного свойства не может быть связано, если, скажем, в форме введена строка. Сообщение об ошибке в форме было общим сообщением для этого исключения.

Решением было добавить свой собственный пакет ресурсов сообщений в контекст моего приложения и добавить собственное сообщение об ошибке для несоответствий типов в этом свойстве. Возможно, вы можете сделать нечто подобное для IllegalArgumentExceptions в определенном поле.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...