Spring 3.0 MVC 绑定枚举大小写敏感

Posted

技术标签:

【中文标题】Spring 3.0 MVC 绑定枚举大小写敏感【英文标题】:Spring 3.0 MVC binding Enums Case Sensitive 【发布时间】:2011-06-04 18:20:13 【问题描述】:

如果我在 Spring 控制器中有这样的 RequestMapping...

@RequestMapping(method = RequestMethod.GET, value = "product")
public ModelAndView getPage(@PathVariable Product product)

Product 是一个枚举。例如。产品.首页

当我请求页面时,mysite.com/home

我明白了

Unable to convert value "home" from type 'java.lang.String' to type 'domain.model.product.Product'; nested exception is java.lang.IllegalArgumentException: No enum const class domain.model.product.Product.home

有没有办法让枚举类型转换器理解小写的 home 实际上是 Home?

我想保持 url 不区分大小写,并且我的 Java 枚举使用标准大写字母。

谢谢

解决方案

public class ProductEnumConverter extends PropertyEditorSupport

    @Override public void setAsText(final String text) throws IllegalArgumentException
    
        setValue(Product.valueOf(WordUtils.capitalizeFully(text.trim())));
    

注册

<bean class="org.springframework.beans.factory.config.CustomEditorConfigurer">
        <property name="customEditors">
            <map>
                <entry key="domain.model.product.Product" value="domain.infrastructure.ProductEnumConverter"/>
            </map>
        </property>
    </bean>

添加到需要特殊转换的控制器

@InitBinder
public void initBinder(WebDataBinder binder)

    binder.registerCustomEditor(Product.class, new ProductEnumConverter());
 

【问题讨论】:

Spring Boot 中有 RelaxedConversionServiceStringToEnumIgnoringCaseConverterFactory 类,但它们不公开。 这里有一个解决方案使用StringToEnumIgnoringCaseConverterFactory***.com/questions/55169848/… 我没有对此进行测试,但看起来这会起作用:vianneyfaivre.com/tech/… 【参考方案1】:

一般来说,您想创建一个新的 PropertyEditor 来为您进行规范化,然后您将其注册到您的 Controller 中,如下所示:

@InitBinder
 public void initBinder(WebDataBinder binder) 

  binder.registerCustomEditor(Product.class,
    new CaseInsensitivePropertyEditor());
 

【讨论】:

不想把这个添加到每个使用这个枚举的控制器上。是否有全球解决方案? @dom farr: 有,但看起来 skaffman 已经打败了我。看看他的回答。 对于 Spring 3.2 用户;您现在可以使用@ControllerAdvice 注册全局初始化绑定器(除其他外)。请参阅the reference guide 了解更多信息。 @GaryF 所以我们不需要创建一个 CustomEditorConfigurer bean 吗?控制器建议中的 init binder 就足够了吗? 似乎不适用于RequestBody。有什么办法吗?【参考方案2】:

我想你必须implement a Custom PropertyEditor。

类似这样的:

public class ProductEditor extends PropertyEditorSupport

    @Override
    public void setAsText(final String text)
        setValue(Product.valueOf(text.toUpperCase()));
    


如何绑定请看GaryF's answer

如果您在枚举常量中使用小写字母(您可能不应该,但仍然如此),这是一个更宽容的版本:

@Override
public void setAsText(final String text)
    Product product = null;
    for(final Product candidate : Product.values())
        if(candidate.name().equalsIgnoreCase(text))
            product = candidate;
            break;
        
    
    setValue(product);

【讨论】:

似乎 GenericConversionService.convert 妨碍了这种类型的解决方案。 @dom farr:是的,如果你使用ConversionService,你还需要implement a custom Converter。 是的,这是我首先尝试的。它只是没有用,所以我来这里问这个问题。也许我没有正确注册?我使用了 ConversionServiceFactoryBean,但正如我所说,不行。 Sean,我正在使用 spring boot,并且在将字符串映射到作为我的控制器参数的对象上的枚举之后,似乎调用了 initbinder 方法。我错过了什么吗? @AlexanderSuraphel 见上面的 cmets。这些天来,默认情况下可能会启用 ConversionService。实现自定义转换器【参考方案3】:

也可以创建一个通用转换器,它可以像这样与所有枚举一起工作:

public class CaseInsensitiveConverter<T extends Enum<T>> extends PropertyEditorSupport 

    private final Class<T> typeParameterClass;

    public CaseInsensitiveConverter(Class<T> typeParameterClass) 
        super();
        this.typeParameterClass = typeParameterClass;
    

    @Override
    public void setAsText(final String text) throws IllegalArgumentException 
        String upper = text.toUpperCase(); // or something more robust
        T value = T.valueOf(typeParameterClass, upper);
        setValue(value);
    

用法:

@InitBinder
public void initBinder(WebDataBinder binder) 
    binder.registerCustomEditor(MyEnum.class, new CaseInsensitiveConverter<>(MyEnum.class));

或者像 skaffman 解释的那样在全球范围内

【讨论】:

很好,我喜欢泛型类!它可能效率不高,但我更喜欢使用 equalsIgnoreCase 而不是你的 toUpperCase+valueOf。 getEnumConstants 应该在 typeParameterClass 字段之外可用。将此实现与@SeanPatrickFloyd 中的方法相结合 重新考虑在每个请求上迭代枚举常量太痛苦了。我将在 CaseInsensitiveConverter 中添加一个 HashMap 以加快映射速度。【参考方案4】:

在 Spring Boot 2 中,您可以使用 ApplicationConversionService。它提供了一些有用的转换器,尤其是org.springframework.boot.convert.StringToEnumIgnoringCaseConverterFactory - 负责将字符串值转换为枚举实例。这是我设法找到的最通用的(我们不需要为每个枚举创建单独的转换器/格式化程序)和最简单的解决方案。

import org.springframework.boot.convert.ApplicationConversionService;
import org.springframework.context.annotation.Configuration;
import org.springframework.format.FormatterRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class AppWebMvcConfigurer implements WebMvcConfigurer 
    @Override
    public void addFormatters(FormatterRegistry registry) 
        ApplicationConversionService.configure(registry);
    

我知道问题是关于 Spring 3 的,但这是在谷歌搜索 spring mvc enums case insensitive 短语时的第一个结果。

【讨论】:

【参考方案5】:

要添加到@GaryF 的答案并解决您对它的评论,您可以通过将全局自定义属性编辑器注入自定义AnnotationMethodHandlerAdapter 来声明它们。 Spring MVC 通常默认注册其中一个,但如果你选择,你可以给它一个特殊配置的,例如

<bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter">
  <property name="webBindingInitializer">
    <bean class="org.springframework.web.bind.support.ConfigurableWebBindingInitializer">
      <property name="propertyEditorRegistrars">
        <list>
          <bean class="com.xyz.MyPropertyEditorRegistrar"/>
        </list>
      </property>
    </bean>
  </property>
</bean>

MyPropertyEditorRegistrarPropertyEditorRegistrar 的一个实例,它反过来用Spring 注册自定义PropertyEditor 对象。

只需声明这一点就足够了。

【讨论】:

@skaffman。如果我删除所有控制器中的单个 initBinder,似乎无法使其正常工作。不过我会继续挖掘这个。谢谢 @dom:这个方法确实有效,我自己使用它,但我可能有一些细节错误 @skaffman。也许这是我的问题:jira.springsource.org/browse/SPR-7077 @dom:是的,很好,这确实是问题所在。您是否特别需要&lt;mvc:annotation-driven/&gt;?除非您使用 Jackson/JSON 映射或 @Valid,否则您可以忽略它。 @skaffman。不幸的是,Jackson/JSON 映射和 @Valid 正在被使用,这意味着我现在必须在每个控制器中坚持使用 @InitBinder。

以上是关于Spring 3.0 MVC 绑定枚举大小写敏感的主要内容,如果未能解决你的问题,请参考以下文章

springmvc 3.0啥时间发部

Spring MVC 从 2.5 到 3.0

使用 Angular JS 在下拉列表中绑定 MVC 枚举列表

在 MVC 4 中将枚举绑定到 DropDownList? [复制]

关于spring mvc 3.0注解,注入失败的问题

Java EE - Servlet 3.0 和 Spring MVC