คลาสใดควรได้รับการ autowired โดย Spring (เมื่อใช้การฉีดพึ่งพา)


32

ฉันได้ใช้การพึ่งพาการฉีดในฤดูใบไม้ผลิมาระยะหนึ่งแล้วและฉันเข้าใจว่ามันทำงานอย่างไรและข้อดีและข้อเสียของการใช้มันคืออะไร อย่างไรก็ตามเมื่อฉันสร้างคลาสใหม่ฉันมักจะสงสัย - คลาสนี้ควรจัดการโดย Spring IOC Container หรือไม่

และฉันไม่ต้องการพูดถึงความแตกต่างระหว่างคำอธิบายประกอบ @Autowired, การกำหนดค่า XML, การฉีด setter, การฉีดคอนสตรัคเตอร์ ฯลฯ คำถามของฉันคือคำถามทั่วไป

สมมติว่าเรามีบริการที่มีตัวแปลง:

@Service
public class Service {

    @Autowired
    private Repository repository;

    @Autowired
    private Converter converter;

    public List<CarDto> getAllCars() {
        List<Car> cars = repository.findAll();
        return converter.mapToDto(cars);
    }
}

@Component
public class Converter {

    public CarDto mapToDto(List<Car> cars) {
        return new ArrayList<CarDto>(); // do the mapping here
    }
}

เห็นได้ชัดว่าตัวแปลงไม่มีการอ้างอิงใด ๆ ดังนั้นจึงไม่จำเป็นที่จะต้องทำการยืนยันอัตโนมัติ แต่สำหรับฉันมันดูเหมือนว่าจะดีกว่าที่เป็น autowired รหัสสะอาดและง่ายต่อการทดสอบ ถ้าฉันเขียนรหัสนี้โดยไม่ DI บริการจะมีลักษณะดังนี้:

@Service
public class Service {

    @Autowired
    private Repository repository;

    public List<CarDto> getAllCars() {
        List<Car> cars = repository.findAll();
        Converter converter = new Converter();
        return converter.mapToDto(cars);
    }
}

ตอนนี้มันยากมากที่จะทดสอบ นอกจากนี้ตัวแปลงใหม่จะถูกสร้างขึ้นสำหรับการดำเนินการแปลงทุกครั้งแม้ว่าจะอยู่ในสถานะเดิมเสมอซึ่งดูเหมือนว่าจะอยู่เหนือหัว

Spring MVC มีรูปแบบที่รู้จักกันดีบางประการ: ตัวควบคุมที่ใช้บริการและบริการโดยใช้ที่เก็บ จากนั้นหาก Repository เป็น autowired (ซึ่งโดยปกติจะเป็น) ก็จะต้องมีการ autowired ด้วย และนี่ค่อนข้างชัดเจน แต่เราจะใช้คำอธิบายประกอบ @Component เมื่อใด หากคุณมีคลาส util static (เช่นตัวแปลง, ตัวแม็พ) - คุณ autowire พวกเขาหรือไม่?

คุณพยายามที่จะทำให้ทุกชั้น autowired? จากนั้นการอ้างอิงระดับทั้งหมดนั้นง่ายต่อการฉีด (อีกครั้งง่ายต่อการเข้าใจและทดสอบได้ง่าย) หรือคุณลอง autowire เฉพาะเมื่อจำเป็นจริงๆเท่านั้น?

ฉันใช้เวลาค้นหากฎทั่วไปบางอย่างเกี่ยวกับเวลาที่จะใช้การลงทะเบียนอัตโนมัติ แต่ฉันไม่พบเคล็ดลับเฉพาะใด ๆ โดยปกติแล้วผู้คนพูดถึง "คุณใช้ DI หรือไม่ (ใช่ / ไม่ใช่)" หรือ "คุณชอบการฉีดประเภทใดที่คุณชอบ" ซึ่งไม่ตอบคำถามของฉัน

ฉันจะขอบคุณสำหรับคำแนะนำเกี่ยวกับหัวข้อนี้!


3
+1 สำหรับสิ่งสำคัญ "เมื่อไหร่มันจะไปไกลเกินไป"
Andy Hunt

ตรวจสอบคำถามนี้และบทความนี้
Laiv

คำตอบ:


8

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

@Autowired
private Converter converter;

หรือ

private Converter converter = new Converter();

หรือถ้าชั้นเรียนไม่มีสภาพเลย

private static final Converter CONVERTER = new Converter();

หนึ่งในเกณฑ์สำคัญที่สปริงควรสร้างอินสแตนซ์และฉีดถั่วคือถั่วนั้นซับซ้อนหรือไม่ที่คุณต้องการจำลองในการทดสอบ? ถ้าเป็นเช่นนั้นแล้วฉีด ตัวอย่างเช่นหากตัวแปลงทำการปัดเศษไปยังระบบภายนอกใด ๆ ให้สร้างเป็นส่วนประกอบแทน หรือถ้าค่าส่งคืนมีแผนภูมิการตัดสินใจขนาดใหญ่ที่มีรูปแบบที่เป็นไปได้หลายสิบรายการตามอินพุตดังนั้นให้จำลอง และอื่น ๆ

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


5

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


2

กฎง่ายๆของฉันนั้นขึ้นอยู่กับบางสิ่งที่คุณพูดว่า: การทดสอบ ถามตัวเองว่า " ฉันสามารถทดสอบมันได้อย่างง่ายดายหรือไม่ " หากคำตอบคือใช่ถ้าไม่มีเหตุผลอื่นฉันก็ยินดีที่จะตอบ ดังนั้นหากคุณพัฒนาแบบทดสอบหน่วยในเวลาเดียวกันคุณกำลังพัฒนาคุณจะประหยัดความเจ็บปวดได้มาก

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

ฉันยังสมมติว่าไม่มีเหตุผลเลยที่จะใช้ตัวแปลง dto ต่างกัน


0

TL; DR: วิธีไฮบริดของการลงทะเบียนอัตโนมัติสำหรับ DI และการส่งต่อคอนสตรัคสำหรับ DI สามารถทำให้โค้ดที่คุณแสดงนั้นง่ายขึ้น

ฉันได้ดูคำถามที่คล้ายกันเนื่องจากบาง weblogic กับข้อผิดพลาดเริ่มต้นฤดูใบไม้ผลิกรอบ / ภาวะแทรกซ้อนที่เกี่ยวข้องกับการพึ่งพาการเริ่มต้นถั่ว @ อัตโนมัติ ฉันได้เริ่มผสมในอีกแนวทาง DI: ตัวสร้างการส่งต่อ มันต้องมีเงื่อนไขเช่นที่คุณอยู่ ("ชัดเจนตัวแปลงไม่มีการอ้างอิงใด ๆ ดังนั้นจึงไม่จำเป็นที่จะต้องทำการยืนยันอัตโนมัติ") อย่างไรก็ตามฉันชอบความยืดหยุ่นและมันก็ค่อนข้างตรงไปตรงมา

@Service
public class Service {

    @Autowired
    private Repository repository;

    public List<CarDto> getAllCars(Converter converter) {
        List<Car> cars = repository.findAll();
        return converter.mapToDto(cars);
    }
    public List<CarDto> getAllCars() {
        Converter converter = new Converter();
        return getAllCars(converter);
    }
}

หรือแม้แต่ปืนไรเฟิลออกคำตอบของร็อบ

@Service
public class Service {

    @Autowired
    private Repository repository;

    private final Converter converter = new Converter(); // static if safe for that

    public List<CarDto> getAllCars(Converter converter) {
        List<Car> cars = repository.findAll();
        return converter.mapToDto(cars);
    }
    public List<CarDto> getAllCars() {
        return getAllCars(converter);
    }
}

อาจไม่จำเป็นต้องมีการเปลี่ยนแปลงส่วนต่อประสานสาธารณะ แต่ฉันต้องการ ยังคงเป็น

public List<CarDto> getAllCars(Converter converter) { ... }

สามารถป้องกันหรือเป็นส่วนตัวเพื่อ จำกัด ขอบเขตสำหรับวัตถุประสงค์การทดสอบ / ส่วนขยายเท่านั้น

จุดสำคัญคือ DI เป็นตัวเลือก มีการระบุค่าเริ่มต้นซึ่งสามารถแทนที่ได้อย่างตรงไปตรงมา มันมีจุดอ่อนเมื่อจำนวนฟิลด์เพิ่มขึ้น แต่สำหรับ 1 หรือ 2 ฟิลด์ฉันต้องการสิ่งนี้ภายใต้เงื่อนไขต่อไปนี้:

  • จำนวนฟิลด์น้อยมาก
  • ใช้ค่าเริ่มต้นเกือบตลอดเวลา (DI คือตะเข็บทดสอบ) หรือค่าเริ่มต้นแทบไม่เคยใช้ (หยุดช่องว่าง) เพราะฉันชอบความสม่ำเสมอดังนั้นนี่เป็นส่วนหนึ่งของโครงสร้างการตัดสินใจของฉันไม่ใช่ข้อ จำกัด การออกแบบที่แท้จริง

จุดสุดท้าย (OT เล็กน้อย แต่เกี่ยวข้องกับวิธีที่เราตัดสินใจว่า / ที่ไหน @autowired): คลาสตัวแปลงตามที่แสดงเป็นเมธอดยูทิลิตี้ (ไม่มีฟิลด์ไม่มีตัวสร้างอาจเป็นแบบคงที่) วิธีแก้ปัญหาอาจจะมีวิธี mapToDto () ในคลาส Cars หรือไม่? นั่นคือผลักดันการแปลงให้เป็นคำจำกัดความของรถยนต์ที่มีความเกี่ยวข้องอย่างใกล้ชิดแล้ว:

@Service
public class Service {

   @Autowired
   private Repository repository;

   public List<CarDto> getAllCars() {
    return repository.findAll().stream.map(c -> c.mapToDto()).collect(Collectors.toList()));
   }
}

-1

ฉันคิดว่าตัวอย่างที่ดีของสิ่งนี้คือ SecurityUtils หรือ UserUtils ด้วยแอพ Spring ใด ๆ ที่จัดการผู้ใช้คุณจะต้องใช้คลาส Util ที่มีวิธีคงที่มากมายเช่น:

getCurrentUser ()

isAuthenticated ()

isCurrentUserInRole (สิทธิ์ในการใช้งาน)

เป็นต้น

และฉันไม่เคยถามคำถามเหล่านี้เลย JHipster (ซึ่งฉันใช้เป็นผู้ตัดสินที่ดีในการฝึกฝน) ไม่ได้


-2

เราสามารถแยกคลาสได้ตามฟังก์ชั่นของคลาสนั้นเช่นคอนโทรลเลอร์, บริการ, พื้นที่เก็บข้อมูล, เอนทิตี้ เราอาจใช้คลาสอื่นสำหรับตรรกะของเราไม่เพียงเพื่อวัตถุประสงค์ในการควบคุมบริการ ฯลฯ ในโอกาสนั้นเราสามารถใส่คำอธิบายประกอบคลาสนั้นด้วย @component นี่จะลงทะเบียนคลาสนั้นโดยอัตโนมัติในคอนเทนเนอร์ฤดูใบไม้ผลิ หากลงทะเบียนแล้วจะมีการจัดการโดยภาชนะสปริง แนวคิดของการพึ่งพาการฉีดและการผกผันของการควบคุมสามารถจ่ายให้กับคลาสที่มีคำอธิบายประกอบ


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