Introdução ao AutoFactory

Introdução ao AutoFactory

*1. Introdução *

Neste tutorial, daremos uma breve introdução ao AutoFactory, do Google.

Este é um gerador de código no nível da fonte que ajuda a gerar fábricas.

===* 2. Configuração do Maven *

Antes de começarmos, vamos adicionar a seguinte dependência ao _pom.xml: _

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

===* 3. Começo rápido*

Vamos agora dar uma olhada rápida no que AutoFactory pode fazer e criar uma classe Phone simples.

Portanto, quando anotamos a classe Phone com _ @ AutoFactory_ e seu parâmetro construtor com _ @ Provided_, obtemos:

@AutoFactory
public class Phone {

    private final Camera camera;

    private final String otherParts;

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

   //...

}

Usamos apenas duas anotações: @AutoFactory e @Provided. Quando precisamos de uma fábrica gerada para nossa classe, podemos anotá-la com _ @ AutoFactory, _ enquanto _ @ Fornecido_ se aplica aos parâmetros construtores dessa classe, e significa que o parâmetro anotado deve ser fornecido por um https://docs injetado .oracle.com/javaee/6/api/javax/inject/Provider.html [Provider].

No snippet acima, esperamos que a Camera seja fornecida por qualquer produtor de câmera e AutoFactory ajude a gerar o seguinte código:

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

   //...

}

Agora temos um PhoneFactory gerado automaticamente por AutoFactory em tempo de compilação e podemos usá-lo para produzir instâncias de telefone:

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

A anotação _ @ AutoFactory_ também pode ser aplicada aos construtores:

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

   //...

}

No snippet acima, aplicamos _ @ AutoFactory_ a ambos os construtores. AutoFactory simplesmente gerará dois métodos de criação para nós adequadamente:

@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_ também suporta parâmetros anotados com _ @ Fornecido_, mas apenas para https://jcp.org/en/jsr/detail?id=330[JSR-330].

Por exemplo, se queremos que o cameraProvider seja “Sony”, podemos alterar a classe Phone para:

@AutoFactory
public class Phone {

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

   //...

}

O AutoFactory manterá os @Named _https://docs.oracle.com/javaee/6/api/javax/inject/Qualifier.html [@Qualifier] _ para que possamos usá-lo, por exemplo, ao usar estruturas de injeção de dependência:

@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. Geração de código personalizado *

Existem vários atributos que podemos usar com a anotação _ @ AutoFactory_ para personalizar o código gerado.

====* 4.1 Nome da classe personalizada *

O nome da classe de fábrica gerada pode ser definido com className :

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

   //...

}

Com a configuração acima, criaremos uma classe chamada SamsungFactory:

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

   //...

}

====* 4.2 non-final Fábricas *

Observe que a classe de fábrica gerada é marcada como final por padrão, portanto, podemos alterar esse comportamento definindo o allowSubclasses atributo para _false: _

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

   //...

}

Agora temos:

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

   //...

}

====* 4.3 Mais recursos *

Além disso, podemos especificar uma lista de interfaces para a fábrica gerada a ser implementada usando o 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/google/auto/factory/AutoFactory.java#L53 [implementando”] parâmetro.

Aqui precisamos do SamsungFactory para produzir smartphones com armazenamento personalizável:

public interface CustomStorage {
    SmartPhone customROMInGB(int romSize);
}
*Observe que os métodos na interface devem retornar instâncias da classe base _SmartPhone _.*
*Então, para gerar uma classe de fábrica com a interface acima implementada, _AutoFactory_ requer construtores relevantes na classe base* :
@AutoFactory(
  className = "SamsungFactory",
  allowSubclasses = true,
  implementing = CustomStorage.class)
public class SmartPhone {

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

   //...

}

Assim, AutoFactory irá gerar o seguinte código:

@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 Fábricas com extensões

Como AutoFactory pode gerar implementações de interface, é natural esperar que ele também possa estender classes e isso é possível:

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

Aqui, estendemos a classe AbstractFactory usando extending. Além disso, devemos observar que cada método abstrato na classe abstrata base (AbstractFactory) deve ter um construtor correspondente na classe concreta (_CustomPhone _) .

Por fim, podemos ver o seguinte código gerado:

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

   //...

}

Podemos ver que AutoFactory é inteligente o suficiente para fazer uso do construtor para implementar o método abstrato correspondente - ótimos recursos como este em AutoFactory certamente economizarão muito tempo e código.

*5. AutoFactory com Guice *

Como mencionamos anteriormente neste artigo, o AutoFactory suporta notaçõesJSR-330, para que possamos integrar a estrutura de injeção de dependência existente com isso.

Primeiro, vamos adicionar Guice ao pom.xml:

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

A versão mais recente do Guice pode ser encontrada here

Agora, demonstraremos como o AutoFactory se integra ao Guice.

Como esperamos que a “Sony” seja a fornecedora da câmera, precisamos injetar um SonyCameraProvider no construtor 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);
    }

   //...

}

Por fim, criaremos a ligação em um módulo 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++));
    }

}

E configuramos o provedor de câmera anotado com _ @ Named (“Sony”) _ em SonyCameraModule para corresponder ao parâmetro do construtor de PhoneFactory.

Agora podemos ver que o Guice está gerenciando a injeção de dependência para nossa fábrica gerada:

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

===* 6. Sob o capô *

*Todas as anotações fornecidas por _AutoFactory_ são processadas no estágio de compilação* , conforme explicamos em detalhes no artigo: link:/java-annotation-processing-builder [como funciona o processamento de anotações no nível de origem.]

7. Conclusão

Neste artigo, apresentamos como usar o AutoFactory e como integrá-lo às fábricas de gravação Guice – podem ser repetitivas e propensas a erros - ferramentas de geração de código como AutoFactory e link:/Introduction-to-autovalue [AutoValue] can economize muito tempo e nos livre de erros sutis.

Como sempre, a implementação completa dos exemplos de código pode ser encontrada over no Github.