Introduzione ad AutoFactory

1. Introduzione

In questo tutorial, daremo una breve introduzione ad AutoFactory , di Google.

Questo è un generatore di codice a livello di sorgente che aiuta a generare fabbriche.

2. Installazione di Maven

Prima di iniziare, aggiungiamo la seguente dipendenza al pom.xml:

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

L'ultima versione può essere trovata qui.

3. Avvio rapido

Diamo ora una rapida occhiata a cosa può fare AutoFactory e creiamo una semplice classe Phone .

Quindi, quando annotiamo la classe Phone con @AutoFactory e il suo parametro di costruzione con @Provided , otteniamo:

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

Abbiamo utilizzato solo due annotazioni: @AutoFactory e @Provided . Quando abbiamo bisogno di una factory generata per la nostra classe, possiamo annotarla con @AutoFactory, mentre @Provided si applica ai parametri del costruttore di questa classe e significa che il parametro annotato dovrebbe essere fornito da un Provider inserito .

Nello snippet sopra, ci aspettiamo che la fotocamera sia fornita da qualsiasi produttore di fotocamere e AutoFactory aiuterà a generare il seguente codice:

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

Ora abbiamo un PhoneFactory generato automaticamente da AutoFactory in fase di compilazione e possiamo usarlo per produrre istanze di telefono:

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

L' annotazione @AutoFactory può essere applicata anche ai costruttori:

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

Nello snippet sopra, abbiamo applicato @AutoFactory a entrambi i costruttori. AutoFactory genererà semplicemente due metodi di creazione per noi di conseguenza:

@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 supporta anche i parametri annotati con @Provided , ma solo per le annotazioni JSR-330.

Ad esempio, se vogliamo che cameraProvider sia "Sony", possiamo modificare la classe Phone in:

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

AutoFactory manterrà @Named @Qualifier in modo che possiamo utilizzarlo, ad esempio, quando si utilizzano framework di inserimento delle dipendenze:

@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. Generazione di codice personalizzato

Ci sono diversi attributi che possiamo usare con l' annotazione @AutoFactory per personalizzare il codice generato.

4.1. Nome classe personalizzato

Il nome della classe factory generata può essere impostato con className :

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

Con la configurazione precedente, creeremo una classe denominata SamsungFactory :

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

4.2. Fabbriche non finali

Nota che la classe factory generata è contrassegnata come finale per impostazione predefinita, quindi possiamo modificare questo comportamento impostando l' attributo allowSubclasses su false:

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

Ora abbiamo:

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

4.3. Più capacità

Inoltre, è possibile specificare un elenco di interfacce per la fabbrica generata da implementare utilizzando il parametro " implementation".

Qui abbiamo bisogno di SamsungFactory per produrre smartphone con memoria personalizzabile:

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

Notare che i metodi nell'interfaccia dovrebbero restituire istanze della classe base SmartPhone .

Quindi, per generare una classe factory con l'interfaccia sopra implementata, AutoFactory richiede costruttori rilevanti nella classe base :

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

Pertanto, AutoFactory genererà il seguente codice:

@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. Fabbriche con estensioni

Poiché AutoFactory può generare implementazioni dell'interfaccia, è naturale aspettarsi che sia in grado di estendere anche le classi e questo è davvero possibile:

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

In questo articolo, abbiamo introdotto come utilizzare AutoFactory e come integrarlo con Guice - le fabbriche di scrittura possono essere ripetitive e soggette a errori - strumenti di generazione di codice come AutoFactory e AutoValue possono farci risparmiare un sacco di tempo e liberarci da piccoli bug .

Come sempre, l'implementazione completa degli esempi di codice può essere trovata su Github.