web-dev-qa-db-fra.com

Comment empêcher la liaison de paramètres d'interpréter les virgules dans Spring 3.0.5?

Considérez la méthode de contrôleur suivante:

@RequestMapping(value = "/test", method = RequestMethod.GET)
public void test(@RequestParam(value = "fq", required = false) String[] filterQuery) {
    logger.debug(fq = " + StringUtils.join(filterQuery, "|"));
}

Voici la sortie pour différentes combinaisons fq:

  1. /test?fq=foo résulte en fq = foo
  2. /test?fq=foo&fq=bar résulte en fq = foo|bar
  3. /test?fq=foo,bar résulte en fq = foo|bar
  4. /test?fq=foo,bar&fq=bash résulte en fq = foo,bar|bash
  5. /test?fq=foo,bar&fq= résulte en fq = foo,bar|

L'exemple 3 est le problème. Je m'attends (veux/ai besoin) à sortir fq = foo,bar.

J'ai essayé d'échapper à la virgule avec \ et en utilisant %3C mais niether travail.

Si je regarde la version de l'objet HttpServletRequest:

String[] fqs = request.getParameterValues("fq");
logger.debug(fqs = " + StringUtils.join(fqs, "|"));

Il imprime la sortie attendue: fqs = foo,bar. Le "problème" concerne donc la liaison de données Spring.

Je pourrais contourner la liaison de Spring et utiliser HttpServletRequest mais je ne veux vraiment pas car j'utilise un backing bean dans mon vrai code (la même chose se produit) et don ne souhaite pas réimplémenter la fonctionnalité de liaison. J'espère que quelqu'un peut fournir un moyen simple d'empêcher ce comportement via un échappement ou un autre mécanisme.

TIA

MISE À JOUR: J'ai posté ce Q sur Twitter et j'ai reçu une réponse disant la sortie attendue apparaît avec Spring 3.0.4.RELEASE. J'ai maintenant confirmé que c'est le cas et qu'il s'agit donc d'une solution temporaire. Je vais continuer et enregistrer cela comme un bug sur le système Spring JIRA. Si quelqu'un peut fournir une solution de contournement ou une correction avec 3.0.5, j'accepterai sa réponse.

53
nickdos

J'ai testé votre code: c'est incroyable, mais je ne peux pas reproduire votre problème. J'ai téléchargé la dernière version de Spring (3.0.5), voici mon contrôleur:

package test;

import org.Apache.commons.lang.StringUtils;
import org.Apache.log4j.Logger;
import org.springframework.stereotype.Controller;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

@Controller
@RequestMapping("/test/**")
public class MyController {

    private static final Logger logger = Logger.getLogger(MyController.class);

    @RequestMapping(value = "/test/params", method = RequestMethod.GET)
    public void test(SearchRequestParams requestParams, BindingResult result) {
    logger.debug("fq = " + StringUtils.join(requestParams.getFq(), "|"));
    }
}

voici ma classe SearchRequestParams:

package test;

public class SearchRequestParams {
    private String[] fq;

    public String[] getFq() {
    return fq;
    }

    public void setFq(String[] fq) {
    this.fq = fq;
    }
}

et voici ma configuration de ressort simple:

<bean id="urlMapping" class="org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping" />

<bean class="test.MyController" />

<bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
    <property name="prefix">
        <value>/WEB-INF/jsp/</value>
    </property>
    <property name="suffix">
        <value>.jsp</value>
    </property>
</bean>

J'ai testé mon code dans Tomcat 7.0.8; quand je tape http://localhost:8080/testweb/test/params.htm?fq=foo,bar Je peux lire dans mon fichier journal cette ligne: DEBUG fq = foo,bar. Quelles sont les différences entre mon code et le vôtre? Est-ce que je fais quelque chose de mal? Je voudrais vous aider, donc si vous avez des doutes ou si je peux faire d'autres tests pour vous, ce sera un plaisir.

MISE À JOUR/SOLUTION
Avec votre code, j'ai reproduit le problème; vous avez le tag <mvc:annotation-driven /> dans la configuration de votre servlet de répartiteur, afin d'utiliser silencieusement un service de conversion par défaut, instance de FormattingConversionService, qui contient un convertisseur par défaut de String vers String[] qui utilise une virgule comme séparateur. Vous devez utiliser un bean de service de conversion différent contenant votre propre convertisseur de String vers String[]. Vous devez utiliser un séparateur différent, j'ai choisi d'utiliser ";" car c'est le séparateur couramment utilisé dans la chaîne de requête ("? first = 1; second = 2; third = 3"):

import org.springframework.core.convert.converter.Converter;
import org.springframework.util.StringUtils;

public class CustomStringToArrayConverter implements Converter<String, String[]>{
   @Override
    public String[] convert(String source) {
        return StringUtils.delimitedListToStringArray(source, ";");
    }
}

Ensuite, vous devez spécifier ce bean de service de conversion dans votre configuration:

<mvc:annotation-driven conversion-service="conversionService" />

<bean id="conversionService" class="org.springframework.context.support.ConversionServiceFactoryBean">
    <property name="converters">
        <list>
            <bean class="au.org.ala.testspringbinding.CustomStringToArrayConverter" />
        </list>
    </property>
</bean>

Le problème a été résolu, vous devez maintenant vérifier les effets secondaires. J'espère que vous n'avez pas besoin dans votre application de la conversion d'origine de String en String[] (avec virgule comme séparateur). ;-)

32
javanna

J'ai trouvé le moyen le plus élégant et le plus court pour moi - ajoutez @InitBinder à un @Controller:

@InitBinder
public void initBinder(WebDataBinder binder) {
    binder.registerCustomEditor(String[].class, new StringArrayPropertyEditor(null));
}

Il convertira String en String [] sans utiliser de séparateur (null param), avec la classe Spring org.springframework.beans.propertyeditors.StringArrayPropertyEditor. Si quelqu'un dans le même projet utilise une nouvelle méthode de conversion par défaut, ce sera correct.

24
TachikomaGT

Comme suggéré par Philip Potter, je poste la "mise à jour" à ma question en tant que réponse, car cela aurait pu être facile à manquer ...

Rétrogradation de Spring 3.0.5.RELEASE à 3.0.4.RELEASE fixed le problème, lors de l'utilisation de @RequestParam annotation, suggérant qu'il s'agit d'un bogue avec 3.0.5.

Cependant, cela ne résout PAS le problème connexe, lors de la liaison à un bean de sauvegarde de formulaire - ce que j'ai dans ma webapp. J'ai testé toutes les versions depuis 3.0.0.RELEASE et j'obtiens le même résultat (/test?fq=foo,bar produit fq = foo|bar).

Par exemple.

@RequestMapping(value = "/test", method = RequestMethod.GET)
public void test(SearchRequestParams requestParams, BindingResult result) {
    logger.debug("fq = " + StringUtils.join(requestParams.getFq(), "|"));
}

SearchRequestParams contient un champ String[] fq.

Si quelqu'un a une solution à ce problème, j'accepterai volontiers sa réponse.

4
nickdos

javanna a déjà souligné la cause profonde correcte. Je voulais juste souligner que vous pouvez également supprimer complètement le StringToArrayConverter comme indiqué ici et ici .

3
Rossen Stoyanchev

Ce qui suit est mon voyage en contournant la délimitation par des virgules dans une demande MVC

Voici ma compréhension de l'évolution de Springs de ce type de solutions: La documentation est très vague sur la dernière solution.

WebMvcConfigAdapter a d'abord implémenté l'interface WebMvcConfig. Cela a finalement été déconseillé

Cela a été remplacé par WebMvcConfigSupport. Cela a finalement été déconseillé, mais c'était mieux que la première solution. Le principal problème est qu'il a désactivé la configuration automatique MVC et que des problèmes secondaires comme swagger.html ne fonctionnaient pas et que les informations sur l'actionneur manquaient de joli format et que la date était devenue une grande décimale.

La dernière est une interface WebMvcConfig révisée qui implémente des méthodes par défaut en utilisant Java 8 fonctionnalités.

Créer une classe dans mon cas, WebConfigUUID pourrait être AnyClass, qui implémente la version ultérieure de WebMvcConfig

Cela vous permet de modifier ce dont vous avez besoin, dans notre cas, un convertisseur personnalisé, sans impact sur quoi que ce soit d'autre ou avoir à en remplacer un autre pour faire fonctionner le swagger, ou à gérer la sortie d'informations de l'actionneur

Voici les deux classes qui ont implémenté ma modification pour contourner la virgule délimitant les chaînes à une liste lors du traitement de la demande MVC:
Il produit toujours une liste de chaînes mais avec une seule valeur.

import Java.util.Collection;
import org.springframework.context.annotation.Configuration;
import org.springframework.format.FormatterRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class WebConfigUUID implements WebMvcConfigurer {
  @Override
  public void addFormatters(FormatterRegistry registry)  {
    registry.removeConvertible(String.class,Collection.class);
    registry.addConverter(String.class,Collection.class,BypassCommaDelimiterConfiguration.commaDelimiterBypassedParsingConverter());
  }
}

import Java.util.ArrayList;
import Java.util.List;
import org.springframework.core.convert.converter.Converter;

public class BypassCommaDelimiterConfiguration  {
    public static Converter<String, List<String>> commaDelimiterBypassedParsingConverter() {
        return new Converter<String, List<String>>() {
            @Override
            public List<String> convert(final String source) {
                final List<String> classes = new ArrayList<String>();
                classes.add(source);
                return classes;
            }
        };
    }
}
0
Joseph Rogers

C'est un hack mais, avez-vous envisagé de passer vos paramètres délimités par '-'

/test?fq=foo-bar results in fq = foo-bar
/test?fq=foo-bar&fq=bash results in fq = foo-bar|bash 

Ou tout autre délimiteur peut-être ~, ou!, Ou ^, ou ???

0
Sumit