I would like to specify the implementing type of BindingResult within a controller. Is there a way to do that? At the moment, it appears that Spring must determine the implementing type itself (which happens to be a BeanPropertyBindingResult). I suspect there is either a configuration that I'm missing somewhere, or I just need to specify the actual type in the Controller's method signature.
Example:
/**
* {#inheritDoc}
*/
#Override
public ModelAndView continue(#ModelAttribute("model") #Valid final T model, final BindingResult results) { ... }
You don't need a custom BindingResult for what you want to do. You need to implement a BindingErrorProcessor and use it in the WebDataBinder. It is responsible for adding the errors to the BindingResult and will allow you to use your custom Errors implementation. Here is how you would use it in your controller...
#Controller
public class MyFormController {
...
#InitBinder
public void initBinder(WebDataBinder binder) {
binder.setBindingErrorProcessor(new MyCustomBindingErrorProcessor());
}
}
Mixing binding and validation is bad practice, classes should have a single responsibility. You should not be doing validation in the BindingResult itself.
You have to add ModelAndView in front of #ModelAttribute like:
public ModelAndView continue(ModelAndView model, #ModelAttribute("model") ObjectType objectName){
}
Related
I have a Spring MVC application where most of the controllers receive the HTTP parameters encapsulated in DTOs, nothing special:
#RequestMapping(...)
public String handleUpdate(#Valid MyDto myDto, BindingResult bindingResult){...}
So the input Http parameters must have values that can be converted to the fields of my DTOs, when this doesn't happen and an binding exception is thrown Spring adds an Error with the exception message to the binding result. For example if myDto has an enum field but the Https param for that field has an invalid value.
The problem is these error messages get back to the UI and they can reveal information about the technologies, frameworks, libraries we use to a possible attacker. I need to hide these technical details behind a generic error message, say "invalid input".
I can add a BindingErrorProcessors to each controller's data binder like:
#InitBinder
public void initBinder(WebDataBinder binder) {
binder.setBindingErrorProcessor(new MyBindingErrorProcessor());
}
but of course I don't what to do this for each controller class.
Question How can I set the BindingErrorProcessor for all my controllers? I think #ControllerAdvice only works for unhandled exceptions, maybe there's something similar?
Just for others looking this up, #ControllerAdvice accepts #InitBinder methods:
#ControllerAdvice
public class MyAdvice {
#InitBinder
public void initBinder(WebDataBinder binder) {
binder.setBindingErrorProcessor(new BindingErrorProcessor() {
#Override
public void processMissingFieldError(String missingField, BindingResult bindingResult) {
log.info("missing field " + missingField);
}
#Override
public void processPropertyAccessException(PropertyAccessException ex, BindingResult bindingResult) {
bindingResult.rejectValue(ex.getPropertyName(), "label.input.error", "label.general.error");
log.info("property access exc. " + ex);
}
}
}
Method processPropertyAccessException will be called for conversion exceptions without adding any new Error to the bindingResult. You will have to add the error yourself.
I know you can get a username easily in a Spring controller by including Principal as a method argument like:
#GetMapping("/username")
#ResponseBody
public String currentUserName(Principal principal) {
return principal.getName();
}
But I am ultimately going to want access to members of a MyCustomUser class that I instantiate from a repository with a findBy method. I can put a helper method in the Controller to do the lookup and return the user based on principal.getName(), but can I go a step further and bind to MyCustomUser directly, like
#GetMapping("/stuff")
#ResponseBody
public String stuff(MyCustomUser user) {
return user.thing();
}
I was looking into creating a converter like (Ref):
#Component
public class PrincipalToMyCustomUserConverter implements Converter<Principal, MyCustomUser> {
private MyCustomUserRepository userRepository;
public PrincipalToApplicationUserConverter(MyCustomUserRepository userRepository) {
this.userRepository = userRepository;
}
#Override
public MyCustomUser convert(Principal source) {
return this.userRepository.findByUsername(source.getName());
}
}
But I don't know if that's an appropriate way to grab the repository, and I don't know how to pass the repository when registering the converter (Ref).
You're correct in that the converter you're proposing is not appropriate. Your converter can convert from an object of type Principal to an object of type MyCustomUser, however, there is no *Principal* by which to convert. The magic behind the principal injection is that Spring actually gets this from the SecurityContextHolder, it is not deserialized from request...though fields present in the request allow Spring to create the Principal. If you truly want to inject MyCustomUser, use a ModelAttribute. ModelAttributes are available to all of your Spring controller methods.
I generally like to keep stuff like this in it's own class, so I would define a class that held this and other #ControllerAdvice in one place, something like this:
#ControllerAdvice
public class SomeControllerAdvice {
#Autowired
private MyCustomUserRepository myCustomUserRepository;
#ModelAttribute
public MyCustomUser getUser(Principal principal) {
return myCustomUserRepository.findByUsername(principal.getName());
}
}
The above should suffice to make MyCustomUser available to all methods. I would note that you probably want a little error handling here, like skip over if principal is null and whatnot, also have your findByUsername method return an Optional so your can address empty returns.
see:
https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/bind/annotation/ModelAttribute.html
https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/bind/annotation/ControllerAdvice.html
I have code that looks like this:
Controller
public String doStuff(#Valid #RequestBody MyClass obj, BindingResult result) {
...
}
Validation
#InitBinder
public void initBinder(WebDataBinder binder, HttpServletRequest request) {
binder.setValidator(new MyValidator());
}
The problem is that I don't know how to "hook up" the obj param with the specific initBinder method I need to run to automagically validate obj with my custom Validator class. I need some way to have a specific #InitBinder method run after the obj param is bound to the request body.
Also, I have to use #RequestBody since the controller method is taking in a JSON payload in the request body.
I think it should be sufficient to have the #InitBinder method in the same Controller class as your doStuff() method.
Alternatively, you can also create a custom annotation for MyValidator and apply it on the class level in MyClass instead of using #InitBinder. This also has the added benefit of being initiated and run on any method call where one of the arguments is #Valid MyClass obj.
If you only have a single parameter type, it is sufficient to have the InitBinder in the same controller class as rorschach mentioned.
Otherwise, you can specify the class name of the parameter (starting with lowercase letter) you want to bind to:
#InitBinder("myClass")
public void initBinder(WebDataBinder binder, HttpServletRequest request) {
binder.setValidator(new MyValidator());
}
public String doStuff(#Valid #RequestBody MyClass obj, BindingResult result) {
...
}
#InitBinder("myClass2")
public void initBinder2(WebDataBinder binder, HttpServletRequest request) {
binder.setValidator(new MyValidator2());
}
public String doStuff2(#Valid #RequestBody MyClass2 obj, BindingResult result) {
...
}
I'm not sure where exactly in the documentation it explains this, but the javadoc for InitBinder does say the following:
Specifying model attribute names or request parameter names here
restricts the init-binder method to those specific
attributes/parameters
So I guess by default the parameter name is the name of the class starting with a lowercase letter.
I've been building Spring MVC (3.2/4.0) controllers with validators for my web application along the lines of what I found in the Spring Petclinic sample application. However, in the example application, validators are created within the relevant controlllers using the new keyword, creating a tight coupling. Now that I'm writing tests to cover this code it is proving difficult to isolate these classes due to this coupling.
Is there a recommended way to decouple validators from controllers? Is there some other solution to this problem?
Here's an example from the Petclinic application of the tight coupling I mean:
#RequestMapping(value = "/owners/{ownerId}/pets/new", method = RequestMethod.POST)
public String processCreationForm(#ModelAttribute("pet") Pet pet, BindingResult result, SessionStatus status) {
new PetValidator().validate(pet, result);
if (result.hasErrors()) {
return "pets/createOrUpdatePetForm";
} else {
this.clinicService.savePet(pet);
status.setComplete();
return "redirect:/owners/{ownerId}";
}
}
Define Petvalidator as a bean in your application-context and make the following changes to your controller
#RequestMapping(value = "/owners/{ownerId}/pets/new", method = RequestMethod.POST)
public String processCreationForm(#ModelAttribute("pet") Pet pet, BindingResult result, SessionStatus status) {
PetValidator petValidator; //change this line
petValidator.validate(pet,result); //change this line
if (result.hasErrors()) {
return "pets/createOrUpdatePetForm";
} else {
this.clinicService.savePet(pet);
status.setComplete();
return "redirect:/owners/{ownerId}";
}
}
And you can use property injection to inject the appropriate Petvalidator to your controller. If ur using component-scanning add the following to autowire the registered bean into your controller.
#Autowired
PetValidator petValidator;
Inside your controller istead of PetValidator petvalidator;
That's what #Validis for:
public String processCreationForm(#ModelAttribute("pet") #Valid Pet pet, BindingResult result, SessionStatus status) {
if (result.hasErrors()) {
There's no need to do the validation yourself. Let Spring handle it automatically.
If your PetValidator is of type org.springframework.validation.Validator you could bind it using WebDataBinder.setValidator().
In your controller or #ControllerAdvice add a method annotated with #InitBinder.
#InitBinder
public void initBinder(WebDataBinder binder) {
// Add the validator. Could be an auto wired instance as well.
binder.setValidator(new PetValidator());
}
Now all objects of a type supported by your validator will be validated automatically.
I have to implement validations for a web app that uses Spring MVC 3. The problem is that the bean class has methods like getProperty("name") and setProperty("name",valueObj). The validations have to be done on the data that is returned by passing different values to getProperty("name") , for eg: getProperty("age") should be greater than 16 and getProperty("state") should be required.
I would like to know if there is any support for validation this kind of Bean and if not, what can be the work around.
Thanks,
Atif
I don't think so. Bean validation is performed on javabeans, i.e. class fields with getters and setters. Even if you can register a custom validator, and make validation work, binding won't work. You would need to also register a custom binder that populates your object. It becomes rather complicated. So stick to the javabeans convention.
It sounds like you want to a custom validation class which implements org.springframework.validation.Validator.
#Component
public class MyValidator implements Validator {
#Override
public boolean supports(Class<?> clazz) {
return MyBean.class.isAssignableFrom(clazz);
}
#Override
public void validate(Object target, Errors errors) {
MyBean myBean = (MyBean) target;
if (StringUtils.isBlank(myBean.getProperty("state"))) {
errors.rejectValue("state", "blank");
}
}
}
In your controller you would do manual validaton like follows:
#Autowired
private MyValidator myValidator;
#RequestMapping(value = "save", method = RequestMethod.POST)
public String save(#ModelAttribute("myBean") MyBean myBean, BindingResult result) {
myValidator.validate(myBean, result);
if (result.hasErrors()) {
...
}
...
}