简体   繁体   中英

The decorator pattern and @Inject

When using Spring's based XML configuration, it's easy to decorate multiple implementations of the same interface and specify the order. For instance, a logging service wraps a transactional service which wraps the actual service.

How can I achieve the same using the javax.inject annotations?

You can use @Named together with @Inject to specify which bean to inject.

A simple example with an injected service:

public class ServiceTest {

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

And the corresponding transaction decorator class:

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

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

This exposes your configuration into your code, so I would recommend doing the decorating logic in a @Configuration class and annotate for example the logging service with @Primary . With this approach your test class can look something like this:

public class ServiceTest {

    @Inject
    private Service service;

And the configuration class:

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

My second example doesn't expose any details about which implementation that will be returned, but it depends on several Spring specific classes.

You can also combine the two solutions. For example use Spring's @Primary annotation on a decorator and let Spring inject this decorator into the instance of the given type.

@Service
@Primary
public class ServiceDecoratorSecuritySupport extends ServiceDecorator {
}

This is the sort of thing you typically use AOP for, rather than writing and wrapping implementations manually (not that you can't do that).

For AOP with Guice, you'd want to create a transactional MethodInterceptor and a logging MethodInterceptor , then use bindInterceptor(Matcher, Matcher, MethodInterceptor) to set which types and methods should be intercepted. The first Matcher matches types to intercept, the second matches methods to intercept. Either can be Matchers.any() , match a specific annotation on a type or method ( @Transactional , say) or whatever you want. Matching methods are then intercepted and handled automatically. Decorator pattern with a lot less boilerplate, basically.

To do it manually, one way would be:

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

Here is how to use it:

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

First decorator:

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

Second decorator:

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

The Module:

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

If bindings configuration looks ugly, you can create Builder/DSL that looks nice.
The drawback is that (comparing with manual chain building) you can not chain the same module twice (ie D2->D2->D1->Impl ) and the boilerplate in the constructor params.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM