Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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?

like image 392
Robert Munteanu Avatar asked Jun 30 '10 12:06

Robert Munteanu


People also ask

What does the decorator pattern do?

Decorator patterns allow a user to add new functionality to an existing object without altering its structure. So, there is no change to the original class. The decorator design pattern is a structural pattern, which provides a wrapper to the existing class.

What are decorators in design patterns?

In object-oriented programming, the decorator pattern is a design pattern that allows behavior to be added to an individual object, dynamically, without affecting the behavior of other objects from the same class.

What is the decorator pattern in Java?

Decorator is a structural pattern that allows adding new behaviors to objects dynamically by placing them inside special wrapper objects, called decorators. Using decorators you can wrap objects countless number of times since both target objects and decorators follow the same interface.

What is the decorator design pattern for Python?

Decorator Method is a Structural Design Pattern which allows you to dynamically attach new behaviors to objects without changing their implementation by placing these objects inside the wrapper objects that contains the behaviors.


2 Answers

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 {
}
like image 134
Espen Avatar answered Sep 20 '22 15:09

Espen


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));
  }
}
like image 43
ColinD Avatar answered Sep 17 '22 15:09

ColinD