4

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?

Christian
  • 6,070
  • 11
  • 53
  • 103
  • 1
    `@RestController(""/foo/bar/v1")` does not do what you think it does. It doesn't do anything for the URL. With this it will create a bean and the name of the bean will be `/foo/bar/v1` it has no effect on the request mapping what so ever. Only a `@RequestMapping` on the class level would have that effect. – M. Deinum Mar 24 '17 at 11:51

1 Answers1

1

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

  }
}
Vasu
  • 21,832
  • 11
  • 51
  • 67
  • That's what I thought, but funnily enough, my example above seems to be working happily on live (even though it doesn't pass my new test)... And the test passes if I just specify the path as `@RestController("/foo/bar/v1")`, without mentioning the path in any `@RequestMapping` annotation (neither on method nor on class level) - although I haven't tried to deploy that yet. Your answer is pretty much what I expected (save I wasn't sure if there was a difference between absolute and relative paths in the method level `@RequestMapping`), but it doesn't seem to agree with what I am observing... :( – Christian Mar 24 '17 at 12:47
  • How did you check the 'live' version? Did you decompile the actual .class and check? Surely, there is a gap, I don't think `@RestController("/foo/bar/v1")` maps the controller to any url, rather it just names the controller as explained above. – Vasu Mar 24 '17 at 12:56
  • Admittedly, I only looked at trunk, not tag, but in trunk the code pretty much looks like the example. I joined a new project, and decided to write a test for the existing code before I introduce my change - only I have issues puzzling together what is actually going on, as you can see from my previous comment and the question... :) And the code from tag is deployed. But no, I didn't decompile. Neither did I actually use a REST client to verify there is no bug. Maybe I should. – Christian Mar 24 '17 at 13:24
  • You need to decompile and check – Vasu Mar 24 '17 at 15:25
  • The decompiled version looks exactly as the example above, when it comes to annotations... But I've seen elsewhere that [MockMvc doesn't read out the `web.xml` file (i.e. deployment descriptor)](http://stackoverflow.com/a/20010156/2018047). Apparently, there are [ways](http://stackoverflow.com/a/25390866/2018047) [to make it work](http://stackoverflow.com/a/28194383/2018047) - and knowing the [different parts of the path](https://coderanch.com/t/169141/#818977) helps... ;) I will have to double-check if `@RestController('path')` on its own really does work, though, or if something was cached… – Christian Mar 29 '17 at 10:51