Вступ до AutoFactory

1. Вступ

У цьому підручнику ми дамо короткий вступ до AutoFactory від Google.

Це генератор коду на вихідному рівні, який допомагає створювати заводи.

2. Налаштування Maven

Перш ніж розпочати, додамо таку залежність до pom.xml:

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

Останню версію можна знайти тут.

3. Швидкий старт

Давайте зараз швидко розглянемо можливості AutoFactory та створимо простий клас телефону .

Отже, коли ми анотуємо клас Phone @AutoFactory, а параметр його конструктора @Provided , ми отримуємо:

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

Ми використовували лише дві анотації: @AutoFactory та @Provided . Коли нам потрібна фабрика, згенерована для нашого класу, ми можемо анотувати її @AutoFactory, тоді як @Provided застосовується до параметрів конструктора цього класу, і це означає, що анотований параметр повинен надаватися ін'єктованим провайдером .

У наведеному вище фрагменті ми очікуємо, що Камеру надаватиме будь-який виробник камер, а AutoFactory допоможе згенерувати такий код:

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

Тепер у нас є PhoneFactory, який автоматично генерується AutoFactory під час компіляції, і ми можемо використовувати його для створення екземплярів телефону:

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

@AutoFactory анотація може бути застосована до конструкторам , а також:

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; } //... }

У наведеному вище фрагменті ми застосували @AutoFactory до обох конструкторів. AutoFactory просто генерує для нас два методи створення відповідно:

@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 також підтримує параметри, позначені @Provided , але лише для анотацій JSR-330.

Наприклад, якщо ми хочемо, щоб cameraProvider був “Sony”, ми можемо змінити клас Phone на:

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

AutoFactory збереже @Named @Qualifier, щоб ми могли ним скористатися, наприклад, під час використання фреймворків 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. Індивідуальне генерація коду

Існує кілька атрибутів, які ми можемо використовувати з анотацією @AutoFactory для налаштування згенерованого коду.

4.1. Назва власного класу

Ім'я сформованого заводського класу можна встановити за допомогою className :

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

З наведеною вище конфігурацією ми створимо клас із назвою SamsungFactory :

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

4.2. незавершені заводи

Зверніть увагу, що згенерований заводський клас за замовчуванням позначений фінальним, тому ми можемо змінити цю поведінку, встановивши для атрибута allowSubclasses значення false:

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

Тепер ми маємо:

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

4.3. Більше можливостей

Крім того, ми можемо вказати перелік інтерфейсів для реалізації заводу, використовуючи параметр « імплементація».

Тут нам потрібен SamsungFactory для виробництва смартфонів із настроюваним сховищем:

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

Зауважте, що методи в інтерфейсі повинні повертати екземпляри базового класу SmartPhone .

Потім, для створення заводського класу з реалізованим вище інтерфейсом, AutoFactory вимагає відповідних конструкторів у базовому класі :

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

Таким чином, AutoFactory генерує такий код:

@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. Заводи з розширеннями

Оскільки AutoFactory може генерувати реалізації інтерфейсу, природно сподіватися, що він зможе також розширити класи, і це справді можливо:

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

У цій статті ми представили, як використовувати AutoFactory та як інтегрувати його з Guice - фабрики написання можуть бути повторюваними та схильними до помилок - інструменти генерації коду, такі як AutoFactory та AutoValue, можуть заощадити нам багато часу та позбавити від тонких помилок .

Як завжди, повну реалізацію зразків коду можна знайти на Github.