¿Cómo puedo tener direcciones URL que no distinguen entre mayúsculas y minúsculas en Spring MVC con asignaciones anotadas?

10 minutos de lectura

avatar de usuario
dave

He anotado asignaciones que funcionan muy bien a través de mi aplicación web spring mvc, sin embargo, distinguen entre mayúsculas y minúsculas. No puedo encontrar una manera de hacer que no distingan entre mayúsculas y minúsculas. (Me encantaría que esto suceda dentro de Spring MVC, en lugar de redirigir el tráfico de alguna manera)

  • Además, agregue la etiqueta ‘Java’, obtendrá muchas más visitas a la página, lo que generalmente significa más respuestas.

    –David Parques

    13 de noviembre de 2010 a las 9:09

  • pregunta similar con una respuesta detallada sobre este problema que hice después de ver esta pregunta. stackoverflow.com/questions/12684183/…

    – Zahid Riaz

    5 de octubre de 2012 a las 9:59


Spring 4.2 admitirá la coincidencia de rutas que no distingue entre mayúsculas y minúsculas. Puedes configurarlo de la siguiente manera:

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

  • ¿Conoce alguna configuración de este tipo para los parámetros de consulta?

    – M22an

    11 de noviembre de 2015 a las 12:16

  • @M22an Lo siento, no lo sé. Creo que puede solicitar la función en jira.spring.io

    – código np

    12 de noviembre de 2015 a las 3:45

  • En Spring Boot 2 o Spring 5, el WebMvcConfigurerAdapter es obsoleto. En su lugar, se debe implementar el WebMvcConfigurer directamente.

    – Esquí alpino

    30 de marzo de 2018 a las 12:37

  • Dos años después, pero aún… WebMvcConfigurerAdapter en Spring 3.2 no tiene el método configurePathMatch. La versión mínima de Spring no es correcta, probablemente 4.2 como se indica en las respuestas anteriores. Más información sobre docs.spring.io/spring/docs/3.2.13.RELEASE/javadoc-api/org/…

    – alf

    26 de abril de 2017 a las 11:40


  • Funciona bastante bien con Spring 3.2.17 y superior. Lo único que falta en el código anterior son varias declaraciones de “retorno” que se omiten por error. Puede encontrar una solución para Spring anterior a 3.2.17, en: newmiancode.blogspot.com/2010/01/…

    – Gjera

    3 de abril de 2019 a las 13:42


  • Gracias por el comentario @Gjera. tiene razón en que no hay declaraciones de devolución. Como mencioné en los ejemplos anteriores, mi código está escrito usando Groovy, lo que le permite omitir el ‘retorno’ y por defecto la última línea de código ejecutada en un método como declaración de retorno. Editaré la publicación para ponerla en ‘negrita’ para que se destaque más.

    – pczeus

    25/04/2019 a las 15:00

De acuerdo a esta publicación web necesita agregar ambos a Mapeo de controlador y un Adaptador de controlador en SpringMVC. La asignación asigna la solicitud a un controlador correspondiente y el adaptador es responsable de ejecutar la solicitud mediante el controlador.

Por lo tanto, debe anular el PathMatcher tanto para el mapeador como para el adaptador.

Ex (hará que todos los @Controllers no distingan entre mayúsculas y minúsculas):

Nuevo emparejador:

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

Añadido casi lo mismo que <mvc:controlado por anotaciones> haría. (Gracias a David Parks por el enlace)

  • Para Spring 3.1, reemplace AnnotationMethodHandlerAdapter con RequestMappingHandlerAdapter. Están usando esta nueva clase en 3.1.

    – smat

    26 de septiembre de 2012 a las 12:52

avatar de usuario
Comunidad


Problema informe para solución por smat


En la solución de smat, hay un pequeño efecto secundario (culparía a spring-mvc por eso).

Primero, AntPathMatcher.doMatch() parece devolver verdadero/falso según la URL solicitada y la cadena de asignación de solicitud del método del controlador (eso es lo único que se debe hacer aquí). Pero, este método también se usa para un propósito más (que no está escrito en documentación!). Otro propósito es recopilar los valores correspondientes para @PathVariable en el método del controlador. Estos valores se recogen en Map<String, String> uriTemplateVariables(último parámetro). Y estos valores recopilados se utilizan para pasar al método del controlador como valor de parámetro.

Por ejemplo, tenemos un método de controlador como este,

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

y si accedemos con URL, /code/AbD
luego con solucion por smat AntPathMatcher.doMatch() colectaré @PathVariable valor en Map<String, String> uriTemplateVariables como userCode->abd. Como estamos en minúsculas en la cadena de ruta, los valores recopilados también están en minúsculas. Y esto el valor del código de usuario en minúsculas se pasa a nuestro controlador.

Pero estoy agradecido a la solución de smat que me sirvió bien hasta ahora sin ningún otro problema.


Solución


Resolvió este problema haciendo una solución alternativa por smat. Sin ruta de minúsculas o cadena de patrón en extendido AntPathMatcher clase, forcé mi extendida AntPathMatcher para usar mi costumbre AntPathStringMatcher. mi costumbre AntPathStringMatcher hace coincidencias entre mayúsculas y minúsculas sin cambiar el caso de la cadena real.

En el siguiente código de solución, la mayor parte del código se copia del código de clase original (el código que quería personalizar estaba oculto para la subclase debido al acceso privado).

AntPathMatcher personalizado,

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 personalizado,

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

  • Para Spring 3.1, reemplace AnnotationMethodHandlerAdapter con RequestMappingHandlerAdapter. Están usando esta nueva clase en 3.1.

    – smat

    26 de septiembre de 2012 a las 12:52

avatar de usuario
shershon

Ejemplo de un archivo de bean en Spring 4.2 y esto SOLO ES COMPATIBLE con 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>

¿Ha sido útil esta solución?

Esta web utiliza cookies propias y de terceros para su correcto funcionamiento y para fines analíticos y para mostrarte publicidad relacionada con sus preferencias en base a un perfil elaborado a partir de tus hábitos de navegación. Al hacer clic en el botón Aceptar, acepta el uso de estas tecnologías y el procesamiento de tus datos para estos propósitos. Configurar y más información
Privacidad