【问题标题】:Spring MVC @PathVariable with dot (.) is getting truncated带有点 (.) 的 Spring MVC @PathVariable 被截断
【发布时间】:2013-04-26 06:34:37
【问题描述】:

这是问题的延续 Spring MVC @PathVariable getting truncated

Spring 论坛声明它已修复(3.2 版本)作为 ContentNegotiationManager 的一部分。请参阅以下链接。
https://jira.springsource.org/browse/SPR-6164
https://jira.springsource.org/browse/SPR-7632

在我的应用程序中,带有 .com 的 requestParameter 被截断。

谁能解释一下如何使用这个新功能?如何在 xml 中进行配置?

注意:spring forum- #1 Spring MVC @PathVariable with dot (.) is getting truncated

【问题讨论】:

    标签: spring rest spring-mvc spring-annotations


    【解决方案1】:

    据我所知,此问题仅出现在请求映射末尾的路径变量中。

    我们能够通过在请求映射中定义正则表达式插件来解决这个问题。

     /somepath/{variable:.+}
    

    【讨论】:

    • 谢谢,我认为这个修复程序更早可用(在 3.2V 之前)?但是我不喜欢这个修复;因为在我的应用程序中必须处理的所有 url 都需要它......并且未来的 URL 实现也需要处理......
    • 这是我在春季 3.0.5 <!-- Spring Configuration needed to avoid URI using dots to be truncated --> <bean class="org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping"> <property name="useDefaultSuffixPattern" value="false" /> </bean>987654322@ 解决问题的方法
    • @Mariusz,语法是{variable_name:regular_expression},所以这里我们有一个名为variable的变量,它的值将使用正则表达式.+进行匹配(其中.表示'任何字符'和@ 987654327@ 表示“一次或多次”)。
    • @StefanHaberl 如果您以常规方式匹配variable,Spring 会使用其后缀检测功能并截断点后的所有内容。当您使用正则表达式匹配时,不会使用这些功能 - 变量仅与您提供的正则表达式匹配。
    • @martin "variable:.+" 在变量中有多个点时不起作用。例如,将电子邮件放在像/path/abc@server.com.au 这样的宁静路径的末尾。控制器甚至没有被调用,但是当只有一个点 /path/abc@server.com 时它可以工作。知道为什么和/或解决方法吗?
    【解决方案2】:

    如果您使用的是 Spring 3.2.x 和 <mvc:annotation-driven />,请创建这个小 BeanPostProcessor

    package spring;
    
    public final class DoNotTruncateMyUrls implements BeanPostProcessor {
        @Override
        public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
            if (bean instanceof RequestMappingHandlerMapping) {
                ((RequestMappingHandlerMapping)bean).setUseSuffixPatternMatch(false);
            }
            return bean;
        }
        @Override
        public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
            return bean;
        }
    }
    

    然后把它放在你的 MVC 配置 xml 中:

    <bean class="spring.DoNotTruncateMyUrls" />
    

    【讨论】:

    • 和ContentNegotiationManager有关系吗?
    • 我的代码只配置了RequestMappingHandlerMapping,这样URLs就不会被截断。 ContentNegotiationManager 是另一种野兽。
    • 这是旧的,但你真的不需要BeanPostProcessor。如果您使用WebMvcConfigurationSupport,您可以覆盖requestMappingHandlerMapping @Bean 方法。如果您使用 XML 配置,您只需声明自己的 RequestMappingHandlerMapping bean 并声明该属性。
    • 非常感谢,我为同一个问题尝试了不同数量的解决方案,只有这个对我有用。 :-)
    【解决方案3】:

    除了 Martin Frey 的回答,这也可以通过在 RequestMapping 值中添加尾部斜杠来解决:

    /path/{variable}/
    

    请记住,此修复不支持可维护性。它现在要求所有 URI 都有一个尾部斜杠——这对于 API 用户/新开发人员来说可能并不明显。因为可能并非所有参数都包含.,因此也可能会产生间歇性错误

    【讨论】:

    • 那甚至是更清洁的解决方案。我不得不找出 IE 根据后缀设置接受标头的困难方式。所以我想在一些 .doc requestmapping 上发布,我总是得到一个下载而不是新的 html 页面。这种方法解决了这个问题。
    • 这对我来说是最简单的解决方案并解决了我的问题;在许多情况下,正则表达式似乎有点矫枉过正
    • 但它与 AngularJS 自动删除尾部斜杠的默认行为相冲突。这可以在最新的 Angular 版本中进行配置,但如果你不知道发生了什么,它需要跟踪几个小时。
    • @dschulten 您刚刚为我节省了数小时的调试时间,谢谢!不过,您应该在答案中提到,HTPP 请求中将需要尾部斜杠。
    • 这很危险!我当然不会推荐它,因为任何实现 API 的人都不会期望它。非常难以维护。
    【解决方案4】:

    Spring 认为最后一个点后面的任何内容都是文件扩展名,例如 .json.xml,并将其截断以检索您的参数。

    所以如果你有/somepath/{variable}

    • /somepath/param/somepath/param.json/somepath/param.xml/somepath/param.anything 将产生一个值为 param 的参数
    • /somepath/param.value.json/somepath/param.value.xml/somepath/param.value.anything 将产生一个值为 param.value 的参数

    如果您按照建议将映射更改为 /somepath/{variable:.+},则任何点(包括最后一个点)都将被视为参数的一部分:

    • /somepath/param 将产生一个值为 param 的参数
    • /somepath/param.json 将产生一个值为 param.json 的参数
    • /somepath/param.xml 将产生一个值为 param.xml 的参数
    • /somepath/param.anything 将产生一个值为 param.anything 的参数
    • /somepath/param.value.json 将产生一个值为 param.value.json 的参数
    • ...

    如果您不关心扩展识别,可以通过覆盖 mvc:annotation-driven automagic 来禁用它:

    <bean id="handlerMapping"
          class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping">
        <property name="contentNegotiationManager" ref="contentNegotiationManager"/>
        <property name="useSuffixPatternMatch" value="false"/>
    </bean>
    

    所以,如果你有/somepath/{variable}

    • /somepath/param/somepath/param.json/somepath/param.xml/somepath/param.anything 将产生一个值为 param 的参数
    • /somepath/param.value.json/somepath/param.value.xml/somepath/param.value.anything 将产生一个值为 param.value 的参数

    注意:与默认配置的区别仅在您具有somepath/something.{variable} 之类的映射时才可见。见Resthub project issue

    如果你想保持扩展管理,从 Spring 3.2 开始你还可以设置 RequestMappingHandlerMapping bean 的 useRegisteredSuffixPatternMatch 属性,以保持 suffixPattern 识别激活但仅限于注册扩展。

    这里你只定义了 json 和 xml 扩展:

    <bean id="handlerMapping"
          class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping">
        <property name="contentNegotiationManager" ref="contentNegotiationManager"/>
        <property name="useRegisteredSuffixPatternMatch" value="true"/>
    </bean>
    
    <bean id="contentNegotiationManager" class="org.springframework.web.accept.ContentNegotiationManagerFactoryBean">
        <property name="favorPathExtension" value="false"/>
        <property name="favorParameter" value="true"/>
        <property name="mediaTypes">
            <value>
                json=application/json
                xml=application/xml
            </value>
        </property>
    </bean>
    

    请注意,mvc:annotation-driven 现在接受 contentNegotiation 选项以提供自定义 bean,但 RequestMappingHandlerMapping 的属性必须更改为 true(默认为 false)(参见 https://jira.springsource.org/browse/SPR-7632)。

    因此,您仍然必须覆盖所有 mvc:annotation-driven 配置。我开了一张 Spring 的票,要求定制 RequestMappingHandlerMapping :https://jira.springsource.org/browse/SPR-11253。如果您有兴趣,请投票。

    在覆盖时,请谨慎考虑自定义执行管理覆盖。否则,您的所有自定义异常映射都将失败。您将不得不重用带有列表 bean 的 messageCoverters :

    <bean id="validator" class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean" />
    <bean id="conversionService" class="org.springframework.format.support.FormattingConversionServiceFactoryBean" />
    
    <util:list id="messageConverters">
        <bean class="your.custom.message.converter.IfAny"></bean>
        <bean class="org.springframework.http.converter.ByteArrayHttpMessageConverter"></bean>
        <bean class="org.springframework.http.converter.StringHttpMessageConverter"></bean>
        <bean class="org.springframework.http.converter.ResourceHttpMessageConverter"></bean>
        <bean class="org.springframework.http.converter.xml.SourceHttpMessageConverter"></bean>
        <bean class="org.springframework.http.converter.xml.XmlAwareFormHttpMessageConverter"></bean>
        <bean class="org.springframework.http.converter.xml.Jaxb2RootElementHttpMessageConverter"></bean>
        <bean class="org.springframework.http.converter.json.MappingJacksonHttpMessageConverter"></bean>
    </util:list>
    
    <bean name="exceptionHandlerExceptionResolver"
          class="org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver">
        <property name="order" value="0"/>
        <property name="messageConverters" ref="messageConverters"/>
    </bean>
    
    <bean name="handlerAdapter"
          class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter">
        <property name="webBindingInitializer">
            <bean class="org.springframework.web.bind.support.ConfigurableWebBindingInitializer">
                <property name="conversionService" ref="conversionService" />
                <property name="validator" ref="validator" />
            </bean>
        </property>
        <property name="messageConverters" ref="messageConverters"/>
    </bean>
    
    <bean id="handlerMapping"
          class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping">
    </bean>
    

    我在我参与的开源项目Resthub 中实施了一组针对这些主题的测试:请参阅https://github.com/resthub/resthub-spring-stack/pull/219/fileshttps://github.com/resthub/resthub-spring-stack/issues/217

    【讨论】:

    • 原谅我是个新手,那么你把 bean 配置放在哪里?它适用于哪个春季版本?
    • @Splash :您必须将这些 bean 定义到您的“标准”Spring applicationContext.xml 文件中。这至少适用于 Spring 3.2。可能(至少部分)之前
    • 我认为这是正确的答案。似乎正是针对 OPs 问题引入了参数“useRegisteredSuffixPatternMatch”。
    • 这对我来说只是解决方案的一半。请参阅@Paul Aerer 的回答。
    【解决方案5】:

    Spring 4 更新:从 4.0.1 开始,您可以使用 PathMatchConfigurer(通过您的 WebMvcConfigurer),例如

    @Configuration
    protected static class AllResources extends WebMvcConfigurerAdapter {
    
        @Override
        public void configurePathMatch(PathMatchConfigurer matcher) {
            matcher.setUseRegisteredSuffixPatternMatch(true);
        }
    
    }
    
    
    @Configuration
    public class WebConfig implements WebMvcConfigurer {
    
       @Override
       public void configurePathMatch(PathMatchConfigurer configurer) {
           configurer.setUseSuffixPatternMatch(false);
       }
    }
    

    在 xml 中,它将是 (https://jira.spring.io/browse/SPR-10163):

    <mvc:annotation-driven>
        [...]
        <mvc:path-matching registered-suffixes-only="true"/>
    </mvc:annotation-driven>
    

    【讨论】:

    • 这是迄今为止最干净的解决方案:关闭导致它的功能,而不是绕过它。反正我们没有使用这个功能,所以问题解决了 - 完美!
    • AllResources 类去哪里了?
    • @ste_irl 在与您的 main 相同的包中添加一个 java 类。
    • 使用matcher.setUseSuffixPatternMatch(false) 完全禁用后缀匹配。
    • 这对我来说只是解决方案的一半。请参阅@Paul Aerer 的回答。
    【解决方案6】:

    解决此问题的一个非常简单的方法是附加一个斜杠...

    例如:

    使用:

    /somepath/filename.jpg/
    

    代替:

    /somepath/filename.jpg
    

    【讨论】:

      【解决方案7】:

      /somepath/{variable:.+} 在 Java 中工作 requestMapping 标记。

      【讨论】:

      • 我更喜欢这个答案,因为它没有显示什么不起作用。
      • 不适用于包含多个点的电子邮件地址。
      • @8bitjunkie Sth like "/{code:.+}" 适用于许多点而不是一个,即61.12.7 它也适用于即k.a.p@o.i.n
      • 跟我的情况一样。它也适用于多个点。 Spring版本是4.3.6.RELEASE
      【解决方案8】:

      在 spring 4.2 的路径名中包含电子邮件地址的完整解决方案是

      <bean id="contentNegotiationManager"
          class="org.springframework.web.accept.ContentNegotiationManagerFactoryBean">
          <property name="favorPathExtension" value="false" />
          <property name="favorParameter" value="true" />
          <property name="mediaTypes">
              <value>
                  json=application/json
                  xml=application/xml
              </value>
          </property>
      </bean>
      <mvc:annotation-driven
          content-negotiation-manager="contentNegotiationManager">
          <mvc:path-matching suffix-pattern="false" registered-suffixes-only="true" />
      </mvc:annotation-driven>
      

      将此添加到 application-xml

      【讨论】:

      • 赞成 - 这是这里唯一明确ContentNegotiationManagerFactoryBean 和 contentNegotiationManager 配置项都是必需的的答案
      【解决方案9】:

      添加“:.+”对我有用,但直到我删除了外部大括号。

      value = {"/username/{id:.+}"} 不起作用

      value = "/username/{id:.+}" 作品

      希望我能帮助到某人:)

      【讨论】:

      • 那是因为花括号对 RegEx 进行评估,而您已经有一些 id
      【解决方案10】:

      这是一种完全依赖于 java 配置的方法:

      import org.springframework.context.annotation.Bean;
      import org.springframework.context.annotation.Configuration;
      import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;
      import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
      
      @Configuration
      public class MvcConfig extends WebMvcConfigurationSupport{
      
          @Bean
          public RequestMappingHandlerMapping requestMappingHandlerMapping() {
              RequestMappingHandlerMapping handlerMapping = super.requestMappingHandlerMapping();
              handlerMapping.setUseSuffixPatternMatch(false);
              handlerMapping.setUseTrailingSlashMatch(false);
              return handlerMapping;
          }
      }
      

      【讨论】:

      • 谢谢,帮我解决了。此外,它非常干净和明确。 +1
      【解决方案11】:

      在 Spring Boot 中,正则表达式解决了类似的问题

      @GetMapping("/path/{param1:.+}")
      

      【讨论】:

      • 请注意,这只适用于一个点。它不适用于电子邮件地址。
      • @8bitjunkie Sth like "/{code:.+}" 适用于许多点而不是一个,即61.12.7 它也适用于即k.a.p@o.i.n
      • @8bitjunkie 我已经用 IP 地址对其进行了测试。它工作得很好。所以,这意味着它适用于多个点。
      【解决方案12】:

      在 Spring Boot Rest Controller 中,我通过以下步骤解决了这些问题:

      休息控制器:

      @GetMapping("/statusByEmail/{email:.+}/")
      public String statusByEmail(@PathVariable(value = "email") String email){
        //code
      }
      

      来自 Rest 客户端:

      Get http://mywebhook.com/statusByEmail/abc.test@gmail.com/
      

      【讨论】:

      • 这个答案依赖于斜杠才能工作。
      • 就像一个魅力(也没有尾随斜杠)。谢谢!
      【解决方案13】:

      对我来说

      @GetMapping(path = "/a/{variableName:.+}")
      

      确实有效,但前提是您还将请求 url 中的“点”编码为“%2E”,然后它才有效。但是要求所有的 URL 都是……这不是“标准”编码,尽管有效。感觉像是一个错误:|

      另一种解决方法,类似于“斜杠”的方式是移动将具有点“内联”的变量例如:

      @GetMapping(path = "/{variableName}/a")

      现在所有点都将被保留,无需修改。

      【讨论】:

        【解决方案14】:

        终于在Spring Docs找到了解决方案:

        要完全禁用文件扩展名,您必须设置以下两项:

         useSuffixPatternMatching(false), see PathMatchConfigurer
        
         favorPathExtension(false), see ContentNegotiationConfigurer
        

        将此添加到我的WebMvcConfigurerAdapter 实现解决了问题:

        @Override
        public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
            configurer.favorPathExtension(false);
        }
        
        @Override
        public void configurePathMatch(PathMatchConfigurer matcher) {
            matcher.setUseSuffixPatternMatch(false);
        }
        

        【讨论】:

          【解决方案15】:

          从 Spring 5.2.4 开始(Spring Boot v2.2.6.RELEASE) PathMatchConfigurer.setUseSuffixPatternMatchContentNegotiationConfigurer.favorPathExtension 已被弃用(https://spring.io/blog/2020/03/24/spring-framework-5-2-5-available-nowhttps://github.com/spring-projects/spring-framework/issues/24179)。

          真正的问题是客户端请求特定的媒体类型(如 .com),而 Spring 默认添加了所有这些媒体类型。在大多数情况下,您的 REST 控制器只会生成 JSON,因此它不支持请求的输出格式 (.com)。 为了克服这个问题,你应该通过更新你的休息控制器(或特定方法)来支持'输出'格式(@RequestMapping(produces = MediaType.ALL_VALUE))并且当然允许像点这样的字符({username:.+})。

          例子:

          @RequestMapping(value = USERNAME, consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
          public class UsernameAPI {
          
              private final UsernameService service;
          
              @GetMapping(value = "/{username:.+}", consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.ALL_VALUE)
              public ResponseEntity isUsernameAlreadyInUse(@PathVariable(value = "username") @Valid @Size(max = 255) String username) {
                  log.debug("Check if username already exists");
                  if (service.doesUsernameExist(username)) {
                      return ResponseEntity.status(HttpStatus.NO_CONTENT).build();
                  }
                  return ResponseEntity.notFound().build();
              }
          }
          

          Spring 5.3 及更高版本将仅匹配已注册的后缀(媒体类型)。

          【讨论】:

            【解决方案16】:

            如果您同时编写后端和前端,另一个简单的解决方案是在前面的 URL 末尾附加一个“/”。如果是这样,您无需更改后端...

            somepath/myemail@gmail.com/
            

            要快乐!

            【讨论】:

              【解决方案17】:

              如果您使用的是 Spring 3.2+,那么以下解决方案将有所帮助。这将处理所有 url,因此肯定比在请求 URI 映射中应用正则表达式模式来允许更好。喜欢/somepath/{variable:.+}

              在xml文件中定义一个bean

              <bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping">
                      <property name="useSuffixPatternMatch" value="false"/>
                      <property name="useRegisteredSuffixPatternMatch" value="true"/>
                  </bean>
              

              标志的用法可以在文档中找到。我剪短来解释一下

              useRegisteredSuffixPatternMatch 的解释据说可以解决问题。来自类中的java doc

              如果启用,映射到“/users”的控制器方法也匹配到 "/users.json" 假设 ".json" 是注册的文件扩展名 提供 {@link #setContentNegotiationManager(ContentNegotiationManager) 内容协商管理器}。这对于只允许特定的 要使用的 URL 扩展以及在“。”的情况下使用。在 URL 路径中 可能导致路径变量内容的解释不明确,(例如,给定 “/users/{user}”和传入的 URL,例如“/users/john.j.joe”和 “/users/john.j.joe.json”)。

              【讨论】:

                【解决方案18】:

                简单的解决方案修复:在 @RequestMapping 中添加正则表达式 {q:.+}

                @RequestMapping("medici/james/Site")
                public class WebSiteController {
                
                    @RequestMapping(value = "/{site:.+}", method = RequestMethod.GET)
                    public ModelAndView display(@PathVariable("site") String site) {
                        return getModelAndView(site, "web site");
                
                    }
                }
                

                现在,对于输入 /site/jamesmedice.com,“site”将显示正确的 james'site

                【讨论】:

                  猜你喜欢
                  • 2011-04-01
                  • 2013-07-07
                  • 2015-11-24
                  • 2013-11-17
                  • 2017-11-10
                  • 2011-12-21
                  • 2016-05-09
                  相关资源
                  最近更新 更多