Question about Spring MVC #ModelAttribute methods, Setting model attributes in a controller #RequestMapping method verses setting attribute individually with #ModelAttribute methods, which one is considered better and is more used?
From design point of view which approach is considered better from the following:
Approach 1
#ModelAttribute("message")
public String addMessage(#PathVariable("userName") String userName, ModelMap model) {
LOGGER.info("addMessage - " + userName);
return "Spring 3 MVC Hello World - " + userName;
}
#RequestMapping(value="/welcome/{userName}", method = RequestMethod.GET)
public String printWelcome(#PathVariable("userName") String userName, ModelMap model) {
LOGGER.info("printWelcome - " + userName);
return "hello";
}
Approach 2
#RequestMapping(value="/welcome/{userName}", method = RequestMethod.GET)
public String printWelcome(#PathVariable("userName") String userName, ModelMap model) {
LOGGER.info("printWelcome - " + userName);
model.addAttribute("message", "Spring 3 MVC Hello World - " + userName);
return "hello";
}
The #ModelAttribute annotation serves two purposes depending on how it is used:
At Method level
Use #ModelAttribute at the method level to provide reference data for the model. #ModelAttribute annotated methods are executed before the chosen #RequestMapping annotated handler method. They effectively pre-populate the implicit model with specific attributes, often loaded from a database. Such an attribute can then already be accessed through #ModelAttribute annotated handler method parameters in the chosen handler method, potentially with binding and validation applied to it.
In other words; a method annotated with #ModelAttribute will populate the specified “key” in the model. This happens BEFORE the #RequestMapping
At Method Parameter level
At Method Parameter level
When you place #ModelAttribute on a method parameter, #ModelAttribute maps a model attribute to the specific, annotated method parameter. This is how the controller gets a reference to the object holding the data entered in the form.
Examples
Method Level
#Controller
public class MyController {
#ModelAttribute("productsList")
public Collection<Product> populateProducts() {
return this.productsService.getProducts();
}
}
So, in the above example, “productsList” in the Model is populated before the the #RequestMapping is performed.
Method parameter level
#Controller
public class MyController {
#RequestMapping(method = RequestMethod.POST)
public String processSubmit(#ModelAttribute("product") Product myProduct, BindingResult result, SessionStatus status) {
new ProductValidator().validate(myProduct, result);
if (result.hasErrors()) {
return "productForm";
}
else {
this.productsService.saveProduct(myProduct);
status.setComplete();
return "productSaved";
}
}
}
Look here for detailed information with examples.
One is not better then the other. They both serve another purpose.
Method: If you need the model for a particular controller to be always populated with certain attributes the method level #ModelAttribute makes more sense.
Parameter: Use it on a parameter when you want to bind data from the request and add it to the model implicitly.
To answer your question on the better approach
I would say approach 2 is better since the data is specific to that handler.
Related
Hello I have question about #ModelAttribute annotation. As i understand, we use #ModelAttribute in method arguments to get data from the model. But it's quite hard to understand clearly when and how its used.
(Code samples are from Spring in Action 5 book)
Why in this case in the code below in public String processOrder() method we do not use #ModelAttribute annotation on #Valid Order order
#Controller
#RequestMapping("/orders")
#SessionAttributes("order")
public class OrderController {
private OrderRepository orderRepo;
public OrderController(OrderRepository orderRepo) {
this.orderRepo = orderRepo;
}
#GetMapping("/current")
public String orderForm(#AuthenticationPrincipal User user,
#ModelAttribute Order order) {
if (order.getDeliveryName() == null) {
order.setDeliveryName(user.getFullname());
}
//following conditions
return "orderForm";
}
#PostMapping
public String processOrder(#Valid Order order, Errors errors, // <<< Here
SessionStatus sessionStatus,
#AuthenticationPrincipal User user) {
if (errors.hasErrors()) {
return "orderForm";
}
order.setUser(user);
orderRepo.save(order);
sessionStatus.setComplete();
return "redirect:/";
}
}
but in this case, DesignTacoController class, #ModelAttribute on a method processDesign() is used on #Valid Taco taco:
#Slf4j
#Controller
#RequestMapping("/design")
public class DesignTacoController {
#PostMapping
public String processDesign(#Valid #ModelAttribute("design") Taco design, // <<< Here
Errors errors, Model model) {
if (errors.hasErrors()) {
return "design";
}
// Save the taco design...
// We'll do this in chapter 3
log.info("Processing design: " + design);
return "redirect:/orders/current";
}
And then in the next chapter author removes #ModelAttribute from processDesign() method from the same DesignTacoController class.
#Controller
#RequestMapping("/design")
#SessionAttributes("order")
#Slf4j
public class DesignTacoController {
#ModelAttribute(name = "order")
public Order order() {
return new Order();
}
#ModelAttribute(name = "design")
public Taco design() {
return new Taco();
}
#PostMapping
public String processDesign(
#Valid Taco taco, Errors errors, // <<< Here
#ModelAttribute Order order) {
log.info(" --- Saving taco");
if (errors.hasErrors()) {
return "design";
}
Taco saved = tacoRepo.save(taco);
order.addDesign(saved);
return "redirect:/orders/current";
}
And in this code snippet(from the code above):
#PostMapping
public String processDesign(
#Valid Taco taco, Errors errors, // <<< Here
#ModelAttribute Order order) {
....
}
quote from book: "The Order parameter is annotated with #ModelAttribute to indicate that its
value should come from the model and that Spring MVC shouldn’t attempt to bind
request parameters to it."
This I don't understand what author meant here, because in all tutorials it is said that when #ModelAttribute is used as a method arguments,it binds request parameters to it. Binds the form data with a POJO bean, model attribute is populated with data from a form submitted.
The documentation is pretty clear on this:
https://docs.spring.io/spring/docs/current/spring-framework-reference/web.html#mvc-ann-methods
#ModelAttribute
For access to an existing attribute in the model (instantiated if not
present) with data binding and validation applied. See #ModelAttribute
as well as Model and DataBinder.
Note that use of #ModelAttribute is optional (for example, to set its
attributes). See “Any other argument” at the end of this table.
.
Any other argument
If a method argument is not matched to any of the earlier values in
this table and it is a simple type (as determined by
BeanUtils#isSimpleProperty, it is a resolved as a #RequestParam.
Otherwise, it is resolved as a #ModelAttribute.
So essentially it is optional. You may wish to use just to make it explicit that that is how the argument is resolved or you may need to use if binding should not happen (by specifying binding = false) See futher: https://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/web/bind/annotation/ModelAttribute.html. It is normally my preference to specify it regardless.
This wasn't clear to me either.
Here we need specify the name if the model attribute.
Because in our view we assume it is named "design" and not "taco".
#PostMapping
public String processDesign(#Valid #ModelAttribute("design") Taco design, Errors errors) {
If we rename the Taco class to Design ...
We don't need to specify the name if the model attribute.
It will be deduced from the simple name of the class.
com.example.Design -> "design"
#PostMapping
public String processDesign(#Valid Design design, Errors errors) {
See the javadoc for ModelAttribute:
The default model attribute name is inferred from the declared
attribute type (i.e. the method parameter type or method return type),
based on the non-qualified class name: e.g. "orderAddress" for class
"mypackage.OrderAddress", or "orderAddressList" for
"List".
What are the main differences between the following Spring Framework classes?
Model
ModelMap
ModelAndView
Using Model.put(String,Object) we can access the values in .jsp files, but ModelMap.addAttribute(String,Object) also did same thing. I do not understand the difference between these classes.
Model is an interface while ModelMap is a class.
ModelAndView is just a container for both a ModelMap and a view object. It allows a controller to return both as a single value.
Differences between Model, ModelMap, and ModelAndView
Model: It is an Interface. It defines a holder for model attributes and primarily designed for adding attributes to the model.
Example:
#RequestMapping(method = RequestMethod.GET)
public String printHello(Model model) {
model.addAttribute("message", "Hello World!!");
return "hello";
}
ModelMap: Implementation of Map for use when building model data for use with UI tools.Supports chained calls and generation of model attribute names.
Example:
#RequestMapping("/helloworld")
public String hello(ModelMap map) {
String helloWorldMessage = "Hello world!";
String welcomeMessage = "Welcome!";
map.addAttribute("helloMessage", helloWorldMessage);
map.addAttribute("welcomeMessage", welcomeMessage);
return "hello";
}
ModelAndView: This class merely holds both to make it possible for a controller to return both model and view in a single return value.
Example:
#RequestMapping("/welcome")
public ModelAndView helloWorld() {
String message = "Hello World!";
return new ModelAndView("welcome", "message", message);
}
Model: is an interface it contains four addAttribute and one merAttribute method.
ModelMap: implements Map interface. It also contains Map method.
ModelAndView: As Bart explain it allows a controller return both as a single value.
i hav a home controller within which i hav 2 methods one is
#RequestMapping(value = "/mypage.te", method = RequestMethod.GET)
public String mypage1(Locale locale, Model model){
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
String name = auth.getName(); //get logged in username
model.addAttribute("username", name);
model.addAttribute("customGroup",grpDao.fetchCustomGroup());
model.addAttribute("serverTime", formattedDate);
model.addAttribute("username", name);
return "mypage";
}
here in this method actually i call grpDao.fetchCustomGroup() method from a Dao class which performs a native query and fetches data and returns and it is saved in customGroup.
now the same fetchcustomGroup() method is to be used in another method i.e
#RequestMapping(value = "/manageGrps.te", method = RequestMethod.GET)
public String man_grp_connections(#RequestParam("id") Integer groupId,#RequestParam("name") String groupName, Model model) {
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
String name = auth.getName();
System.out.println("I am in the fetchCustomGroup controller");
int profileid=grpDao.getProfileId(name);
//model.addAttribute("customGroup",grpDao.fetchCustomGroup());
model.addAttribute("memberList",grpDao.fetchGroupMembers(groupId,profileid));
model.addAttribute("groupid",groupId);
model.addAttribute("profileid",profileid);
model.addAttribute("groupName",groupName);
System.out.println("groupid="+groupId);
System.out.println("groupName="+groupName);
return "manageGrps";
}
so instead of calling the fetchCustomGroup() in both the methods i just want to call in only one method and use the result in both the methods in the home controller.
so how can i use customGroup in another method to use the result of the fetchCustomGroup()
I think that what you want is to avoid executing the query twice. This can be done in different ways. The easiest way would be to assign the response to a variable in your controller and then use a getter instead of the dao. Controllers are singleton by default. Something like :
private Foo customGroup;
private synchronized Foo getCustomGroup() {
if(customGroup == null) {
customGroup = grpDao.fetchCustomGroup();
}
return customGroup;
}
And then use getCustomGroup() instead of grpDao.fetchCustomGroup()
I don't know what you are using for your persistence but using cache would also be a good idea to avoid executing the query twice.
I have a session attribute : user, and I have a url that I want to be viewed by both logged in users and publically by people not logged in as a user.
So what I want to do is this :
#Controller("myController")
#SessionAttributes({"user"})
public class MyController {
#RequestMapping(value = "/MyPage/{id}", method = RequestMethod.GET)
public ModelAndView getPage(#PathVariable Integer id) {
return modelandview1;
}
#RequestMapping(value = "/MyPage/{id}", method = RequestMethod.GET)
public ModelAndView getPage(#PathVariable Integer id, #ModelAttribute User user){
return modelandview2;
}
However, I have a feeling its not going to work ... suggestions very welcome.
You only need the second method, the one that takes the User agument as well. When it's called without request attributes available to populate the User model, you'll just get a User instance with all null (or all default) field values, then in the body of the method you treat each situation accordingly
I don't think it's a right case for #SessionAttributes. This annotation is usually used to keep original instance of a form-backing object, to avoid passing irrelevant parts of its state via hidden form fields.
Your sceanrio is completely different, thus it would be better to use HttpSession explicitly:
#RequestMapping(value = "/MyPage/{id}", method = RequestMethod.GET)
public ModelAndView getPage(#PathVariable Integer id, HttpSession session) {
User user = (User) session.getAttribute(...);
if (user != null) {
...
} else {
...
}
}
Also note that #ModelAttribute is a subject to data binding - user can change its fields by passing request parameters. You definitely don't want it in this case.
I'm running a webapp in Spring Web MVC 3.0 and I have a number of controller methods whose signatures are roughly as follows:
#RequestMapping(value = "/{level1}/{level2}/foo", method = RequestMethod.POST)
public ModelAndView createFoo(#PathVariable long level1,
#PathVariable long level2,
#RequestParam("foo_name") String fooname,
#RequestParam(value = "description", required = false) String description);
I'd like to add some validation - for example, description should be limited to a certain length or fooname should only contain certain characters. If this validation fails, I want to return a message to the user rather than just throw some unchecked exception (which would happen anyway if I let the data percolate down to the DAO layer). I'm aware of JSR303 but have not worked with it and don't quite understand how to apply it in a Spring context.
From what I understand, another option would be to bind the #RequestBody to an entire domain object and add validation constraints there, but currently my code is set up to accept individual parameters as shown above.
What is the most straightforward way to apply validation to input parameters using this approach?
This seems to be possible now (tried with Spring 4.1.2), see https://raymondhlee.wordpress.com/2015/08/29/validating-spring-mvc-request-mapping-method-parameters/
Extract from above page:
Add MethodValidationPostProcessor to Spring #Configuration class:
#Bean
public MethodValidationPostProcessor methodValidationPostProcessor() {
return new MethodValidationPostProcessor();
}
Add #Validated to controller class
Use #Size just before #RequestParam
#RequestMapping("/hi")
public String sayHi(#Size(max = 10, message = "name should at most 10 characters long") #RequestParam("name") String name) {
return "Hi " + name;
}
Handle ConstraintViolationException in an #ExceptionHandler method
There's nothing built in to do that, not yet anyway. With the current release versions you will still need to use the WebDataBinder to bind your parameters onto an object if you want automagic validation. It's worth learning to do if you're using SpringMVC, even if it's not your first choice for this task.
It looks something like this:
public ModelAndView createFoo(#PathVariable long level1,
#PathVariable long level2,
#Valid #ModelAttribute() FooWrapper fooWrapper,
BindingResult errors) {
if (errors.hasErrors() {
//handle errors, can just return if using Spring form:error tags.
}
}
public static class FooWrapper {
#NotNull
#Size(max=32)
private String fooName;
private String description;
//getset
}
If you have Hibernate Validator 4 or later on your classpath and use the default dispatcher setup it should "Just work."
Editing since the comments were getting kind of large:
Any Object that's in your method signature that's not one of the 'expected' ones Spring knows how to inject, such as HttpRequest, ModelMap, etc, will get data bound. This is accomplished for simple cases just by matching the request param names against bean property names and calling setters. The #ModelAttribute there is just a personal style thing, in this case it isn't doing anything. The JSR-303 integration with the #Valid on a method parameter wires in through the WebDataBinder. If you use #RequestBody, you're using an object marshaller based on the content type spring determines for the request body (usually just from the http header.) The dispatcher servlet (AnnotationMethodHandlerAdapter really) doesn't have a way to 'flip the validation switch' for any arbitrary marshaller. It just passes the web request content along to the message converter and gets back a Object. No BindingResult object is generated, so there's nowhere to set the Errors anyway.
You can still just inject your validator into the controller and run it on the object you get, it just doesn't have the magic integration with the #Valid on the request parameter populating the BindingResult for you.
If you have multiple request parameters that need to be validated (with Http GET or POST). You might as well create a custom model class and use #Valid along with #ModelAttribute to validate the parameters. This way you can use Hibernate Validator or javax.validator api to validate the params. It goes something like this:
Request Method:
#RequestMapping(value="/doSomething", method=RequestMethod.GET)
public Model dosomething(#Valid #ModelAttribute ModelRequest modelRequest, BindingResult result, Model model) {
if (result.hasErrors()) {
throw new SomeException("invalid request params");
}
//to access the request params
modelRequest.getFirstParam();
modelRequest.getSecondParam();
...
}
ModelRequest class:
class ModelRequest {
#NotNull
private String firstParam;
#Size(min = 1, max = 10, message = "You messed up!")
private String secondParam;
//Setters and getters
public void setFirstParam (String firstParam) {
this.firstParam = firstParam;
}
public String getFirstParam() {
return firstParam;
}
...
}
Hope that helps.