Trying to understand the order in which #RequestMapping in Spring MVC works. I have two different controllers as follows
#Controller
public class TestController {
#RequestMapping(value="/test", method=RequestMethod.GET, produces="application/json")
public #ResponseBody RequestResponse test() {
log.info("test processed");
....
}
}
#Controller
#RequestMapping("/user")
public class UserController {
#RequestMapping(value="/test", method=RequestMethod.GET, produces="application/json")
public #ResponseBody RequestResponse userTest() {
log.info("user test processed");
....
}
}
When I make a request www.test.com/user/test, I was expecting UserController.userTest() method to be called, but what I see is that TestController.test() method is being called.
Here's the logging turned on for Spring
DEBUG; tid:http-bio-8080-exec-8; DispatcherServlet; DispatcherServlet with name 'dispatcher' processing GET request for [/test-web/user/test]
DEBUG; tid:http-bio-8080-exec-8; AbstractHandlerMethodMapping; Looking up handler method for path /test
DEBUG; tid:http-bio-8080-exec-8; AbstractHandlerMethodMapping; Returning handler method [public com.test.dto.RequestResponse com.test.controller.TestController.test()]
DEBUG; tid:http-bio-8080-exec-8; AbstractBeanFactory; Returning cached instance of singleton bean 'testController'
Can someone clarify on the order of Type and Method level #RequestMapping order or any documentation about this?
Debugged some more and based on the debugging I can deduce that the way it seems to be working (and this is by no means an answer to my question) is that AbstractHandlerMethodMapping in Spring evaluates the requests from right to left.
For an incoming request /user/test, AbstractHandlerMethodMapping would first lookup any handler method for /test, if found (which in my case it does) it would pass the request to that method - TestController.test(), in this case. If it doesn't find any mapped handler methods then it would look for any handler method for /user/test.
I find that a bit bizzare, but this is what I have observed in the logs. Can anyone substantiate this with some official documentation?
First class-level RequestMapping is checked and then the method-level one. So the behavior you're telling is not possible afaik.
Related
It might be hard to explain why, but I have this situation where I need to get the request url mapping string of currently requested url.
Like if I have a GET URL as "/Test/x/{number}"
I want to get "/Test/x/{number}" not "/Test/x/1"
can I get the actual declared url string in interceptor?
If this is possible how can I achieve this
You can implement a HanderInterceptor to intercept, pre or post, request and introspect the method being called.
public class LoggingMethodInterceptor implements HandlerInterceptor {
Logger log = LoggerFactory.getLogger(LoggingMethodInterceptor.class);
#Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
HandlerMethod method = (HandlerMethod) handler;
GetMapping mapping = method.getMethodAnnotation(GetMapping.class);
log.info("URL is {}", Arrays.toString(mapping.value()));
return true;
}
}
This will output, URL is [/hello/{placeholder}]
Full example can be found here, https://github.com/Flaw101/spring-method-interceptor
You could add more logic to introspect only certain methods, certain types of requests etc. etc.
I think that you can get it with reflection and getting #RequestMapping anotations.
for example when you use
#RequestMapping(value = "/Test/x/{number}", method = RequestMethod.GET)
the value is what you are looking for if I got it right!
You only must find the controller class type.
Its possible I think but I didn't test it.
Check this:
In a Spring-mvc interceptor, how can I access to the handler controller method?
First it may be solved if the HandlerMethod was right but if you get cast error then you must get the controller class [I think].
When you get the controller class then you can looking for the method with according #RequestMapping annotation.
So
1- Find the controller class type
2- Search all methods with in the class by reflection
3- Check method annotations with specified url and specified method [GET / POST]
4- select the best candidate
If you have more than two URL parameter this method is not good!
I need to check if only specific http method is available for some url.
For example, if there is a controller like this
#Controller
public class FooController {
#RequestMapping(value = "bar", method = RequestMethod.GET)
public void bar() {/*do something*/};
...
}
For controller test I use junit(4.10), spring-test(3.2.10) and easymock(3.1).
If I write test like this
#Test
public void testBar() throws Exception {
mockMvc.perform(post("bar").session(session))
.andExpect(/*some application's default error response*/);
}
it will pass (although test calls post-method, not get-method).
So I'm looking for a proper way to make sure, that my rest resources are only avaiable by request methods specified in documentation. Two solutions came to my mind:
write tests with wrong request methods and somehow check resource is not available
add custom exception resolver to process org.springframework.web.HttpRequestMethodNotSupportedException: Request method '__' not supported and return same application default error response, but with https status 405 Method not allowed.
What would you suggest and how to check in controller test request method?
Thanks in advance.
You would need to check the status of all the request methods, you could do it using andExpect with status().isMethodNotAllowed() or status().isNotFound() depends on your needs:
Examples:
get: mockMvc.perform(get("bar").andExpect(status().isNotFound()) or mockMvc.perform(get("bar").andExpect(status().isMethodNotAllowed())
Do the same same for put, delete, ....
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.
Is there existing solution to get mapped URL from (controller-name, action-name) in Spring MVC3, like UrlHelper in asp.net mvc or rails? I think it's very useful!
thx....
Probably, you want something like this:
in your #Controller class, you can add to your "action" method extra parameter of type HttpServletRequest.
Example:
#Controller
public class HelloWorldController {
#RequestMapping("/helloWorld")
public void helloWorld(HttpServletRequest request) {
//do call #getRequestURI(), or #getRequestURL(), or #getPathInfo()
}
}
With default configuration, Spring will "automagically" inject request, and then you can extract path info by calling one of HttpServletRequest#getPathInfo(), HttpServletRequest#getRequestUrl() methods (see explanation here: http://docs.oracle.com/javaee/6/api/javax/servlet/http/HttpServletRequest.html#method_summary)
In Spring 3 MVC, I have a controller that I call SettingsController, and it has methods such as displayUsers() for displaying a list of users, saveUser(), and deleteUser(). SettingsContoller also controls roles and other things.
I'd love to be able to use URL routing such that /settings/users would call displayUsers(), /settings/users/save would call saveUser(), and /settings/users/delete would call deleteUser().
My code is below, and I'm getting the error message that follows the code. What am I doing wrong? Thanks!
#Controller
#RequestMapping("/settings")
public class SettingsController {
#Transactional
#RequestMapping(value = {"/users/save"}, method = {RequestMethod.POST})
public ModelAndView saveUser(details removed){
//details removed
}
#RequestMapping(value = {"/users/delete"}, method = {RequestMethod.POST})
public ModelAndView deleteUser(details removed){
//details removed
}
#RequestMapping(value = {"/users"}, method = RequestMethod.GET)
public ModelAndView settingsUsers(details removed){
//details removed
}
}
Error:
HTTP ERROR: 500
Could not resolve view with name 'settings/users/delete' in servlet with name 'spring'
RequestURI=/das-portal/srv/settings/users/delete
Caused by:
javax.servlet.ServletException: Could not resolve view with name 'settings/users/delete' in servlet with name 'spring'
at org.springframework.web.servlet.DispatcherServlet.render(DispatcherServlet.java:1029)
...
It looks to me like you've set up your controller correctly. As you pointed out, the problem might be in how Spring parses annotations upon start up.
How did you configure Sprint to parse annotations such as #Controller? Do you explicitly set up any sort of HandlerMapping? If you use <context:component-scan>, then it registers a DefaultAnnotationHandlerMapping for you.
The good news is that you can chain multiple handler mapping classes together. The DispatcherServlet will check each one in the order that you specify via the order property of the handler mapping beans (in other words, use the order property to indicate the precedence of your handlers).
So, throw <bean class="org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping"/> into your configuration and set its order property as appropriate.
What about using just one method checking mode?
#RequestMapping(value = "/users/{action}", method = RequestMethod.POST)
public String userAction(#PathVariable String action, ...) {
if (mode.equals("save")) {
//your save code here
}
}