I want to pass multiple variable parameters (including none) for a #ModelAttribute evaluation from html thymeleaf page to a method:
//provide 0 or X params
#ModelAttribute("url")
public String url(String... params) {
return "generatedurl";
}
The following thymeleaf statements should be possible:
th:href="#{${url()}"
th:href="#{${url('page')}}"
th:href="#{${url('page', 'sort')}}"
But it does not work. Why?
#ModelAttribute is used to bind common objects to the model.
You are returning a String generatedurl every time from your method url() annotated with #ModelAttribute. So in your Thymleaf view every time yo do ${url} you get generatedurl.
One workaround for your problem could be this
#ModelAttribute("url")
public void url(Model model) {
model.addAttribute("url","YOUR_URL");
model.addAttribute("sort","age");
}
As a workaround: add the enclosing class as a model parameter, and call the method on this param from thymeleaf:
#Controller
public class PageController {
#GetMapping
public String persons(Model model) {
model.addAttribute("util", this);
return "persons";
}
public String url(String... params) {
//evaluate...
return "generatedurl";
}
}
It's then possible to access the methods as normal:
th:href="#{${url()}"
th:href="#{${url('page')}}"
th:href="#{${url('page', 'sort')}}"
Only drawback is of course having to add the class to the model explicit.
Another choice would be to initialize the model with the parameter permutations that I need. But that's less flexible:
#ModelAttribute
public void init(Model model) {
model.addAttribute("plainUrl", ...);
model.addAttribute("pageUrl", ...);
model.addAttribute("pageSizeUrl", ...);
}
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".
When modifying a ModelAttribute that is listed as a SessionAttribute, why doesent it keep its new value?
Every time I make a request to the example below, it prints out "Initial value.", which is a correct value for the first request. But after the first request, its value should be "new value".
Why doesent ModelAttribute store its value?
I have a base class. All servlets extending this:
#SessionAttributes(value = {"test_string", "something"})
public abstract class Base<T>
{
public abstract T request(
#ModelAttribute("test_string") String _test_string,
ModelAndView _mv);
#ModelAttribute("test_string")
private String getTest()
{
return "Initial value.";
}
}
I have a specific servlet:
#Controller
public class InterfaceController extends Base<String>
{
#PostMapping(value = "/interface")
#ResponseBody
#Override
public String request(
#ModelAttribute("test_string") String _test_string,
ModelAndView _mv)
{
System.out.println(_test_string);
_test_string = "new value";
return "whatever content";
}
}
I'm no Spring MVC expert but your problem seems to be understanding Java pass-by-reference and String inmutability. I've made a diagram to help you understand what the problem is but you may need to research more info about it.
When you invoke sysout, you are printing the value pointed by "_test_string" (method argument), that at this point is the same that ModelAttribute "test_string".
When you assign "new value" to "_test_string" (method argument), notice that you're NOT changing the value of "test_string" (ModelAttribute)
It's what I think you have to do to overwrite the value stored in the Model.
Is it possible to differenciate those two request URIs in one controller handler :
http://my-uri/
http://my-uri/?with_empty_param
HttpServletRequest object has a ParameterMap object that maps parameter name and its value.
With this map we can check if a parameter was passed in servlet request.
// Check if with_empty_param parameter exists
if (request.getParameterMap().containsKey("with_empty_param ")) {
String with_empty_param = request.getParameter("with_empty_param ");
}
If you want to use Spring way you could do:
#RequestMapping(value = {"/init"}, method = RequestMethod.GET)
public String methodName(
#RequestParam Map<String,String> allParams, ModelMap model) {
if (allParams.containsKey("with_empty_param ")) {
...
}
The #RequestMapping annotation has a params argument which you can use for that.
#RequestMapping
public void method1() {}
And with a check on the param.
#RequestMapping(params={"param"})
public void method2() {}
You can also use the ! to negate the check, so if that param isn't present.
#RequestMapping(params={"!param"})
public void method3() {}
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.
I'm coding a JSP view that has to send multiple objects to a Spring #Controller once submitted.
The controller's handler has the following signature:
public ModelAndView handlerX(#ModelAttribute ModelMap model){
I've tried something like this in my JSP:
<form method="post" action="action">
<spring:bind path="objectX.name">
<input type="text" name="${status.expression}" value="${status.value}" readonly="readonly" />
</spring:bind>
But when the debugger reaches the controller, the model object doesn't contain any of the form's values.
Can anybody please give some advice on how to design the form? I think I can not wrap the two different objects in a Command-type-object, as the controller's handler accepts only a ModelMap. Thank you very much!
You could solve this problem by tailoring your own HandlerMethodArgumentResolver to bind request paramaters sent from your form to your model argument. For example:
public class RequestToModelBindingArgumentResolver implements HandlerMethodArgumentResolver, Ordered {
#Override
public boolean supportsParameter(final MethodParameter parameter) {
return parameter.hasParameterAnnotation(ModelAttribute.class) &&
parameter.getParameterType() == ModelMap.class;
}
#Override
public Object resolveArgument(final MethodParameter parameter, final ModelAndViewContainer mavContainer, final NativeWebRequest webRequest, final WebDataBinderFactory binderFactory) throws Exception {
ModelMap model = mavContainer.getModel();
Map<String, String[]> requestParameters = webRequest.getParameterMap();
// Bind all request parameters to the model
for (String param : requestParameters.keySet()) {
String[] values = requestParameters.get(param);
if (values.length == 1) {
model.addAttribute(param, values[0]);
} else {
model.addAttribute(param, values);
}
}
return model;
}
#Override
public int getOrder() {
return HIGHEST_PRECEDENCE;
}
}
An instance of this class can be added to the argument resolver list your application configuration.
#Override
public void addArgumentResolvers(final List<HandlerMethodArgumentResolver> argumentResolvers) {
argumentResolvers.add(new RequestToModelBindingArgumentResolver());
}
There is a problem though. Arguments annotated with #ModelAttribute will be resolved by ModelAttributeMethodProcessor and since no ordering of HandlerMethodArgumentResolvers takes place it will always be the first to resolve the value. This means that if you would add a custom argument resolver it will never be reached. This means that we need to find a way to sort the collection of resolvers (That is the reason the resolver implements Ordered).
One easy way to sort the resolver collection is to inject the RequestmappingHandlerAdapter into the configuration.
#Autowired
private RequestMappingHandlerAdapter adapter;
Now we need a method to be invoked after the configuration is constructed so we have a chance to sort the collection of resolvers.
#PostConstruct
public void orderArgumentResolvers() {
List<HandlerMethodArgumentResolver> resolvers = new ArrayList<>(adapter.getArgumentResolvers());
Collections.sort(resolvers, new OrderComparator());
adapter.setArgumentResolvers(resolvers);
}
Since adapter.getArgumentResolvers() will return a unmodifiable list we need to jump a little hoop here before the sorting can commence. After sorting, the RequestToModelBindingArgumentResolver instance will be on top of the list and the first to respond to the support() call.
But hey! I think it's much easier to just alter the handlers signature :)
You can create a form having fields matching the form fields and then get that form like this:
#RequestMapping(method= RequestMethod.POST)
public Response add(#RequestBody Form form, HttpServletRequest request){
//The form element's fields must match with fields in your form. Especially the names and types.
}
Apart from these 2 parameters that are passed in add(), you can pass many more