I have a controller that works, but I do not understand why it works. Here it is:
#Controller
#RequestMapping("/someUrl")
public class MyController {
#Autowired
private SomeService someService;
#RequestMapping(method = RequestMethod.GET)
public String page() throws ApplicationException {
return "page";
}
#ModelAttribute("modelAttribute")
public PageModel loadPageModel() throws ApplicationException {
return someService.createPageModel();
}
}
Visiting "/someUrl" will get me to the view of the name "page", as it is expected. What is puzzling, is that despite the model attribute "modelAttribute", or an object of type "PageModel" is not referenced anywhere near the method page, the modelAttribute is still visible for the view. I am happy that this works, but I do not understand why. Any thoughts?
#ModelAttribute is somewhat overloaded, and serves two main functions depending on where it's declared.
With method parameter:
#RequestMapping(value="/create", method=POST)
public String createPage(#ModelAttribute("pageModel") PageModel pageModel) ...
For example, when #ModelAttribute is declared with a method parameter, and you submit parameters to that method, Spring will try to bind the submitted params to an object of type PageModel. Where does it get the PageModel: 1) if there's one currently in Spring managed model, use that 2) otherwise it will create a PageModel using the default constructor of PageModel object
At the method declaration (how you're using it):
#ModelAttribute("modelAttribute")
public PageModel loadPageModel() throws ApplicationException {
return someService.createPageModel();
}
In your case, when#ModelAttribute is declared at the method level, it tells Spring that the method will provide a PageModel before any RequestMapping is called for a particular request. The created PageModel instance is put into Springs managed model under a "modelAttribute" key. Then from your views, you should be able to reference that PageModel.
So the request goes something like this (i'm only showing relevant events):
request for /someUrl
Spring maps request to page() method
Spring see's that page() method is annotated with #RequestMapping so it calls all methods annotated with #ModelAttribute (at the method level) before executing page()
redirect to "page" view, where a PageModel under "modelAttribute" key is available for you to use
Note:
if your loadPageModel() method did not return a PageModel, it would still be called.
if you had multiple methods declared with #ModelAttribute at the method level, they would all be called each time a method with #RequestMapping is executed
As the contract for the ModelAttribute annotation puts it:
Annotation that binds a (..) method return value to a named model attribute, exposed to a web view.
Supported for controller classes with #RequestMapping methods.
In other words you are adding an attribute implicitely by returning it.
Why this works?
I don't know how internally Spring is doing it but what is enough for us to know is how we can make it eligible for Spring MVC to identify and make it available to the view which is well documented:
having #RequestMapping Controller methods (in your case implicitly defined at the class level)
having one of the supported return types that are combined with the ModelAttribute annotation.
of course being within the component-scan area
Have a good look at ModelAttributeMethodProcessor and lookup the handleReturnValue method. This method will be executed on controller methods annotated with #ModelAttribute.
/**
* Add non-null return values to the {#link ModelAndViewContainer}.
*/
#Override
public void handleReturnValue(
Object returnValue, MethodParameter returnType,
ModelAndViewContainer mavContainer, NativeWebRequest webRequest)
throws Exception {
if (returnValue != null) {
String name = ModelFactory.getNameForReturnValue(returnValue, returnType);
mavContainer.addAttribute(name, returnValue);
}
}
You can see it simply adds it's return value to the model and is invoked with every request concerning that particular controller.
P.s. The starting point of most "magic" operations can be found in RequestMappingHandlerAdapter which is the default HandlerAdapter for modern Spring MVC applications.
Related
Background:
I am developing an web application with Spring MVC.
I want to make an aspect that is executed on POST requests and not executed on GET requests, because I want to inject the logic that prevent POST requests which are sent before completion of HTML rendering.
#RequestMapping(value = "/aaa", method = RequestMethod.POST)
public String methodForPost(AnDto dto, Model model) {
// the aspect should be executed on this method
}
#RequestMapping(value = "/bbb", method = RequestMethod.GET)
public String methodForGET(AnDto dto, Model model) {
// the aspect shouldn't be executed on this method
}
Question:
How can I specify method with an parameterized annotation and its value with #Pointcut ?
How can I specify method with an parameterized annotation and its value in <aop:pointcut> in Spring applicationContext.xml?
#Around(value="#annotation(RequestMapping)")
public Object display(ProceedingJoinPoint joinPoint, RequestMapping requestMapping ) throws Throwable {
// You have access to requestMapping. From which you can get whether its get or post and decide to proceed or not.
}
More info http://docs.spring.io/spring/docs/current/spring-framework-reference/html/aop.html#aop-ataspectj-advice-params-passing
You might have to extend this to intercept only for Requestmapping's in your package. Because this intercepts every RequestMappig you might have in your project including one used by the libraries which you might be using, which is a burden.
Let´s say that I have an abstract class A, that class is inside my MainClass, then I have the implementation of this class B and C. Using ModelAndAttribute I render in some scenarios MainClass with B and in others MainClass with C implementation. Both has the same commit method in the controller, so when I request this ModelAndAttribute in the form I´m waiting MainClass with B or C implementation as was render before, but Spring is it´s consider is A all the time "NullValueInNestedPathException, Invalid property, value of nested property is null".
Any idea if Spring has a mechanism to determine which implementation class is receiving?.
I´ve read this http://www.cowtowncoder.com/blog/archives/2010/03/entry_372.html and looks promising but I cannot interact or I dont want interact with Jackson about how serialize the entities.
Regards.
Spring will not decide which implementation to instantiate.
You will have to add some code to decide which of the 2 class needs to be initiated
We have similar requirement
we pass the type ClassB or ClassC in request parameter
We have method
#ModelAttribute (value = "mainClass")
public void populateObjectBasedOnRequestParameter (
ModelMap modelMap, HttpServletRequest request)
this method basically reads the request parameter and instantiate the mainClass with correct implementation of class A
For the form submit
#RequestMapping (value = "/{workItemId}", method = RequestMethod.POST, params = "save", consumes = MediaType.ALL_VALUE)
public String commit(
#ModelAttribute ("mainClass")MainClass mainClass,
BindingResult result, Principal principal,
HttpServletRequest request, final ModelMap model) {
when the form is submitted with request parameter
populateObjectBasedOnRequestParameter() this method is executed so the model map has the initiated implementation for Class A when commit() method is executed
Using spring MVC, I need to store my object into session and I should use the same object in several jsp pages using jstl. I have tried like this:
ModelAndView modelAndView = new ModelAndView("admin/addproduct", "category", categorynameList);
But I can't access the category to other jsp pages except addproduct page.
How can I do that?
Spring MVC provides more than one mechanisms that hide the plain use of HttpSession from you.
Have a look at the #SessionAttributes annotation, which allows you to define the attributes that will be stored in the session by your controller; this mechanism is mainly intended to maintain the conversational state for your handler and that state is usually cleared once the conversation is complete.
Also, you can define your bean as session scoped in the application context and then make use of a ScopedProxyFactoryBean (by simply adding an <aop:scoped-proxy/> element in your bean definition), thus being able to inject that bean in your singleton controllers.
You can pass the session directly to any annotated controller method:
#RequestMapping("somePathName")
public String someHandler(HttpSession session) {
session.setAttribute(...
The annotation #SessionAttributes is used on class level. Typically it's used on the #Controller class. It's 'value' element is of type String[] whose values are the matching names used in #ModelAttribute either on method level or on handler's method parameter level.
Let's consider following arrangement:
#Controller
#SessionAttributes("visitor")
#RequestMapping("/trades")
public class TradeController {
#ModelAttribute("visitor")
public Visitor getVisitor (....) {
return new Visitor(....);
}
....
}
In above code, when a request comes in, the first thing Spring will do is to notice #SessionAttributes('visitor') and then attempt to find the value of 'visitor' in javax.servlet.http.HttpSession. If it doesn't find the value, then the method with #ModelAttribute having the same name 'visitor' (method getVisitor()) will be invoked. The returned value from such method will be used to populate the session with name 'visitor'.
Now that 'visitor' with it's value is already in HttpSession, let's consider following arrangement:
#Controller
#SessionAttributes("visitor")
#RequestMapping("/trades")
public class TradeController {
#RequestMapping("/**")
public String handleRequestById (#ModelAttribute("visitor") Visitor visitor,
Model model,
HttpServletRequest request) {
model.addAttribute("msg", "trades request, serving page " +
request.getRequestURI());
visitor.addPageVisited(request.getRequestURI());
return "traders-page";
}
.......
}
On finding #ModelAttribute("visitor") in the target handler method, Spring will retrieve the value of 'visitor' from the session and will assign the value to the Visitor parameter and will invoke the method. At this step, Spring doesn't care how the session was populated with 'visitor', Whether it was populated using the last arrangement or some other way, it doesn't matter, Spring only requires the annotation #SessionAttributes('visitor'), the handler method parameter with #ModelAttribute("visitor") and the value of 'visitor' in HttpSession. If spring can't find it in the session and last arrangement is not available then following exception will be thrown:
org.springframework.web.HttpSessionRequiredException: Expected session attribute 'visitor'
In other words: when the target handler method is invoked first time in a given session, then method with #ModelAttribute('visitor) is invoked, the returned value is populated in HttpSession and the handler method with the same value is invoked. For the subsequent requests within the same HTTPSession, Spring retrieves the same object from the session and doesn't call the method with #ModelAttribute('visitor') again till the end of the session
Following is the complete controller class:
#Controller
#SessionAttributes("visitor")
#RequestMapping("/trades")
public class TradeController {
#RequestMapping("/**")
public String handleRequestById (#ModelAttribute("visitor") Visitor visitor,
Model model,
HttpServletRequest request) {
model.addAttribute("msg", "trades request, serving page " +
request.getRequestURI());
visitor.addPageVisited(request.getRequestURI());
return "traders-page";
}
#ModelAttribute("visitor")
public Visitor getVisitor (HttpServletRequest request) {
return new Visitor(request.getRemoteAddr());
}
}
In above example we are tracking the user visited pages within the same HTTP session. We are using the wildcard '/**' so that all request starting with 'trades' will map to this controller.
Inject session to your controller classes, store your object there and use it when you need it, e.g.:
#Controller
public class SomeController {
//#Autowired
//private HttpSession session; This is not desired. See the discussion in the
//comments.
#RequestMapping("somePathName")
public String someHandler(HttpSession session) { //Session will be injected by
//Spring without any additional annotations.
//...
session.setAttribute("category", yourObject);
}
}
If you need to access category on other pages than admin/addproduct you will need to add it to respective models as follows:
#RequestMapping("somePathName")
public String someHandler(Model model) {
//...
model.addAttribute("category", yourObject);
//...
return "yourPageName";
}
Update: according to ALex's comment that injecting HttpSession instance into field is strongly not desired due to thread safety concerns I've changed the source respectively.
This is the code example from Spring 3.1 Spring Source Blog: From XML to #Configuration I'm trying to implement in my application (which was done in Spring 2.0 not by me so it's lot of learning).
#FeatureConfiguration
class MvcFeatures {
#Feature
public MvcAnnotationDriven annotationDriven(ConversionService conversionService) {
return new MvcAnnotationDriven().conversionService(conversionService)
.argumentResolvers(new CustomArgumentResolver());
}
// ...
}
However, I can't understand the point of .argumentResolvers(new CustomArgumentResolver()) and their CustomArgumentResolver looks like bellow. What's the point of it?
public class CustomArgumentResolver implements WebArgumentResolver {
#Override
public Object resolveArgument(MethodParameter param, NativeWebRequest request) throws Exception {
RequestAttribute attr = param.getParameterAnnotation(RequestAttribute.class);
if (attr != null) {
return request.getAttribute(attr.value(), WebRequest.SCOPE_REQUEST);
} else {
return WebArgumentResolver.UNRESOLVED;
}
}
}
To add to #GaryF's answer, and to clarify some points, Spring 2.5 introduced annotated controllers, which replaced the old interface-style controllers of Spring 2.0. These new controllers have methods with no fixed parameters - the method declares the parameters that it needs to do its job, and nothing more.
For example, say a controller method needed one thing to do its job - a request parameter that contains the ID of an object from the database. In Spring 2.0, you would need to implement something like AbstractController.handleRequestInternal(), e.g
protected ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response) {
String id = request.getParameter("id");
MyThing obj = getObjById(id);
//... do stuff and return ModelAndView
}
Spring 2.5 made that easier:
#RequestMapping
public ModelAndView handle(String id) {
MyThing obj = getObjById(id);
//... do stuff and return ModelAndView
}
Here, we only declare parameters for the stuff we need.
So far so good, but this is where a custom WebArgumentResolver comes in. Say I want to remove the getObjById from my controller altogether, because maybe I think it clutters up the code, and maybe it's used across many other controller methods. Instead, I want to do this:
#RequestMapping
public ModelAndView handle(MyThing id) {
//... do stuff and return ModelAndView
}
It's even simpler, and has a bare minimum of boilerplate code. A custom WebArgumentResolver can be registered with the app-context which recognises parameters of type MyThing, and knows how to extract the information from the request. Spring invokes that resolver, and passes the result to the controller method.
Custom resolvers aren't commonly used, but can be very handy in the right situation.
The example in your question uses CustomArgumentResolver to resolve the example's custom RequestAttribute class. The resolver pulls out request attributes and binds them to RequestAttribute objects, so that they can be declared as controller method parameters.
WebArgumentResolvers are a way for you to specify how the parameters of MVC-mapped methods should be resolved. If you'd like to use a custom object as a parameter for an MVC-mapped method, Spring tries to figure out how make sense of it in it's own way. Typically this would happen through binding, where some http parameters you submit match up with the fields of the object and Spring matches them up and creates a new object for you.
If you ever have a situation where the submitted parameters don't match up quite so neatly with your method parameters, WebArgumentResolvers are there to fill in the gap: you provide custom logic so Spring doesn't have to figure it out.
In your example, param is one such parameter to be matched up. This piece of custom code first checks if the parameter has an #RequestAttribute annotation. If it does, then the custom code pulls the value from that object and looks it up as an attribute on the http request, returning it. It it does not have that annotation, then the method returns the UNRESOLVED value, which simply indicates that this WebArgumentResolver doesn't know anything about this particular parameter and Spring should try a different method (such as binding).
Imagine a code like this one:
#RequestMapping(value="/users", method=RequestMethod.GET)
public String list(Model model) {
...
}
#InitBinder("user")
public void initBinder(WebDataBinder binder) {
binder.setDisallowedFields("password"); // Don't allow user to override the value
}
#ModelAttribute("user")
public User prepareUser(#RequestParam("username") String username){
...
}
#RequestMapping(value="/user/save", method=RequestMethod.POST)
public String save(#ModelAttribute("user") User user, Model model) {
...
}
I use an init binder to avoid that a field can be binded and I mark a method (prepareUser()) with #ModelAttribute to prepare my User object before it is binded. So when I invoke /user/save initBinder() and prepareUser() are executed.
I have set "user" in both #InitBinder and #ModelAttribute so Spring-MVC could understand that this methods should only be applied before executing a method with #ModelAttribute("user").
The problem is that the method annotated with #ModelAttribute("user") is executed before every mapped method of this controller. For example if I invoke /users prepareUser is executed before list() method. How can I make that this preparer is only executed before save() method having all the methods in the same controller?
Thanks
That's not really what #ModelAttribute is for. If you use it as a method parameter, it puts the annotated parameter into the model (that's fine). If you put it on a method, it's called every time to provide reference data that every method in the controller should have access to.
If you want to take control of building up your User object, you have several options. The two that are most obvious to me are:
Use your InitBinder method to add a new custom editor (a PropertyEditor class) for building User objects,
Use the conversion service in Spring 3 to convert string usernames to User objects.