Introduction à AutoFactory

1. Introduction

Dans ce didacticiel, nous allons présenter brièvement AutoFactory de Google.

Ceci est un générateur de code source qui aide à générer des usines.

2. Configuration Maven

Avant de commencer, ajoutons la dépendance suivante au fichier pom.xml:

<dependency>
    <groupId>com.google.auto.factory</groupId>
    <artifactId>auto-factory</artifactId>
    <version>1.0-beta5</version>
</dependency>

La dernière version peut être trouvée ici .

3. Démarrage rapide

Voyons maintenant ce que AutoFactory peut faire et créons une classe Phone simple.

Ainsi, lorsque nous annotons la classe Phone avec @ AutoFactory et son paramètre de constructeur avec @ Provided , nous obtenons:

@AutoFactory
public class Phone {

    private final Camera camera;

    private final String otherParts;

    PhoneAssembler(@Provided Camera camera, String otherParts) {
        this.camera = camera;
        this.otherParts = otherParts;
    }

   //...

}

Nous n’avons utilisé que deux annotations:

Lorsque nous avons besoin d’une fabrique générée pour notre classe, nous pouvons l’annoter avec @ AutoFactory, alors que @ Provided s’applique aux paramètres du constructeur de cette classe. Cela signifie que le paramètre annoté doit être fourni par un https://docs injecté .oracle.com/javaee/6/api/javax/inject/Provider.html[ Fournisseur ].

Dans l’extrait de code ci-dessus, nous nous attendons à ce que la caméra soit fournie par tout producteur de caméra et AutoFactory aidera à générer le code suivant:

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

    private final Provider<Camera> cameraProvider;

    @Inject
    PhoneAssemblerFactory(Provider<Camera> cameraProvider) {
        this.cameraProvider = checkNotNull(cameraProvider, 1);
    }

    PhoneAssembler create(String otherParts) {
      return new PhoneAssembler(
        checkNotNull(cameraProvider.get(), 1),
        checkNotNull(otherParts, 2));
    }

   //...

}

Nous avons maintenant un PhoneFactory généré automatiquement par AutoFactory au moment de la compilation, et nous pouvons l’utiliser pour produire des instances de téléphone:

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

L’annotation @ AutoFactory peut également être appliquée aux constructeurs:

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

   //...

}

Dans l’extrait ci-dessus, nous avons appliqué @ AutoFactory aux deux constructeurs.

AutoFactory générera simplement deux méthodes de création pour nous en conséquence:

@Generated(value = "com.google.auto.factory.processor.AutoFactoryProcessor")
public final class ClassicPhoneFactory {
    private final Provider<String> java__lang__StringProvider;

    @Inject
    public ClassicPhoneFactory(Provider<String> 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 prend également en charge les paramètres annotés avec @ Provided , mais uniquement pour les annotations JSR-330 . **

Par exemple, si nous voulons que cameraProvider soit «Sony», nous pouvons modifier la classe Phone en

@AutoFactory
public class Phone {

    PhoneAssembler(
      @Provided @Named("Sony") Camera camera, String otherParts) {
        this.camera = camera;
        this.otherParts = otherParts;
    }

   //...

}

AutoFactory conservera le https://docs.oracle.com/javaee/6/api/javax/inject/Named.html @Qualifier afin que nous puissions l’utiliser, par exemple, lorsque vous utilisez des frameworks Dependency Injection:

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

    private final Provider<Camera> cameraProvider;

    @Inject
    PhoneAssemblerFactory(@Named("Sony") Provider<Camera> cameraProvider) {
      this.cameraProvider = checkNotNull(cameraProvider, 1);
    }

   //...

}

4. Génération de code personnalisé

Plusieurs annotations peuvent être utilisées avec l’annotation @ AutoFactory pour personnaliser le code généré.

4.1. Nom de classe personnalisé

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

   //...

}

Avec la configuration ci-dessus, nous allons créer une classe nommée SamsungFactory :

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

   //...

}

4.2. non-final Usines

Notez que la classe de fabrique générée est marquée comme finale par défaut. Nous pouvons donc modifier ce comportement en définissant la allowSubclasses attribut à false:

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

   //...

}

Maintenant nous avons:

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

   //...

}

4.3. Plus de capacités

De plus, nous pouvons spécifier une liste d’interfaces que la fabrique générée doit implémenter à l’aide de https://github.com/google/auto/blob/master/factory/src/main/java/com/google/auto/factory/AutoFactory . .java # L53 _https://github.com/google/auto/blob/master/factory/src/main/java/com/goo/auto/factory/AutoFactory.java#L53[implementation”]. _

Ici, nous avons besoin du SamsungFactory pour produire des smartphones avec un stockage personnalisable:

public interface CustomStorage {
    SmartPhone customROMInGB(int romSize);
}
  • Notez que les méthodes de l’interface doivent renvoyer des instances de la classe de base SmartPhone . **

  • Ensuite, pour générer une classe de fabrique avec l’interface ci-dessus implémentée, AutoFactory nécessite des constructeurs pertinents dans la classe de base ** :

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

    public SmartPhone(int romSize){
       //...
    }

   //...

}

Ainsi, AutoFactory générera le code suivant:

@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. Usines avec extensions

Étant donné que AutoFactory peut générer des implémentations d’interface, il est naturel de s’attendre à ce qu’il puisse également étendre les classes, ce qui est effectivement possible:

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

De plus, notons que chaque méthode abstraite de la classe abstraite de base ( AbstractFactory ) doit avoir un constructeur correspondant dans la classe concrète ( CustomPhone ) .

Enfin, nous pouvons voir le code généré suivant:

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

   //...

}

Nous pouvons voir que AutoFactory est suffisamment intelligent pour utiliser le constructeur pour implémenter la méthode abstraite correspondante - de telles fonctionnalités exceptionnelles dans AutoFactory nous feront certainement gagner beaucoup de temps et de code.

5. AutoFactory avec Guice

Comme nous l’avons mentionné précédemment dans cet article, AutoFactory prend en charge les JSR-330 annotations , afin d’intégrer le cadre d’injection de dépendance existant. avec ça.

Premièrement, ajoutons Guice au pom.xml :

<dependency>
    <groupId>com.google.inject</groupId>
    <artifactId>guice</artifactId>
    <version>4.2.0</version>
</dependency>

La dernière version de Guice est disponible à l’adresse here .

Nous allons maintenant montrer à quel point AutoFactory s’intègre à Guice

Comme nous nous attendons à ce que «Sony» soit le fournisseur de caméras, nous devons injecter un SonyCameraProvider au constructeur de PhoneFactory :

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

    private final Provider<Camera> cameraProvider;

    @Inject
    public PhoneFactory(@Named("Sony") Provider<Camera> cameraProvider) {
        this.cameraProvider = checkNotNull(cameraProvider, 1);
    }

   //...

}

Enfin, nous allons faire la liaison dans un module Guice :

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++));
    }

}

Et nous avons paramétré le fournisseur de caméra annoté avec @ Named («Sony») dans SonyCameraModule pour qu’il corresponde au paramètre de constructeur de PhoneFactory .

Nous pouvons maintenant voir que Guice gère l’injection de dépendance pour notre usine générée:

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

6. Sous la capuche

  • Toutes les annotations fournies par AutoFactory sont traitées dans la phase de compilation ** , comme nous l’avons expliqué en détail dans l’article:

7. Conclusion

Dans cet article, nous avons expliqué comment utiliser AutoFactory et comment l’intégrer à Guice -- Les usines d’écriture peuvent être répétitives et sources d’erreurs - des outils de génération de code tels que AutoFactory et lien:/introduction-to-autovalue[ AutoValue__]peuvent économisez-nous beaucoup de temps et libérez-nous des bugs subtils.

Comme toujours, l’implémentation complète des exemples de code est disponible à l’adresse over sur Github .