2009-03-27 14 views
13

Używam Spring do wprowadzania formularzy i sprawdzania poprawności. Polecenie kontrolera formularza zawiera model, który jest edytowany. Niektóre atrybuty modelu są niestandardowe. Na przykład osobisty numer ubezpieczenia społecznego to niestandardowy typ SSN.Sprawdzanie poprawności sprężyny, jak wygenerować specyficzny komunikat o błędzie przez narzędzie Property Agent

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

i owijania Osoba w poleceniu forma Wiosna EDIT:

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

Od wiosny nie wie, jak do konwersji tekstu do SSN, zarejestrować edytora klienta ze spoiwem sterownika forma za:

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

i SsnEditor jest po prostu zwyczaj java.beans.PropertyEditor który może konwertować tekst do obiektu 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 
    } 
} 

Jeśli setAsText spotkania tekst jest nieważny i nie może być przekształcony w SSN, a następnie generuje IllegalArgumentException (na PropertyEditorsetAsText jest specyfikacja). Problem mam jest to, że tekst do obiektu konwersji (poprzez PropertyEditor.setAsText()) odbywa przed moja Wiosna walidator jest tzw. Gdy setAsText rzuca IllegalArgumentException, Spring po prostu wyświetla ogólny komunikat o błędzie zdefiniowany w errors.properties. Potrzebuję konkretnego komunikatu o błędzie, który zależy od dokładnego powodu, dla którego wprowadzony numer SSN jest nieprawidłowy. PropertyEditor.setAsText() określałby przyczynę. Próbowałem umieścić tekst przyczyny błędu w tekście IllegalArgumentException, ale Spring traktuje to jako ogólny błąd.

Czy istnieje rozwiązanie tego problemu? Aby powtórzyć, potrzebuję konkretnego komunikatu o błędzie wygenerowanego przez PropertyEditor w celu wypłynięcia na komunikat o błędzie w formularzu Spring. Jedyną alternatywą, o której mogę pomyśleć, to przechowywanie SSN jako tekstu w komendzie i sprawdzenie poprawności w walidatorze. Konwersja tekstu na obiekt SSN będzie miała postać onSubmit. Jest to mniej pożądane, ponieważ moja forma (i model) ma wiele właściwości i nie chcę tworzyć i utrzymywać polecenia, które mają każdy atrybut modelu jako pole tekstowe.

Powyższe to tylko przykład, mój kod nie jest rzeczywista Osoba/SSN, więc nie ma potrzeby, aby odpowiedzieć „dlaczego nie przechowuje SSN jako tekst ...”

+0

@Steve Kuo Dodałem do oryginalnej odpowiedzi –

Odpowiedz

6

Próbujesz zrobić walidację w segregatorze. To nie jest celem segregatora. Segregator powinien łączyć parametry żądania z obiektem, nic więcej. Edytor właściwości konwertuje ciągi znaków do obiektów i na odwrót - nie jest przeznaczony do wykonywania innych czynności.

Innymi słowy, musisz wziąć pod uwagę oddzielenie obaw - próbujesz dopasować funkcjonalność do obiektu, który nigdy nie był przeznaczony do robienia niczego więcej niż przekształcenie ciągu w obiekt i na odwrót.

Można rozważyć podzielenie obiektu SSN na wiele łatwych do sprawdzenia pól, które można łatwo powiązać (obiekty typu String, podstawowe obiekty, takie jak daty itp.). W ten sposób możesz użyć walidatora po powiązaniu, aby sprawdzić, czy SSN jest poprawny, lub możesz ustawić błąd bezpośrednio. Za pomocą edytora właściwości rzucasz wyjątek IllegalArgumentException, Spring konwertuje go na błąd niezgodności typu, ponieważ tak właśnie jest - ciąg nie jest zgodny z oczekiwanym typem. To wszystko, co to jest. Z drugiej strony może to zrobić weryfikator. Możesz użyć tagu wiązania sprężynowego do powiązania z polami zagnieżdżonymi, o ile wystąpienie SSN jest zapełnione - najpierw musi zostać zainicjowane za pomocą new(). Na przykład:

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

Jeśli naprawdę chcesz się utrzymywać na tej drodze mają jednak swoją redaktor własność zachować listę błędów - jeśli jest rzucić IllegalArgumentException, dodać go do listy, a następnie wyrzucić IllegalArgumentException (w razie potrzeby złap i ponownie rzuć). Ponieważ możesz skonstruować edytor właściwości w tym samym wątku co powiązanie, będzie on bezpieczny dla wątków, jeśli po prostu zastąpisz domyślne zachowanie edytora właściwości - musisz znaleźć hak, którego używa do wiązania i nadpisać go - wykonaj ten sam edytor właściwości rejestracja, którą teraz wykonujesz (z wyjątkiem tej samej metody, aby zachować odniesienie do edytora), a następnie po zakończeniu wiązania można zarejestrować błędy, pobierając listę z edytora, jeśli udostępniasz publiczny dostęp . Po pobraniu listy można ją przetworzyć i odpowiednio dodać błędy.

+3

Bałem się tego i jestem trochę rozczarowany obsługą formularza Springa. Z tego, co słyszę, "poprawne" podejście wiosenne polega na przeprowadzeniu walidacji przed związaniem. Oznacza to tworzenie polecenia formularza z polami łańcuchowymi. –

+0

Ciąg dalszy z komentarza wstępnego: Walidator sprawdza poprawność tych pól. Funkcja onSubmit kontrolera dokona konwersji pól łańcuchowych na ich poprawny typ i ustawi wartość w modelu podstawowym. To jest ból, ponieważ teraz muszę ponownie utworzyć każde pole edytowalne jako ciąg znaków w poleceniu formularza. –

+0

Nie, sprawdzasz po wiązaniu - powiązanie nie ma na celu sprawdzania poprawności czegokolwiek - jeśli tego chcesz, inne alternatywne podejście polega na utworzeniu listy błędów w określonym obiekcie i zapisaniu w niej błędów po związaniu i sprawdzacz poprawności sprawdza tę listę ... – MetroidFan2002

0

ten brzmi podobnie do problemu I miał z NumberFormatExceptions, gdy wartość dla właściwości integer nie mogła być związana, jeśli, powiedzmy, String został wprowadzony w formularzu. Komunikat o błędzie w formularzu był ogólną wiadomością dla tego wyjątku.

Rozwiązaniem było dodać własną wiadomość pakiet zasobów do mojego kontekstu aplikacji i dodać własną wiadomość o błędzie na typ niedopasowań na tej nieruchomości. Być może możesz zrobić coś podobnego do IllegalArgumentExceptions na określonym polu.

5

Jak powiedział:

Co chcę jest komunikat specyficzny błąd generowany przez PropertyEditor do powierzchni do komunikatu o błędzie w postaci wiosennego

za kulisami, wykorzystuje Wiosna MVC strategia BindingErrorProcessor do przetwarzania brakujących błędów w polach oraz do tłumaczenia wyjątku PropertyAccessException na FieldError. Więc jeśli chcesz, aby zastąpić domyślny Wiosna strategię MVC BindingErrorProcessor należy podać strategię BindingErrorProcessor według:

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); 
    } 

} 

w celu sprawdzenia, zróbmy następujące

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!"); 
     } 

    }); 
} 

Teraz nasza klasa 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!"); 
    } 

} 

pozdrowienia,

+0

Aby to działało, musiałem mieć BindingResult na sygnaturze metody procesu zamiast BindException: 'public void processPropertyAccessException (PropertyAccessException accessException, BindingResult bindingResult)' – carlosHT

0

wierzę, można po prostu tr y umieścić to w źródle wiadomości:

typeMismatch.person.ssn = Niepoprawny format SSN

1

jako uzupełnienie do odpowiedzi @Arthur Ronalda, to jak skończyło się na wdrożenie tego:

On kontroler:

setBindingErrorProcessor(new CustomBindingErrorProcessor()); 

I wtedy wiązanie klasy procesor błędu:

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); 
     } 

    } 

}   

Tak więc sygnatura metody procesora przyjmuje BindingResult zamiast BindException w tej wersji.