Konversi tipe Spring MVC: PropertyEditor atau Converter?

129

Saya mencari cara termudah dan paling sederhana untuk mengikat dan mengkonversi data di Spring MVC. Jika memungkinkan, tanpa melakukan konfigurasi xml.

Sejauh ini saya telah menggunakan PropertyEditors seperti:

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

}

dan

...
public class MyController {

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

    ...

}

Sederhana: kedua konversi didefinisikan dalam kelas yang sama, dan pengikatannya mudah. Jika saya ingin melakukan pengikatan umum di semua pengontrol saya, saya masih bisa menambahkan 3 baris dalam konfigurasi xml saya .


Tetapi Spring 3.x memperkenalkan cara baru untuk melakukannya, menggunakan Pengonversi :

Dalam wadah Spring, sistem ini dapat digunakan sebagai alternatif untuk PropertyEditors

Jadi katakanlah saya ingin menggunakan Pengonversi karena ini adalah "alternatif terbaru". Saya harus membuat dua konverter:

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

}

Kelemahan pertama: Saya harus membuat dua kelas. Manfaat: tidak perlu membuang berkat kemurahan hati.

Lalu, bagaimana cara saya mengikat data konverter?

Kelemahan kedua: Saya belum menemukan cara sederhana (anotasi atau fasilitas program lain) untuk melakukannya di controller: tidak seperti someSpringObject.registerCustomConverter(...);.

Satu-satunya cara yang saya temukan akan membosankan, tidak sederhana, dan hanya tentang pengikatan lintas-pengontrol umum:

  • Konfigurasi 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>
  • Konfigurasi Java ( hanya di Spring 3.1+ ):

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

Dengan semua kelemahan ini, mengapa menggunakan Pengonversi? Apakah saya melewatkan sesuatu? Apakah ada trik lain yang tidak saya sadari?

Saya tergoda untuk terus menggunakan PropertyEditors ... Mengikat jauh lebih mudah dan lebih cepat.

Jerome Dalbert
sumber
Catatan (saya tersandung juga, menggunakan Spring 3.2.17): ketika menggunakan <mvc: annotation-driven /> ada kebutuhan untuk benar-benar merujuk ke konversiLayanan kacang ini: <mvc: konversi-driven layanan konversi = "conversionService" />
mauhiz
addFormatters (...) harus bersifat publik. Juga sejak 5.0 WebMvcConfigurerAdapter sudah tidak digunakan lagi.
Paco Abato

Jawaban:

55

Dengan semua kelemahan ini, mengapa menggunakan Pengonversi? Apakah saya melewatkan sesuatu? Apakah ada trik lain yang tidak saya sadari?

Tidak, saya pikir Anda telah mendeskripsikan PropertyEditor dan Konverter dengan sangat komprehensif, bagaimana masing-masing dinyatakan dan terdaftar.

Dalam pikiran saya, PropertyEditor terbatas dalam cakupan - mereka membantu mengkonversi String ke tipe, dan string ini biasanya berasal dari UI, dan mendaftarkan PropertyEditor menggunakan @InitBinder dan menggunakan WebDataBinder masuk akal.

Konverter di sisi lain lebih umum, ini dimaksudkan untuk konversi APAPUN dalam sistem - tidak hanya untuk konversi terkait UI (String ke jenis target). Misalnya, Integrasi Spring menggunakan konverter secara ekstensif untuk mengubah muatan pesan ke jenis yang diinginkan.

Saya pikir untuk aliran terkait UI PropertyEditors masih tepat terutama untuk kasus di mana Anda perlu melakukan sesuatu yang khusus untuk properti perintah tertentu. Untuk kasus lain, saya akan mengambil rekomendasi dari referensi Spring dan menulis konverter sebagai gantinya (misalnya, untuk mengkonversi dari id Panjang ke entitas katakan, sebagai sampel).

Biju Kunjummen
sumber
5
Hal lain yang baik bahwa konverter adalah stateless, sementara editor properti stateful dan dibuat berkali-kali dan diimplementasikan dengan banyak panggilan api, saya tidak berpikir bahwa ini akan berdampak besar pada kinerja tetapi konverter hanya lebih bersih dan lebih sederhana.
Boris Treukhov
1
@Boris cleaner ya, tapi tidak lebih sederhana, terutama untuk pemula: Anda harus menulis 2 kelas konverter + menambahkan beberapa baris dalam xml config atau java config. Saya berbicara tentang pengiriman / tampilan formulir MVC Musim Semi, dengan konversi umum (bukan hanya entitas).
Jerome Dalbert
16
  1. Untuk ke / dari konversi String gunakan formatters (implementasikan org.springframework.format.Formatter ) alih-alih konverter. Ini memiliki metode cetak (...) dan parse (...) , jadi Anda hanya perlu satu kelas, bukan dua. Untuk mendaftarkannya, gunakan FormattingConversionServiceFactoryBean , yang dapat mendaftarkan konverter dan pemformat, alih-alih ConversionServiceFactoryBean .
  2. Item Formatter baru memiliki beberapa manfaat tambahan:
    • Antarmuka Formatter memasok objek Lokal dalam metode cetak (...) dan parse (...) , sehingga konversi string Anda bisa peka terhadap lokal
    • Selain pemformat pra-terdaftar, FormattingConversionServiceFactoryBean dilengkapi dengan beberapa objek AnnotationFormatterFactory yang dipra-pra-pendaftaran yang berguna , yang memungkinkan Anda menentukan parameter pemformatan tambahan melalui anotasi. Misalnya: @RequestParam@DateTimeFormat (pattern = "MM-dd-yy")LocalDate baseDate ... Tidak terlalu sulit untuk membuat kelas AnnotationFormatterFactory Anda sendiri , lihat Spring's NumberFormatAnnotationFormatterFactory untuk contoh sederhana. Saya pikir ini menghilangkan kebutuhan di formatters / editor khusus kontroler. Gunakan satu ConversionService untuk semua pengontrol dan sesuaikan pemformatan melalui anotasi.
  3. Saya setuju bahwa jika Anda masih memerlukan konversi string khusus pengontrol, cara paling sederhana adalah masih menggunakan editor properti khusus. (Saya mencoba memanggil ' binder.setConversionService (...) ' dalam metode @InitBinder saya , tetapi gagal, karena objek binder datang dengan layanan konversi 'global' yang telah ditetapkan. Sepertinya kelas konversi per-controller tidak disarankan dalam Musim semi 3).
Alexander
sumber
7

Yang paling sederhana (dengan asumsi bahwa Anda menggunakan kerangka kerja tetap), tetapi bukan cara yang sempurna adalah dengan mengimplementasikan konverter entitas generik melalui ConditionalGenericConverterantarmuka yang akan mengkonversi entitas menggunakan metadata mereka.

Misalnya, jika Anda menggunakan JPA, konverter ini mungkin terlihat jika kelas yang ditentukan memiliki @Entityanotasi, dan menggunakan @Idbidang beranotasi untuk mengekstrak informasi dan melakukan pencarian secara otomatis menggunakan nilai String yang disediakan sebagai Id untuk pencarian.

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

ConditionalGenericConverter adalah "senjata pamungkas" dari API konversi Spring, tetapi sedang diimplementasikan setelah itu akan dapat memproses sebagian besar konversi entitas, menghemat waktu pengembang - ini sangat melegakan ketika Anda hanya menentukan kelas entitas sebagai parameter controller Anda dan tidak pernah berpikir untuk mengimplementasikan konverter baru (kecuali untuk jenis khusus dan non-entitas, tentu saja).

Boris Treukhov
sumber
Solusi yang bagus untuk berurusan dengan konversi entitas saja, terima kasih atas triknya. Tidak sederhana di awal karena Anda harus menulis satu kelas lagi, tetapi sederhana dan hemat waktu dalam jangka panjang.
Jerome Dalbert
Btw konverter semacam itu dapat diimplementasikan untuk semua jenis yang mematuhi beberapa kontrak generik - contoh lain: jika enum Anda menerapkan beberapa antarmuka pencarian balik umum - maka Anda juga akan dapat menerapkan konverter generik (akan mirip dengan stackoverflow.com / pertanyaan / 5178622 / ... )
Boris Treukhov
@JeromeDalbert ya itu agak sulit bagi pemula untuk melakukan hal-hal yang berat, tetapi jika Anda memiliki tim pengembang itu akan lebih sederhana) PS Dan itu akan menjadi membosankan untuk mendaftarkan editor properti yang sama setiap kali pada formulir mengikat pula)
Boris Treukhov
1

Anda dapat memilah-milah kebutuhan untuk memiliki dua kelas Konverter terpisah dengan menerapkan dua Konverter sebagai kelas statis dalam.

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

Anda masih perlu mendaftarkan keduanya secara terpisah, tetapi setidaknya ini mengurangi jumlah file yang perlu Anda modifikasi jika Anda membuat perubahan.

ntm
sumber