Einführung in AutoFactory

1. Einleitung

In diesem Tutorial geben wir eine kurze Einführung in AutoFactory von Google.

Dies ist ein Codegenerator auf Quellenebene, mit dessen Hilfe Fabriken generiert werden können.

2. Maven Setup

Bevor wir beginnen, fügen wir der pom.xml die folgende Abhängigkeit hinzu :

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

Die neueste Version finden Sie hier.

3. Schnellstart

Lassen Sie uns nun einen kurzen Blick auf die Funktionen von AutoFactory werfen und eine einfache Telefonklasse erstellen .

Wenn wir also die Phone- Klasse mit @AutoFactory und ihren Konstruktorparameter mit @Provided kommentieren , erhalten wir:

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

Wir haben nur zwei Anmerkungen verwendet: @AutoFactory und @Provided . Wenn für unsere Klasse eine Factory generiert werden muss, können wir sie mit @AutoFactory mit Anmerkungen versehen, während @Provided für Konstruktorparameter dieser Klasse gilt. Dies bedeutet, dass der mit Anmerkungen versehene Parameter von einem injizierten Anbieter bereitgestellt werden sollte .

Im obigen Snippet erwarten wir, dass die Kamera von jedem Kamerahersteller bereitgestellt wird und AutoFactory dabei hilft, den folgenden Code zu generieren:

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

Jetzt haben wir eine PhoneFactory automatisch generierte Autofabrik bei der Kompilierung, und wir können es Telefon - Instanzen erzeugen verwenden:

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

Die Annotation @AutoFactory kann auch auf Konstruktoren angewendet werden:

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

Im obigen Snippet haben wir @AutoFactory auf beide Konstruktoren angewendet . AutoFactory generiert einfach zwei Erstellungsmethoden für uns entsprechend:

@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 unterstützt auch mit @Provided annotierte Parameter , jedoch nur für JSR-330-Annotationen.

Wenn wir beispielsweise möchten, dass der cameraProvider "Sony" ist, können wir die Telefonklasse in Folgendes ändern :

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

AutoFactory behält den @Named @Qualifier bei, damit wir ihn beispielsweise bei der Verwendung von Dependency Injection-Frameworks verwenden können:

@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. Benutzerdefinierte Codegenerierung

Es gibt verschiedene Attribute, die wir mit der Annotation @AutoFactory verwenden können , um den generierten Code anzupassen.

4.1. Benutzerdefinierter Klassenname

Der Name der generierten Factory-Klasse kann mit className festgelegt werden :

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

Mit der obigen Konfiguration erstellen wir eine Klasse mit dem Namen SamsungFactory :

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

4.2. nicht endgültige Fabriken

Beachten Sie, dass die generierte Factory-Klasse standardmäßig als final markiert ist. Sie können dieses Verhalten ändern, indem Sie das Attribut allowSubclasses auf false setzen:

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

Jetzt haben wir:

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

4.3. Weitere Funktionen

Darüber hinaus können wir mithilfe des Parameters " Implementieren" eine Liste von Schnittstellen angeben, die von der generierten Factory implementiert werden sollen .

Hier benötigen wir die SamsungFactory , um Smartphones mit anpassbarem Speicher herzustellen:

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

Beachten Sie, dass Methoden in der Schnittstelle Instanzen der Basisklasse SmartPhone zurückgeben sollten .

Um eine Factory-Klasse mit der oben implementierten Schnittstelle zu generieren, benötigt AutoFactory relevante Konstruktoren in der Basisklasse :

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

Daher generiert AutoFactory den folgenden Code:

@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. Fabriken mit Erweiterungen

Da AutoFactory Schnittstellenimplementierungen generieren kann, ist es natürlich zu erwarten, dass es auch Klassen erweitern kann, und dies ist in der Tat möglich:

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 diesem Artikel haben wir die Verwendung von AutoFactory und die Integration in Guice vorgestellt. Das Schreiben von Fabriken kann sich wiederholen und fehleranfällig sein. Tools zur Codegenerierung wie AutoFactory und AutoValue können uns viel Zeit sparen und uns von subtilen Fehlern befreien .

Wie immer finden Sie die vollständige Implementierung der Codebeispiele auf Github.