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:
// 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;
}
}
// 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.
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 serfinal
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 stubabstract
. 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:
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();
}
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:
public abstract class CommandManager {
public Object process(Object commandState) {
Command command = createCommand();
command.setState(commandState);
return command.execute();
}
@Lookup("myCommand")
protected abstract Command createCommand();
}
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:
public abstract class CommandManager {
public Object process(Object commandState) {
Command command = createCommand();
command.setState(commandState);
return command.execute();
}
@Lookup
protected abstract Command createCommand();
}
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:
public class MyValueCalculator {
public String computeValue(String input) {
// some real code...
}
// some other methods...
}
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:
/**
* 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 ...;
}
}
/**
* 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.