2010-06-30 33 views
9

Al usar la configuración XML basada en Spring, es fácil decorar múltiples implementaciones de la misma interfaz y especificar el orden. Por ejemplo, un servicio de registro envuelve un servicio transaccional que envuelve el servicio real.El patrón de decorador y @Inject

¿Cómo puedo lograr lo mismo usando las anotaciones javax.inject?

Respuesta

4

Este es el tipo de cosas para las que normalmente usa AOP, en lugar de escribir y empaquetar implementaciones manualmente (no es que no pueda hacer eso).

Para AOP con Guice, usted desea crear una transaccional MethodInterceptor y una tala MethodInterceptor, a continuación, utilizar bindInterceptor(Matcher, Matcher, MethodInterceptor) para establecer qué tipos y métodos deben ser interceptada. El primer Matcher coincide con los tipos para interceptar, el segundo coincide con los métodos para interceptar. Cualquiera puede ser Matchers.any(), hacer coincidir una anotación específica en un tipo o método (@Transactional, por ejemplo) o lo que quieras. Los métodos coincidentes se interceptan y manejan automáticamente. Patrón de decorador con mucho menos repetitivo, básicamente.

Para hacerlo de forma manual, una solución sería:

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

Gracias por la respuesta. En realidad estoy usando Spring, así que esto no es posible para mí, estaba buscando usar solo el paquete 'javax.inject'. Pero es bueno saber de todos modos. –

7

Se puede utilizar junto con @Named@Inject para especificar qué frijol a inyectar.

Un ejemplo sencillo con un servicio inyectada:

public class ServiceTest { 

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

y la correspondiente clase decoradora de transacción:

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

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

Esto expone la configuración en su código, por lo que recomiendo hacer la lógica de decoración en una clase @Configuration y anotar, por ejemplo, el servicio de registro con @Primary. Con este enfoque a su clase de prueba puede ser algo como esto:

public class ServiceTest { 

    @Inject 
    private Service service; 

Y la clase de configuración:

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

Mi segundo ejemplo no expone ningún detalle acerca de qué aplicación que serán devueltos, pero depende de varias clases específicas de primavera.

También puede combinar las dos soluciones. Por ejemplo, use la anotación de Spring @Primary en un decorador y deje que Spring inyecte este decorador en la instancia del tipo dado.

@Service 
@Primary 
public class ServiceDecoratorSecuritySupport extends ServiceDecorator { 
} 
2
@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; 
    } 
    ... 
    ... 
} 

Aquí es cómo usarlo:

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

primer decorador:

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

Segunda decorador:

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

El Módulo:

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

Si la configuración fijaciones se ve feo, puede crear Constructor/DSL que se ve bien.
El inconveniente es que (en comparación con la construcción de cadena manual) no se puede encadenar el mismo módulo dos veces (es decir, D2-> D2-> D1-> Impl) y el modelo en los parámetros del constructor.

+0

la configuración de enlace mostrada arriba se ve fea. Puede consultar [decorice] (https://github.com/beluchin/decorice) para abordar la enfermedad repetitiva. – beluchin

Cuestiones relacionadas