У меня есть аннотированные сопоставления, отлично работающие в моем веб-приложении spring mvc, однако они чувствительны к регистру. Я не могу найти способ сделать их нечувствительными к регистру. (Я бы хотел, чтобы это произошло в spring MVC, а не как-то перенаправлять трафик)
Как я могу иметь нечувствительные к регистру URL-адреса в Spring MVC с аннотированными сопоставлениями
Ответ 1
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);
}
}
Ответ 2
В соответствии с этой веб-страницей вам нужно добавить как HandlerMapping и HandlerAdapter в Spring MVC. 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"/>
Ответ 3
В Spring 3. 2+/Spring Boot теперь вы можете настроить сопоставление URL без учета регистра, используя упрощенную конфигурацию Java.
Сначала вам нужно создать 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 с минимальной конфигурацией
Ответ 4
Проблема для отчета от smat
В решении от smat, есть один небольшой побочный эффект (я бы обвинил spring -mvc для этого).
Сначала AntPathMatcher.doMatch()
, кажется, возвращает true/false в зависимости от строки запроса-url и контроллера-метода (что единственное, что нужно сделать здесь). Но этот метод используется и для еще одной цели (которая не написана в документации!). Другая цель - собрать соответствующие значения для @PathVariable
в методе контроллера. Эти значения собираются в Map<String, String> uriTemplateVariables
(последний параметр). И эти собранные значения используются для передачи контроллеру-методу в качестве значения параметра.
Например, у нас есть такой метод-контроллер,
@RequestMapping("/code/{userCode}")
public String getCode(@PathVariable("userCode") String userCode) {
System.out.println(userCode);
}
и если мы получим URL, /code/AbD
затем с решением smat AntPathMatcher.doMatch()
будет собирать значение @PathVariable
в Map<String, String> uriTemplateVariables
как userCode->abd
. Поскольку мы опускаем строку пути, собранные значения также имеют более низкую обсадную колонну. И это нижнее значение userCode передается нашему контроллеру.
Но я благодарен решению от smat, который до сих пор хорошо меня обслуживал без каких-либо других проблем.
Решение
Решил эту проблему, выполняя работу над решением 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
Пример из файла bean в Spring 4.2, и это ТОЛЬКО ПОДДЕРЖИВАЕТ 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 дня, вот несколько примеров:
Этот пример подсказывает, что это возможно:
Он ссылается на этот класс в Spring
Мое предположение (и это именно так, предположение) состоит в том, что вам нужно расширить <mvc:annotation-driven/>
и реализовать индивидуальный beans с правильными параметрами, чтобы сделать его нечувствительным к регистру. См:
http://rapid-web.tumblr.com/post/296916668/what-does-annotation-driven-do
Последнее замечание, я заметил где-то еще в чтении, что он сказал, что все пути по умолчанию в нижнем регистре, проверили ли вы, что /MyPath
не обрабатывается @RequestMapping("/mypath")
?
Опять же, просто пища для размышлений, насколько я могу это сделать. Может быть, он доставит вас достаточно далеко, чтобы задать более конкретный вопрос, который приведет вас к ответу - что как-то это работает. Удачи!