11

I have 2 spring controller mappings:

@Controller
public class ContentController {

    @RequestMapping(value = "**/{content}.html")
    public String content(@PathVariable String content, Model model, HttpServletRequest request) {
    }
}

@Controller
public class HomeController {

    @RequestMapping(value = "**/home")
    public String home(HttpServletRequest request, Model model) {
    }
}

The following url matches both mappings: /home.html

However, I want to ensure that the 'content' mapping ALWAYS has priority over the 'home' mapping. Is there a way I can specify that?

JasonStoltz
  • 3,040
  • 4
  • 27
  • 37
  • 1
    Are those two methods in the same .java file? – sp00m Jan 21 '13 at 14:45
  • Seperate files. Updated question to reflect that. – JasonStoltz Jan 21 '13 at 14:47
  • is there a reason you're using such a greedy path for your mapping? – dardo Jan 21 '13 at 15:00
  • Maybe you should use a filter instead, you will be sure that the filter will be called prior to controllers. – Benoit Wickramarachi Jan 21 '13 at 15:27
  • @dardo That's how we currently do our mappings, so I want to maintain the current url structures, just with in a Spring MVC envrionent ... i don't want to introduce any new path elements and break our pathing. – JasonStoltz Jan 21 '13 at 16:49
  • @BenoitWickramarachi Are you suggesting a ServletFilter or a Spring Interceptor? I could probably use a ServletFilter to inject a url param and forward (/home.html --> /content/home.html) and modify content mapping. Not sure how it would work with a Spring Interceptor. – JasonStoltz Jan 21 '13 at 16:54
  • It looks like by default, Spring will prefer the exact path match. So I'd be trying to override some default Spring behavior: http://stackoverflow.com/questions/2326912/ordered-requestmapping-in-spring-mvc – JasonStoltz Jan 21 '13 at 17:36
  • Yes, I was thinking of a Servlet Filter (but an Interceptor could work as well to redirect the request http://static.springsource.org/spring/docs/3.0.x/spring-framework-reference/html/mvc.html#mvc-handlermapping-interceptor) – Benoit Wickramarachi Jan 21 '13 at 17:53

4 Answers4

7

I had very similar problem recently where I had to two kind of ambiguous URLs:

  • One was accepting a year, say /2012, /2013 etc. I created it using mapping with regular expression like this: @RequestMapping("/{year:(?:19|20)\\d{2}}")
  • Another one was accepting a content identifier (aka slug). I created it using mapping like @RequestMapping("/{slug}").

It was important that the "year" controller method was taking precedence over the "slug" one. Unfortunately (for me) Spring was always using the "slug" controller method.

As Spring MVC prefers more specific mappings, I had to make my "slug" pattern less specific. Based on Path Pattern Comparison documentation, I added wild card to the slug mapping: @RequestMapping("/{slug}**")

My controllers look like that and now listByYear is called when a year (/2012, /1998 etc) is in URL.

@Controller
public class ContentController
{
    @RequestMapping(value = "/{slug}**")
    public String content(@PathVariable("slug") final String slug)
    {
        return "content";
    }
}

and

@Controller
public class IndexController
{
    @RequestMapping("/{year:(?:19|20)\\d{2}}")
    public String listByYear()
    {
        return "list";
    }
}

This is not exactly how to set up a priority (which in my opinion would be an amazing feature) but give some sort of "nice" workaround and might be handy in the future.

Tom
  • 26,212
  • 21
  • 100
  • 111
1

URL mapping is determined by the order the mappings are discovered.

So you could force one controller to be created in spring context after the other controller.

This can be done using the depends-on="" attribute in the bean definition (in xml).

I am not sure if it uses the first mapping it finds, or the last.

Also, this is just theory, I have not actually tried this.

I see log messages like this:

17:29:01.618 [main] INF S o.s.w.s.h.SimpleUrlHandlerMapping - Mapped URL path [/resources/**] onto handler    'org.springframework.web.servlet.resource.ResourceHttpRequestHandler#0'
17:29:01.625 [main] INF S o.s.w.s.h.SimpleUrlHandlerMapping - Mapped URL path [/**] onto handler 'org.springframework.web.servlet.resource.DefaultServletHttpRequestHandler#0'

So worth having a lok at SimpleUrlHandlerMapping to see how it works.

Solubris
  • 3,603
  • 2
  • 22
  • 37
  • I tried this and it didn't appear to work. I'm guessing that has to do with specificity of the mappings though ... meaning, /home is considered more specific than {content}.html, so it is evaluated first. A hunch anyway edit: http://stackoverflow.com/questions/2326912/ordered-requestmapping-in-spring-mvc – JasonStoltz Jan 21 '13 at 17:32
  • 2
    Even if this does work, I don't think it will have the desired effect. Spring will always prefer the more specific mapping, I believe. – JasonStoltz Jan 21 '13 at 17:39
  • What about this: http://rapaul.com/2010/02/14/request-mapping-ordering/ It says ordering (within a class) matters. Also, how about the "!" operator, could that help? – Solubris Jan 21 '13 at 18:01
0

Automatic ordering on specificy does works as expected now. (Using spring 5.0.10)

In AntPatternComparator

        /**
         * Compare two patterns to determine which should match first, i.e. which
         * is the most specific regarding the current path.
         * @return a negative integer, zero, or a positive integer as pattern1 is
         * more specific, equally specific, or less specific than pattern2.
         */
        @Override
        public int compare(String pattern1, String pattern2)

Which is used from AbstractUrlHandlerMapping::lookupHandler(String urlPath, HttpServletRequest request)

        Comparator<String> patternComparator = getPathMatcher().getPatternComparator(urlPath);
        if (!matchingPatterns.isEmpty()) {
            matchingPatterns.sort(patternComparator);
            if (logger.isDebugEnabled()) {
                logger.debug("Matching patterns for request [" + urlPath + "] are " + matchingPatterns);
            }
            bestMatch = matchingPatterns.get(0);
        }
Markus T
  • 1,554
  • 13
  • 14
-3

This url(/home.html) doesn't matches both mappings:

    @RequestMapping(value = "**/{content}.html")
    ...
    @RequestMapping(value = "**/home")
    …

It just match the first one because it has a suffix ".html" !

Jintian DENG
  • 3,152
  • 20
  • 22