Panduan untuk Google Guice

1. Perkenalan

Artikel ini akan membahas dasar-dasar Google Guice . Kita akan melihat pendekatan untuk menyelesaikan tugas Dependency Injection (DI) dasar di Guice.

Kami juga akan membandingkan dan membedakan pendekatan Guice dengan kerangka kerja DI yang lebih mapan seperti Spring and Contexts and Dependency Injection (CDI).

Artikel ini menganggap pembaca memiliki pemahaman tentang dasar-dasar pola Injeksi Ketergantungan.

2. Penyiapan

Untuk menggunakan Google Guice dalam proyek Maven Anda, Anda perlu menambahkan dependensi berikut ke pom.xml Anda :

 com.google.inject guice 4.1.0  

Ada juga kumpulan ekstensi Guice (kita akan membahasnya nanti) di sini, serta modul pihak ketiga untuk memperluas kemampuan Guice (terutama dengan menyediakan integrasi ke kerangka kerja Java yang lebih mapan).

3. Injeksi Ketergantungan Dasar Dengan Guice

3.1. Aplikasi Sampel kami

Kami akan bekerja dengan skenario di mana kami merancang kelas yang mendukung tiga alat komunikasi dalam bisnis helpdesk: Email, SMS, dan IM.

Pertimbangkan kelasnya:

public class Communication { @Inject private Logger logger; @Inject private Communicator communicator; public Communication(Boolean keepRecords) { if (keepRecords) { System.out.println("Message logging enabled"); } } public boolean sendMessage(String message) { return communicator.sendMessage(message); } }

Kelas Komunikasi ini adalah unit dasar komunikasi. Contoh dari kelas ini digunakan untuk mengirim pesan melalui saluran komunikasi yang tersedia. Seperti yang ditunjukkan di atas, Komunikasi memiliki Communicator yang kita gunakan untuk melakukan transmisi pesan yang sebenarnya.

Titik masuk dasar ke Guice adalah Injector:

public static void main(String[] args){ Injector injector = Guice.createInjector(new BasicModule()); Communication comms = injector.getInstance(Communication.class); } 

Metode utama ini mengambil sebuah instance dari kelas Komunikasi kita . Ini juga memperkenalkan konsep dasar Guice: the Module (menggunakan BasicModule dalam contoh ini). The Modul adalah unit dasar dari definisi binding (atau kabel, seperti yang dikenal di Spring).

Guice telah mengadopsi pendekatan kode-pertama untuk injeksi ketergantungan dan manajemen sehingga Anda tidak akan berurusan dengan banyak XML di luar kotak.

Dalam contoh di atas, pohon ketergantungan Komunikasi akan secara implisit dimasukkan menggunakan fitur yang disebut pengikatan just-in-time , asalkan kelas memiliki konstruktor no-arg default. Ini telah menjadi fitur di Guice sejak awal dan hanya tersedia di Spring sejak v4.3.

3.2. Guice Bindings

Binding ke Guice seperti kabel ke Spring. Dengan binding, Anda menentukan cara Guice akan memasukkan dependensi ke dalam kelas.

Pengikatan ditentukan dalam implementasi com.google.inject.AbstractModule :

public class BasicModule extends AbstractModule { @Override protected void configure() { bind(Communicator.class).to(DefaultCommunicatorImpl.class); } }

Implementasi modul ini menetapkan bahwa instance dari Default CommunicatorImpl akan dimasukkan ke mana pun variabel Communicator ditemukan.

Inkarnasi lain dari mekanisme ini adalah pengikatan bernama . Pertimbangkan deklarasi variabel berikut:

@Inject @Named("DefaultCommunicator") Communicator communicator; 

Untuk ini, kami akan memiliki definisi pengikatan berikut:

@Override protected void configure() { bind(Communicator.class) .annotatedWith(Names.named("DefaultCommunicator")) .to(Communicator.class); } 

Pengikatan ini akan memberikan instance Communicator ke variabel yang dianotasi dengan anotasi @Named ("DefaultCommunicator") .

Anda akan melihat @Inject dan @Named penjelasan tampaknya penjelasan pinjaman dari Jakarta EE ini CDI, dan mereka. Mereka ada dalam paket com.google.inject. * - Anda harus berhati-hati untuk mengimpor dari paket yang tepat saat menggunakan IDE.

Tip: Sementara kami hanya mengatakan untuk menggunakan Guice disediakan @Inject dan @Named , itu bermanfaat untuk dicatat bahwa Guice tidak memberikan dukungan untuk javax.inject.Inject dan javax.inject.Named, antara penjelasan lain Jakarta EE.

Anda juga bisa memasukkan dependensi yang tidak memiliki konstruktor no-arg default menggunakan pengikatan konstruktor :

public class BasicModule extends AbstractModule { @Override protected void configure() { bind(Boolean.class).toInstance(true); bind(Communication.class).toConstructor( Communication.class.getConstructor(Boolean.TYPE)); } 

Cuplikan di atas akan memasukkan instance Komunikasi menggunakan konstruktor yang menggunakan argumen boolean . Kami menyediakan benar argumen ke konstruktor dengan mendefinisikan ditargetkan mengikat dari Boolean kelas.

Pengikatan tanpa target ini akan dengan bersemangat diberikan ke konstruktor apa pun dalam pengikatan yang menerima parameter boolean . Dengan pendekatan ini, semua dependensi Komunikasi dimasukkan.

Pendekatan lain untuk pengikatan khusus konstruktor adalah pengikatan instance , di mana kami menyediakan sebuah instance langsung di pengikatan:

public class BasicModule extends AbstractModule { @Override protected void configure() { bind(Communication.class) .toInstance(new Communication(true)); } }

Pengikatan ini akan memberikan instance kelas Komunikasi di mana pun variabel Komunikasi dideklarasikan.

Dalam kasus ini, bagaimanapun, pohon ketergantungan kelas tidak akan secara otomatis dihubungkan. Anda harus membatasi penggunaan mode ini jika tidak diperlukan inisialisasi atau injeksi ketergantungan yang berat.

4. Jenis Injeksi Ketergantungan

Guice mendukung jenis suntikan standar yang Anda harapkan dengan pola DI. Di kelas Communicator , kita perlu memasukkan berbagai jenis CommunicationMode .

4.1. Injeksi Lapangan

@Inject @Named("SMSComms") CommunicationMode smsComms;

Gunakan anotasi @Named opsional sebagai kualifikasi untuk menerapkan injeksi bertarget berdasarkan namanya

4.2. Metode Injeksi

Di sini kami menggunakan metode penyetel untuk mencapai injeksi:

@Inject public void setEmailCommunicator(@Named("EmailComms") CommunicationMode emailComms) { this.emailComms = emailComms; } 

4.3. Injeksi Konstruktor

Anda juga dapat memasukkan dependensi menggunakan konstruktor:

@Inject public Communication(@Named("IMComms") CommunicationMode imComms) { this.imComms= imComms; } 

4.4. Suntikan Implisit

Guice will implicitly inject some general purpose components like the Injector and an instance of java.util.Logger, among others. You'll notice we are using loggers all through the samples but you won't find an actual binding for them.

5. Scoping in Guice

Guice supports the scopes and scoping mechanisms we have grown used to in other DI frameworks. Guice defaults to providing a new instance of a defined dependency.

5.1. Singleton

Let's inject a singleton into our application:

bind(Communicator.class).annotatedWith(Names.named("AnotherCommunicator")) .to(Communicator.class).in(Scopes.SINGLETON); 

The in(Scopes.SINGLETON) specifies that any Communicator field with the @Named(“AnotherCommunicator”) will get a singleton injected. This singleton is lazily initiated by default.

5.2. Eager Singleton

Now, let's inject an eager singleton:

bind(Communicator.class).annotatedWith(Names.named("AnotherCommunicator")) .to(Communicator.class) .asEagerSingleton(); 

The asEagerSingleton() call defines the singleton as eagerly instantiated.

In addition to these two scopes, Guice supports custom scopes as well as the web-only @RequestScoped and @SessionScoped annotations, supplied by Jakarta EE (there are no Guice-supplied versions of those annotations).

6. Aspect Oriented Programming in Guice

Guice is compliant with the AOPAlliance's specifications for aspect-oriented programming. We can implement the quintessential logging interceptor, which we will use to track message sending in our example, in only four steps.

Step 1 – Implement the AOPAlliance's MethodInterceptor:

public class MessageLogger implements MethodInterceptor { @Inject Logger logger; @Override public Object invoke(MethodInvocation invocation) throws Throwable { Object[] objectArray = invocation.getArguments(); for (Object object : objectArray) { logger.info("Sending message: " + object.toString()); } return invocation.proceed(); } } 

Step 2 – Define a Plain Java Annotation:

@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface MessageSentLoggable { } 

Step 3 – Define a Binding for a Matcher:

Matcher is a Guice class that we use do specify the components that our AOP annotation will apply to. In this case, we want the annotation to apply to implementations of CommunicationMode:

public class AOPModule extends AbstractModule { @Override protected void configure() { bindInterceptor( Matchers.any(), Matchers.annotatedWith(MessageSentLoggable.class), new MessageLogger() ); } } 

We have specified a Matcher here that will apply our MessageLogger interceptor to any class, that has the MessageSentLoggable annotation applied to its methods.

Step 4 – Apply Our Annotation to Our Communicationmode and Load Our Module

@Override @MessageSentLoggable public boolean sendMessage(String message) { logger.info("SMS message sent"); return true; } public static void main(String[] args) { Injector injector = Guice.createInjector(new BasicModule(), new AOPModule()); Communication comms = injector.getInstance(Communication.class); }

7. Conclusion

Having looked at basic Guice functionality, we can see where the inspiration for Guice came from Spring.

Bersamaan dengan dukungannya untuk JSR-330, Guice bertujuan untuk menjadi kerangka kerja DI yang berfokus pada injeksi (sedangkan Spring menyediakan seluruh ekosistem untuk kenyamanan pemrograman, tidak hanya DI), yang ditargetkan untuk pengembang yang menginginkan fleksibilitas DI.

Guice juga sangat dapat dikembangkan, memungkinkan pemrogram untuk menulis plugin portabel yang menghasilkan penggunaan kerangka kerja yang fleksibel dan kreatif. Ini merupakan tambahan dari integrasi ekstensif yang telah disediakan Guice untuk sebagian besar kerangka kerja dan platform populer seperti Servlets, JSF, JPA, dan OSGi, untuk beberapa nama.

Anda dapat menemukan semua kode sumber yang digunakan dalam tutorial ini di proyek GitHub kami.