style="display:inline-block;width:728px;height:90px"
data-ad-client="ca-pub-5164839828746352"
data-ad-slot="7563230308">

Dagger 2

Inyección de dependencias con Dagger 2

Dagger 2 es un framework de inyección de dependencias que se diferencia de otros en que la inyección se hace en tiempo de compilación con código generado en vez de en tiempo de ejecución utilizando reflection, esto tiene como ventajar principal la mejora en el desempeño de la aplicación pero tambien tiene otros como una mejor trazabilidad en los stacktraces cuando algo sale mal ( en vez de solo ver Method.invoke, invoke por todos lados ) o que se puede obtener errores de dependencias durante la fase de compilación en vez de en runtime.

Aquí hay un video donde se explica que es, cómo se compara con otros ( Guice, Spring, Dagger 1) y los beneficios que tiene.

Ejemplo

Voy a hacer un ejemplo que espero me salga sencillo:

Digamos que tengo una app para buscar palabras en páginas web, lo componen tres clases:

- SearchApp contiene el main
- SearchInPage hace la búsqueda
- HttpClient obtiene el contenido de una página

La clase SearchInPage tiene una dependencia con HttpClient para obtener una página web y podríamos decir que también tiene como dependencia el valor del host sobre el que va a buscar.

Las dependencias se verían así:

// SearchInPage
package app;
...
public class SearchInPage {
    private HttpClient client;
    private String host;

    public SearchInPage(String host) {
        this.host = host;
        this.client = new HttpClient(); // <-- aqui!!
    }

    public List<String> search( String keywork ) {
    ....
    }
}

No es nada terrible, pues se trata de un ejemplo sencillo, pero en la vida real el número de dependencias puede crecer fuera de control o cuando se requiere que esas dependencias se compartan entre objetos ( ejemplo: más clases necesitan el mismo cliente ) o cuando se requiere hacer una configuración compleja antes de poder usarlo ( ejemplo: agregarle certificados al cliente o credenciales etc.) tener código así dificulta mucho al mantenimiento lo cual trae consigo bugs, mala calidad, tedio, etc.

   public SearchInPage(String host) {
        this.host = host;
        this.client = new HttpClient(); // <-- aqui!!
    }

El código completo en este punto se puede ver en este link

Usando Dagger 2

Una forma de resolver esto es usando FactoryMethods que solo empeoran el código y en muchos casos son tediosos de construir, otra es usando frameworks de inyección de dependencias que es lo que vamos a hacer.

Con Dagger se requiere usar como mínimo 3 artefactos:

- Quien necesita la dependencia y quién la va a proveer: Esto se indica con las anotaciones @Inject, @Provides ( muy similares a @Autowired y @Bean de Spring para quien lo conozca )
- Modulos: clases marcadas con la anotación @Module donde van a estar todos los "@Provides" para que Dagger sepa donde buscar (Similares a la anotación @Config de Spring )
y
- Componentes: interfaces marcadas con la anotación @Component que tienen un método con lo que se necesita crear en nuestro caso será la clase SearchInApp (no hay equivalente en Spring pues ahí se hace en runtime )

Entonces los cambios a hacer son:

Agregar anotación @Inject en la clase SearchInPage y quitar la creación explícita del cliente quedando así:

public class SearchInPage {
    @Inject HttpClient client;
    @Inject String host;
 
    @Inject
    public SearchInPage() {}
    ...

Nota: en la versión actual se puede hacer inyección de atributos, de constructor y de métodos (setters) pero en mi prueba no pude hacer de constructor y de atributos al mismo tiempo. Así que de acuerdo a la doc, puse los atributos para ser inyectados y el constructor no-args también marcado como no inyectado
Nota 2: No es recomendable usar atributos no-privados a menos que se tenga la certeza que el código no será usado fuera del ámbito para el cual es creado (una aplicación). Si existe la posibilidad de que el código se comparta (en una biblioteca) lo mejor es usar inyección de setters

Bueno pues ya muchas notas.

Ahora se define el módulo que va a proveer esas dependencias:

package app;
 
import dagger.Module;
import dagger.Provides;
 
@Module
public class SearchModule {
 
    @Provides HttpClient provideHttpClient() {
        return new HttpClient();
    }
    @Provides String provideHost() {
        return "http://www.iana.org/domains/reserved";
    }
}

Que es el mismo código que estaba en la clase originalmente, solo que al moverla de lugar se permite que se pueda desacoplar

package app;
 
import dagger.Component;
 
@Component( modules = SearchModule.class )
public interface SearchComponent {
    SearchInPage searchInPage();
}

El componente dice que va a usar el modulo: SearchModule para poder crear una instancia de SearchInPage ( el método que tiene ), es una interfaz que será satisfecha por código que Dagger 2 generará para armar las dependencias de forma correcta, por debajo tiene código como este:

~/c/j/dagger2example (master) $ ls -ltra app/*.java
-rw-r--r--  1 oreyes  1998171563   788 Jun  9 03:52 app/SearchModule_ProvideHttpClientFactory.java
-rw-r--r--  1 oreyes  1998171563   748 Jun  9 03:52 app/SearchModule_ProvideHostFactory.java
-rw-r--r--  1 oreyes  1998171563  1107 Jun  9 03:52 app/SearchInPage_MembersInjector.java
-rw-r--r--  1 oreyes  1998171563   795 Jun  9 03:52 app/SearchInPage_Factory.java
-rw-r--r--  1 oreyes  1998171563  1828 Jun  9 03:52 app/DaggerSearchComponent.java
~/c/j/dagger2example (master) $ more app/DaggerSearchComponent.java
package app;

import dagger.MembersInjector;
import javax.annotation.Generated;
import javax.inject.Provider;

@Generated("dagger.internal.codegen.ComponentProcessor")
public final class DaggerSearchComponent implements SearchComponent {
  private Provider<HttpClient> provideHttpClientProvider;
  private Provider<String> provideHostProvider;
  private MembersInjector<SearchInPage> searchInPageMembersInjector;
  private Provider<SearchInPage> searchInPageProvider;

  private DaggerSearchComponent(Builder builder) {
    assert builder != null;
    initialize(builder);
  }

  public static Builder builder() {
    return new Builder();
  }

  public static SearchComponent create() {
    return builder().build();
  }

  private void initialize(final Builder builder) {
    this.provideHttpClientProvider = SearchModule_ProvideHttpClientFactory.create(builder.searchModule);
    this.provideHostProvider = SearchModule_ProvideHostFactory.create(builder.searchModule);
    this.searchInPageMembersInjector = SearchInPage_MembersInjector.create(provideHttpClientProvider, provideHostProvider);
    this.searchInPageProvider = SearchInPage_Factory.create(searchInPageMembersInjector);
  }
.....etc. etc.

Factories, builders, providers, injectors y demás etc's que estan bien para que una máquina los cree, no para que el humano lo tenga que escribir.

Finalmente en la clase principal se usa el componente para crear el objeto. La clase que implementa la interfaz tiene el prefijo "Dagger..."

package app;
 
public class SearchApp {
        public static void main( String ... args ) {
        SearchInPage a = DaggerSearchComponent.create().searchInPage();
        for ( String s : a.search("domains") ) {
            System.out.println(s);
        }
    }
}

El código final se ve aquí

Para compilarlo se requiere tener Dagger en el classpath, se puede usar Maven o Gradle o simplemente un script con los jars necesarios, que pongo en esta revisión

Conclusión

Dagger resuelve resuelve la inyección de dependencias generando código en vez de usar reflection. Aún sigue en desarrollo, fue adoptado por Google haciendo un fork de Dagger 1 de la compañía Square y tiene como uso principal el desarrollo de aplicaciones Android donde el desempeño es muy importante por el espacio reducido y donde el lanzamiento de aplicaciones es muy importante.

Existen muchas más opciones para usar Dagger, como definición de "ámbitos" ( scopes ) como Aplicación, Usuario o Actividad muy útil para Android. También puede hacer inyección tardía, calificadores ( @Qualifier ) y varias cosas más.

La documentación del proyecto está aquí:

http://google.github.io/dagger/

Y del repositorio de github que tiene más ejemplos:

https://github.com/google/dagger

Comentarios

Opciones de visualización de comentarios

Seleccione la forma que prefiera para mostrar los comentarios y haga clic en «Guardar las opciones» para activar los cambios.
Imagen de ezamudio

qué horror

Me recordó a EJB 2 (¿o era 1?) donde tenías que hacer el Locator y el no sé qué y como tres componentes distintos, MAS la interfaz del EJB, todo para proveer este rollo y al final todo era en código duro.

Si al final vas a conectar todo por código... el beneficio de esta cosa me parece mínimo.

Spring también tiene una cosa por ahí para conectar todo por código, una especie de application context en Java. Me pareció algo sin sentido.

Al contrario, es para que no

Al contrario, es para que no conectes todo a mano.

Se parece porque los EJB's eran para tener objetos "empresariales" que funcionaran en varios servidores al mismo tiempo y no tener que aventarte el protocolo a mano, pero tenias que definir una interfaz, hace la implementación de esa interfaz, hacer un home que te iba a buscar es Bean y un home factory que te creara el home que iba a buscar el bean por tí y ... pfff

Esto no tiene nada que ver con eso, pero se parece porque en algún lugar le tienes que decir como crear cosas.

Un ejemplo de su utilidad es cuando tienes una clase que se usa mucho en tu aplicación / aplicaciones pero tiene una inicialización compleja ( eg un componente que envía transaccione bancarias) En vez de estarle haciendo copy/paste a un código que ya funciona o estar creando clases Factory ( con su respectiva interfaz XyzFactory y su XyzFactoryImpl ) para armarlo, lo anotas (jaja sin albur) con @Inject y el framework encuentra el tipo correcto por tí entre los módulos definidos.

Más aún para hacer pruebas se pueden inyectar versiones mock de esa clase compleja sin modificar to código.

El hecho que se genere código en vez de usar reflection es transparente para el programador porque ese código no lo usas, ni lo ves.

Spring utiliza reflection y esto tiene además de la penalidad en el desempeño (que es mínima) stacktraces como estos:

at com.blogspot.nurkiewicz.BookService.listBooks()
at com.blogspot.nurkiewicz.BookService$$FastClassByCGLIB$$e7645040.invoke()
at net.sf.cglib.proxy.MethodProxy.invoke()
at org.springframework.aop.framework.Cglib2AopProxy$CglibMethodInvocation.invokeJoinpoint()
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed()
at org.springframework.aop.aspectj.MethodInvocationProceedingJoinPoint.proceed()
at com.blogspot.nurkiewicz.LoggingAspect.logging()
at sun.reflect.NativeMethodAccessorImpl.invoke0()
at sun.reflect.NativeMethodAccessorImpl.invoke()
at sun.reflect.DelegatingMethodAccessorImpl.invoke()
at java.lang.reflect.Method.invoke()
at org.springframework.aop.aspectj.AbstractAspectJAdvice.invokeAdviceMethodWithGivenArgs()
at org.springframework.aop.aspectj.AbstractAspectJAdvice.invokeAdviceMethod()
at org.springframework.aop.aspectj.AspectJAroundAdvice.invoke()
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed()
at org.springframework.aop.interceptor.AbstractTraceInterceptor.invoke()
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed()
at org.springframework.transaction.interceptor.TransactionInterceptor.invoke()
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed()
at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke()
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed()
at org.springframework.aop.framework.Cglib2AopProxy$DynamicAdvisedInterceptor.intercept()
at com.blogspot.nurkiewicz.BookService$$EnhancerByCGLIB$$7cb147e4.listBooks()
at com.blogspot.nurkiewicz.web.BookController.listBooks()

( ejemplo de: http://www.nurkiewicz.com/2012/03/filtering-irrelevant-stack-trace-lines... )

Donde en el stacktrace salen muchas de las clases que intervinieron para instanciar el objeto pero no ayudan mucho. Anteriormente la configuración se hacía con XML (ewww) ahora ya utiliza annotations.

Imagen de ezamudio

XML

Yo hago mi config de Spring con una mezcla de XML y anotaciones. Las anotaciones son muy útiles pero son más rígidas, el XML es más flexible para estar reemplazando componentes en aplicaciones complejas.

Si, de hecho el configurar

Si, de hecho el configurar con anotaciones es más restringido y por ello te avientas lo que te haga falta por código.

Estoy viendo que el módulo es opcional si las clases saben como crearse solas ( con un constructor marcado con @Inject )

Por ejemplo si Foo depende de Bar que a su vez depende de Baz el código sería:

class Foo {
    @Inject Bar bar;
    @Inject Foo(){}
}
class Bar {
    @Inject Baz baz;
    @Inject Bar(){}
}
class Baz {
    @Inject Baz(){}
}
@Component()
interface FooComponent {
    Foo foo();
}

class App {
    public static void main( String ... args ) {
        Foo foo = DaggerFooComponent.create().foo();
    }
}

Creo que quedaría mejor aún si la raíz (Foo) pudiera ser anotada como @Component, pero la razón de ese diseño la desconozco ( tiene que ser una interfaz o clase abstracta),

Re: Dagger 2

 

meme

style="display:inline-block;width:728px;height:90px"
data-ad-client="ca-pub-5164839828746352"
data-ad-slot="7563230308">