Java 如何在带有注释映射的 Spring MVC 中使用不区分大小写的 URL

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/4150039/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-14 12:05:21  来源:igfitidea点击:

How can I have case insensitive URLS in Spring MVC with annotated mappings

javaspringspring-mvcmappingcase-insensitive

提问by Dave

I have annotated mappings working great through my spring mvc web app, however, they are case sensitive. I cannot find a way to make them case insensitive. (I'd love to make this happen within Spring MVC, rather than redirecting traffic somehow)

我已经注释了通过我的 spring mvc web 应用程序工作得很好的映射,但是,它们区分大小写。我找不到让它们不区分大小写的方法。(我很想在 Spring MVC 中实现这一点,而不是以某种方式重定向流量)

回答by David Parks

Well, I can't answer your question (I tried, I thought I could figure it out). But seeing as you haven't received any responses in 2 days, here are some leads at least:

好吧,我无法回答你的问题(我试过了,我想我能弄明白)。但是看到您在 2 天内没有收到任何回复,这里至少有一些线索:

This example seems to suggest it's possible:

这个例子似乎表明这是可能的:

http://webcache.googleusercontent.com/search?q=cache:ELj-ZQ8G4z0J:www.springbyexample.org/examples/sdms-simple-spring-mvc-web-module.html+case+insensitive+requestmapping+spring&cd=3&hl=en&ct=clnk&client=firefox-a

http://webcache.googleusercontent.com/search?q=cache:ELj-ZQ8G4z0J:www.springbyexample.org/examples/sdms-simple-spring-mvc-web-module.html+case+insensitive+requestmapping+spring&cd= 3&hl=en&ct=clnk&client=firefox-a

It references this class in Spring

它在 Spring 中引用了这个类

http://static.springsource.org/spring/docs/3.0.4.RELEASE/javadoc-api/org/springframework/web/servlet/mvc/support/ControllerClassNameHandlerMapping.html

http://static.springsource.org/spring/docs/3.0.4.RELEASE/javadoc-api/org/springframework/web/servlet/mvc/support/ControllerClassNameHandlerMapping.html

My guess (and it's just that, a guess), is that you need to expand <mvc:annotation-driven/>and implement the individual beans with the correct parameters to make it case-insensitive. See:

我的猜测(而且只是猜测)是您需要<mvc:annotation-driven/>使用正确的参数扩展和实现各个 bean 以使其不区分大小写。看:

http://rapid-web.tumblr.com/post/296916668/what-does-annotation-driven-do

http://rapid-web.tumblr.com/post/296916668/what-does-annotation-driven-do

A last note, I noticed somewhere else in reading that it said that all paths default to lower case, have you verified that /MyPathisn't handled by @RequestMapping("/mypath")?

最后一点,我在阅读的其他地方注意到它说所有路径默认为小写,您是否确认/MyPath未处理@RequestMapping("/mypath")

Again, just food for thought as best I can do. Maybe it'll get you far enough along to ask a more specific question that leads you to the answer - that's how these things work sometimes. Good luck!

再一次,我能做的就是最好的思考。也许它会让你走得足够远,提出一个更具体的问题来引导你找到答案——这就是这些事情有时是如何运作的。祝你好运!

回答by smat

According to this webpostyou need to add both a HandlerMappingand a HandlerAdapterin Spring MVC. The Mapping maps the request to a corresponding controller, and the adapter is responsible to execute the request using the controller.

根据this webpost,您需要在Spring MVC中同时添加一个HandlerMapping和一个HandlerAdapter。Mapping 将请求映射到相应的控制器,适配器负责使用控制器执行请求。

You therefore need to override the PathMatcherfor both the mapper and adapter.

因此,您需要覆盖映射器和适配器的PathMatcher

Ex (will make all @Controllers case-insensitive):

Ex(将使所有@Controllers 不区分大小写):

New Matcher:

新匹配器:

public class CaseInsenseticePathMatcher extends AntPathMatcher {
    @Override
    protected boolean doMatch(String pattern, String path, boolean fullMatch, Map<String, String> uriTemplateVariables) {
        System.err.println(pattern + " -- " + path);
        return super.doMatch(pattern.toLowerCase(), path.toLowerCase(), fullMatch, uriTemplateVariables);
    }
}

applicationContext.xml:

应用上下文.xml:

<bean id="matcher" class="test.CaseInsenseticePathMatcher"/>

<bean class="org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping">
    <property name="pathMatcher" ref="matcher"/>
</bean>

<bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter">
    <property name="pathMatcher" ref="matcher"/>
    <property name="webBindingInitializer">
        <bean class="org.springframework.web.bind.support.ConfigurableWebBindingInitializer"/>
    </property>
    <property name="messageConverters">
        <list>
            <bean class="org.springframework.http.converter.ByteArrayHttpMessageConverter"/>
            <bean class="org.springframework.http.converter.StringHttpMessageConverter"/>
            <bean class="org.springframework.http.converter.FormHttpMessageConverter"/>
            <bean class="org.springframework.http.converter.xml.SourceHttpMessageConverter"/>
        </list>
    </property>
</bean>

<bean id="conversion-service" class="org.springframework.format.support.FormattingConversionServiceFactoryBean"/>

Added about the same that <mvc:annotation-driven> would do. (Thanks to David Parks for the link)

添加了与 < mvc:annotation-driven>相同的内容。(感谢大卫帕克斯的链接)

回答by samir



Problemreport for solution by smat

smat解决问题报告



In solution by smat, there is one little side-effect (I would blame spring-mvc for that).

smat 的解决方案中,有一个小的副作用(我会为此归咎于 spring-mvc)。

At first, AntPathMatcher.doMatch()seems to return true/false depending on requested-url and controller-method's request-mapping string (That's the only thing should be done here). But, this method is used for one more purpose as well (which is not written in documentation!). Another purpose is to collect corresponding values for @PathVariablein controller-method. These values are collected in Map<String, String> uriTemplateVariables(last parameter).And these collected values are used to pass to controller-method as parameter value.

起初,AntPathMatcher.doMatch()似乎根据requested-url和controller-method的请求映射字符串返回true/false(这是唯一应该在这里做的事情)。但是,这种方法也用于另一个目的(它没有写在文档中!)。另一个目的是@PathVariable在控制器方法中收集相应的值。这些值收集在Map<String, String> uriTemplateVariables(最后一个参数)中。这些收集的值用于作为参数值传递给控制器​​方法。

For example, we have controller-method like this,

例如,我们有这样的控制器方法,

@RequestMapping("/code/{userCode}")
public String getCode(@PathVariable("userCode") String userCode) {
    System.out.println(userCode);
}

and if we access with URL, /code/AbDthen with solution by smatAntPathMatcher.doMatch()will collect @PathVariablevalue in Map<String, String> uriTemplateVariablesas userCode->abd. As we are lower-casing the path string, values collected are also lower-cased. And this lower-cased userCode value is passed to our controller.

如果我们使用 URL 访问,/code/AbD那么使用smat 解决方案AntPathMatcher.doMatch()将收集as 中的@PathVariable值。由于我们将路径字符串小写,因此收集的值也是小写的。并将这个小写的 userCode 值传递给我们的控制器Map<String, String> uriTemplateVariablesuserCode->abd

But, I am thankful to solution by smatwhich served me well so far without any other problems.

但是,我很感谢smat 的解决方案,到目前为止,它对我很好,没有任何其他问题。



Solution

解决方案



Solved this problem by doing work around solution by smat. Without lower-casing path or pattern string in extended AntPathMatcherclass, I forced my extended AntPathMatcherto use my custom AntPathStringMatcher. my custom AntPathStringMatcherdoes case-insesitive matching without changing the case of actual string.

通过 smat 解决解决方案解决了这个问题。在扩展AntPathMatcher类中没有小写路径或模式字符串,我强迫我的扩展AntPathMatcher使用我的自定义AntPathStringMatcher. 我的习惯AntPathStringMatcher在不改变实际字符串的情况下进行不区分大小写的匹配。

In following solution code most of the code is copied from original class code(code I wanted to customize was hidden for subclass because of private access).

在以下解决方案代码中,大部分代码是从原始类代码复制的(由于私有访问,我想自定义的代码对于子类隐藏了)。

Custom AntPathMatcher,

自定义 AntPathMatcher,

public class CaseInsensitivePathMatcher extends AntPathMatcher {

private final Map<String, CaseInsensitiveAntPathStringMatcher> stringMatcherCache = new ConcurrentHashMap<String, CaseInsensitiveAntPathStringMatcher>();

/**
 * Actually match the given <code>path</code> against the given
 * <code>pattern</code>.
 * 
 * @param pattern
 *            the pattern to match against
 * @param path
 *            the path String to test
 * @param fullMatch
 *            whether a full pattern match is required (else a pattern match
 *            as far as the given base path goes is sufficient)
 * @return <code>true</code> if the supplied <code>path</code> matched,
 *         <code>false</code> if it didn't
 */
protected boolean doMatch(String pattern, String path, boolean fullMatch, Map<String, String> uriTemplateVariables) {

    if (path.startsWith(AntPathMatcher.DEFAULT_PATH_SEPARATOR) != pattern.startsWith(AntPathMatcher.DEFAULT_PATH_SEPARATOR)) {
        return false;
    }

    String[] pattDirs = StringUtils.tokenizeToStringArray(pattern, AntPathMatcher.DEFAULT_PATH_SEPARATOR);
    String[] pathDirs = StringUtils.tokenizeToStringArray(path, AntPathMatcher.DEFAULT_PATH_SEPARATOR);

    int pattIdxStart = 0;
    int pattIdxEnd = pattDirs.length - 1;
    int pathIdxStart = 0;
    int pathIdxEnd = pathDirs.length - 1;

    // Match all elements up to the first **
    while (pattIdxStart <= pattIdxEnd && pathIdxStart <= pathIdxEnd) {
        String patDir = pattDirs[pattIdxStart];
        if ("**".equals(patDir)) {
            break;
        }
        if (!matchStrings(patDir, pathDirs[pathIdxStart], uriTemplateVariables)) {
            return false;
        }
        pattIdxStart++;
        pathIdxStart++;
    }

    if (pathIdxStart > pathIdxEnd) {
        // Path is exhausted, only match if rest of pattern is * or **'s
        if (pattIdxStart > pattIdxEnd) {
            return (pattern.endsWith(AntPathMatcher.DEFAULT_PATH_SEPARATOR) ? path.endsWith(AntPathMatcher.DEFAULT_PATH_SEPARATOR) : !path
                    .endsWith(AntPathMatcher.DEFAULT_PATH_SEPARATOR));
        }
        if (!fullMatch) {
            return true;
        }
        if (pattIdxStart == pattIdxEnd && pattDirs[pattIdxStart].equals("*") && path.endsWith(AntPathMatcher.DEFAULT_PATH_SEPARATOR)) {
            return true;
        }
        for (int i = pattIdxStart; i <= pattIdxEnd; i++) {
            if (!pattDirs[i].equals("**")) {
                return false;
            }
        }
        return true;
    } else if (pattIdxStart > pattIdxEnd) {
        // String not exhausted, but pattern is. Failure.
        return false;
    } else if (!fullMatch && "**".equals(pattDirs[pattIdxStart])) {
        // Path start definitely matches due to "**" part in pattern.
        return true;
    }

    // up to last '**'
    while (pattIdxStart <= pattIdxEnd && pathIdxStart <= pathIdxEnd) {
        String patDir = pattDirs[pattIdxEnd];
        if (patDir.equals("**")) {
            break;
        }
        if (!matchStrings(patDir, pathDirs[pathIdxEnd], uriTemplateVariables)) {
            return false;
        }
        pattIdxEnd--;
        pathIdxEnd--;
    }
    if (pathIdxStart > pathIdxEnd) {
        // String is exhausted
        for (int i = pattIdxStart; i <= pattIdxEnd; i++) {
            if (!pattDirs[i].equals("**")) {
                return false;
            }
        }
        return true;
    }

    while (pattIdxStart != pattIdxEnd && pathIdxStart <= pathIdxEnd) {
        int patIdxTmp = -1;
        for (int i = pattIdxStart + 1; i <= pattIdxEnd; i++) {
            if (pattDirs[i].equals("**")) {
                patIdxTmp = i;
                break;
            }
        }
        if (patIdxTmp == pattIdxStart + 1) {
            // '**/**' situation, so skip one
            pattIdxStart++;
            continue;
        }
        // Find the pattern between padIdxStart & padIdxTmp in str between
        // strIdxStart & strIdxEnd
        int patLength = (patIdxTmp - pattIdxStart - 1);
        int strLength = (pathIdxEnd - pathIdxStart + 1);
        int foundIdx = -1;

        strLoop: for (int i = 0; i <= strLength - patLength; i++) {
            for (int j = 0; j < patLength; j++) {
                String subPat = pattDirs[pattIdxStart + j + 1];
                String subStr = pathDirs[pathIdxStart + i + j];
                if (!matchStrings(subPat, subStr, uriTemplateVariables)) {
                    continue strLoop;
                }
            }
            foundIdx = pathIdxStart + i;
            break;
        }

        if (foundIdx == -1) {
            return false;
        }

        pattIdxStart = patIdxTmp;
        pathIdxStart = foundIdx + patLength;
    }

    for (int i = pattIdxStart; i <= pattIdxEnd; i++) {
        if (!pattDirs[i].equals("**")) {
            return false;
        }
    }

    return true;
}

/**
 * Tests whether or not a string matches against a pattern. The pattern may
 * contain two special characters:<br>
 * '*' means zero or more characters<br>
 * '?' means one and only one character
 * 
 * @param pattern
 *            pattern to match against. Must not be <code>null</code>.
 * @param str
 *            string which must be matched against the pattern. Must not be
 *            <code>null</code>.
 * @return <code>true</code> if the string matches against the pattern, or
 *         <code>false</code> otherwise.
 */
private boolean matchStrings(String pattern, String str, Map<String, String> uriTemplateVariables) {
    CaseInsensitiveAntPathStringMatcher matcher = this.stringMatcherCache.get(pattern);
    if (matcher == null) {
        matcher = new CaseInsensitiveAntPathStringMatcher(pattern);
        this.stringMatcherCache.put(pattern, matcher);
    }
    return matcher.matchStrings(str, uriTemplateVariables);
}

}

}

Custom AntPathStringMatcher,

自定义 AntPathStringMatcher,

public class CaseInsensitiveAntPathStringMatcher {
private static final Pattern GLOB_PATTERN = Pattern.compile("\?|\*|\{((?:\{[^/]+?\}|[^/{}]|\\[{}])+?)\}");

private static final String DEFAULT_VARIABLE_PATTERN = "(.*)";

private final Pattern pattern;

private final List<String> variableNames = new LinkedList<String>();


/** Construct a new instance of the <code>AntPatchStringMatcher</code>. */
CaseInsensitiveAntPathStringMatcher(String pattern) {
    this.pattern = createPattern(pattern);
}

private Pattern createPattern(String pattern) {
    StringBuilder patternBuilder = new StringBuilder();
    Matcher m = GLOB_PATTERN.matcher(pattern);
    int end = 0;
    while (m.find()) {
        patternBuilder.append(quote(pattern, end, m.start()));
        String match = m.group();
        if ("?".equals(match)) {
            patternBuilder.append('.');
        }
        else if ("*".equals(match)) {
            patternBuilder.append(".*");
        }
        else if (match.startsWith("{") && match.endsWith("}")) {
            int colonIdx = match.indexOf(':');
            if (colonIdx == -1) {
                patternBuilder.append(DEFAULT_VARIABLE_PATTERN);
                variableNames.add(m.group(1));
            }
            else {
                String variablePattern = match.substring(colonIdx + 1, match.length() - 1);
                patternBuilder.append('(');
                patternBuilder.append(variablePattern);
                patternBuilder.append(')');
                String variableName = match.substring(1, colonIdx);
                variableNames.add(variableName);
            }
        }
        end = m.end();
    }
    patternBuilder.append(quote(pattern, end, pattern.length()));
    return Pattern.compile(patternBuilder.toString(), Pattern.CASE_INSENSITIVE);    // this line is updated to create case-insensitive pattern object
}

private String quote(String s, int start, int end) {
    if (start == end) {
        return "";
    }
    return Pattern.quote(s.substring(start, end));
}

/**
 * Main entry point.
 *
 * @return <code>true</code> if the string matches against the pattern, or <code>false</code> otherwise.
 */
public boolean matchStrings(String str, Map<String, String> uriTemplateVariables) {
    Matcher matcher = pattern.matcher(str);
    if (matcher.matches()) {
        if (uriTemplateVariables != null) {
            // SPR-8455
            Assert.isTrue(variableNames.size() == matcher.groupCount(),
                    "The number of capturing groups in the pattern segment " + pattern +
                    " does not match the number of URI template variables it defines, which can occur if " +
                    " capturing groups are used in a URI template regex. Use non-capturing groups instead.");
            for (int i = 1; i <= matcher.groupCount(); i++) {
                String name = this.variableNames.get(i - 1);
                String value = matcher.group(i);
                uriTemplateVariables.put(name, value);
            }
        }
        return true;
    }
    else {
        return false;
    }
}

回答by pczeus

In Spring 3.2+ / Spring Boot, you can now set up case insensitive URL matching using the simplified Java config.

在 Spring 3.2+ / Spring Boot 中,您现在可以使用简化的 Java 配置设置不区分大小写的 URL 匹配。

First you need to create the CaseInsensitivePathMatcher.groovy or Java class:

首先,您需要创建 CaseInsensitivePathMatcher.groovy 或 Java 类:

import org.springframework.util.AntPathMatcher

class CaseInsensitivePathMatcher extends AntPathMatcher{

    @Override
    protected boolean doMatch(String pattern, String path, boolean fullMatch, Map<String, String> uriTemplateVariables) {
        super.doMatch(pattern.toLowerCase(), path.toLowerCase(), fullMatch, uriTemplateVariables)
    }
}

Next, to make this happen, you should have a class annotated with Springs @Configuration that extends the WebMvcConfigurerAdapter class as shown below (Note that my code is contained within .groovy classes, hence the 'return' keyword is not required in the example):

接下来,要实现这一点,您应该有一个用 Springs @Configuration 注释的类,它扩展了 WebMvcConfigurerAdapter 类,如下所示(请注意,我的代码包含在 .groovy 类中,因此示例中不需要 'return' 关键字) :

@Configuration
public class ApplicationConfig extends WebMvcConfigurerAdapter

Then add the following 2 methods to the class:

然后将以下2个方法添加到类中:

/**
 * Creates a patchMatcher bean that matches case insensitively
 * @return PathMatcher
 */
@Bean
public PathMatcher pathMatcher() {
    new CaseInsensitivePathMatcher()
}

/**
 * Overrides the configurePathMatch() method in WebMvcConfigurerAdapter
 * <br/>Allows us to set a custom path matcher, used by the MVC for @RequestMapping's
     * @param configurer
     */
    @Override
    public void configurePathMatch(PathMatchConfigurer configurer) {
        configurer.pathMatcher = pathMatcher()
    }
}


That's it, you should now be all setup for case insensitive URL's with minimal configuration


就是这样,您现在应该以最少的配置为不区分大小写的 URL 进行所有设置

回答by npcode

Spring 4.2 will support case-insensitive path matching.You can configure it as follows:

Spring 4.2 将支持不区分大小写的路径匹配。您可以按如下方式配置它:

@Configuration
public class WebConfig extends WebMvcConfigurerAdapter {
    @Override
    public void configurePathMatch(PathMatchConfigurer configurer) {
        AntPathMatcher matcher = new AntPathMatcher();
        matcher.setCaseSensitive(false);
        configurer.setPathMatcher(matcher);
    }
}

回答by shershon

Example from a bean file in Spring 4.2 and this is ONLY SUPPORTED v4.2+:

Spring 4.2 中 bean 文件的示例,仅支持 v4.2+:

<mvc:annotation-driven validator="validator">
   <mvc:path-matching path-matcher="pathMatcher" />
</mvc:annotation-driven>

...

<!--Set endpoints case insensitive, spring is case-sensitive by default-->
<bean id="pathMatcher" class="org.springframework.util.AntPathMatcher">
  <property name="caseSensitive" value="false" />
</bean>