Kelas mana yang harus diotomasi oleh Spring (kapan harus menggunakan injeksi ketergantungan)?

32

Saya telah menggunakan Dependency Injection di Spring untuk beberapa waktu sekarang, dan saya mengerti cara kerjanya dan apa saja kelebihan dan kekurangan dari menggunakannya. Namun, ketika saya membuat kelas baru saya sering bertanya-tanya - Haruskah kelas ini dikelola oleh Spring IOC Container?

Dan saya tidak ingin membicarakan perbedaan antara anotasi @Autowired, konfigurasi XML, injeksi setter, injeksi konstruktor, dll. Pertanyaan saya bersifat umum.

Katakanlah kita memiliki Layanan dengan Konverter:

@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
    }
}

Jelas, konverter tidak memiliki dependensi, jadi konverter tidak perlu diautow. Tetapi bagi saya tampaknya lebih baik sebagai autowired. Kode lebih bersih dan mudah diuji. Jika saya menulis kode ini tanpa DI, layanan akan terlihat seperti itu:

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

Sekarang jauh lebih sulit untuk mengujinya. Selain itu, konverter baru akan dibuat untuk setiap operasi konversi, meskipun selalu dalam keadaan yang sama, yang tampak seperti overhead.

Ada beberapa pola terkenal di Spring MVC: Pengontrol menggunakan Layanan dan Layanan menggunakan Repositori. Kemudian, jika Repositori diotorisasi secara otomatis (yang biasanya demikian), maka Layanan juga harus diotorisasi secara otomatis. Dan ini cukup jelas. Tapi kapan kita menggunakan anotasi @Component? Jika Anda memiliki beberapa kelas util statis (seperti konverter, pemetaan) - apakah Anda mengotomatiskannya?

Apakah Anda mencoba membuat semua kelas diotomatiskan? Maka semua dependensi kelas mudah disuntikkan (sekali lagi, mudah dimengerti dan mudah diuji). Atau apakah Anda mencoba autowire hanya ketika itu benar-benar diperlukan?

Saya telah menghabiskan waktu mencari beberapa aturan umum tentang kapan harus menggunakan autowiring, tetapi saya tidak dapat menemukan tips khusus. Biasanya, orang berbicara tentang "apakah Anda menggunakan DI? (Ya / tidak)" atau "jenis injeksi ketergantungan apa yang Anda sukai", yang tidak menjawab pertanyaan saya.

Saya akan berterima kasih atas tips tentang topik ini!

Kacper86
sumber
3
+1 untuk dasarnya "Kapan terlalu jauh?"
Andy Hunt
Lihatlah pertanyaan ini dan artikel
Laiv

Jawaban:

8

Setuju dengan komentar @ ericW, dan hanya ingin menambahkan ingat Anda dapat menggunakan inisialisasi untuk menjaga kode Anda tetap kompak:

@Autowired
private Converter converter;

atau

private Converter converter = new Converter();

atau, jika kelas benar-benar tidak memiliki keadaan sama sekali

private static final Converter CONVERTER = new Converter();

Salah satu kriteria utama untuk apakah Spring harus memberi contoh dan menyuntikkan kacang adalah, apakah kacang itu begitu rumit sehingga Anda ingin mengejeknya dalam pengujian? Jika demikian, maka suntikkan. Misalnya, jika konverter melakukan perjalanan bolak-balik ke sistem eksternal apa pun, maka buatlah komponen itu sebagai gantinya. Atau jika nilai kembali memiliki pohon keputusan besar dengan puluhan variasi yang mungkin berdasarkan input, maka mengejeknya. Dan seterusnya.

Anda telah melakukan pekerjaan yang baik untuk menggulung fungsionalitas itu dan merangkumnya, jadi sekarang ini hanya pertanyaan apakah cukup kompleks untuk dianggap sebagai "unit" terpisah untuk pengujian.

rampok
sumber
5

Saya tidak berpikir Anda harus @Autowired semua kelas Anda, itu harus tergantung pada penggunaan nyata, untuk skenario Anda harus lebih baik menggunakan metode statis daripada @Autowired. Saya belum melihat manfaat apa pun menggunakan @Autowired untuk kelas-kelas utilitas sederhana, dan itu benar-benar akan meningkatkan biaya wadah Spring jika tidak menggunakannya dengan benar.

ericW
sumber
2

Aturan dasar saya didasarkan pada sesuatu yang Anda katakan: testability. Tanyakan pada diri sendiri " Bisakah saya mengujinya dengan mudah? ". Jika jawabannya adalah ya maka, karena tidak ada alasan lain saya akan setuju. Jadi, jika Anda mengembangkan unit test pada saat yang bersamaan dengan perkembangan Anda, Anda akan menghemat banyak rasa sakit.

Satu-satunya masalah yang potensial adalah jika konverter gagal, tes layanan Anda juga akan gagal. Beberapa orang akan mengatakan bahwa Anda harus mengejek hasil konverter dalam unit test. Dengan cara ini Anda dapat mengidentifikasi kesalahan lebih cepat. Tetapi ada harganya: Anda harus mengejek semua hasil konverter ketika konverter sebenarnya bisa melakukan pekerjaannya.

Saya juga berasumsi bahwa tidak ada alasan sama sekali untuk menggunakan Pengonversi dto yang berbeda.

Borjab
sumber
0

TL; DR: Pendekatan hibrid autowiring untuk DI, dan penerusan konstruktor untuk DI dapat menyederhanakan kode yang Anda sajikan.

Saya telah melihat pertanyaan serupa karena beberapa weblogic dengan kesalahan / komplikasi startup kerangka kerja semi melibatkan dependensi inisialisasi @autowired bean. Saya sudah mulai mencampur dalam pendekatan DI lain: forwarding konstruktor. Ini membutuhkan kondisi seperti yang Anda sajikan ("Jelas, konverter tidak memiliki dependensi, sehingga tidak perlu untuk di-autowired."). Namun demikian, saya suka banyak untuk fleksibilitas dan itu masih cukup lurus ke depan.

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

atau bahkan sebagai jawaban Rob

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

Mungkin tidak memerlukan perubahan antarmuka publik tetapi saya akan melakukannya. Masih

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

dapat dibuat terlindungi atau pribadi untuk lingkup bawah hanya untuk tujuan pengujian / ekstensi.

Poin kuncinya adalah DI adalah opsional. Default disediakan, yang dapat diganti secara langsung. Ini memiliki kelemahan karena jumlah bidang bertambah, tetapi untuk 1, mungkin 2 bidang, saya lebih suka ini dalam kondisi berikut:

  • Jumlah bidang yang sangat kecil
  • Default hampir selalu digunakan (DI adalah test seam) atau Default hampir tidak pernah digunakan (stop gap) karena saya suka konsistensi, jadi ini adalah bagian dari pohon keputusan saya, bukan kendala desain yang sebenarnya

Poin terakhir (sedikit OT, tetapi terkait dengan bagaimana kita memutuskan apa / di mana @ autowired): kelas konverter, seperti yang disajikan, adalah metode utilitas (tidak ada bidang, tidak ada konstruktor, bisa statis). Mungkin solusinya akan memiliki semacam metode mapToDto () di kelas Cars? Yaitu, dorong injeksi konversi ke definisi Cars, di mana kemungkinan sudah sangat terkait:

@Service
public class Service {

   @Autowired
   private Repository repository;

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

Saya pikir contoh yang baik dari ini adalah sesuatu seperti SecurityUtils atau UserUtils. Dengan aplikasi Musim Semi mana pun yang mengelola pengguna, Anda akan memerlukan beberapa kelas Util dengan sejumlah metode statis seperti:

getCurrentUser ()

isAuthenticated ()

isCurrentUserInRole (Otoritas otoritas)

dll.

dan saya tidak pernah otomatis ini. JHipster (yang saya gunakan sebagai hakim praktik terbaik) tidak cukup.

Janome314
sumber
-2

Kita dapat memisahkan kelas-kelas berdasarkan fungsi kelas itu sedemikian rupa sehingga pengontrol, layanan, repositori, entitas. Kita dapat menggunakan kelas-kelas lain hanya untuk logika kita bukan untuk tujuan pengontrol, layanan dll, pada kesempatan itu kita bisa membuat catatan kelas itu dengan @component. Ini akan secara otomatis mendaftarkan kelas itu dalam wadah pegas. Jika sudah terdaftar maka akan dikelola oleh spring container. Konsep injeksi ketergantungan dan inversi kontrol dapat diberikan ke kelas yang dijelaskan.

SathishSakthi
sumber
3
posting ini agak sulit dibaca (dinding teks). Maukah Anda mengeditnya menjadi bentuk yang lebih baik?
nyamuk