Usando Spring Config de Java, necesito adquirir / instanciar un bean de ámbito prototipo con argumentos de constructor que solo se pueden obtener en tiempo de ejecución. Considere el siguiente ejemplo de código (simplificado por brevedad):
@Autowired
private ApplicationContext appCtx;
public void onRequest(Request request) {
//request is already validated
String name = request.getParameter("name");
Thing thing = appCtx.getBean(Thing.class, name);
//System.out.println(thing.getName()); //prints name
}
donde la clase Thing se define de la siguiente manera:
public class Thing {
private final String name;
@Autowired
private SomeComponent someComponent;
@Autowired
private AnotherComponent anotherComponent;
public Thing(String name) {
this.name = name;
}
public String getName() {
return this.name;
}
}
El aviso name
es final
: solo se puede suministrar a través de un constructor y garantiza la inmutabilidad. Las otras dependencias son dependencias específicas de la implementación de la Thing
clase, y no deben conocerse (estrechamente relacionadas) con la implementación del controlador de solicitudes.
Este código funciona perfectamente con la configuración Spring XML, por ejemplo:
<bean id="thing", class="com.whatever.Thing" scope="prototype">
<!-- other post-instantiation properties omitted -->
</bean>
¿Cómo logro lo mismo con la configuración de Java? Lo siguiente no funciona con Spring 3.x:
@Bean
@Scope("prototype")
public Thing thing(String name) {
return new Thing(name);
}
Ahora, podría crear una Fábrica, por ejemplo:
public interface ThingFactory {
public Thing createThing(String name);
}
Pero eso invalida el punto de usar Spring para reemplazar el patrón de diseño ServiceLocator y Factory , lo que sería ideal para este caso de uso.
Si Spring Java Config pudiera hacer esto, podría evitar:
- definir una interfaz de fábrica
- definir una implementación de Factory
- escribir pruebas para la implementación de Factory
Eso es un montón de trabajo (relativamente hablando) para algo tan trivial que Spring ya admite a través de la configuración XML.
Thing
implementación es en realidad más compleja y tiene dependencias de otros beans (simplemente los omití por brevedad). Como tal, no quiero que la implementación del controlador de Solicitud sepa sobre ellos, ya que esto acoplaría estrechamente el controlador a las API / beans que no necesita. Actualizaré la pregunta para reflejar su pregunta (excelente).@Qualifier
parámetros a un setter@Autowired
en el setter.@Bean
obras. Se@Bean
llama al método con los argumentos apropiados a los que pasógetBean(..)
.Respuestas:
En una
@Configuration
clase, un@Bean
método asíse utiliza para registrar una definición de bean y proporcionar la fábrica para crear el bean . El bean que define solo se instancia mediante solicitud utilizando argumentos que se determinan directamente o escaneando eso
ApplicationContext
.En el caso de un
prototype
bean, se crea un nuevo objeto cada vez y, por lo tanto,@Bean
también se ejecuta el método correspondiente .Puede recuperar un bean a
ApplicationContext
través de suBeanFactory#getBean(String name, Object... args)
método que estableceEn otras palabras, para este
prototype
bean de ámbito, está proporcionando los argumentos que se utilizarán, no en el constructor de la clase de bean, sino en la@Bean
invocación del método.Esto es al menos cierto para las versiones Spring 4+.
fuente
@Bean
método a la invocación manual. Si alguna vez se llamará@Autowire Thing
al@Bean
método, probablemente muera al no poder inyectar el parámetro. Lo mismo si tu@Autowire List<Thing>
. Encontré esto un poco frágil.@Autowire
?Con Spring> 4.0 y Java 8 puedes hacer esto de forma más segura:
Uso:
Así que ahora puedes obtener tu bean en tiempo de ejecución. Este es un patrón de fábrica, por supuesto, pero puede ahorrar algo de tiempo escribiendo clases específicas como
ThingFactory
(sin embargo, tendrá que escribir personalizado@FunctionalInterface
para pasar más de dos parámetros).fuente
fabric
lugar defactory
mi mal :)Provider
o aObjectFactory
, ¿o me equivoco? Y en mi ejemplo, puede pasarle un parámetro de cadena (o cualquier parámetro)@Bean
y hacerScope
anotaciones sobre elThing thing
método. Además, este método puede hacerse privado para ocultarse y dejar solo la fábrica.Desde la primavera 4.3, hay una nueva forma de hacerlo, que fue cosida para ese tema.
ObjectProvider : le permite simplemente agregarlo como una dependencia a su bean de alcance Prototype "discutido" e instanciarlo utilizando el argumento.
Aquí hay un ejemplo simple de cómo usarlo:
Por supuesto, esto imprimirá una cadena de saludo cuando llame a usePrototype.
fuente
ACTUALIZADO por comentario
Primero, no estoy seguro de por qué dices "esto no funciona" para algo que funciona bien en Spring 3.x. Sospecho que algo debe estar mal en su configuración en alguna parte.
Esto funciona:
-- Archivo de configuración:
- Archivo de prueba para ejecutar:
Usando Spring 3.2.8 y Java 7, da este resultado:
Por eso se solicita el frijol 'Singleton' dos veces. Sin embargo, como era de esperar, Spring solo lo crea una vez. La segunda vez ve que tiene ese bean y simplemente devuelve el objeto existente. El constructor (método @Bean) no se invoca por segunda vez. En deferencia a esto, cuando se solicita el Bean 'Prototype' del mismo objeto de contexto dos veces, vemos que la referencia cambia en la salida Y que el constructor (método @Bean) se invoca dos veces.
Entonces, la pregunta es cómo inyectar un singleton en un prototipo. ¡La clase de configuración anterior muestra cómo hacerlo también! Debería pasar todas esas referencias al constructor. Esto permitirá que la clase creada sea un POJO puro, así como hacer que los objetos de referencia contenidos sean inmutables como deberían ser. Por lo tanto, el servicio de transferencia podría verse así:
Si escribe Pruebas unitarias, estará tan feliz de haber creado las clases sin todo @Autowired. Si necesita componentes con conexión automática, mantenga los locales en los archivos de configuración de Java.
Esto llamará al siguiente método en BeanFactory. Observe en la descripción cómo está destinado para su caso de uso exacto.
fuente
Puede lograr un efecto similar simplemente usando una clase interna :
fuente
en su archivo xml de beans use el atributo scope = "prototype"
fuente
Respuesta tardía con un enfoque ligeramente diferente. Es un seguimiento de esta pregunta reciente. que hace referencia a esta pregunta en sí.
Sí, como se dijo, puede declarar el bean prototipo que acepta un parámetro en una
@Configuration
clase que permite crear un nuevo bean en cada inyección.Eso hará de esta
@Configuration
clase una fábrica y, para no darle demasiadas responsabilidades a esta fábrica, esto no debe incluir otros granos.Pero también puede inyectar ese bean de configuración para crear
Thing
s:Es a la vez seguro de tipo y conciso.
fuente
@Configuration
si ese mecanismo cambió.BeanFactory#getBean()
. Pero eso es mucho peor en términos de acoplamiento, ya que es una fábrica que permite obtener / instanciar cualquier bean de la aplicación y no solo cuál necesita el bean actual. Con tal uso, puede mezclar las responsabilidades de su clase muy fácilmente, ya que las dependencias que puede obtener son ilimitadas, lo que realmente no es aconsejable, pero es un caso excepcional.