Spring 3.0 MVC binding Enums Case Sensitive

2019-01-10 22:35发布

问题:

If I have a RequestMapping in a Spring controller like so...

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

And Product is an enum. eg. Product.Home

When I request the page, mysite.com/home

I get

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

Is there a way to have the enum type converter to understand that lower case home is actually Home?

I'd like to keep the url case insensitive and my Java enums with standard capital letters.

Thanks

Solution

public class ProductEnumConverter extends PropertyEditorSupport
{
    @Override public void setAsText(final String text) throws IllegalArgumentException
    {
        setValue(Product.valueOf(WordUtils.capitalizeFully(text.trim())));
    }
}

registering it

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

Add to controllers that need special conversion

@InitBinder
public void initBinder(WebDataBinder binder)
{
    binder.registerCustomEditor(Product.class, new ProductEnumConverter());
} 

回答1:

Broadly speaking, you want to create a new PropertyEditor that does the normalisation for you, and then you register that in your Controller like so:

@InitBinder
 public void initBinder(WebDataBinder binder) {

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


回答2:

I think you will have to implement a Custom PropertyEditor.

Something like this:

public class ProductEditor extends PropertyEditorSupport{

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

}

See GaryF's answer on how to bind it

Here's a more tolerant version in case you use lower case in your enum constants (which you probably shouldn't, but still):

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


回答3:

It's also possible to create a generic converter that will work with all Enums like this:

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

Usage:

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

Or globally as skaffman explains



回答4:

To add to @GaryF's answer, and to address your comment to it, you can declare global custom property editors by injecting them into a custom AnnotationMethodHandlerAdapter. Spring MVC normally registers one of these by default, but you can give it a specially-configured one if you choose, e.g.

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

MyPropertyEditorRegistrar is an instance of PropertyEditorRegistrar, which in turns registers custom PropertyEditor objects with Spring.

Simply declaring this should be enough.



回答5:

In Spring Boot 2 you can use ApplicationConversionService. It provides some useful converters, especially org.springframework.boot.convert.StringToEnumIgnoringCaseConverterFactory - responsible for converting a string value to an enum instance. This is the most generic (we don't need to create separate converter/formatter per enum) and simplest solution I've managed to find.

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

I know that questions is regarding Spring 3 but this is the first result in google when searching for a spring mvc enums case insensitive phrase.