Trying to create REST-ful URLs with multiple dots in the “filename” part - Spring 3.0 MVC

后端 未结 7 1547
旧巷少年郎
旧巷少年郎 2020-12-03 02:11

I\'m using Spring MVC (3.0) with annotation-driven controllers. I would like to create REST-ful URLs for resources and be able to not require (but still opt

相关标签:
7条回答
  • 2020-12-03 02:19

    JFY: in Spring 4 this issue is fixed via: WebMvcConfigurerAdapter.

    @Configuration 
    class MvcConfiguration extends WebMvcConfigurerAdapter {
    
    @Override
    public void configurePathMatch(PathMatchConfigurer configurer) {
        configurer.setUseSuffixPatternMatch(false);
    }
    }
    

    Or via WebMvcConfigurationSupport like here.

    and in Spring 5:

    @Configuration
    public class MvcConfiguration implements WebMvcConfigurer {
    
        @Override
        public void configurePathMatch(PathMatchConfigurer configurer) {
            configurer.setUseSuffixPatternMatch(false);
        }
    }
    
    0 讨论(0)
  • 2020-12-03 02:26

    The @PathVariable pattern matching is a bit twitchy when it comes to dots in the URL (see SPR-5778). You can make it less twitchy (but more picky), and get better control over dot-heavy URLs, by setting the useDefaultSuffixPattern property on DefaultAnnotationHandlerMapping to false.

    If you haven't already explicitly declared a DefaultAnnotationHandlerMapping in your context (and most people don't since it's declared implicitly for you), then you can add it explicitly, and set that property.

    0 讨论(0)
  • 2020-12-03 02:26

    Probably, it's an ugly hack, I just wanted to explore extensibility of Spring @MVC. Here is a customized PathMatcher. It uses $ in the pattern as the end marker - if pattern ends with it, marker is removed and pattern is matched by the default matcher, but if pattern has $ in the middle (e.g. ...$.*), such a pattern is not matched.

    public class CustomPathMatcher implements PathMatcher {
        private PathMatcher target;
    
        public CustomPathMatcher() {
            target = new AntPathMatcher();
        }
    
        public String combine(String pattern1, String pattern2) {
            return target.combine(pattern1, pattern2); 
        }
    
        public String extractPathWithinPattern(String pattern, String path) {
            if (isEncoded(pattern)) {
                pattern = resolvePattern(pattern);
                if (pattern == null) return "";
            }
            return target.extractPathWithinPattern(pattern, path);
        }
    
        public Map<String, String> extractUriTemplateVariables(String pattern,
                String path) {
            if (isEncoded(pattern)) {
                pattern = resolvePattern(pattern);
                if (pattern == null) return Collections.emptyMap();
            }
            return target.extractUriTemplateVariables(pattern, path);
        }
    
        public Comparator<String> getPatternComparator(String pattern) {
            final Comparator<String> targetComparator = target.getPatternComparator(pattern);
            return new Comparator<String>() {
                public int compare(String o1, String o2) {
                    if (isEncoded(o1)) {
                        if (isEncoded(o2)) {
                            return 0;
                        } else {
                            return -1;
                        }
                    } else if (isEncoded(o2)) {
                        return 1;
                    }
                    return targetComparator.compare(o1, o2);
                }        
            };
        }
    
        public boolean isPattern(String pattern) {
            if (isEncoded(pattern)) {
                pattern = resolvePattern(pattern);
                if (pattern == null) return true;
            }
            return target.isPattern(pattern);
        }
    
        public boolean match(String pattern, String path) {
            if (isEncoded(pattern)) {
                pattern = resolvePattern(pattern);
                if (pattern == null) return false;
            }
            return target.match(pattern, path);
        }
    
        public boolean matchStart(String pattern, String path) {
            if (isEncoded(pattern)) {
                pattern = resolvePattern(pattern);
                if (pattern == null) return false;
            }
            return target.match(pattern, path);
        }
    
        private boolean isEncoded(String pattern) {
            return pattern != null && pattern.contains("$");
        }
    
        private String resolvePattern(String pattern) {
            int i = pattern.indexOf('$');
            if (i < 0) return pattern;
            else if (i == pattern.length() - 1) {
                return pattern.substring(0, i);
            } else {
                String tail = pattern.substring(i + 1);
                if (tail.startsWith(".")) return null;
                else return pattern.substring(0, i) + tail;
            }
        }
    }
    

    Config:

    <bean id = "pathMatcher" class = "sample.CustomPathMatcher" />
    
    <bean class = "org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter">
        <property name = "pathMatcher" ref="pathMatcher" />
    </bean>
    
    <bean class = "org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping">
        <property name = "pathMatcher" ref="pathMatcher" />
    </bean>
    

    And usage (given "/hello/1.2.3", value is "1.2.3"):

    @RequestMapping(value = "/hello/{value}$", method = RequestMethod.GET)
    public String hello(@PathVariable("value") String value, ModelMap model)
    

    EDIT:: Now doesn't break "trailing slash doesn't matter" rule

    0 讨论(0)
  • 2020-12-03 02:27
    <!-- language: lang-java -->
    

    @Controller public class MyController { @RequestMapping(value="/widgets/{preDot}.{postDot}") public void getResource(@PathVariable String preDot, @PathVariable String postDot) { String fullPath = preDot + "." + postDot; //... } }

    // Above code should match /widgets/111.222.333.444

    0 讨论(0)
  • 2020-12-03 02:29

    To add to skaffman's answer, if you're using <mvc:annotation-driven/> and you want to override the useDefaultSuffixPattern value, you can replace the <mvc:annotation-driven> tag with the following:

    <!-- Maps requests to @Controllers based on @RequestMapping("path") annotation values -->
    <bean class="org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping">
        <property name="order" value="1" />
        <property name="useDefaultSuffixPattern" value="false" />
    </bean>
    
    <!-- Enables annotated @Controllers -->
    <bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter" />
    
    0 讨论(0)
  • 2020-12-03 02:30

    Spring 3.2 has changed, and suggests that you set properties on the RequestMappingHandlerMapping bean, either explicitly (if not using the mvc namespace) or by using a BeanPostProcessor such as the following (you'll need to scan or instantiate it):

    @Component
    public class IncludeExtensionsInRequestParamPostProcessor implements BeanPostProcessor {
        @Override
        public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
            if (bean instanceof RequestMappingHandlerMapping) {
                RequestMappingHandlerMapping mapping = (RequestMappingHandlerMapping)bean;
                mapping.setUseRegisteredSuffixPatternMatch(false);
                mapping.setUseSuffixPatternMatch(false);
            }
            return bean;
        }
    
        @Override
        public Object postProcessAfterInitialization(Object bean, String beanName) { return bean; }
    }
    

    You can also just append :.* to your @RequestMapping, e.g. "/{documentPath:.*}" (see JIRA comment)

    0 讨论(0)
提交回复
热议问题