vendredi 20 mars 2015

Passing object from @ControllerAdvice to @Controller in Spring MVC

I want to check if user exists in ControllerAdvice and treat user as @ModelAttribute if user exists. On the other hand, I also want to access user object in @Controller directly. So I add @ModelAttribute annotation on the parameter of @RequestMapping method.


I'm using @ControllerAdvice like:



@ControllerAdvice
public class UserAdvice {

@Autowired
private UserService userService;

@ModelAttribute("user")
public User user(@PathVariable("username") String username) {
User user = userService.findByUsername(username);
if (user != null) {
return user;
}
user = userService.findById(username);
if (user == null) {
throw new ResourceNotFoundException("user not found");
}

return user;
}
}


And UserController Like:



@RestController
@RequestMapping("/users/{username}")
public class UserController {

public static final Logger logger = LoggerFactory.getLogger(UserCourseListController.class);

@Autowired
private CourseService courseService;

@RequestMapping(value = "", method = RequestMethod.GET)
public void getUser(@ModelAttribute("user") User user, Model model) {
logger.info("{}", user);//user is null
logger.info("{}", model.asMap().get("user"));// not null
}

}


But now, the parameter user that annotated with @ModelAttribute is null while there is a "user" obj in Model Map.


Is there any mistakes I've made in this scenario? Or any misunderstanding of the concepts of @ModelAttribute and @ControllerAdvice?


Thanks very much!


Update


From Docs of Springframework:



Once present in the model, the argument’s fields should be populated from all request parameters that have matching names.



So We cannot add @ModelAttribute to method parameters annotated by @RequestMapping directly because Spring will do data binding from request(not Model)。


Finally I found a solution——HandlerMethodArgumentResolver. It can resolve method arguments on each @RequestMapping method and do some work on resolving arguments. An example of Java Config is below:



public class Config extends WebMvcConfigurerAdapter {

@Bean(name = "auditorBean")
public AuditorAware<User> auditorAwareBean() {
return () -> null;
}

@Bean
public HttpMessageConverters customConverters() {
return new HttpMessageConverters(new MappingJackson2HttpMessageConverter());
}

@Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
argumentResolvers.add(new HandlerMethodArgumentResolver() {
@Override
public boolean supportsParameter(MethodParameter parameter) {
return parameter.getParameterType().equals(User.class);
}

@Override
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
return mavContainer.getDefaultModel().get(parameter.getParameterName());
}
});
}

}


We resolve method arguments from model via parameter.getParameterName(). It mean that the name of method argument(user) must be equal to the value of @ModelAttrubute defined in @ControllerAdvice. You can also use any other naming conventions to implement the binding.


Aucun commentaire:

Enregistrer un commentaire