java - example - que es un bean en spring
Spring Java Config: ¿cómo se crea un @Bean de ámbito prototipo con argumentos de tiempo de ejecución? (3)
Con Spring> 4.0 y Java 8 puedes hacer esto de forma más segura:
@Configuration
public class ServiceConfig {
@Bean
public Function<String, Thing> thingFactory() {
return name -> thing(name); // or this::thing
}
@Bean
@Scope(value = "prototype")
public Thing thing(String name) {
return new Thing(name);
}
}
Uso:
@Autowired
private Function<String, Thing> thingFactory;
public void onRequest(Request request) {
//request is already validated
String name = request.getParameter("name");
Thing thing = thingFactory.apply(name);
// ...
}
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, deberá escribir @FunctionalInterface
personalizado para aprobar más de dos parámetros).
Utilizando Java Config de Spring, necesito adquirir / crear una instancia de un bean con prototipo con argumentos de constructor que solo se pueden obtener en el tiempo de ejecución. Considere el siguiente ejemplo de código (simplificado para la 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 de Cosa 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 name
aviso es final
: solo se puede proporcionar a través de un constructor y garantiza la inmutabilidad. Las otras dependencias son dependencias específicas de la implementación de la clase Thing
y no deben conocerse (estrechamente acopladas) a la implementación del controlador de solicitudes.
Este código funciona perfectamente con Spring XML config, 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:
@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 frustra todo el punto de utilizar Spring para reemplazar el patrón de diseño ServiceLocator y Factory , que sería ideal para este caso de uso.
Si Spring Java Config pudiera hacer esto, podría evitar:
- definiendo una interfaz de Fábrica
- definiendo una implementación de fábrica
- redacción de pruebas para la implementación de fábrica
Eso es mucho trabajo (hablando relativamente) para algo tan trivial que Spring ya admite a través de la configuración XML.
En una clase @Configuration
, un método @Bean
como tal
@Bean
@Scope("prototype")
public Thing thing(String name) {
return new Thing(name);
}
se usa para registrar una definición de frijol y proporcionar a la fábrica la creación del frijol . El bean que define solo se crea una instancia bajo petición utilizando argumentos que se determinan directamente o mediante el análisis de ese ApplicationContext
.
En el caso de un bean prototype
, se crea un nuevo objeto cada vez y, por lo tanto, también se ejecuta el método @Bean
correspondiente.
Puede recuperar un bean de ApplicationContext
través de su BeanFactory#getBean(String name, Object... args)
que indica
Permite especificar argumentos de constructor explícitos / argumentos de método de fábrica, anulando los argumentos predeterminados especificados (si los hay) en la definición de bean.
Parámetros:
argumenta argumentos para usar si se crea un prototipo usando argumentos explícitos para un método de fábrica estático. No es válido utilizar un valor de args no nulo en ningún otro caso.
En otras palabras, para este prototype
bean con ámbito, está proporcionando los argumentos que se utilizarán, no en el constructor de la clase de bean, sino en la @Bean
método @Bean
.
Esto es al menos cierto para las versiones Spring 4+.
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 tu configuración en alguna parte.
Esto funciona:
-- Archivo de configuración:
@Configuration
public class ServiceConfig {
// only here to demo execution order
private int count = 1;
@Bean
@Scope(value = "prototype")
public TransferService myFirstService(String param) {
System.out.println("value of count:" + count++);
return new TransferServiceImpl(aSingletonBean(), param);
}
@Bean
public AccountRepository aSingletonBean() {
System.out.println("value of count:" + count++);
return new InMemoryAccountRepository();
}
}
- Archivo de prueba para ejecutar:
@Test
public void prototypeTest() {
// create the spring container using the ServiceConfig @Configuration class
ApplicationContext ctx = new AnnotationConfigApplicationContext(ServiceConfig.class);
Object singleton = ctx.getBean("aSingletonBean");
System.out.println(singleton.toString());
singleton = ctx.getBean("aSingletonBean");
System.out.println(singleton.toString());
TransferService transferService = ctx.getBean("myFirstService", "simulated Dynamic Parameter One");
System.out.println(transferService.toString());
transferService = ctx.getBean("myFirstService", "simulated Dynamic Parameter Two");
System.out.println(transferService.toString());
}
Usando Spring 3.2.8 y Java 7, da esta salida:
value of count:1
com.spring3demo.account.repository.InMemoryAccountRepository@4da8692d
com.spring3demo.account.repository.InMemoryAccountRepository@4da8692d
value of count:2
Using name value of: simulated Dynamic Parameter One
com.spring3demo.account.service.TransferServiceImpl@634d6f2c
value of count:3
Using name value of: simulated Dynamic Parameter Two
com.spring3demo.account.service.TransferServiceImpl@70bde4a2
Por lo tanto, el frijol ''Singleton'' se solicita dos veces. Sin embargo, como era de esperar, Spring solo lo crea una vez. La segunda vez ve que tiene ese bean y solo devuelve el objeto existente. El constructor (método @Bean) no se invoca por segunda vez. En deferencia a esto, cuando se solicita el Bean ''Prototipo'' desde el 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 y que los objetos de referencia contenidos sean inmutables como deberían ser. Entonces, el servicio de transferencia podría ser algo así como:
public class TransferServiceImpl implements TransferService {
private final String name;
private final AccountRepository accountRepository;
public TransferServiceImpl(AccountRepository accountRepository, String name) {
this.name = name;
// system out here is only because this is a dumb test usage
System.out.println("Using name value of: " + this.name);
this.accountRepository = accountRepository;
}
....
}
Si escribes Pruebas unitarias estarás muy feliz de haber creado las clases sin todos los @Autowired. Si necesita componentes autoconectados, mantenga esos locales en los archivos de configuración de Java.
Esto llamará al método a continuación en BeanFactory. Tenga en cuenta en la descripción cómo esto es para su caso de uso exacto.
/**
* Return an instance, which may be shared or independent, of the specified bean.
* <p>Allows for specifying explicit constructor arguments / factory method arguments,
* overriding the specified default arguments (if any) in the bean definition.
* @param name the name of the bean to retrieve
* @param args arguments to use if creating a prototype using explicit arguments to a
* static factory method. It is invalid to use a non-null args value in any other case.
* @return an instance of the bean
* @throws NoSuchBeanDefinitionException if there is no such bean definition
* @throws BeanDefinitionStoreException if arguments have been given but
* the affected bean isn''t a prototype
* @throws BeansException if the bean could not be created
* @since 2.5
*/
Object getBean(String name, Object... args) throws BeansException;