Шаблон декоратора и @Inject - PullRequest
9 голосов
/ 30 июня 2010

При использовании конфигурации XML на основе Spring легко декорировать несколько реализаций одного и того же интерфейса и указывать порядок. Например, служба ведения журнала оборачивает транзакционную службу, которая упаковывает фактическую службу.

Как можно добиться того же, используя аннотации javax.inject?

Ответы [ 3 ]

7 голосов
/ 30 июня 2010

Вы можете использовать @Named вместе с @Inject, чтобы указать, какой бин нужно вводить.

Простой пример с внедренным сервисом:

public class ServiceTest {

    @Inject
    @Named("transactionDecorator")
    private Service service;
}

И соответствующий класс декоратора транзакции:

@org.springframework.stereotype.Service("transactionDecorator")
public class ServiceDecoratorTransactionSupport extends ServiceDecorator {

    @Inject
    @Named("serviceBean")
    public ServiceDecoratorTransactionSupport(Service service) {
        super(service);
    }
}

Это выставляет вашу конфигурацию в ваш код, поэтому я бы рекомендовал сделать логику декорирования в классе @Configuration и аннотировать, например, службу регистрации с помощью @Primary. При таком подходе ваш тестовый класс может выглядеть примерно так:

public class ServiceTest {

    @Inject
    private Service service;

И класс конфигурации:

@Configuration
public class DecoratorConfig {

    @Bean
    @Primary
    public ServiceDecorator serviceDecoratorSecurity() {
        return new ServiceDecoratorSecuritySupport(
                  serviceDecoratorTransactionSupport());
    }

    @Bean
    public ServiceDecorator serviceDecoratorTransactionSupport() {
        return new ServiceDecoratorTransactionSupport(serviceBean());
    }

    @Bean
    public Service serviceBean() {
        return new ServiceImpl(serviceRepositoryEverythingOkayStub());
    }

    @Bean
    public ServiceRepository serviceRepositoryEverythingOkayStub() {
        return new ServiceRepositoryEverythingOkStub();
    }
}

Мой второй пример не раскрывает никаких подробностей о том, какая реализация будет возвращена, но это зависит от нескольких специфических для Spring классов.

Вы также можете объединить два решения. Например, используйте аннотацию Spring @Primary на декораторе и позвольте Spring внедрить этот декоратор в экземпляр данного типа.

@Service
@Primary
public class ServiceDecoratorSecuritySupport extends ServiceDecorator {
}
4 голосов
/ 30 июня 2010

Это тот тип вещей, для которого вы обычно используете AOP, а не для написания и переноса реализаций вручную (не то, что вы не можете этого сделать).

Для AOP с Guice вам нужно создатьтранзакции MethodInterceptor и ведение журнала MethodInterceptor, затем используйте bindInterceptor(Matcher, Matcher, MethodInterceptor), чтобы указать, какие типы и методы должны быть перехвачены.Первый Matcher соответствует типам для перехвата, второй соответствует методам для перехвата.Любой из них может быть Matchers.any(), соответствовать определенной аннотации для типа или метода (скажем, @Transactional) или как угодно.Методы сопоставления затем перехватываются и обрабатываются автоматически.Шаблон декоратора с гораздо меньшим количеством шаблонов, в основном.

Чтобы сделать это вручную, одним из способов будет:

class ServiceModule extends PrivateModule {
  @Override protected void configure() {
    bind(Service.class).annotatedWith(Real.class).to(RealService.class);
  }

  @Provides @Exposed
  protected Service provideService(@Real Service service) {
    return new LoggingService(new TransactionalService(service));
  }
}
2 голосов
/ 01 июня 2011
@Target(PARAMETER)
@Retention(RUNTIME)
@BindingAnnotation
public @interface Decorate {
  Class<?> value();
}

/* see com.google.inject.name.NamedImpl for rest of  
    the methods DecorateImpl must implement */
public class DecorateImpl implements Decorate, Serializable {

  private final Class<?> value;

  private DecorateImpl(Class<?> val) {
    value = val;
  }

  public static Decorate get(Class<?> clazz) {
    return new DecorateImpl(clazz);
  }

  public Class<?> value() {
    return value;
  }
  ...
  ...
}

Вот как его использовать:

public interface ApService {
  String foo(String s);
}

public class ApImpl implements ApService {

  private final String name;

  @Inject
  public ApImpl(@Named("ApImpl.name") String name) {
    this.name = name;
  }

  @Override
  public String foo(String s) {
    return name + ":" + s;
  }
}

Первый декоратор:

public class ApDecorator implements ApService {

  private final ApService dcrtd;
  private final String name;

  @Inject
  public ApDecorator(@Decorate(ApDecorator.class) ApService dcrtd,
      @Named("ApDecorator.name") String name) {
    this.dcrtd = dcrtd;
    this.name = name;
  }

  public String foo(String s) {
    return name + ":" + s + ":"+dcrtd.foo(s);
  }
}

Второй декоратор:

public class D2 implements ApService {

  private final ApService dcrt;

  @Inject
  public D2(@Decorate(D2.class) ApService dcrt) {
    this.dcrt = dcrt;
  }

  @Override
  public String foo(String s) {
    return "D2:" + s + ":" + dcrt.foo(s);
  }
}

public class DecoratingTest {

  @Test
  public void test_decorating_provider() throws Exception {
    Injector inj = Guice.createInjector(new DecoratingModule());
    ApService mi = inj.getInstance(ApService.class);
    assertTrue(mi.foo("z").matches("D2:z:D:z:I:z"));
  }
}

Модуль:

class DecoratingModule extends AbstractModule {

  @Override
  protected void configure() {
    bindConstant().annotatedWith(Names.named("ApImpl.name")).to("I");
    bindConstant().annotatedWith(Names.named("ApDecorator.name")).to("D");
    bind(ApService.class).
      annotatedWith(DecorateImpl.get(ApDecorator.class)).
      to(AnImpl.class);
    bind(ApService.class).
      annotatedWith(DecorateImpl.get(D2.class)).
      to(ApDecorator.class);
    bind(ApService.class).to(D2.class);
  }
}

Если конфигурация привязок выглядит некрасиво, вы можете создать Builder / DSL, который выглядит красиво.
Недостатком является то, что (по сравнению с ручным построением цепочки) вы не можете связать один и тот же модуль дважды (т.е. * 1015).* D2-> D2-> D1-> Impl ) и шаблон в параметрах конструктора.

...