【问题标题】:How can I have case insensitive URLS in Spring MVC with annotated mappings如何在带有注释映射的 Spring MVC 中使用不区分大小写的 URL
【发布时间】:2010-11-10 23:15:59
【问题描述】:

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

【问题讨论】:

  • 另外,添加标签“Java”它会给你带来更多的页面浏览量,这通常意味着更多的答案。
  • 类似的问题,我在看到这个问题后提出了关于这个问题的详细答案。 stackoverflow.com/questions/12684183/…

标签: java spring spring-mvc mapping case-insensitive


【解决方案1】:

Spring 4.2 will support case-insensitive path matching.可以这样配置:

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

【讨论】:

  • 你知道查询参数有这样的配置吗?
  • @M22an 抱歉,我不知道。我想您可以通过jira.spring.io 请求该功能
  • 在 Spring Boot 2 或 Spring 5 中,WebMvcConfigurerAdapter 已弃用。相反,应该直接实现WebMvcConfigurer
【解决方案2】:

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

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

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

新匹配器:

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:

<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"/>

添加的内容与 mvc:annotation-driven> 差不多。 (感谢 David Parks 的链接)

【讨论】:

  • 对于 Spring 3.1 使用 RequestMappingHandlerAdapter 替换 AnnotationMethodHandlerAdapter。他们在 3.1 中使用了这个新类。
【解决方案3】:

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

首先您需要创建 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)
    }
}

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

@Configuration
public class ApplicationConfig extends WebMvcConfigurerAdapter

然后在类中添加以下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()
    }
}


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

【讨论】:

  • 两年后,但仍然... WebMvcConfigurerAdapter 在 Spring 3.2 中没有方法 configurePathMatch。最低 Spring 版本不正确,可能是 4.2,如上面的答案中所述。更多信息docs.spring.io/spring/docs/3.2.13.RELEASE/javadoc-api/org/…
  • 适用于 Spring 3.2.17 及更高版本。上面代码中唯一缺少的是几个被错误省略的“return”语句。您可以在以下位置找到 Spring 3.2.17 之前的解决方案:newmiancode.blogspot.com/2010/01/…
  • 感谢@Gjera 的评论。你是正确的,没有返回语句。正如我在上面的示例中提到的,我的代码是使用 Groovy 编写的,它允许您省略“return”并将方法中执行的最后一行代码默认为 return 语句。我将编辑帖子使其以“粗体”显示,这样它会更加突出。
【解决方案4】:

问题报告solution by smat


solution by smat 中,有一点副作用(我会责怪 spring-mvc)。

起初,AntPathMatcher.doMatch() 似乎根据请求的 url 和控制器方法的请求映射字符串返回真/假(这是唯一应该在这里做的事情)。但是,这种方法还有一个用途(不是写在documentation!)。另一个目的是在控制器方法中收集@PathVariable 的对应值。这些值收集在Map&lt;String, String&gt; uriTemplateVariables(最后一个参数)中。这些收集的值用于作为参数值传递给控制器​​方法。

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

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

如果我们使用 URL 访问,/code/AbD 然后使用solution by smat AntPathMatcher.doMatch() 将在Map&lt;String, String&gt; uriTemplateVariables 中收集@PathVariable 值作为userCode-&gt;abd。由于我们将路径字符串小写,因此收集的值也是小写的。这个小写的 userCode 值被传递给我们的控制器

但是,我很感谢solution by smat,到目前为止它对我的帮助很好,没有任何其他问题。


解决方案


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

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

自定义 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);
}

}

自定义 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;
    }
}

【讨论】:

    【解决方案5】:

    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>
    

    【讨论】:

      【解决方案6】:

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

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

      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

      它在 Spring 中引用了这个类

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

      我的猜测(只是猜测)是您需要扩展 &lt;mvc:annotation-driven/&gt; 并使用正确的参数实现各个 bean 以使其不区分大小写。见:

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

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

      再次,我能做的只是深思熟虑。也许它会让你走得更远,问一个更具体的问题,引导你找到答案——有时这些事情就是这样运作的。祝你好运!

      【讨论】:

        【解决方案7】:

        Spring 自 5.3 版以来默认不再使用 PathMatcher,而是使用 PathPatternMatcher。要配置 PathPatternMatcher,请执行以下操作:

        @Configuration
        public class WebConfig extends WebMvcConfigurerAdapter {
            @Override
            public void configurePathMatch(PathMatchConfigurer configurer) {
                PathPatternParser patternParser = new PathPatternParser();
                patternParser.setCaseSensitive(false);
                configurer.setPatternParser(patternParser);
            }
        }
        

        【讨论】:

          猜你喜欢
          • 2012-09-22
          • 1970-01-01
          • 2011-02-18
          • 2019-06-26
          • 2018-12-06
          • 1970-01-01
          • 2020-07-13
          • 2017-01-27
          • 1970-01-01
          相关资源
          最近更新 更多