Dependency Injection

Dependency Injection

La inyección de dependencias es un proceso mediante el cual los objetos definen sus dependencias solamente a través de los argumentos del constructor, argumentos en un método factory, o propiedades que son establecidas en el objeto instanciado una vez construido o devuelto desde un método factory.

El código es más limpio con el principio de inyección de dependencia, y el desacoplamiento es más efectivo cuando los objetos son devueltos junto con sus dependencias. El objeto no busca sus dependencias y no sabe la ubicación o la clase de sus dependencias. Como resultado, tus clases son mas fáciles de testear, particularmente cuando las dependencias están en interfaces y clases base abstractas, que permiten utilizar “stubs” o implementaciones de mocks en las pruebas unitarias.

La inyección de dependencias existe en dos variantes principales: Inyección usando el constructor y inyección basada en setters

Constructor-based Dependency Injection

LA inyección de dependencias basado en constructores se logra gracias al contenedor que invoca el constructor con un número de argumentos, cada uno representando una dependencia. Llamar a un método estático como factory con argumentos específicos es casi lo mismo, y esta discusión trata los argumentos de un constructor y de un método de fábrica estático de manera similar. El siguiente ejemplo muestra una clase que solo puede recibir inyección de dependencias a través de la inyección por constructor:

public class SimpleMovieLister {

	// the SimpleMovieLister has a dependency on a MovieFinder
	private final MovieFinder movieFinder;

	// a constructor so that the Spring container can inject a MovieFinder
	public SimpleMovieLister(MovieFinder movieFinder) {
		this.movieFinder = movieFinder;
	}

	// business logic that actually uses the injected MovieFinder is omitted...
}

Observa que no hay nada especial en esta clase. Es un POJO que no depende de interfaces, clases base o anotaciones específicas del contenedor.

Constructor Argument Resolution

La resolución de los argumentos del constructor se produce utilizando el tipo del argumento. Si no existe ninguna ambigüedad potencial en los argumentos del constructor de una definición de un bean, el orden en el que se definen los argumentos del constructor en una definición de bean es el orden en el que dichos argumentos se suministran al constructor apropiado cuando se instancia el bean. Considera la siguiente clase:

package x.y;

public class ThingOne {

	public ThingOne(ThingTwo thingTwo, ThingThree thingThree) {
		// ...
	}
}

Asumiendo que las clases ThingTwo y ThingThree no tienen relación de herencia, no existe ambigüedad. Por lo tanto, la siguiente configuración funciona bien, y no necesitas especificar los indices de los argumentos del constructor o tipos explícitamente en el elemento <constructor-arg/>

<beans>
	<bean id="beanOne" class="x.y.ThingOne">
		<constructor-arg ref="beanTwo"/>
		<constructor-arg ref="beanThree"/>
	</bean>

	<bean id="beanTwo" class="x.y.ThingTwo"/>

	<bean id="beanThree" class="x.y.ThingThree"/>
</beans>

Cuando otro bean es referenciado, el tipo es conocido, y el match puede ocurrir (como en el caso anterior). Cuando un tipo simple es usado, como por ejemplo, <value>true</value> , Spring no puede determinar el tipo del valor, y no puede matchear los tipos sin ayuda. Considera el siguiente ejemplo:

package examples;

public class ExampleBean {

	// Number of years to calculate the Ultimate Answer
	private final int years;

	// The Answer to Life, the Universe, and Everything
	private final String ultimateAnswer;

	public ExampleBean(int years, String ultimateAnswer) {
		this.years = years;
		this.ultimateAnswer = ultimateAnswer;
	}
}

Constructor argument type matching

En el escenario anterior, el contenedor puede usar el matcheo de tipos con tipos simples (string, int, etc) si especificas en los argumentos del constructor el tipo via type , como muestra el siguiente ejemplo:

<bean id="exampleBean" class="examples.ExampleBean">
	<constructor-arg type="int" value="7500000"/>
	<constructor-arg type="java.lang.String" value="42"/>
</bean>

Constructor argument index

Puedes usar el atributo index para especificar explícitamente un índice y el valor para cada uno dentro de los argumentos del constructor:

<bean id="exampleBean" class="examples.ExampleBean">
	<constructor-arg index="0" value="7500000"/>
	<constructor-arg index="1" value="42"/>
</bean>

Además de resolver la ambigüedad de multiples argumentos de tipo simple, especificar un indice resuelve la ambigüedad de cuando se tiene dos argumentos del mismo tipo:

<bean id="exampleBean" class="examples.ExampleBean">
	<constructor-arg index="0" value="7500000"/>
	<constructor-arg index="1" value="42"/>
</bean>

ℹ️ El índice comienza con la posición 0

Constructor argument name

También puedes usar el nombre de los parámetros cuando hay ambigüedad, como muestra el siguiente ejemplo:

<bean id="exampleBean" class="examples.ExampleBean">
	<constructor-arg name="years" value="7500000"/>
	<constructor-arg name="ultimateAnswer" value="42"/>
</bean>

Ten en cuenta, que para hacer esto funcionar, tú código debe ser compilado usando el flag -parameters activado para que Spring pueda buscar los nombres de los parámetros en el constructor. Si no quieres o no puedes compilar el código con el flag -parameters , puedes usar la anotación JDK @ConstructorProperties para explicitamente nombrar a tus argumentos del constructor. La clase de ejemplo tiene el siguiente aspecto:

package examples;

public class ExampleBean {

	// Fields omitted

	@ConstructorProperties({"years", "ultimateAnswer"})
	public ExampleBean(int years, String ultimateAnswer) {
		this.years = years;
		this.ultimateAnswer = ultimateAnswer;
	}
}

Setter-based Dependency Injection

La inyección de dependencia usando el constructor es lograda por el contenedor llamando a los métodos setters de tu bean después de invocar un constructor sin argumentos o un método estático que sea factory para instanciar el bean.

El siguiente ejemplo muestra una clase que solamente puede inyectar dependencias usando puramente la inyección con setter. Esta clase es un POJO que no tiene dependencias de interfaces o clases base del contenedor o anotaciones

public class SimpleMovieLister {

	// the SimpleMovieLister has a dependency on the MovieFinder
	private MovieFinder movieFinder;

	// a setter method so that the Spring container can inject a MovieFinder
	public void setMovieFinder(MovieFinder movieFinder) {
		this.movieFinder = movieFinder;
	}

	// business logic that actually uses the injected MovieFinder is omitted...
}

ApplicationContext soporta inyección de dependencia basado en el constructor y basado en setters para los beans que maneja. También soporta inyección de dependencia basado en setter después de que algunas dependencias ya hayan sido inyectadas a través de la inyección basada en constructor. Tú configuras las dependencias en la forma de un BeanDefinition , el cual utilizas en conjunto con instancias de PropertyEditor para convertir las propiedades de un formato a otro. Sin embargo, la mayoría de usuarios de Spring no trabajan con estas clases directamente (es decir, programáticamente) pero si con definiciones de beans en XML, o componentes anotados (es decir, clases anotadas con @Component, @Controller, etc), o métodos con la anotación @Bean en la configuración basada en java (clases con @Configuration).

Estas fuentes son entonces convertidas internamente en instancias de BeanDefinition y usadas para cargar el contenedor IoC de la instancia de Spring.

Constructor-based or setter-based DI?

Dado que puedes combinar la inyección de dependencia basado en constructor y la basada en setter, es una buena regla usar el constructor para dependencias requeridas y los metodos setters o metodos de configuración para las dependencias opcionales. Ten en cuenta que usar la anotación @Autowired en un método setter puede ser usado para indicar que la dependencia es una requerida; Sin embargo, la inyección basada en constructor es preferible con la validación programatica de los argumentos.

El equipo de Spring generalmente se inclinan por la inyección en el constructor, dado que te deja implementar componentes como objetos inmutables y se asegura que las dependencias requeridas no son null.

Además, los componentes con inyección en el constructor son siempre devueltos al código cliente en un estado completo de inicialización. Como nota aparte, un gran números de argumentos en el constructor es una mala señal de código malo, implica que la clase tienen demasiadas responsabilidades y debería ser refactorizada para abordar mejorar la separación de responsabilidades.

La inyección con setter debería principalmente ser usada para dependencias opcionales que pueden asignar valores por defecto razonables dentro de la clase. Por otro lado, los checkeos de not-null deben ser realizados en cualquier sitio donde el código usa las dependencias. Un beneficio de la inyección en los setters es que los metodos setters hacen los objetos de esa clase fácil de reconfigurar o reinyectar más tarde. El manejo a traves de JMX MBeans es por tanto, un caso convincente de uso para inyección usando setters.

Usa el estilo de inyección de dependencias que tenga mayor sentido para una clase en particular. A veces, cuando manejas con clases de terceros para los cuales no tienes la fuente, la elección es tuya. Si por ejemplo una clase de terceros no tiene métodos setters, entonces la inyección por constructor será la única opción.

Dependency Resolution Process

El contenedor realiza la resolución de dependencias de un bean de la siguiente manera:

  • El ApplicationContext es creado e inicializado con la configuración de metadata que describen a cada uno de los beans. La configuración de metadata puede ser especificada con XML, código Java o anotaciones.
  • Por cada bean, sus dependencias son expresadas en forma de propiedades, argumentos del constructor, o argumentos en un método factory estático (Si usas eso en vez de el constructor normal). Estas dependencias son suministradas, cuando se crea el bean.
  • Cada propiedad o argumento del constructor es una definición del valor que establecer, o una referencia a otro bean dentro del contenedor.
  • Cada propiedad o argumento del constructor el cual es un valor es convertido del formato especificado al tipo actual del valor de la propiedad o argumento del constructor. Por defecto, Spring puede convertir un valor suministrado como string a todos los tipos incorporados como int , long , String , boolean , etc

El contenedor de spring valida la configuración por cada bean a medida que el contenedor es creado. Sin embargo, las propiedades del bean mismo no son establecidas hasta que el bean es verdaderamente creado. Los beans que tienen el scope singleton y establecidas para ser pre-instanciadas (comportamiento por defecto) son creadas cuando el contenedor es creado. Los scopes están definidos en Bean Scopes. En caso contrario, el bean es creado solamente cuando es requerido. La creación de un bean potencialmente puede causar que un grafo de beans sea creado, a medida que las dependencias de los beans y las dependencias de sus dependencias son creadas y asignadas. Ten en cuenta que el fallo en la resolución en el match puede aparecer más tarde, esto es, en la primera creación del bean afectado.

Circular dependencies

Sí predominantemente usas inyección a través del constructor, es posible crear un escenario de dependencias circulares que no es posible de resolver.

Por ejemplo: La clase A requiere una instancia de la clase B a través del constructor, y la clase B requiere una instancia de la clase A a través del constructor. Si configuras beans para las clases A y B para que sean inyectadas la una en la otra, el contenedor de spring IoC detecta la referencia circular en tiempo de ejecución y lanza BeanCurrentlyInCreationException

Una posible solución es editar el código fuente de alguna de las clases para ser configuradas usando setters en vez de los constructores. De forma alternativa, evitar usar la inyección en el constructor y usar la inyección basada en setters solamente. En otras palabras, aunque no es recomendable, puedes configurar las dependencias circulares usando la inyección mediante setter.

A diferencia del típico caso (sin dependencias circulares), una dependencia circular entre el bean A y el bean B fuerza a uno de los beans a ser inyectados en el otro antes de ser completamente inicializado el mismo

Generalmente puedes confiar en que Spring realiza las cosas correctas. Es capaz de detectar problemas en la configuración, como referencias a bean no existentes o dependencias circulares, en el momento de carga del contenedor. Spring establece las propiedades y resuelve las dependencias lo más tarde posible, cuando el bean es actualmente creado. Esto significa que cuando el contenedor de Spring ha cargado correctamente puede después generarte una excepción cuando le pides un objeto si hay algún problema creando el objeto o alguna de sus dependencias.

Por ejemplo, el bean lanza una excepción como resultado de una propiedad faltante o incorrecta. Este potencial problema de visibilidad posterior de algun problema de configuración es la razón por la que las implementaciones de ApplicationContext realizan una pre-instanciación de beans singleton.

Al coste de algo de tiempo y memoria en crear estos beans antes de que sean realmente necesarios, descubres errores en la configuración cuando el ApplicationContext es creado, no después. Puedes seguir sobreescribiendo este comportamiento por defecto y que los beans singleton se inicialicen de manera lazy, en vez de ser pre-instanciados de manera prematura.

Si no existe dependencias circulares, cuando una o más beans colaboradores sean inyectados en un bean dependiente, cada bean colaborador está totalmente configurado antes de ser inyectado en el bean dependiente. Esto significa que, si el bean A tiene una dependencia en el Bean B, el contenedor de Spring IoC configura completamente el bean B antes de invocar el método setter en el bean A. En otras palabras, el bean es instanciado (si no es un singleton pre-instanciado), sus dependencias son establecidas, y los métodos del ciclo de vida son invocados.

  • Singleton beans → Se crean al iniciar el contexto (pre-instanciación), a menos que uses @Lazy.
  • Prototype beans → Siempre se crean solo cuando se solicitan (lazy por naturaleza).
  • Inyección (constructor o setter) no cambia el momento de creación, lo que importa es si el bean es singleton o prototype.

Examples of Dependency Injection

En este ejemplo se usa la configuración de metadata basada en XML para la inyección a traves de setters. Una pequeña parte del archivo XML de configuración de Spring contendría unas definiciones de beans como las siguientes:

<bean id="exampleBean" class="examples.ExampleBean">
	<!-- setter injection using the nested ref element -->
	<property name="beanOne">
		<ref bean="anotherExampleBean"/>
	</property>

	<!-- setter injection using the neater ref attribute -->
	<property name="beanTwo" ref="yetAnotherBean"/>
	<property name="integerProperty" value="1"/>
</bean>

<bean id="anotherExampleBean" class="examples.AnotherBean"/>
<bean id="yetAnotherBean" class="examples.YetAnotherBean"/> 

El siguiente ejemplo muestra la correspondiente clase ExampleBean

public class ExampleBean {

	private AnotherBean beanOne;

	private YetAnotherBean beanTwo;

	private int i;

	public void setBeanOne(AnotherBean beanOne) {
		this.beanOne = beanOne;
	}

	public void setBeanTwo(YetAnotherBean beanTwo) {
		this.beanTwo = beanTwo;
	}

	public void setIntegerProperty(int i) {
		this.i = i;
	}
}

En el ejemplo anterior, los setters son declarados para matchear con las propiedades especificadas en el archivo XML. El siguiente ejemplo muestra una configuración para inyección a través del constructor

<bean id="exampleBean" class="examples.ExampleBean">
	<!-- constructor injection using the nested ref element -->
	<constructor-arg>
		<ref bean="anotherExampleBean"/>
	</constructor-arg>

	<!-- constructor injection using the neater ref attribute -->
	<constructor-arg ref="yetAnotherBean"/>

	<constructor-arg type="int" value="1"/>
</bean>

<bean id="anotherExampleBean" class="examples.AnotherBean"/>
<bean id="yetAnotherBean" class="examples.YetAnotherBean"/>

El siguiente ejemplo muestra la correspondiente clase ExampleBean

public class ExampleBean {

	private AnotherBean beanOne;

	private YetAnotherBean beanTwo;

	private int i;

	public ExampleBean(
		AnotherBean anotherBean, YetAnotherBean yetAnotherBean, int i) {
		this.beanOne = anotherBean;
		this.beanTwo = yetAnotherBean;
		this.i = i;
	}
}

Los argumentos del constructor especificados en la definición del bean son usados como argumentos para el constructor de ExampleBean

Ahora considera una variante de este ejemplo, el cual, en vez de usar un constructor, se le dice a Spring de llamar a un método estático que actúa como factory para devolver una instancia de un objeto.

<bean id="exampleBean" class="examples.ExampleBean" factory-method="createInstance">
	<constructor-arg ref="anotherExampleBean"/>
	<constructor-arg ref="yetAnotherBean"/>
	<constructor-arg value="1"/>
</bean>

<bean id="anotherExampleBean" class="examples.AnotherBean"/>
<bean id="yetAnotherBean" class="examples.YetAnotherBean"/>

El siguiente ejemplo muestra la correspondiente clase ExampleBean

public class ExampleBean {

	// a private constructor
	private ExampleBean(...) {
		...
	}

	// a static factory method; the arguments to this method can be
	// considered the dependencies of the bean that is returned,
	// regardless of how those arguments are actually used.
	public static ExampleBean createInstance (
		AnotherBean anotherBean, YetAnotherBean yetAnotherBean, int i) {

		ExampleBean eb = new ExampleBean (...);
		// some other operations...
		return eb;
	}
}

Los argumentos al método estático de la factory son suministrados por el elemento <constructor-arg/>, exactamente igual como un constructor hubiese sido utilizado. El tipo de la clase que se devuelve por el método factory no tiene por que ser del mismo tipo que la clase que contiene el método estático de la factory (a pesar de que en este ejemplo lo es). Una instancia (otro bean) puede ser usando de manera similar usando un método factory(sin static) para devolver un objeto(pero utilizando el atributo factory-bean en vez del atributo class en la definición del bean)