การแปลงประเภท 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());
    }

    ...

}

เป็นเรื่องง่าย: การแปลงทั้งสองถูกกำหนดไว้ในคลาสเดียวกันและการเชื่อมโยงนั้นตรงไปตรงมา ถ้าฉันต้องการทำการเชื่อมโยงทั่วไปกับคอนโทรลเลอร์ทั้งหมดของฉันฉันยังสามารถเพิ่ม3 บรรทัดในการกำหนดค่า xml ของฉันได้


แต่ Spring 3.x ได้นำเสนอวิธีการใหม่โดยใช้ตัวแปลง :

ภายในคอนเทนเนอร์ 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

addFormatters (... ) ต้องเป็นสาธารณะ นอกจากนี้ตั้งแต่ 5.0 WebMvcConfigurerAdapter เลิกใช้แล้ว
Paco Abato

คำตอบ:


55

ด้วยข้อเสียเหล่านี้ทำไมต้องใช้ Converters? ฉันพลาดอะไรไปรึเปล่า ? มีเทคนิคอื่น ๆ ที่ฉันไม่ทราบหรือไม่?

ไม่ฉันคิดว่าคุณได้อธิบายอย่างละเอียดทั้ง PropertyEditor และ Converter แล้ววิธีการประกาศและลงทะเบียนแต่ละรายการ

ในใจของฉัน PropertyEditors มีขอบเขต จำกัด - ช่วยแปลง String เป็นประเภทและโดยทั่วไปแล้วสตริงนี้มาจาก UI ดังนั้นการลงทะเบียน PropertyEditor โดยใช้ @InitBinder และการใช้ WebDataBinder จึงสมเหตุสมผล

ในทางกลับกันตัวแปลงมีลักษณะทั่วไปมากกว่าซึ่งมีไว้สำหรับการแปลงใด ๆ ในระบบไม่ใช่เฉพาะสำหรับการแปลงที่เกี่ยวข้องกับ UI (ประเภทสตริงที่จะกำหนดเป้าหมาย) ตัวอย่างเช่น Spring Integration ใช้ตัวแปลงสำหรับการแปลงส่วนข้อมูลข้อความเป็นประเภทที่ต้องการ

ฉันคิดว่าสำหรับโฟลว์ที่เกี่ยวข้องกับ UI PropertyEditors ยังคงเหมาะสมโดยเฉพาะอย่างยิ่งสำหรับกรณีที่คุณต้องทำสิ่งที่กำหนดเองสำหรับคุณสมบัติคำสั่งเฉพาะ สำหรับกรณีอื่นฉันจะใช้คำแนะนำจาก Spring reference และเขียนตัวแปลงแทน (เช่นการแปลงจาก Long id เป็นเอนทิตีพูดเป็นตัวอย่าง)


5
สิ่งที่ดีอีกประการหนึ่งที่ผู้แปลงเป็นคนไร้สัญชาติในขณะที่บรรณาธิการทรัพย์สินมีสถานะและสร้างขึ้นหลายครั้งและใช้งานกับการเรียก API จำนวนมากฉันไม่คิดว่าสิ่งนี้จะส่งผลกระทบที่สำคัญต่อประสิทธิภาพ แต่ตัวแปลงนั้นสะอาดและเรียบง่ายกว่า
Boris Treukhov

1
@ Boris Cleaner ใช่ แต่ไม่ง่ายกว่าโดยเฉพาะอย่างยิ่งสำหรับผู้เริ่มต้น: คุณต้องเขียนคลาสตัวแปลง 2 ตัว + เพิ่มหลายบรรทัดใน xml config หรือ java config ฉันกำลังพูดถึงการส่ง / แสดงแบบฟอร์ม Spring MVC พร้อมการแปลงทั่วไป (ไม่ใช่เฉพาะเอนทิตี)
Jerome Dalbert

16
  1. สำหรับการแปลงเป็น / จากสตริงให้ใช้รูปแบบ (ใช้org.springframework.format.Formatter ) แทนตัวแปลง มีวิธีการพิมพ์ (... )และแยกวิเคราะห์ (... )ดังนั้นคุณต้องมีเพียงคลาสเดียวแทนที่จะเป็นสอง หากต้องการลงทะเบียนพวกเขาใช้FormattingConversionServiceFactoryBeanซึ่งสามารถลงทะเบียนทั้งสองแปลงและจัดรูปแบบแทนการConversionServiceFactoryBean
  2. สิ่งใหม่ ๆ ของ Formatter มีประโยชน์เพิ่มเติมสองสามประการ:
    • อินเทอร์เฟซของฟอร์แมตเตอร์จะจัดหาอ็อบเจ็กต์ Locale ในเมธอดprint (... )และparse (... )ดังนั้นการแปลงสตริงของคุณจึงต้องคำนึงถึงโลแคล
    • นอกเหนือจากรูปแบบที่ลงทะเบียนไว้ล่วงหน้าแล้วFormattingConversionServiceFactoryBean ยังมาพร้อมกับวัตถุAnnotationFormatterFactory ที่ลงทะเบียนไว้ล่วงหน้าซึ่งช่วยให้คุณสามารถระบุพารามิเตอร์การจัดรูปแบบเพิ่มเติมผ่านคำอธิบายประกอบ ตัวอย่างเช่น @RequestParam@DateTimeFormat (รูปแบบ = "MM-DD-yy")LocalDate baseDate ... การสร้างคลาสAnnotationFormatterFactoryของคุณเองนั้นไม่ใช่เรื่องยากโปรดดูที่ Spring's NumberFormatAnnotationFormatterFactoryสำหรับตัวอย่างง่ายๆ ฉันคิดว่าสิ่งนี้ช่วยลดความจำเป็นในการฟอร์แมต / ตัวแก้ไขเฉพาะคอนโทรลเลอร์ ใช้ConversionServiceหนึ่งรายการสำหรับคอนโทรลเลอร์ทั้งหมดและปรับแต่งการจัดรูปแบบผ่านคำอธิบายประกอบ
  3. ฉันยอมรับว่าหากคุณยังต้องการการแปลงสตริงเฉพาะคอนโทรลเลอร์วิธีที่ง่ายที่สุดคือการใช้ตัวแก้ไขคุณสมบัติที่กำหนดเอง (ฉันพยายามเรียก ' binder.setConversionService (... ) ' ในเมธอด@InitBinderของฉันแต่มันล้มเหลวเนื่องจากอ็อบเจ็กต์ binder มาพร้อมกับบริการการแปลง 'global' ที่ตั้งค่าไว้แล้วดูเหมือนว่าคลาสการแปลงต่อคอนโทรลเลอร์จะไม่สนับสนุนใน ฤดูใบไม้ผลิ 3).

7

วิธีที่ง่ายที่สุด (สมมติว่าคุณกำลังใช้เฟรมเวิร์กการคงอยู่) แต่ไม่ใช่วิธีที่ดีที่สุดคือการใช้ตัวแปลงเอนทิตีทั่วไปผ่านConditionalGenericConverterอินเทอร์เฟซที่จะแปลงเอนทิตีโดยใช้ข้อมูลเมตา

ตัวอย่างเช่นหากคุณใช้ JPA ตัวแปลงนี้อาจดูว่าคลาสที่ระบุมี@Entityคำอธิบายประกอบหรือไม่และใช้@Idฟิลด์คำอธิบายประกอบเพื่อดึงข้อมูลและดำเนินการค้นหาโดยอัตโนมัติโดยใช้ค่าสตริงที่ให้มาเป็น Id สำหรับการค้นหา

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

ConditionalGenericConverter เป็น "อาวุธขั้นสูงสุด" ของ Spring Convertion API แต่เมื่อนำไปใช้งานได้จะสามารถประมวลผลการแปลงเอนทิตีส่วนใหญ่ได้ช่วยประหยัดเวลาของนักพัฒนา - เป็นความโล่งใจอย่างมากเมื่อคุณระบุคลาสเอนทิตีเป็นพารามิเตอร์ของคอนโทรลเลอร์ของคุณและไม่เคยคิดถึงการใช้งาน ตัวแปลงใหม่ (ยกเว้นประเภทที่กำหนดเองและไม่ใช่เอนทิตีแน่นอน)


ทางออกที่ดีในการจัดการกับการแปลงเอนทิตีเท่านั้นขอบคุณสำหรับเคล็ดลับ ไม่ใช่เรื่องง่ายในตอนเริ่มต้นเพราะคุณต้องเขียนอีกหนึ่งคลาส แต่จะง่ายและประหยัดเวลาในระยะยาว
Jerome Dalbert

Btw ตัวแปลงดังกล่าวสามารถใช้งานได้กับทุกประเภทที่เป็นไปตามสัญญาทั่วไป - อีกตัวอย่างหนึ่ง: หาก enums ของคุณใช้อินเทอร์เฟซการค้นหาแบบย้อนกลับทั่วไป - คุณจะสามารถใช้ตัวแปลงทั่วไปได้ (จะคล้ายกับstackoverflow.com / คำถาม / 5178622 / … )
Boris Treukhov

@JeromeDalbert ใช่มันเป็นเรื่องยากสำหรับผู้เริ่มต้นที่จะทำของหนัก ๆ แต่ถ้าคุณมีทีมนักพัฒนามันจะง่ายกว่านี้) ป.ล. และมันจะน่าเบื่อที่จะลงทะเบียนผู้แก้ไขคุณสมบัติเดิมทุกครั้งในการผูกแบบฟอร์มอยู่ดี)
Boris Treukhov

1

คุณสามารถจัดการกับความจำเป็นในการมีคลาส 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) { ... }
    }
}

คุณยังคงต้องลงทะเบียนทั้งสองไฟล์แยกกัน แต่อย่างน้อยก็ช่วยลดจำนวนไฟล์ที่คุณต้องแก้ไขหากคุณทำการเปลี่ยนแปลงใด ๆ

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.