Configuración interna del mecanismo de autoensamblado en Spring Framework

El mecanismo de autoensamblado (autowire) en Spring surge como respuesta a un problema recurrente en la configuración XML tradicional: la proliferación excesiva de etiquetas <property> dentro de cada definición de bean. Cuando un componente requiere múltiples dependencias, el archivo de configuración se vuelve extenso, difícil de leer y propenso a errores.

Ejemplo base

Definamos una interfaz que represente una criatura:

public interface Criatura {
    void alimentar();
}

Una implementación concreta:

public class Tigre implements Criatura {

    @Override
    public void alimentar() {
        System.out.println("El tigre se está alimentando");
    }

    @Override
    public String toString() {
        return "Soy un tigre";
    }
}

Un contenedor que mantiene una referencia a una criatura:

public class Reserva {

    private Criatura criatura;

    public Criatura getCriatura() {
        return criatura;
    }

    public void setCriatura(Criatura criatura) {
        this.criatura = criatura;
    }

    @Override
    public String toString() {
        return criatura != null ? criatura.toString() : "Reserva vacía";
    }
}

En la configuración XML clásica, se inyectan las dependencias explícitamente:

<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="tigre" class="com.ejemplo.Tigre" />

    <bean id="reserva" class="com.ejemplo.Reserva">
        <property name="criatura" ref="tigre" />
    </bean>

</beans>

Activación del autoensamblado

El atributo default-autowire en la etiqueta raíz <beans> permite activar el autoensamblado de forma global. Los valores disponibles son:

  • no: no se aplica autoensamblado; cada dependencia requiere su etiqueta <property>.
  • byName: Spring busca un bean cuyo identificador coincida con el nombre de la propiedad.
  • byType: Spring busca un bean cuyo tipo coincida con el tipo de la propiedad.
  • constructor: similar a byType, pero la inyección se realiza vía constructor.

Autoensamblado por nombre (byName)

<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans.xsd"
    default-autowire="byName">

    <bean id="criatura" class="com.ejemplo.Tigre" />
    <bean id="reserva" class="com.ejemplo.Reserva" />

</beans>

Aquí el bean Tigre recibe el identificador criatura, que coincide exactamente con el nombre del atributo en la clase Reserva. Spring detecta esta coincidencia y realiza la inyección automáticamente mediante el método setCriatura.

Autoensamblado por tipo (byType)

<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans.xsd"
    default-autowire="byType">

    <bean id="tigre" class="com.ejemplo.Tigre" />
    <bean id="reserva" class="com.ejemplo.Reserva" />

</beans>

En este caso, Spring no se basa en el identificador del bean, sino en el tipo. Dado que la propiedad criatura es de tipo Criatura y Tigre implementa dicha interfaz, el contenedor resuelve la dependencia automáticamente.

Conflicto por múltiples candidatos del mismo tipo

Si definimos una segunda implementación de Criatura:

public class Leon implements Criatura {

    @Override
    public void alimentar() {
        System.out.println("El león se está alimentando");
    }

    @Override
    public String toString() {
        return "Soy un león";
    }
}

Y la registramos junto con Tigre:

<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans.xsd"
    default-autowire="byType">

    <bean id="tigre" class="com.ejemplo.Tigre" />
    <bean id="leon" class="com.ejemplo.Leon" />
    <bean id="reserva" class="com.ejemplo.Reserva" />

</beans>

Spring lanzará una excepción NoUniqueBeanDefinitionException, ya que encuentra dos beans que implementan Criatura (tigre y leon) y no puede decidir cuál inyectar.

Solución 1: Excluir un bean del autoensamblado

El atributo autowire-candidate="false" indica que un bean no participará como candidato en el mecanismo de autoensamblado:

<bean id="tigre" class="com.ejemplo.Tigre" autowire-candidate="false" />
<bean id="leon" class="com.ejemplo.Leon" />
<bean id="reserva" class="com.ejemplo.Reserva" />

De este modo, el único candidato disponible para el tipo Criatura es leon, y Spring lo inyecta sin ambigüedad.

Solución 2: Marcar un bean como prioritario

El atributo primary="true" señala que, entre todos los candidatos del mismo tipo, ese bean debe ser el seleccionado por defecto:

<bean id="tigre" class="com.ejemplo.Tigre" />
<bean id="leon" class="com.ejemplo.Leon" primary="true" />
<bean id="reserva" class="com.ejemplo.Reserva" />

Ambas estrategias resuelven el conflicto, pero desde enfoques opuestos: la primera reduce el conjunto de candidatos y la segunda establece una preferencia explícita.

Etiquetas: Spring Framework autowired Inyección de Dependencias XML configuration IoC Container

Publicado el 7-20 17:53