En la mayoría de los casos de uso, la mayoría de los beans en un contenedor son singletons. Si un bean singleton necesita interactuar con otro bean singleton, o un bean no singleton necesita interactuar con otro bean no singleton, entonces normalmente la forma de lidiar con una dependencia es definir un bean como una propiedad del otro. El problema surge si los ciclos de vida de los granos son diferentes. Supongamos que un bean singleton A debe usar un bean no singleton (prototipo) B, por ejemplo, cada vez que se llama a un método en A. El contenedor crea el bean singleton A solo una vez y, por lo tanto, la capacidad de establecer propiedades aparece solo una vez. El contenedor no puede proporcionar al bean A una nueva instancia del bean B cada vez que sea necesario.

La solución es renunciar a cierto grado de inversión de control. Puede hacer que el bean A sea consciente del contenedor implementando la interfaz ApplicationContextAware y llamando a getBean("B") en el contenedor, solicitando una instancia del bean B (normalmente nueva) siempre que El bean A lo necesita. El siguiente ejemplo demuestra este enfoque:

Java

// class that uses a stateful Command style class to perform some processing on
package fiona.apple;
// Spring-API imports
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
public class CommandManager implements ApplicationContextAware {
    private ApplicationContext applicationContext;
    public Object process(Map commandState) {
        // create a new instance of the corresponding
        Command Command command = createCommand();
        // set the state for the (expectedly brand new)
        Command instance command.setState(commandState);
        return command.execute();
    }
    protected Command createCommand() {
         // note the Spring API dependency!
         return this.applicationContext.getBean("command", Command.class);
    }
    public void setApplicationContext(
         ApplicationContext applicationContext) throws BeansException {
         this.applicationContext = applicationContext;
        }
}            
Kotlin

// class that uses a stateful Command style class to perform specific processing
package fiona.apple
// Spring-API imports
import org.springframework.context.ApplicationContext
import org.springframework.context.ApplicationContextAware
class CommandManager : ApplicationContextAware {
    private lateinit var applicationContext: ApplicationContext
    fun process(commandState: Map<*, *>): Any {
        // create a new instance of the corresponding Command
        val command = createCommand()
        // set the state for the (expectedly brand new)
        Command instance command.state = commandState
        return command.execute()
    }
    // note the dependency on the Spring API !
    protected fun createCommand() =
            applicationContext.getBean("command", Command::class.java)
    override fun setApplicationContext(applicationContext: ApplicationContext) {
        this.applicationContext = applicationContext
    }
}

La opción anterior no es deseable porque el código comercial (capa de dominio) conoce y está vinculado al marco Spring. La inyección de métodos es una característica del contenedor Spring IoC ligeramente avanzada que le permite manejar este caso de uso de manera limpia.

Puedes leer Más información sobre la conveniencia de la inyección de dependencia a través de un método en esta entrada de blog .

Inyección de dependencia mediante búsqueda

La inyección de dependencia mediante búsqueda es la capacidad de un contenedor para anular los métodos de los beans administrados por contenedor y devolver el resultado de la búsqueda de otro bean con nombre en el contenedor. La búsqueda normalmente implica un prototipo de bean, como en el escenario descrito en la sección anterior. El marco Spring implementa esta inyección de dependencia a través de un método, utilizando la generación de código de bytes de la biblioteca CGLIB para generar dinámicamente una subclase que anula el método.

  • Para que esta subclase dinámica funcione, la clase que contiene las subclases contenedoras de Spring Bean no puede ser final, y el método anulado no puede ser final tampoco.

  • La prueba unitaria de una clase que tiene un método abstract requiere que subclases la clase tú mismo y proporciones una implementación de la función stub abstract.

  • También se necesitan métodos concretos para escanear componentes, que requieren clases específicas.

  • Otra limitación clave es que los métodos de búsqueda no funcionan con los métodos de fábrica y, en particular, con los métodos @Bean en las clases de configuración, ya que en este caso el contenedor no es responsable de la creación de instancias y, por lo tanto, no puede crear una subclase generada en tiempo de ejecución en la mosca.

En el caso de la clase CommandManager en el fragmento de código anterior, el contenedor Spring anula dinámicamente la implementación del método. createCommand() . La clase CommandManager no tiene ninguna dependencia de Spring, como muestra el ejemplo reelaborado:

Java

package fiona.apple;
// no more Spring imports!
public abstract class CommandManager {
    public Object process(Object commandState) {
       // create a new instance of the corresponding interface
       Command Command command = createCommand();
       // set the state for the (expectedly brand new)
       Command instance command.setState(commandState);
       return command.execute();
       }
       // ok... but where is the implementation of this method?
       protected abstract Command createCommand();
}
Kotlin

package fiona.apple
// no more Spring imports!
abstract class CommandManager {
    fun process(commandState: Any): Any {
        // create a new instance of the appropriate Command interface
        val command = createCommand()
        // set the state for the (expectedly brand new) Command instance
        command.state = commandState
        return command. execute()
    }
    // ok... but where is the implementation of this method?
    protected abstract fun createCommand(): Command
}

En la clase de cliente que contiene el método a inyectar (en este caso CommandManager ), debe tener una firma como esta:

 <public|protected> [abstract] <return-type>  theMethodName(no-arguments);

Si un método es un abstracto, entonces la subclase generada dinámicamente implementa ese método. De lo contrario, la subclase generada dinámicamente anula el método concreto definido en la clase original. Considere el siguiente ejemplo:


< !-- a stateful bean deployed as a prototype (not a singleton) -->
<bean id="myCommand" class="fiona.apple.AsyncCommand" scope="prototype">
    <!-- inject dependencies here as needed -->
</bean>
<!-- commandProcessor uses statefulCommandHelper -->
<bean id="commandManager" class="fiona.apple.CommandManager">
    <lookup-method name="createCommand" bean="myCommand"/>
</bean>

El bean identificado como commandManager llama a su propio createCommand() siempre que necesita una nueva instancia del bean myCommand. La implementación del bean myCommand como prototipo debe realizarse con precaución a menos que sea absolutamente necesario. Si es un bean singleton, entonces se devolverá la misma instancia del bean myCommand cada vez.

Alternativamente, en un modelo de componente basado en anotaciones, puede declarar una búsqueda método que utiliza la anotación @Lookup, como se muestra en el siguiente ejemplo:

Java

public abstract class CommandManager {
    public Object process(Object commandState) {
        Command command = createCommand();
        command.setState(commandState);
        return command.execute();
    }
    @Lookup("myCommand")
    protected abstract Command createCommand();
}
Kotlin

abstract class CommandManager {
    fun process(commandState: Any): Any {
        val command = createCommand()
        command.state = commandState
        return command.execute()
    }
    @Lookup("myCommand")
    protected abstract fun createCommand(): Command
}

O más típicamente, puede confiar en que el bean de destino se resuelva de acuerdo con el tipo de retorno declarado del método de búsqueda:

Java

public abstract class CommandManager {
    public Object process(Object commandState) {
        Command command = createCommand();
        command.setState(commandState);
        return command.execute();
    }
    @Lookup
    protected abstract Command createCommand();
}
Kotlin

abstract class CommandManager {
    fun process(commandState: Any): Any {
        val command = createCommand()
        command.state = commandState
        return command.execute()
    }
    @Lookup
    protected abstract fun createCommand(): Command
}

Tenga en cuenta que normalmente, tales Los métodos de búsqueda anotados deben declararse con una implementación de función stub específica para que sean compatibles con las reglas de escaneo de Spring Bean, donde las clases abstractas se ignoran de forma predeterminada. Esta restricción no se aplica a clases de beans explícitamente registradas o importadas explícitamente.

Otra forma de acceder a beans de destino que se encuentran en diferentes ámbitos es Implementación de puntos de ObjectFactory/Provider. Consulte "Beans en el alcance como dependencias".

También puede encontrar el ServiceLocatorFactoryBean (en el paquete org.springframework.beans.factory.config) útil.

Reemplazo de método personalizado

Una forma menos útil de inyección de dependencia de método que la inyección de dependencia de método de búsqueda es la capacidad de reemplazar arbitrariamente métodos en un bean administrado con otra implementación de método. Puede omitir con seguridad el resto de esta sección hasta que necesite esta funcionalidad.

Con los metadatos de configuración basados en XML, puede utilizar el elemento replaced-method para reemplazar la implementación de un método existente. con otro para el frijol expandido. Considere la siguiente clase, que tiene un método computeValue que debemos anular:

Java

public class MyValueCalculator {
    public String computeValue(String input) {
        // some real code...
    }
    // some other methods...
}
Kotlin
 
class MyValueCalculator {
    fun computeValue(input: String): String {
        // some real code...
    }
    // some other methods...
}

Una clase que implementa la interfaz org.springframework.beans.factory.support.MethodReplacer proporciona una nueva definición de método, como se muestra en el siguiente ejemplo:

Java
    
/**
 * is intended to override an existing computeValue(String).
 * implementation in MyValueCalculator
 */
public class ReplacementComputeValue implements MethodReplacer {
    public Object reimplement(Object o, Method m, Object[] args) throws Throwable {
        // get the input value, work with it and return the calculated result
        String input = (String) args [0];
        ...
        return ...;
    }
}
class="spring-block-title">Kotlin

/**
 * is intended to override an existing computeValue(String).
 * implementation in MyValueCalculator
 */
 class ReplacementComputeValue : MethodReplacer {
    override fun reimplement(obj: Any, method: Method, args: Array<out Any>): Any {
        // get the input value, work with it and return the calculated result
        val input = args[0] as String;
        ...
        return ...;
    }
}

La definición de un bean para expandir la clase original y establecer el método de anulación se verá así:


<bean id="myValueCalculator" class="x.y.z.MyValueCalculator">
    <!-- arbitrary method replacement -->
    <replaced-method name="computeValue" replacer="replacementComputeValue">
        <arg-type>String</arg-type>
    </replaced-method>
</bean>
<bean id="replacementComputeValue" class="a.b.c.ReplacementComputeValue"/>

Puedes usar uno o más elementos <arg- escriba/> dentro de un elemento <replaced-method/> para especificar la firma del método que se anula. Solo se necesita una firma para los argumentos si el método está sobrecargado y hay múltiples variantes en la clase. Por conveniencia, la cadena de tipo para el argumento puede ser una subcadena del nombre de tipo completo. Por ejemplo, todo lo siguiente corresponde a java.lang.String:


java.lang.String
String
Str

Dado que el número de argumentos suele ser suficiente para distinguir entre cada opción posible, este acceso directo puede ahorrar mucho tiempo al permitirle ingresar solo la cadena más corta que coincida con el tipo de argumento.