1. Введение
В этом уроке мы дадим краткое введение в AutoFactory
от Google.
Это генератор кода на уровне исходного кода, который помогает создавать фабрики.
2. Настройка Мавена
Прежде чем мы начнем, добавим в pom.xml следующую зависимость:
<dependency>
<groupId>com.google.auto.factory</groupId>
<artifactId>auto-factory</artifactId>
<version>1.0-beta5</version>
</dependency>
Последнюю версию можно найти здесь .
3. Быстрый старт
Давайте теперь кратко рассмотрим, что может сделать AutoFactory
, и создадим простой класс Phone .
Итак, когда мы аннотируем класс 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
применяется к параметрам конструктора этого класса, и это означает, что аннотированный параметр должен быть предоставлен внедренным Provider
.
В приведенном выше фрагменте мы ожидаем, что камера
будет предоставлена любым производителем камеры, и AutoFactory
поможет сгенерировать следующий код:
@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));
}
// ...
}
Теперь у нас есть 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<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
также поддерживает параметры, аннотированные с помощью @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<Camera> cameraProvider;
@Inject
PhoneAssemblerFactory(@Named("Sony") Provider<Camera> 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. неокончательные
Фабрики
Обратите внимание, что сгенерированный фабричный класс по умолчанию помечен как final, поэтому мы можем изменить это поведение, установив для атрибута 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;
}
}
Здесь мы расширили класс AbstractFactory , используя
расширение
. Также следует отметить, что каждый абстрактный метод в базовом абстрактном классе ( AbstractFactory
) должен иметь соответствующий конструктор в конкретном классе ( CustomPhone
) .
Наконец, мы можем увидеть следующий сгенерированный код:
@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);
}
//...
}
Мы видим, что AutoFactory
достаточно умен, чтобы использовать конструктор для реализации соответствующего абстрактного метода — такие замечательные функции в AutoFactory
, несомненно, сэкономят нам много времени и кода.
5. AutoFactory
с Guice
Как мы упоминали ранее в этой статье, AutoFactory
поддерживает аннотации JSR-330 , поэтому мы можем интегрировать с ним существующую структуру внедрения зависимостей.
Во-первых, добавим Guice
в pom.xml
:
<dependency>
<groupId>com.google.inject</groupId>
<artifactId>guice</artifactId>
<version>4.2.0</version>
</dependency>
Последнюю версию Guice
можно найти здесь .
Теперь мы продемонстрируем, насколько хорошо AutoFactory
интегрируется с Guice
.
Поскольку мы ожидаем, что «Sony» будет поставщиком камеры, нам нужно внедрить SonyCameraProvider
в конструктор 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);
}
//...
}
Наконец, мы сделаем привязку в модуле 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++));
}
}
И мы устанавливаем поставщика камеры с аннотацией @Named("Sony")
в SonyCameraModule
, чтобы он соответствовал параметру конструктора PhoneFactory
.
Теперь мы видим, что Guice
управляет внедрением зависимостей для нашей сгенерированной фабрики:
Injector injector = Guice.createInjector(new SonyCameraModule());
PhoneFactory injectedFactory = injector.getInstance(PhoneFactory.class);
Phone xperia = injectedFactory.create("Xperia");
6. Под капотом
Все аннотации, предоставляемые AutoFactory
, обрабатываются на этапе компиляции , как мы подробно объясняли в статье: как работает обработка аннотаций на уровне исходного кода.
7. Заключение
В этой статье мы рассказали, как использовать AutoFactory и как интегрировать его с Guice —
написание фабрик может быть повторяющимся и подверженным ошибкам — инструменты генерации кода, такие как AutoFactory
и AutoValue,
могут сэкономить нам много времени и избавить нас от незначительных ошибок. .
Как всегда, полную реализацию примеров кода можно найти на Github .