Dependency Injection con Guice - GTUG
Post on 15-Jan-2015
688 Views
Preview:
DESCRIPTION
Transcript
Dependency Injection con Guice
@jordi923 octubre 2010
roadmapMotivación
Problema
3 aproximaciones y media
Más DI con Guice
Unit Testing
Conclusiones
#bcngtug #guice
motivación
diseño
Diseño típico de una aplicación
problema común
Cómo obtienen los clientes los servicios?
3 aproximaciones y media
The Factory Pattern (Patrón Fábrica) + Service Locator
Dependency Injection a mano
Dependency Injection con Guice
parte constante
public interface Dictionary { void spellchecking(Text text);} public class CatalanDictionary implements Dictionary { @Override public void spellchecking(Text text) { // comprobación en catalán } }
Cualquier servicio se mantiene independientemente de la aproximación
Aproximación #1
The Factory Pattern
factory pattern / client
public class PhoneClient { Dictionary dictionary;
public void sendSMS(Text text) { dictionary = DictionaryFactory.getInstance(); dictionary.spellchecking(text); }
}
factory pattern / servicepublic static class DictionaryFactory { static class DictionaryHolder { static Dictionary instance = new CatalanDictionary(); }
public static Dictionary getInstance() { return DictionaryHolder.instance; }
public static void setInstance(Dictionary mock) { DictionaryHolder.instance = mock; }}
factory pattern / consideraciones
Hay que escribir una fábrica para cada objeto y para cada dependencia - boilerplate code
Hay que escribir una fábrica para cada variación en las dependencias.
El cliente establece sus dependencias en tiempo de compilación - Están encapsuladas.
Propagación viral de clases estáticas - static Difícil reutilizar el código cliente en otro contexto.
Hay que modificar el código para poder hacer unit testing.
Aproximación #1.5
Service Locator
service locator
Tipo especial de fábrica. Hereda casi todos sus problemas. Introduce nuevos problemas. eg: no type-safety
Dictionary dictionary = (Dictionary) new ServiceLocator() .get("CatalanDictionary");
Aproximación #2
Dependency Injection a mano
el principio de hollywood
public class PhoneClient {
private final Dictionary dictionary; public PhoneClient(Dictionary dictionary) { this.dictionary = dictionary }
public void sendSMS(Text text) { dictionary.spellchecking(text); }
}
"Don't call us, we'll call you"
DI manual / ventajas
No podemos crear un cliente sin los servicios necesarios. Dependencias claras.
Podemos reutilizar el cliente con múltiples implementaciones de un mismo servicio. Las dependencias pasan de estar en tiempo de compilación a estar a nivel de de aplicación.
La clase es testeable.
Pero aun tenemos que escribir más codigo fábrica!
Aproximación #3
Dependency Injection con Guice
Google GuiceFramework de DI creado por Google (2007).
Toda aplicación Java lo utilitza: Google Docs, GMail, Adwords...
objetivos Evitar escribir fábricas y boilerplate code
Más comprobaciones de tipos - Type safety
Flexibilidad y facilidad para escribir buen código
@Inject
public class PhoneClient {
private final Dictionary dictionary; @Inject public PhoneClient(Dictionary dictionary) { this.dictionary = dictionary }
public void sendSMS(Text text) { dictionary.spellchecking(text); }
}
Aplicamos @Inject para obtener las dependencias.
@Injections
@Inject private Dictionary dictionary;
field injection
method injection@Inject public void setDictionary(Dictionary dictionary) { this.dictionary = dictionary;}
constructor injectionpublic class PhoneClient { private final Dictionary dictionary; @Inject public PhoneClient(Dictionary dictionary) { this.dictionary = dictionary }}
modules
public class DictionaryModule extends AbstractModule { protected void configure() { bind(Dictionary.class).to(CatalanDictionary.class); }}
Tenemos Modules que configuran Guice. no XML... yay!
public class DictionaryModule implements Module { public void configure(Binder binder) { binder.bind(Dictionary.class).to(CatalanDictionary.class); }}
versión DRY (Don't Repeat Yourself)
versión JIT (Just In Time)
@ImplementedBy(CatalanDictionary.class)public class Dictionary {}
bootstrappingIniciando una aplicaciónpublic class PhoneRunner { public static void main(String[] args) { Injector injector = Guice .createInjector(new DictionaryModule()); PhoneClient phone = Injector .getInstance(PhoneClient.class);
phone.sendSMS(new Text("foo text")); }}
consideraciones @Inject es viral. Solo una clase debería tratar con el Injector.
más Guice i DI…
múltiples implementaciones
public class DictionaryModule extends AbstractModule { protected void configure() { bind(Dictionary.class) .annotatedWith(Catalan.class) .to(CatalanDictionary.class); }}
Binding annotationspublic class PhoneClient { private final Dictionary dictionary;
@Inject public PhoneClient(@Catalan Dictionary dictionary) { this.dictionary = dictionary
}}
Hay que configurarlo en un Module
provider patternServicios que dependen del tiempo de ejecución, de librerias de terceros o que se acaban
public interface Provider<T> { public T get();}
public class CoffeeJunkie { CupOfCoffee cupOfcoffee; @Inject public CoffeeJunkie(CupOfCoffee cupOfcoffee) { this.cupOfcoffee = cupOfcoffee; } public void drinkTwoCupsOfCoffe() { cupOfcoffee.drink(); cupOfcoffee.drink(); // <-- is empty! }}
provider injection
public class CoffeeModule extends AbstractModule { protected void configure() { bind(CupOfCoffee.class).toProvider(new Provider<CupOfCoffee>() { public CupOfCoffee get() { return new CupOfCoffe(); // cuidado con el new! } }); }}
public class CoffeeJunkie { Provider<CupOfCoffee> cupsProvider; @Inject public CoffeeJunkie(Provider<CupOfCoffee> cupOfcoffee) { this.cupsProvider = cupsProvider; } public void drinkTwoCupsOfCoffe() { cupsProvider.get().drink(); cupsProvider.get().drink(); // <-- ok! }}
@Providessince guice 2.0
Métodos que nos dan un objeto en concreto. Tienen que estar en un Module
public class CoffeeModule extends AbstractModule { protected void configure() { // ... }
@Provides @WithMilk public CupOfCoffee provideCupOfCoffeWithMilk() { return new CupOfCoffe.builder().withMilk().build(); }}
scopesPolítica de reutilización de instancias No-scope (por defecto) Singleton Web: RequestScope, SessionScope...
@Singleton public class CatalanDictionary ... { ... }
public class DictionaryModule extends AbstractModule { protected void configure() { bind(Dictionary.class) .to(CatalanDictionary.class) .in(Scopes.SINGLETON); }}
o en un Module
otras característicasAspect Oriented Programming con AOP Allicance
bindInterceptor( Matchers.any(), // classes Matchers.annotatedWith(Foo.class), // methods new FooInterceptor() // interceptor);
Cargar constantes con comprobaciones de tipo. gtug.properties -> String gtug; int members;
Injections opcionales, estáticas, ... @Inject(optional=true) Date launchDate;
Integraciones: JNDI, Spring, JMX, Struts2, OSGi...
guice 2.0Multibindings Hello Multibinder y MapBinder / útil para plugins
@Inject ImageFinder(Set<UriBuilder> uriBuilders) { ... }
// modulepublic void configure() { Multibinder<UriBuilder> uriBinder = Multibinder.newSetBinder(binder(), UriBuilder.class); uriBinder.addBinding().to(S3UriBinder.class);}
AssistedInject Hello @Assisted
Private modules Soluciona el robot-legs problem: Dos grafos del mismo objeto ligeramente distintos
unit testing
unit testing
Aplicando DI con Guice, nuestro código ya es testeable!
Código (método) que ejecuta un otro código para comprobar su validez. funcionamientoPrecondición - Clase a testear - Postcondición (asserts)
característicasRápido, repetible, automático.
frameworksJunit, TestNG, Mockito, EasyMock, JMock
test con junit
public class StringsTest { @Test public void stripAllHTMLForAGivenText() { String html = "<a>Link</a>"; String expected = "Link"; assertEquals(expected,Strings.stripHTML(html)); } }
public class Strings {
public static String stripHTML(String input) { return input.replaceAll("</?+(\\b)[ˆ<>]++>", ""); } }
unit test
test con dependencias
// Class under test CreditCardProcessor creditCardProcessor;
@Testpublic void chargeCreditCard() { creditCardProcessor = Guice.createInjector() .getInstance(CreditCardProcessor.class); CreditCard c = new CreditCard("9999 0000 7777", 5, 2009); creditCardProcessor.charge(c, 30.0); assertThat(creditCardProcessor.balance(c), is(-30.0));}
es incorrecto!
@Inject public CreditCardProcessor(Queue queue) { this.queue = queue; }
unit test…?
test con dependencias / mocks
@Testpublic void chargeCreditCard() { Queue queue = mock(Queue.class); CreditCard c = new CreditCard("9999 0000 7777", 5, 2009);
creditCardProcessor = new CreditCardProcessor(queue); creditCardProcessor.charge(c, 30.0);
verify(queue).enqueue(c, 30.0);}
Dependencias falsas: mocks (mockito)
para acabar…
futuro de dependency injection Google + SpringSource = JSR-330 acabada Spring 3 - annotation based Maven 3 - de plexus a guice
guice 3.0 Soporte para JSR-330 guice-persist integrado (antiguo warp-persist) Más SPI y Extension Points
y más guice! GIN - GWT INjection, subset de Guice RoboGuice - Guice en Android. Inject de views, resources...
gracias!
jordi@donky.org@jordi9
top related