Pengantar AutoFactory

1. Perkenalan

Dalam tutorial ini, kami akan memberikan pengenalan singkat tentang AutoFactory , dari Google.

Ini adalah generator kode tingkat sumber yang membantu menghasilkan pabrik.

2. Pengaturan Maven

Sebelum kita mulai, mari tambahkan dependensi berikut ke pom.xml:

 com.google.auto.factory auto-factory 1.0-beta5 

Versi terbaru dapat ditemukan di sini.

3. Memulai Cepat

Sekarang mari kita lihat sekilas apa yang dapat dilakukan AutoFactory dan membuat kelas Telepon sederhana .

Jadi, ketika kita menganotasi kelas Phone dengan @AutoFactory dan parameter konstruktornya dengan @Provided , kita mendapatkan:

@AutoFactory public class Phone { private final Camera camera; private final String otherParts; PhoneAssembler(@Provided Camera camera, String otherParts) { this.camera = camera; this.otherParts = otherParts; } //... }

Kami hanya menggunakan dua anotasi: @AutoFactory dan @Provided . Saat kita membutuhkan pabrik yang dibuat untuk kelas kita, kita bisa menganotasinya dengan @AutoFactory, sedangkan @Provided berlaku untuk parameter konstruktor kelas ini, dan itu berarti parameter yang dianotasi harus disediakan oleh Penyedia yang dimasukkan .

Dalam cuplikan di atas, kami mengharapkan Kamera disediakan oleh produsen kamera mana pun dan Pabrik Otomatis akan membantu menghasilkan kode berikut:

@Generated("com.google.auto.factory.processor.AutoFactoryProcessor") public final class PhoneFactory { private final Provider cameraProvider; @Inject PhoneAssemblerFactory(Provider cameraProvider) { this.cameraProvider = checkNotNull(cameraProvider, 1); } PhoneAssembler create(String otherParts) { return new PhoneAssembler( checkNotNull(cameraProvider.get(), 1), checkNotNull(otherParts, 2)); } // ... }

Sekarang kami memiliki PhoneFactory yang dibuat secara otomatis oleh AutoFactory pada waktu kompilasi, dan kami dapat menggunakannya untuk menghasilkan instance telepon:

PhoneFactory phoneFactory = new PhoneFactory( () -> new Camera("Unknown", "XXX")); Phone simplePhone = phoneFactory.create("other parts");

The @AutoFactory penjelasan dapat diterapkan untuk konstruktor juga:

public class ClassicPhone { private final String dialpad; private final String ringer; private String otherParts; @AutoFactory public ClassicPhone( @Provided String dialpad, @Provided String ringer) { this.dialpad = dialpad; this.ringer = ringer; } @AutoFactory public ClassicPhone(String otherParts) { this("defaultDialPad", "defaultRinger"); this.otherParts = otherParts; } //... }

Dalam cuplikan di atas, kami menerapkan @AutoFactory ke kedua konstruktor. AutoFactory hanya akan menghasilkan dua metode pembuatan untuk kita sesuai:

@Generated(value = "com.google.auto.factory.processor.AutoFactoryProcessor") public final class ClassicPhoneFactory { private final Provider java_lang_StringProvider; @Inject public ClassicPhoneFactory(Provider java_lang_StringProvider) { this.java_lang_StringProvider = checkNotNull(java_lang_StringProvider, 1); } public ClassicPhone create() { return new ClassicPhone( checkNotNull(java_lang_StringProvider.get(), 1), checkNotNull(java_lang_StringProvider.get(), 2)); } public ClassicPhone create(String otherParts) { return new ClassicPhone(checkNotNull(otherParts, 1)); } //... }

AutoFactory juga mendukung parameter yang dianotasi dengan @Provided , tetapi hanya untuk anotasi JSR-330.

Misalnya, jika kita ingin cameraProvider menjadi "Sony", kita dapat mengubah kelas Telepon menjadi:

@AutoFactory public class Phone { PhoneAssembler( @Provided @Named("Sony") Camera camera, String otherParts) { this.camera = camera; this.otherParts = otherParts; } //... }

AutoFactory akan mempertahankan @Named @Qualifier sehingga kami dapat menggunakannya, misalnya, saat menggunakan kerangka kerja Dependency Injection:

@Generated("com.google.auto.factory.processor.AutoFactoryProcessor") public final class PhoneFactory { private final Provider cameraProvider; @Inject PhoneAssemblerFactory(@Named("Sony") Provider cameraProvider) { this.cameraProvider = checkNotNull(cameraProvider, 1); } //... }

4. Pembuatan Kode yang Disesuaikan

Ada beberapa atribut yang dapat kita gunakan dengan anotasi @AutoFactory untuk menyesuaikan kode yang dihasilkan.

4.1. Nama Kelas Kustom

Nama kelas pabrik yang dihasilkan dapat diatur dengan className :

@AutoFactory(className = "SamsungFactory") public class SmartPhone { //... }

Dengan konfigurasi di atas, kami akan membuat kelas bernama SamsungFactory :

@Generated("com.google.auto.factory.processor.AutoFactoryProcessor") public final class SamsungFactory { //... }

4.2. Pabrik non-final

Perhatikan bahwa kelas pabrik yang dihasilkan ditandai sebagai final secara default, jadi kita dapat mengubah perilaku ini dengan menyetel atribut allowSubclasses ke false:

@AutoFactory( className = "SamsungFactory", allowSubclasses = true) public class SmartPhone { //... }

Sekarang kita punya:

@Generated("com.google.auto.factory.processor.AutoFactoryProcessor") public class SamsungFactory { //... }

4.3. Lebih Banyak Kemampuan

Selain itu, kita dapat menetapkan daftar antarmuka untuk pabrik yang dihasilkan untuk diimplementasikan menggunakan parameter "implementation".

Di sini kami membutuhkan SamsungFactory untuk memproduksi smartphone dengan penyimpanan yang dapat disesuaikan:

public interface CustomStorage { SmartPhone customROMInGB(int romSize); }

Perhatikan bahwa metode di antarmuka harus mengembalikan instance dari kelas dasar SmartPhone .

Kemudian, untuk menghasilkan kelas pabrik dengan antarmuka di atas diimplementasikan, AutoFactory membutuhkan konstruktor yang relevan di kelas dasar :

@AutoFactory( className = "SamsungFactory", allowSubclasses = true, implementing = CustomStorage.class) public class SmartPhone { public SmartPhone(int romSize){ //... } //... }

Jadi, AutoFactory akan menghasilkan kode berikut:

@Generated("com.google.auto.factory.processor.AutoFactoryProcessor") public class SamsungFactory implements CustomStorage { //... public SmartPhone create(int romSize) { return new SmartPhone(romSize); } @Override public SmartPhone customROMInGB(int romSize) { return create(romSize); } }

4.4. Pabrik Dengan Ekstensi

Karena AutoFactory dapat menghasilkan implementasi antarmuka, wajar jika mengharapkannya untuk dapat memperluas kelas juga dan ini memang mungkin:

public abstract class AbstractFactory { abstract CustomPhone newInstance(String brand); } @AutoFactory(extending = AbstractFactory.class) public class CustomPhone { private final String brand; public CustomPhone(String brand) { this.brand = brand; } }

Here, we extended the AbstractFactory class using extending. Also, we should note that each abstract method in the base abstract class (AbstractFactory) should have a corresponding constructor in the concrete class (CustomPhone).

Finally, we can see the following generated code:

@Generated("com.google.auto.factory.processor.AutoFactoryProcessor") public final class CustomPhoneFactory extends AbstractFactory { @Inject public CustomPhoneFactory() { } public CustomPhone create(String brand) { return new CustomPhone(checkNotNull(brand, 1)); } @Override public CustomPhone newInstance(String brand) { return create(brand); } //... }

We can see that AutoFactory is smart enough to make use of the constructor to implement the corresponding abstract method – great features like this in AutoFactory surely will save us lots of time and code.

5. AutoFactory With Guice

As we mentioned earlier in this article, AutoFactory supports JSR-330 annotations, so we can integrate existing dependency injection framework with it.

First, let's add Guice to the pom.xml:

 com.google.inject guice 4.2.0 

The latest version of Guice can be found here.

Now, we'll demonstrate how well AutoFactory integrates with Guice.

As we expect “Sony” to be the camera provider, we need to inject a SonyCameraProvider to PhoneFactory‘s constructor:

@Generated("com.google.auto.factory.processor.AutoFactoryProcessor") public final class PhoneFactory { private final Provider cameraProvider; @Inject public PhoneFactory(@Named("Sony") Provider cameraProvider) { this.cameraProvider = checkNotNull(cameraProvider, 1); } //... }

Finally, we'll make the binding in a Guice module:

public class SonyCameraModule extends AbstractModule { private static int SONY_CAMERA_SERIAL = 1; @Named("Sony") @Provides Camera cameraProvider() { return new Camera( "Sony", String.format("%03d", SONY_CAMERA_SERIAL++)); } }

And we set the camera provider annotated with @Named(“Sony”) in SonyCameraModule to match PhoneFactory‘s constructor parameter.

Now we can see that Guice is managing dependency injection for our generated factory:

Injector injector = Guice.createInjector(new SonyCameraModule()); PhoneFactory injectedFactory = injector.getInstance(PhoneFactory.class); Phone xperia = injectedFactory.create("Xperia");

6. Under the Hood

All annotations provided by AutoFactory are processed in the compilation stage, as we have explained in detail in the article: how the source-level annotation processing works.

7. Conclusion

Di artikel ini, kami telah memperkenalkan cara menggunakan AutoFactory, dan cara mengintegrasikannya dengan Guice - pabrik penulisan bisa berulang dan rawan kesalahan - alat pembuatan kode seperti AutoFactory dan AutoValue dapat menghemat banyak waktu dan membebaskan kita dari bug halus .

Seperti biasa, implementasi penuh dari contoh kode dapat ditemukan di Github.