Spring MVC类型转换:PropertyEditor还是Converter?


129

我正在寻找在Spring MVC中绑定和转换数据的最简单方法。如果可能,不进行任何xml配置。

到目前为止,我一直在像这样使用PropertyEditors

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

}

...
public class MyController {

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

    ...

}

很简单:两个转换都在同一个类中定义,并且绑定很简单。如果我想在所有控制器之间进行常规绑定,则仍可以在xml config中添加3行


但是Spring 3.x引入了一种使用Converters的新方法:

在Spring容器中,此系统可用作PropertyEditors的替代方案

假设我要使用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();
    }

}

第一个缺点:我必须上两节课。好处:由于通用性而无需强制转换。

然后,如何简单地将数据绑定到转换器?

第二个缺点:我还没有找到在控制器中执行此操作的任何简单方法(注释或其他编程工具):类似于someSpringObject.registerCustomConverter(...);

我发现的唯一方法将是乏味的,不简单的,并且仅关于常规的跨控制器绑定:

  • XML配置

    <bean id="conversionService"
      class="org.springframework.context.support.ConversionServiceFactoryBean">
        <property name="converters">
            <set>
                <bean class="somepackage.StringToCategory"/>
                <bean class="somepackage.CategoryToString"/>
            </set>
        </property>
    </bean>
    
  • Java配置仅在Spring 3.1+中):

    @EnableWebMvc
    @Configuration
    public class WebConfig extends WebMvcConfigurerAdapter {
    
        @Override
        protected void addFormatters(FormatterRegistry registry) {
            registry.addConverter(new StringToCategory());
            registry.addConverter(new CategoryToString());
        }
    
    }
    

有所有这些缺点,为什么要使用Converters?我想念什么吗?还有其他我不知道的技巧吗?

我很想继续使用PropertyEditors ...绑定更加容易和快捷。


注意(我也偶然发现,使用Spring 3.2.17):当使用<mvc:annotation-driven />时,实际上需要引用此conversionService bean:<mvc:annotation-driven conversion-service =“ conversionService” />
mauhiz '16

addFormatters(...)必须是公共的。同样从5.0开始不推荐使用WebMvcConfigurerAdapter。
Paco Abato

Answers:


55

有所有这些缺点,为什么要使用Converters?我想念什么吗?还有其他我不知道的技巧吗?

不,我认为您已经非常全面地描述了PropertyEditor和Converter,以及如何声明和注册它们。

在我看来,PropertyEditor的范围有限-它们有助于将String转换为类型,并且该字符串通常来自UI,因此使用@InitBinder和WebDataBinder注册PropertyEditor是有意义的。

另一方面,Converter更通用,它旨在用于系统中的任何转换-不仅用于与UI相关的转换(从字符串到目标类型)。例如,Spring Integration广泛使用转换器将消息有效负载转换为所需类型。

我认为对于与UI相关的流程,PropertyEditor仍然适用,特别是对于需要为特定命令属性进行自定义的情况。对于其他情况,我将从Spring参考中获取建议,并编写一个转换器(例如,将Long ID转换为实体,例如作为示例)。


5
转换器是无状态的,这是另一件好事,而属性编辑器是有状态的,并且创建了许多次,并通过许多api调用实现,但我认为这不会对性能产生任何重大影响,但是转换器更加简洁。
鲍里斯·特鲁霍霍夫

1
@Boris cleaner是的,但并不简单,特别是对于初学者:您必须编写2个转换器类+在xml config或java config中添加几行。我说的是Spring MVC表单的提交/显示以及常规转换(不仅限于实体)。
杰罗姆·达伯特

16
  1. 对于从字符串到/从字符串的转换,请使用格式器(实现org.springframework.format.Formatter)而不是转换器。它具有print(...)parse(...)方法,因此您只需要一个类,而不是两个类。要注册它们,请使用FormattingConversionServiceFactoryBean,它可以注册转换器和格式化程序,而不是ConversionServiceFactoryBean
  2. Formatter的新功能还有其他一些好处:
    • Formatter接口在其print(...)parse(...)方法中提供Locale对象,因此您的字符串转换可以是对语言环境敏感的
    • 除了预先注册的格式化程序外,FormattingConversionServiceFactoryBean还附带了几个方便的预先注册的AnnotationFormatterFactory对象,使您可以通过注释指定其他格式化参数。例如: @RequestParam@DateTimeFormat(pattern =“ MM-dd-yy”)LocalDate baseDate ... 创建自己的AnnotationFormatterFactory类不是很困难,有关简单示例,请参见Spring的NumberFormatAnnotationFormatterFactory。我认为这消除了特定于控制器的格式化程序/编辑器的需要。对所有控制器使用一个ConversionService,并通过注释自定义格式。
  3. 我同意,如果您仍然需要某些特定于控制器的字符串转换,最简单的方法仍然是使用自定义属性编辑器。(我试图在我的@InitBinder方法中调用' binder.setConversionService(...) ' ,但是失败了,因为绑定对象已经设置了'global'转换服务。在这种情况下,不鼓励使用按控制器转换类春天3)。

7

最简单的方法(假设您使用的是持久性框架),但不是完美的方法是通过ConditionalGenericConverter接口实现通用实体转换器,该接口将使用其元数据转换实体。

例如,如果您使用的是JPA,则此转换器可以查看指定的类是否具有@Entity注释,并使用带@Id注释的字段提取信息,并使用提供的String值作为查找的ID自动执行查找。

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

ConditionalGenericConverter 是Spring转换API的“终极武器”,但是一旦能够处理大多数实体转换,就可以实施,从而节省了开发人员的时间-当您仅将实体类指定为控制器的参数并且从不考虑实现时,这是一个极大的缓解一个新的转换器(当然,自定义和非实体类型除外)。


不错的解决方案,只处理实体转换,谢谢。从一开始就不那么简单,因为您必须再编写一堂课,但从长远来看却很简单且省时。
杰罗姆·达伯特

顺便说一句,这样的转换器可以针对遵循某种通用协定的任何类型实现-另一个示例:如果您的枚举实现了一些通用的反向查找接口-那么您也将能够实现通用转换器(类似于stackoverflow.com / questions / 5178622 /…
鲍里斯·特鲁霍霍夫

@JeromeDalbert是的,对于初学者来说,做一些繁重的工作有点困难,但是如果您有开发人员团队,它将变得更简单)PS并且每次在表单绑定上每次都注册相同的属性编辑器将变得很无聊)
鲍里斯·特鲁霍夫(Boris Treukhov)2012年

1

通过将两个Converter实现为静态内部类,可以解决需要两个单独的Converter类的问题。

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) { ... }
    }
}

您仍然需要分别注册这两个文件,但是至少可以减少进行任何更改后需要修改的文件数量。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.