validator starter example custom bean java validation spring

java - starter - spring custom validator



Validación de primavera, cómo hacer que PropertyEditor genere un mensaje de error específico (5)

Como dijo:

Lo que quiero es que el mensaje de error específico generado por el PropertyEditor aparezca en la superficie del mensaje de error en el formulario de Spring

Detrás de escena, Spring MVC usa una estrategia BindingErrorProcessor para procesar errores de campo faltantes y para traducir una PropertyAccessException a FieldError . Por lo tanto, si desea anular la estrategia Spring MVC BindingErrorProcessor predeterminada, debe proporcionar una estrategia BindingErrorProcessor de acuerdo con:

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

Para probar, hagamos lo siguiente

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

Ahora nuestra clase de prueba

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

Saludos,

Estoy usando Spring para ingresar y validar formularios. El comando del controlador de formulario contiene el modelo que se está editando. Algunos de los atributos del modelo son un tipo personalizado. Por ejemplo, el número de la seguridad social de la persona es un tipo de SSN personalizado.

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

y envolviendo el comando de edición Person in a Spring:

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

Como Spring no sabe cómo convertir texto en un SSN, registro un editor de cliente con el archivador del controlador de formulario:

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

y SsnEditor es solo un editor de propiedades personalizado java.beans.PropertyEditor que puede convertir texto en un objeto 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 } }

Si setAsText encuentra texto que no es válido y no se puede convertir a un SSN, arroja IllegalArgumentException (por setAsText PropertyEditor ). El problema que estoy teniendo es que la conversión de texto a objeto (a través de PropertyEditor.setAsText() ) tiene lugar antes de que se llame mi validador de Spring. Cuando setAsText lanza IllegalArgumentException , Spring simplemente muestra el mensaje de error genérico definido en errors.properties . Lo que quiero es un mensaje de error específico que depende de la razón exacta por la cual el SSN ingresado no es válido. PropertyEditor.setAsText() determinará el motivo. Intenté incrustar el texto de la razón de error en el texto de IllegalArgumentException , pero Spring solo lo trata como un error genérico.

¿Hay una solución para esto? Para repetir, lo que quiero es que el mensaje de error específico generado por el PropertyEditor en la superficie del mensaje de error en el formulario de Spring. La única alternativa que puedo pensar es almacenar el SSN como texto en el comando y realizar la validación en el validador. La conversión de objeto de texto a SSN se realizará en el formulario onSubmit . Esto es menos deseable ya que mi formulario (y modelo) tiene muchas propiedades y no quiero tener que crear y mantener un comando que tenga todos y cada uno de los atributos del modelo como un campo de texto.

Lo anterior es solo un ejemplo, mi código actual no es Persona / SSN, por lo que no es necesario responder con "por qué no almacenar el SSN como texto ..."


Como seguimiento de la respuesta de @Arthur Ronald, así es como terminé implementando esto:

En el controlador:

setBindingErrorProcessor(new CustomBindingErrorProcessor());

Y luego la clase de procesador de error de enlace:

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

Por lo tanto, la firma del método del procesador toma un BindingResult en lugar de una BindException en esta versión.


Creo que podrías tratar de poner esto en tu fuente de mensajes:

typeMismatch.person.ssn = Formato SSN incorrecto


Estás tratando de hacer la validación en una carpeta. Ese no es el propósito del encuadernador. Se supone que una carpeta debe vincular los parámetros de solicitud a su objeto de respaldo, nada más. Un editor de propiedades convierte Strings en objetos y viceversa, no está diseñado para hacer nada más.

En otras palabras, debe considerar la separación de las preocupaciones: está tratando de calzar la funcionalidad en un objeto que nunca tuvo la intención de hacer nada más que convertir una cadena en un objeto y viceversa.

Puede considerar dividir su objeto SSN en múltiples campos validados que se pueden enlazar fácilmente (objetos String, objetos básicos como fechas, etc.). De esta forma, puede usar un validador después del enlace para verificar que el SSN sea correcto, o puede establecer un error directamente. Con un editor de propiedades, lanza una IllegalArgumentException, Spring lo convierte en un error de tipo no coincidente porque eso es lo que es: la cadena no coincide con el tipo que se espera. Eso es todo lo que es. Un validador, por otro lado, puede hacer esto. Puede usar la etiqueta de enlace de resorte para enlazar a los campos anidados, siempre que la instancia de SSN esté llena; primero debe inicializarse con new (). Por ejemplo:

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

Sin embargo, si realmente desea persistir en esta ruta, solicite a su editor de propiedades que mantenga una lista de errores; si desea lanzar una IllegalArgumentException, agréguela a la lista y luego eche IllegalArgumentException (catch and rethrow si es necesario). Dado que puede construir su editor de propiedades con el mismo hilo que el enlace, será seguro si reemplaza el comportamiento predeterminado del editor de propiedades; necesita encontrar el enlace que usa para hacer el enlace y anularlo, haga el mismo editor de propiedades. registro que está haciendo ahora (excepto en el mismo método, para que pueda mantener la referencia a su editor) y luego al final de la vinculación, puede registrar errores recuperando la lista de su editor si proporciona un acceso público . Una vez que se recupera la lista, puede procesarla y agregar sus errores en consecuencia.


Esto suena similar a un problema que tuve con NumberFormatExceptions cuando el valor para una propiedad entera no se pudo vincular si, por ejemplo, se ingresó una Cadena en el formulario. El mensaje de error en el formulario era un mensaje genérico para esa excepción.

La solución fue agregar mi propio paquete de recursos de mensaje al contexto de mi aplicación y agregar mi propio mensaje de error para las discrepancias de tipo en esa propiedad. Quizás pueda hacer algo similar para IllegalArgumentExceptions en un campo específico.