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.
Related
I have the following:
Aspect:
#Aspect
#Component
public class AuthorizeUserAspect {
#Autowired
PermissionService permissionService;
#Around("#annotation(AuthorizeUser)")
public Object authorize(ProceedingJoinPoint joinPoint) throws Throwable {
...
}
}
Interface:
#Target(ElementType.METHOD)
#Retention(RetentionPolicy.RUNTIME)
public #interface AuthorizeUser {
}
Controller:
#GetMapping("/businesses/{businessId}/test")
public ResponseEntity<List<BusinessDto>> getBusinessListAsClient(Principal jwtAuthUser, #PathVariable String businessId) throws Exception {
...
}
I need to pass in the named parameters from the method signature, plus an additional custom permission parameter (READ / WRITE), so something like this:
#AuthorizeUser(Principal jwtAuthUser, String businessId, permission = <Enum or String>)
#GetMapping("/businesses/{businessId}/test")
Is this possible? I don't really care how it's done as long as I can get these values in my authorize function to use them.
I know I can do something like:
joinPoint.getArgs();
to get the parameters by position, but I want this to be more generic, and certain controllers can have different params as 1st and second for example, so that would not work, plus I have no idea
how to get the last value.
Note: The authorize is just an example, but I'd like to do this for other custom annotation parameters as well.
Maybe you should read the Spring AOP manual. How about this?
#Around("#annotation(authorizeUser)")
public Object authorize(ProceedingJoinPoint joinPoint, AuthorizeUser authorizeUser)
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
In the process of developing our app, we found ourselves doing something like this in our controller:
#RequestMapping(value = "foo", method = RequestMethod.POST)
#ResponseBody
public SimpleMasterWidget doStuff(#RequestBody ClientData clientData, ServerData serverData) throws Exception
{
pushClientDataToServerData(clientData, serverData);
//stuff specific to this operation on the serverData
return whatever;
}
The pushClientDataToServerData(clientData, serverData); call gets duplicated in every POST request. Now, I understand that there really shouldn't be any need to push clientData to serverData, but there is legacy code that forces us to do this.
Currently we are implementing the HandlerMethodArgumentResolver interface to resolve the ServerData argument. To remove all the duplicated pushClientDataToServerData calls I want to be able to resolve the ServerData argument with the ClientData already pushed into it.
Now in the argument resolver, I have access to the NativeWebRequest so I could probably get the request body through there and deserialize it and all that. However, I don't want to have to reimplement what Spring is already doing with the #RequestBody annotation.
So my question boils down to this: Is there a way I can call into Spring's stuff in the argument resolver so that I can get the deserialized ClientData object and call pushClientDataToServerData right within the argument resolver? Then all the controller methods would just look like this:
#RequestMapping(value = "foo", method = RequestMethod.POST)
#ResponseBody
public SimpleMasterWidget doStuff(ServerData serverData) throws Exception
{
//stuff specific to this operation on the serverData
return whatever;
}
And the pushClientDataToServerData call would be in only one place.
Thanks in advance.
Looks like a good candidate for Spring AOP. Below is just an example of pointcut, you should improve it to catch all possible methods:
#Component
#Aspect
public class PushClientDataAspect {
#Before(value = "execution(* com.xyz.myapp.dao.*.*(..) && args(clientData, serverData) && #annotation(org.springframework.web.bind.annotation.ResponseBody))")
public void pushClientData(ClientData clientData, ServerData serverData) {
pushClientDataToServerData(clientData, serverData);
}
}
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){
}
As of Spring MVC 3, AbstractCommandController is deprecated so you can no longer specify the command class in setCommandClass(). Instead you hard-code the command class in the parameter list of a request handler. For example,
#RequestMapping(method = RequestMethod.POST)
public void show(HttpServletRequest request, #ModelAttribute("employee") Employee employee)
My problem is that I'm developing a generic page that allows the user to edit a generic bean, so the command class isn't known until the run-time. If the variable beanClass holds the command class, with AbstractCommandController, you would simply do the following,
setCommandClass(beanClass)
Since I can't declare the command object as a method parameter, is there any way to have Spring bind request parameters to a generic bean in the body of the request handler?
Instantiation of the command object is the only place where Spring needs to know a command class. However, you can override it with #ModelAttribute-annotated method:
#RequestMapping(method = RequestMethod.POST)
public void show(HttpServletRequest request,
#ModelAttribute("objectToShow") Object objectToShow)
{
...
}
#ModelAttribute("objectToShow")
public Object createCommandObject() {
return getCommandClass().newInstance();
}
By the way, Spring also works fine with the real generics:
public abstract class GenericController<T> {
#RequestMapping("/edit")
public ModelAndView edit(#ModelAttribute("t") T t) { ... }
}
#Controller #RequestMapping("/foo")
public class FooController extends GenericController<Foo> { ... }