Spring Servlet mapping confusion - java

Basically I have a mapping like /A/B/something whose mapping is given as:
#Controller
#RequestMapping("/B")
public class BController {
...
#RequestMapping(value = "/something", method = RequestMethod.POST)
public ModelAndView func1()....
func1() gets called.
In web.xml, the definition is given only for A.
So its something like:
<servlet>
<servlet-name>A</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<load-on-startup>2</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>A</servlet-name>
<url-pattern>/A/*</url-pattern>
</servlet-mapping>
In applicationcontext.xml, there is component scan for this BController.
<context:component-scan base-package="BController" />
So, my question is:
How does servlet know to move from /A's mapping to /B's mapping. How
is func1() getting called?
If applicationcontext.xml directly takes /B into
consideration, why is func1() not getting called after I delete /A from the call ( If I call /B/something from my application, it
gives an error. )?
Any help is appreciated.
Thanks :)

The spring DispatcherServlet is a central component which dispatches all requests to the registered handlers (the controllers). In your case, it has been mapped to the /A relative path:
<url-pattern>/A/*</url-pattern>
The line above basically tells the web container, that each and every request which starts with /A relative to the host should be handled by the spring DispatcherServlet. From this point further, it's up to Spring to handle the mappings.
#RequestMapping("/B")
The line above adds up to the DispatcherServlet, so to this point it maps to /A/B
#RequestMapping(value = "/something")
Similar to the previous request mapping, the line above adds up to the class level mapping, so the method func1 will finally be called with the following call:
POST <yourhost>/A/B/something
Hope that helps.

Related

How are absolute paths applied when combining #RequestMapping, #RestController, and web.xml, and why does my test fail?

We have a servlet defined in our web.xml:
<servlet>
<servlet-name>foo</servlet-name>
<servlet-class>
org.springframework.web.servlet.DispatcherServlet
</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>foo</servlet-name>
<url-pattern>/foo/*</url-pattern>
</servlet-mapping>
In our controller class, we use both #RequestMapping and #RestController.
package com.example.foo;
#RestController("/foo/bar/v1")
public class Baz {
#RequestMapping(value="/bar/v1/abc" /* ... */)
public String doXyz() {
Now, I know the Spring documentation for RequestMapping says
When used at the type level, all method-level mappings inherit this
primary mapping, narrowing it for a specific handler method.
In other words, when I define a #RequestMapping("/foo") on class level, anything I define on the method level would be appended. For the example above, if the path was defined in a #RequestMapping("/foo/bar/v1") on class level, instead of in the #RestController, I would thus expect /foo/bar/v1/bar/v1/abc - or /foo/bar/v1/abc if the path on the method were relative. Is that understanding of mine correct?
Now, apparently, #RestController("/foo/bar/v1") has the same effect as #RequestMapping("/foo/bar/v1") in terms of request path mapping - is that observation correct, too?
If not, why does above code still work? Is the /foo/ part picked up from the web.xml?
Now, if I leave the code as is, the following test fails:
MvcResult result = mockMvc.perform(
get("/foo/bar/v1")
.accept(MediaType.APPLICATION_JSON_VALUE) //...
My guess is it fails because
it doesn't read the web.xml, hence doesn't know about the /foo prefix
the path in #RequestMapping("bar/v1") on the method level doesn't actually narrow down the one in #restController("/foo/bar/v1") after all.
The error message I get is
javax.servlet.ServletException: No adapter for handler [com.example.foo.Baz#5b800468]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler
This is what the context config looks like that the test loads (besides #WebAppConfiguration):
<context:annotation-config/>
<mvc:annotation-driven/>
<context:component-scan base-package="com.example.foo" />
How do the annotations and web.xml really work together, and what do I need to configure to make the test pass?
In other words, when I define a #RequestMapping("/foo") on class
level, anything I define on the method level would be appended. For
the example above, if the path was defined in a
#RequestMapping("/foo/bar/v1") on class level, instead of in the
#RestController, I would thus expect /foo/bar/v1/bar/v1/abc - or
/foo/bar/v1/abc if the path on the method were relative. Is that
understanding of mine correct?
If you define #RequestMapping(value="/foo/bar/v1") at class level and #RequestMapping(value="/bar/v1/abc") at method level , then, your complete url will become as /foo/bar/v1/bar/v1/abc i.e., both the request mapping strings will be appended simply (i.e., it will not work like /foo/bar/v1/abc)
#RestController("/foo/bar/v1") has the same effect as
#RequestMapping("/foo/bar/v1") in terms of request path mapping - is
that observation correct, too?
No, it is not the same effect, because when you add #RestController("/foo/bar/v1") at the class level, this has nothing to do with the request url mapping, rather here, you are simply naming your controller bean as /foo/bar/v1, this has no effect on request mapping url.
How do the annotations and web.xml really work together, and what do I
need to configure to make the test pass?
servlet-mapping in web.xml only acts as the global url to the DispatcherServlet (Front Controller) and then the DispatcherServlet will delegate the request to the respective Controller methods by evaluating the url against the RequestMapping.
In order to make the test pass with the url /foo/bar/v1, you can map the controller as below:
#RestController
#RequestMapping(value="/foo/")
public class Baz {
#RequestMapping(value="bar/v1", method=RequestMethod.GET)
public String doXyz() {
}
}

Spring mvc servlet url does not map correctly

When I go to the first url, it calls my home() method in the controller but when I go to the second url it does not call my homeTest() method. Why is that?
I get 404 error.
http://localhost:9083/MYAPP/foo ------ first url
http://localhost:9083/MYAPP/foo/bar ------ second url
web.xml
<servlet>
<servlet-name>springServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/spring/appServlet/servlet-context.xml</param-value>
</init-param>
<load-on-startup>2</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>springServlet</servlet-name>
<url-pattern>/foo/*</url-pattern>
</servlet-mapping>
Controller:
#RequestMapping(value="/foo", method = RequestMethod.GET)
public String home(Model model){
return "home";
}
#RequestMapping(value="/foo/bar", method = RequestMethod.GET)
public String homeTest(Model model){
return "home";
}
You need to configure your RequestMappingHandlerMapping.
The official documentation goes into detail about handler mappings and some of their properties. The relevant one here is alwaysUseFullPath:
alwaysUseFullPath If true, Spring uses the full path within the current Servlet context to find an appropriate handler. If false
(the default), the path within the current Servlet mapping is used.
For example, if a Servlet is mapped using /testing/* and the
alwaysUseFullPath property is set to true, /testing/viewPage.html
is used, whereas if the property is set to false, /viewPage.html is
used
In short, when trying to find a mapping for /foo/bar, it removes the part that was matched by the Servlet environment, the /foo, and only uses the /bar to find a handler. You have no handler mapped to /bar.
By setting the property above to true, it will use the full path.
You can configure this in a #Configuration annotated WebMvcConfigurationSupport subclass, by overriding requestMappingHandlerMapping
#Override
public RequestMappingHandlerMapping requestMappingHandlerMapping() {
RequestMappingHandlerMapping handlerMapping = super.requestMappingHandlerMapping();
handlerMapping.setAlwaysUseFullPath(true);
return handlerMapping;
}
Or whatever mechanism is appropriate for your configuration (there's an XML equivalent for example).
There's a special case for the exact match of /foo. It's not particularly relevant here.
Just change:
<url-pattern>/foo/*</url-pattern>
To
<url-pattern>/foo/**</url-pattern>

Trouble with servlet url-pattern matching with wildcards

I am having difficulty getting requests mapped to the correct servlet when the servlet-mapping url-pattern uses a wildcard. I want all requests that begin with "/profile-api" to be mapped to a new REST service I'll be writing soon.
From web.xml:
<!-- default servlet -->
<servlet-mapping>
<servlet-name>professional</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
<!-- profile api -->
<servlet-mapping>
<servlet-name>profile-api</servlet-name>
<url-pattern>/profile-api/*</url-pattern>
</servlet-mapping>
Pseudo-code from the controller that allows a third-party system to update a user's "email communication opt-in status" (OptinResponse is a domain class that will ultimately be transformed to JSON and returned to caller):
#Controller
#RequestMapping("/profile-api")
public class ProfileAPIController {
#RequestMapping(method = RequestMethod.GET, value="/setoptin/{userID}")
public #ResponseBody OptinResponse setOptinStatus(#PathVariable String userID) {
return new OptinResponse("200", "Successfully set optin status for user: " + userID);
}
}
I would expect a request to "{localhost}/profile-api/setoptin/12345" to be correctly routed to the ProfileAPIController, but it is not.
Changing the servlet-mapping url-pattern to be more specific but still generic also fails:
<servlet-mapping>
<servlet-name>profile-api</servlet-name>
<url-pattern>/profile-api/setoptin/*</url-pattern>
</servlet-mapping>
The ONLY way I have been able to get my request routed as intended is to include the full, exact path:
<servlet-mapping>
<servlet-name>profile-api</servlet-name>
<url-pattern>/profile-api/setoptin/12345</url-pattern>
</servlet-mapping>
Obviously, that's unacceptable, as the user id must be variable.
In all cases, the request is instead mapped to the default "professional" servlet. I have tried reordering the servlet-mapping nodes to no avail. I have "alwaysUseFullPath" set to "true" in the AnnotationMethodHandlerAdapter bean in the servlet config (but have tried it as "false", too). I feel as though I'm overlooking something simple, but can't see the forest for the trees.

Spring MVC Can't configure Dispatcher Servlet right

I'm having trouble configuring the Dispatcher for Spring. What I am trying to achieve is:
Build REST WebService to receive requests
Have HTML + Ajax pages consuming the data (Therefore, I don't have Views in my Spring project)
So far I have only 2 HTML pages: Login (using j_security_check) and Main page. Both very simple. I also have a simple controller:
MainController.java
#RestController //Or #Controller and #ResponseBody, no difference, right?
public class MainController {
#RequestMapping("rest/main/data")
public String getData () {
return "{data: \"DATA HUEHUE\"}"; // Yes, I'm brazilian
}
}
And I have tried the following configuration for web.xml and dispatcher-servlet.xml:
web.xml:
<servlet>
<servlet-name>dispatcher</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>dispatcher</servlet-name>
<url-pattern>/rest/*</url-pattern>
</servlet-mapping>
dispatcher-servlet.xml
<context:component-scan base-package="com.example.controller"/>
This doesn't work. I get the message INFO: Mapped URL path [/rest/main/data] onto handler 'mainController' but when I try to access I get No mapping found for HTTP request with URI [/myapp/rest/main/data] in DispatcherServlet with name 'dispatcher'
I also have tried:
On web.xml: <url-pattern>/</url-pattern>
On dispatcher-servlet: The same
What happened: The controller DID work but the application also tried to map my login.html and couldnt find a match so I got 404 ;-;
I'm aware of that "standard" configuration using a prefix and a sufix, but since I dont have views here I dont think that's the right approach.
I'm kinda new at Spring (as you may have noticed), so please be gentle on the answers.
Any ideas?
Thanks in advance :)
My project tree:
-project
--src
---main
----webapp
-----WEB-INF
------web.xml
------weblogic.xml
------dispatcher-servlet.xml
-----www
------main.html
-----login.html
(Login is outside www)
With the first approach if you modify the controller code to have /rest/main/data It will work.
#RestController //Or #Controller and #ResponseBody, no difference, right?
public class MainController {
#RequestMapping("/rest/main/data")
public String getData () {
return "{data: \"DATA HUEHUE\"}"; // Yes, I'm brazilian
}
}
What is happening in happening in the second approach is that since you have Spring Security configured you need to be authenticated first but for that it finds the Login.html and can not find it. This may be because of incorrect configuration.

In a servlet mapping in Spring MVC how do I map the root of a url pattern directory?

<servlet-mapping>
<servlet-name>testServlet</servlet-name>
<url-pattern>/test/*</url-pattern>
</servlet-mapping>
If I hit /test/page the above will work. However, hitting /test or /test/ will not work. I'm using Spring MVC, and my request mapping is as follows:
#RequestMapping(value = {"","/"})
EDIT:
I'm in the process of verifying with an independent project, but this appears to be a bug with Spring's UrlPathHelper. The following method returns an incorrect path when there is both a context and a servlet path, and you hit the servlet without a trailing slash.
public String getPathWithinApplication(HttpServletRequest request) {
String contextPath = getContextPath(request);
String requestUri = getRequestUri(request);
if (StringUtils.startsWithIgnoreCase(requestUri, contextPath)) {
// Normal case: URI contains context path.
String path = requestUri.substring(contextPath.length());
return (StringUtils.hasText(path) ? path : "/");
}
else {
// Special case: rather unusual.
return requestUri;
}
}
Just as an example let's say I have a context of "admin" and the following servlet-mapping:
<servlet-mapping>
<servlet-name>usersServlet</servlet-name>
<url-pattern>/users/*</url-pattern>
</servlet-mapping>
Now I have a request mapping in one of my controllers like this:
#RequestMapping(value = {"","/"})
If I hit /admin/users it will not work. However, if I hit /admin/users/ it will work. Now if I change my request mapping to the following then they will both work:
#RequestMapping(value = {"/users","/"})
However, now the URL /admin/users/users will also work (which is not what I would want).
Yevgeniy is correct, but if your DispatcherServlet is taking over for the default servlet, you have to add this to your web.xml:
<welcome-file-list>
<welcome-file>/</welcome-file>
</welcome-file-list>
my setup usually looks like this:
<servlet-mapping>
<servlet-name>testServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
controller, where i assume you want to handle /test and /test/ equally:
#Controller
public class MyController {
#RequestMapping("/test")
public String test() {
return "redirect:/welcome";
}
#RequestMapping("/test/")
public String test() {
return "redirect:/welcome";
}
#RequestMapping("/welcome")
public void test(ModelMap model) {
// do your stuff
}
}
setup like this would cause DispatcherServlet to handle requests for *.css and *.js files, which is not desired in most cases. i think this is the problem Bhavik describes. For those resources you can you the ResourceController like this:
<mvc:resources mapping="/css/**" location="/resources/css/" />
<mvc:resources mapping="/js/**" location="/resources/js/" />
files from /resources/css and /resources/js will be served without forcing you to write a extra controller.
First of all, the difference between mapping dispatcher servlet to "/" and to "/*".
There is a difference!
When mapping to "/*", all URL requests (including something like this "/WEB-INF/jsp/.../index.jsp") are mapped to dispatcher servlet.
Secondly, when using Spring + Tiles, and returning some JSP in your tiles definition, it is treated as an internal forward request, and handled by the same servlet as the original request.
In my example, I invoke root URL "/", which is properly caught by home() method, and then forwarded to "index.jsp" by Tiles, which is again being handled by Dispatcher Servlet.
Obviously, dispatcher servlet cannot handle "index.jsp", because there is no controller for it.
Yeah, it is ugly, but looks like this is the way it works.
So, the only solution I've found so far: to change "/*" back to "/" in web.xml. This way JSPs are rendered properly by Tomcat's jsp servlet, I guess, and not dispatcher servlet.
Unfortunately, this fix will break the ROOT URL dispatching by Spring, so you need to leave the idea of using ROOT URL + Tiles for now.
Please note that adding explicit servlet mapping ".jsp -> Tomcat jsp in web.xml doesn't help, when using "/*", and it sucks.
Still the problem is not resolved.
Also this is the problem in Spring MVC 3.0
A way without touch the web.xml file is by set the map to the default welcome file path.
#RequestMapping("/index.html")
In my case, every url was working except of the root "/" url.
The problem was that i didn't deleted the index.htm file inside of my projects' webapp root folder.

Categories

Resources