Command object automatically added to model? - spring

I have a controller method like this:
#RequestMapping("/hello")
public String hello(UserForm user) {
return "hello";
}
It receives some request parameters in the UserForm command object. But I have not written any code to add the object to the Model. Still, in the view hello.jsp, I'm able to access the data, like this:
Hello, ${userForm.name}!
Does it mean that Spring MVC adds command objects to the Model automatically?

You don't need #ModelAttribute just to use a Bean as a parameter.
You'll need to use #ModelAttribute or model.addAttribute() to load default data into your model - for example from a database.

Most of the Spring controllers in the real world accept a lot of different types of parameters - Path variables, URL parameters, request headers, request body and sometimes even the entire HTTP Request object. This provides a flexible mechanism to create APIs. Spring is really good at parsing these parameters in to Java types as long as there is an ObjectMapper (like Jackson) configured to take care of the de-serialization.
The RequestMappingHandlerAdapter makes sure the arguments of the method are resolved from the HttpServletRequest.
Spring model data created prior to (or during) the handler method
execution gets copied to the HttpServletRequest before the next view
is rendered.
By now, Spring has processed the HTTP request and it creates the ModelAndView object from the method’s return value. Also, note that you are not required to return a ModelAndView instance from a controller method. You may return a view name, or a ResponseEntity or a POJO that will be converted to a JSON response etc.
ServletInvocableHandlerMethod invocableMethod
= createInvocableHandlerMethod(handlerMethod);
if (this.argumentResolvers != null) {
invocableMethod.setHandlerMethodArgumentResolvers(
this.argumentResolvers);
}
if (this.returnValueHandlers != null) {
invocableMethod.setHandlerMethodReturnValueHandlers(
this.returnValueHandlers);
}
The returnValueHandlers object is a composite of HandlerMethodReturnValueHandler objects. There are also a lot of different value handlers that can process the result of your method to create ModelAndViewobject expected by the adapter.
Then, it has to render the HTML page that the user will see in the browser. It does that based on the model and the selected view encapsulated in the ModelAndView object.
Now, at this stage, the view gets access to the userForm (as in your example above) from the request scope.

Related

Spring #JsonView how can use request parameter or header

#JsonView
how can use like parameter from request:
#JsonView(header="range")
when response value,read request header range to exclude/include some field
JsonView provides "static" view mapping. so for your dynamic behaviour you can do like this:
// actual request handling is happened here
private ResponseEntity<SomeObject> processRequest();
// request handling with view limit in result
#JsonView(YourDefinedView.class)
#RequestMapping(value = "/request", headers={"range=include"})
public ResponseEntity<SomeObject> processWithView() {
return processRequest();
}
// request handling without view (no headers specified)
#RequestMapping(value = "/request")
public ResponseEntity<SomeObject> processWithoutView() {
return processRequest();
}
this will map your client to same request url, but depending on header it will provide view or not. Than you can create a set of methods, that will be using different #JsonView depending on headers information.
But with this you will limit only the data transfered to client, and the whole data load will happen on server. For example with database and JPA, if you would like not to fetch from database all that data you will end with javax.persistence.NamedEntityGraphs, which will change the general logic of your application - and will at the end of the day produce 2 different methods.
And if you would like to expose custom header with list of fields, to be serialized - custom DTO object, or Map<String, Object> (ugly-ugly-ugly) or custom HandlerMethodReturnValueHandler comes to your help.

Spring controller, why is the returned view ignored?

So, say I have an existing, working page Display Cashier, which displays information about a cashier in a shop. Now, I add a button to this page that looks like:
Manager
The request-mapping for this URL maps it (successfully) to a controller: HandleGetManager
the HandleGetManager controller looks like this:
#Controller
public class HandleGetManager{
private employeeBO employeeBO; //BO handles all business logic
//spring hooks
public HandleGetManager(){}
public void setemployeeBo(employeeBO employeeBO){
this.employeeBO = employeeBO;
}
//get controller
#RequestMapping(method=RequestMethod.GET)
public String getManager(#RequestParam String cashierId){
Long managerId = employeeBO.getManagerByCashierId(cashierId);
String redirectUrl = "/displayManager.ctl?managerId=" + managerId.toString();
return redirectUrl;
}
}
Here's what happens when I try it:
I hit the new button on the Display Cashier page, I expect the following to happen:
The browser sends a get request to the indicated URL
The spring request-mapping ensures that the flow of control is passed to this class.
the #RequestMapping(method=RequestMethod.GET) piece ensures that this method is evoked
The #RequestParam String cashierId instructs Spring to parse the URL and pass the cashierId value into this method as a parameter.
The EmployeeBo has been injected into the controller via spring.
The Business logic takes place, envoking the BO and the managerId var is populated with the correct value.
The method returns the name of a different view, with a new managerId URL arg appended
Now, up until this point, everything goes to plan. What I expect to happen next is:
the browsers is directed to that URL
whereupon it will send a get request to that url,
the whole process will start again in another controller, with a different URL and a different URL arg.
instead what happens is:
this controller returns the name of a different view
The browser is redirected to a half-right, half wrong URL: handleGetManager.ctl?managerId=12345
The URL argument changes, but the name of the controller does not, despite my explicitly returning it
I get an error
What am I doing wrong? Have I missed something?
Assuming you have a UrlBasedViewResolver in your MVC configuration, the String value you return is a View name. The ViewResolver will take that name and try to resolve a View for it.
What you seem to want to do is to have a 301 response with a redirect. With view names, you do that by specifying a redirect: prefix in your view name. It's described in the documentation, here.
Here's a question/answer explaining all the (default) ways you can perform a redirect:
How can I prevent Spring MVC from doing a redirect?

Can a Spring MVC controller return both a HttpServletResponse and a view?

My existing code is like:
String myController(#PathVariable someId, ModelMap map){
....
return "myViewName";
}
Now I want to set a cookie in some cases, so I need to get hold of a HttpServletResponse obj. Can I just add such a response obj to the list of params and operate on it in the controller?
If so, I wonder how my own response is kind of reconciled with the response generated by the JSP that resolves the "myViewName".
Yes.
#RequestMapping
public String myController(#PathVariable someId, ModelMap map, HttpServletResponse response) {
// Do what you need to do on the response, like set a cookie
return "myViewName";
}
Regarding your other question : "how my own response is kind of reconciled with the response generated by the JSP that resolves the "myViewName"."
When you return a view say "myViewName", it will be resolved to a particular resource (JSP View or JSON View or any other view). Once that view resource is obtained depending on what you return, that view does the rendering on to the response. This response object is the same that was passed to the controller function (myController). So say if you set some cookie/headers on the response in the controller function, the response that is being used by the view to do the rendering will also have the same properties.
In case you want to handle the actual rendering/response yourself, you can always get the outputstream of the response and write to it and close the stream. Then the view that you return is just ignored as the dispatcher will check that the response is already handled and will just do post handle stuff.
Hope that clears up for anyone looking for the dispatcher logic behind it.

In Spring 3.2, does RedirectAttributes really pass the attributes themselves? Losing elements

NOTE: Ultimately my goal is simply to change the resulting URL from "/public/academy/register?param=blah" to a customized SEO-ified URL, as shown in the code. If I'm on the wrong path by trying to change from returning a "success view" JSP in the POST mapping to instead using post-redirect-get (which is good practice anyway), I'm open to suggestions.
Below are two methods: the POST request mapping to retrieve a registration form and process it, and the mapping method for the success page. I'm adding a flash attribute to redirect, which holds the form POSTed to the first method.
The form has a property hierarchy of Form -> Schedule -> Course -> Content -> Vendors, where each is its own class object except that Vendors is a SortedSet<Vendor>. When I load the success page, I get a Hibernate exception stating that the Vendors could not be lazily initialized. Why is it so far down the chain that it stops loading, or more basically, why is it losing this property value in the first place? When I set a breakpoint before the return, the RedirectAttributes object has the Vendors populated in the form I passed to it. What gives?
#RequestMapping(value = "/public/academy/register", method = RequestMethod.POST)
public String processSubmit(Site site, Section section, User user,
#ModelAttribute #Valid AcademyRegistrationForm form,
BindingResult result, Model model, RedirectAttributes redirectAttributes) {
validator.validate(form, result);
if (site.isUseStates()
&& StringUtils.isBlank(form.getBooker().getState())) {
result.rejectValue("booker.state",
"gui.page.academy.attendee.state");
}
if (result.hasErrors()) {
LOG.debug("Form has errors: {}", result.getAllErrors());
return "common/academy-registration";
}
// Form is valid when no errors are present. Complete the registration.
AcademyRegistration registration = form.toAcademyRegistration();
academyService.performRegistration(registration, site);
redirectAttributes.addFlashAttribute(form);
String redirectUrl = "redirect:/public/academy/register/"
+ registration.getSchedule().getCourse().getContent().getSeoNavTitle()
+ "-completed";
return redirectUrl;
}
#RequestMapping(value="/public/academy/register/**-completed", method=RequestMethod.GET)
public String displayRegistrationSuccess(#ModelAttribute("academyRegistrationForm") final AcademyRegistrationForm form)
{
SortedSet<Vendor> dummy = form.getSchedule().getCourse().getContent().getVendors();
return "common/academy-registration-success";
}
Here's the exception:
Oct 2, 2013 2:11:31 PM org.apache.catalina.core.ApplicationDispatcher invoke
SEVERE: Servlet.service() for servlet jsp threw exception
org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: com.horn.cms.domain.Content.vendors, could not initialize proxy - no Session
Here's what I assume happens (until you update with the details):
AcademyRegistration registration = form.toAcademyRegistration();
academyService.performRegistration(registration, site);
does some Hibernate queries and retrieves some persisten entities lazily, ie. they haven't been initialized. The loading that did happen probably occurred in some Hibernate Session (do you have a #Transactional somewhere?). The Session is closed and dis-associated from the lazily loaded object.
You then add the form object, which has some nested reference to the lazily loaded entity (it'll be a hibernate proxy), to the RedirectAttributes. This in itself is not a problem because all you're doing is passing a reference.
The request handling completes by sending a 302 response. Your client will then make the new request that is handled by displayRegistrationSuccess() and hits this line
SortedSet<Vendor> dummy = form.getSchedule().getCourse().getContent().getVendors();
Here, the form object is the same as was added in the previous request. One of the objects in this reference chain is your Hibernate proxy that was lazily initialized. Because the object is no longer associated with a Session, Hibernate complains and you get the exception you get.
It's not a good idea to pass around (across request boundaries) objects that depend on persistent state. Instead, you should pass around an ID that you use to retrieve the entity. The alternative is to fully initialize your object inside your academyService method.

Spring MVC can #RequestMapping annotation be used to match url based on httpsession parameters

I need to map #RequestMapping to two different controller methods based on http session attributes.
I see that #RequestMapping matches based on value, method, param and headers.
Is there a way to map based on session attribute?
If I use param, I need to force client to send request param
e.g.: aVersion=1 or aVersion=2.
If there is any example to override #RequestMapping? Can you point me to it.
Yes and no. You can add a HttpSession parameter to a your method in your controller. From the Spring MVC documentation:
Session object (Servlet API): of type HttpSession. An argument of this type enforces the presence of a corresponding session. As a consequence, such an argument is never null.
However, you must do the session attribute check manually afaik:
#RequestMapping("/some_url")
void someMethod(HttpSession session) {
Object firstAttr = session.getAttribute("firstAttribute");
if (firstAttr != null) {
doSomethingWithFirstAttribute(firstAttr);
}
Object secondAttr = session.getAttribute("secondAttribute");
if (secondAttr != null) {
doSomethingWithSecondAttribute(secondAttr);
}
}

Resources