Christian
Christian

Reputation: 6440

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

  1. it doesn't read the web.xml, hence doesn't know about the /foo prefix
  2. 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?

Upvotes: 4

Views: 1988

Answers (1)

Vasu
Vasu

Reputation: 22442

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() { 

  }
}

Upvotes: 1

Related Questions