Попытка создать REST-ful URL с несколькими точками в части имени файла - Spring 3.0 MVC - PullRequest
15 голосов
/ 17 января 2010

Я использую Spring MVC (3.0) с контроллерами на основе аннотаций. Я хотел бы создать REST-ful URL-адреса для ресурсов и иметь возможность не требовать (но по желанию разрешить) расширение файла в конце URL-адреса (но предполагать тип содержимого HTML, если расширение отсутствует). Это работает "из коробки" с Spring MVC, если в части имени файла нет точек (точка / точка).

Однако для некоторых моих URL-адресов требуется идентификатор с точками в имени. Например. как это:

http://company.com/widgets/123.456.789.500

В этом случае Spring ищет тип содержимого для расширения .500 и не находит никаких ошибок. Я могу использовать обходные пути, такие как добавление .html в конец, кодирование идентификатора или добавление косой черты. Я не доволен ни одним из них, но, вероятно, мог бы жить с добавлением .html.

Я безуспешно искал способ переопределения определения расширения файла по умолчанию в Spring.

Можно ли настроить или отключить обнаружение расширения файла для данного метода контроллера или шаблона URL и т. Д.?

Ответы [ 7 ]

12 голосов
/ 17 января 2010

Вероятно, это уродливый хак, я просто хотел изучить расширяемость Spring @MVC. Вот индивидуальный PathMatcher. Он использует $ в шаблоне в качестве маркера конца - если шаблон заканчивается им, маркер удаляется и шаблон сопоставляется с помощью сопоставления по умолчанию, но если шаблон имеет $ в середине (например, ...$.*), такой шаблон не соответствует.

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>

И использование (с учетом "/hello/1.2.3", value равно "1.2.3"):

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

РЕДАКТИРОВАТЬ: : теперь не нарушает правило "косая черта не имеет значения"

12 голосов
/ 17 января 2010

Совпадение с шаблоном @PathVariable немного раздражает, когда дело доходит до точек в URL (см. SPR-5778 ). Вы можете сделать его менее раздражительным (но более требовательным) и лучше управлять URL-адресами, заполненными точками, установив для свойства useDefaultSuffixPattern значение DefaultAnnotationHandlerMapping в false.

Если вы еще не объявили явно DefaultAnnotationHandlerMapping в своем контексте (а большинство людей этого не сделали, поскольку он объявлен неявно для вас), вы можете добавить его явно и установить это свойство.

6 голосов
/ 19 января 2012
<!-- 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; //... } }

// Код выше должен соответствовать /widgets/111.222.333.444

4 голосов
/ 17 января 2013

Spring 3.2 был изменен и предлагает установить свойства для компонента RequestMappingHandlerMapping либо явно (если не используется пространство имен mvc) , либо с помощью BeanPostProcessor , например, следующего нужно отсканировать или создать его экземпляр):

@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; }
}

Вы также можете просто добавить :.* к своему @RequestMapping, например, "/{documentPath:.*}" (см. Комментарий JIRA)

3 голосов
/ 12 февраля 2010

У меня была такая же проблема, и я также решил ее с помощью собственного PathMatcher. Мое решение несколько проще того, что предлагал акставт. Мой PathMatcher также имеет частную конечную цель AntPathMatcher, и он делегирует ему все вызовы без изменений, кроме метода match ():

@Override
public boolean match(String pattern, String path) {
    return pattern.endsWith(".*") ? false : target.match(pattern, path);
}

Это работает, потому что Spring пытается сопоставить контроллеры, добавляя ". " в конец. Например, с отображением пути "/ widgets / {id}" и URL-адресом /widgets/1.2.3.4 "Spring пытается сначала выполнить повторное совпадение" /widgets/-199IDID. Я бы}". Первый будет совпадать, но для идентификатора останется только «1.2.3».

Мой PatchMatcher специально отклоняет шаблоны, оканчивающиеся на ". *", Поэтому первая попытка завершается неудачно, а вторая совпадает.

Если вы используете ContentNegotiatingViewResolver, вы все равно можете указать тип содержимого в URL-адресах, используя параметр запроса "format" (если параметр favParameter имеет значение true).

-jarppe

2 голосов
/ 20 января 2015

JFY: в Spring 4 эта проблема исправлена ​​через: WebMvcConfigurerAdapter.

@Configuration 
class MvcConfiguration extends WebMvcConfigurerAdapter {

@Override
public void configurePathMatch(PathMatchConfigurer configurer) {
    configurer.setUseSuffixPatternMatch(false);
}
}

Или через WebMvcConfigurationSupport, например здесь .

и Пружина 5 :

@Configuration
public class MvcConfiguration implements WebMvcConfigurer {

    @Override
    public void configurePathMatch(PathMatchConfigurer configurer) {
        configurer.setUseSuffixPatternMatch(false);
    }
}
1 голос
/ 06 марта 2012

Чтобы добавить к ответу Скаффмана, если вы используете <mvc:annotation-driven/> и хотите переопределить значение useDefaultSuffixPattern, вы можете заменить тег <mvc:annotation-driven> на следующий:

<!-- 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" />
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...