ChatGPT解决这个技术问题 Extra ChatGPT

Spring MVC type conversion : PropertyEditor or Converter?

I am looking for the easiest and simplest way to bind and convert data in Spring MVC. If possible, without doing any xml configuration.

So far I've been using PropertyEditors like so :

public class CategoryEditor extends PropertyEditorSupport {

    // Converts a String to a Category (when submitting form)
    @Override
    public void setAsText(String text) {
        Category c = new Category(text);
        this.setValue(c);
    }

    // Converts a Category to a String (when displaying form)
    @Override
    public String getAsText() {
        Category c = (Category) this.getValue();
        return c.getName();
    }

}

and

...
public class MyController {

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

    ...

}

It is simple : both conversion are defined in the same class, and the binding is straightforward. If I wanted to do a general binding across all my controllers, I could still add 3 lines in my xml config.

But Spring 3.x introduced a new way to do it, using Converters :

Within a Spring container, this system can be used as an alternative to PropertyEditors

So let's say I want to use Converters because it is "the latest alternative". I would have to create two converters :

public class StringToCategory implements Converter<String, Category> {

    @Override
    public Category convert(String source) {
        Category c = new Category(source);
        return c;
    }

}

public class CategoryToString implements Converter<Category, String> {

    @Override
    public String convert(Category source) {
        return source.getName();
    }

}

First drawback : I have to make two classes. Benefit : no need to cast thanks to genericity.

Then, how do I simply data bind the converters ?

Second drawback : I haven't found any simple way (annotations or other programmatic facilities) to do it in a controller : nothing like someSpringObject.registerCustomConverter(...);.

The only ways I've found would be tedious, not simple, and only about general cross-controller binding :

XML config :

Java config (only in Spring 3.1+) : @EnableWebMvc @Configuration public class WebConfig extends WebMvcConfigurerAdapter { @Override protected void addFormatters(FormatterRegistry registry) { registry.addConverter(new StringToCategory()); registry.addConverter(new CategoryToString()); } }

With all these drawbacks, why using Converters ? Am I missing something ? Are there other tricks that I am not aware of ?

I am tempted to go on using PropertyEditors... Binding is much easier and quicker.

Note (I stumbled too, using Spring 3.2.17) :when using there is a need to actually refer to this conversionService bean:
addFormatters(...) must be public. Also since 5.0 WebMvcConfigurerAdapter is deprecated.
If looking for a newer alternative in Spring 3. You could check Spring Field Formatters (Replacement for PropertyEditor) docs.spring.io/spring-framework/docs/current/reference/html/…

B
Biju Kunjummen

With all these drawbacks, why using Converters ? Am I missing something ? Are there other tricks that I am not aware of ?

No, I think you have very comprehensively described both PropertyEditor and Converter, how each one is declared and registered.

In my mind, PropertyEditors are limited in scope - they help convert String to a type, and this string typically comes from UI, and so registering a PropertyEditor using @InitBinder and using WebDataBinder makes sense.

Converter on the other hand is more generic, it is intended for ANY conversion in the system - not just for UI related conversions(String to target type). For eg, Spring Integration uses a converter extensively for converting a message payload to a desired type.

I think for UI related flows PropertyEditors are still appropriate especially for the case where you need to do something custom for a specific command property. For other cases, I would take the recommendation from Spring reference and write a converter instead(for eg, to convert from a Long id to an entity say, as a sample).


Another good thing that converters are stateless, while property editors are stateful and created many times and implemented with many api calls, I don't think that this will have any major impact on the performance but converters are just cleaner and simpler.
@Boris cleaner yes, but not simpler, especially for a beginner : you have to write 2 converter classes + add several lines of in xml config or java config. I am talking about Spring MVC form submitting/displaying, with general conversions (not only entities).
A
Alexander

For to/from String conversions use formatters (implement org.springframework.format.Formatter) instead of converters. It has print(...) and parse(...) methods, so you need only one class instead of two. To register them, use FormattingConversionServiceFactoryBean, which can register both converters and formatters, instead of ConversionServiceFactoryBean. The new Formatter stuff has a couple of additional benefits: Formatter interface supplies the Locale object in its print(...) and parse(...) methods, so your string conversion can be locale-sensitive In addition to the pre-registered formatters, FormattingConversionServiceFactoryBean comes with a couple of handy preregistered AnnotationFormatterFactory objects, that allow you to specify additional formatting parameters via annotation. For example: @RequestParam @DateTimeFormat(pattern="MM-dd-yy") LocalDate baseDate ... It's not very difficult to create your own AnnotationFormatterFactory classes, see Spring's NumberFormatAnnotationFormatterFactory for a simple example. I think this eliminates the need in controller-specific formatters/editors. Use one ConversionService for all controllers and customize the formatting via annotations. I agree that if you still needs some controller-specific string conversion, the simplest way is still to use custom property editor. (I tried to call 'binder.setConversionService(...)' in my @InitBinder method, but it fails, since the binder object comes with the 'global' conversion service already set. Seems like per-controller conversion classes are discouraged in Spring 3).


B
Boris Treukhov

The simplest (assuming that you are using a persistence framework), but not the perfect way is to implement a generic entity converter via ConditionalGenericConverter interface that will convert entities using their metadata.

For example, if you are using JPA, this converter may look if the specified class has @Entity annotation, and use @Id annotated field to extract information and perform the lookup automatically using the supplied String value as an Id for lookup.

public interface ConditionalGenericConverter extends GenericConverter {
    boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType);
}

ConditionalGenericConverter is an "ultimate weapon" of Spring convertion API, but being implemented once it will be able to process most of entity convertions, saving developer time - it's a great relief when you just specify entity classes as parameters of your controller and never think about implementing a new converter(except for custom and non-entity types, of course).


Nice solution to deal with entity conversion only, thanks for the trick. Not simple at the beginning as you have to write one more class, but simple and time savy in the long run.
Btw such a converter can be implemented for any types that adher to some generic contract - another example: if your enums implement some common reverse lookup interface - then you also will be able to implement a generic converter (it will be similar to stackoverflow.com/questions/5178622/…)
@JeromeDalbert yes it's a little difficult for a beginner to do some heavy weight stuff, but if you have a team of developers it will be simpler ) P.S. And it will become boring to register the same property editors every time on form binding anyway )
n
ntm

You can sort of work around the need for having two separate Converter classes by implementing the two Converters as static inner classes.

public class FooConverter {
    public static class BarToBaz implements Converter<Bar, Baz> {
        @Override public Baz convert(Bar bar) { ... }
    }
    public static class BazToBar implements Converter<Baz, Bar> {
        @Override public Bar convert(Baz baz) { ... }
    }
}

You would still need to register both of them separately, but at least this cuts down on the number of files you need to modify if you make any changes.