java - sts - Agregar un Bean preconstruido a un contexto de aplicación Spring
spring wikipedia (6)
Estoy escribiendo una clase que implementa el siguiente método:
public void run(javax.sql.DataSource dataSource);
Dentro de este método, deseo construir un contexto de aplicación Spring usando un archivo de configuración similar al siguiente:
<bean id="dataSource" abstract="true" />
<bean id="dao" class="my.Dao">
<property name="dataSource" ref="dataSource" />
</bean>
¿Es posible obligar a Spring a utilizar el objeto DataSource que se transfiere a mi método dondequiera que se haga referencia al bean "dataSource" en el archivo de configuración?
He estado en la misma situación. Como nadie propuso mi solución (y creo que mi solución es más elegante), la agregaré aquí para las generaciones futuras :-)
La solución consta de dos pasos:
- crea parent ApplicationContext y registra tu bean existente en él.
- crear Child ApplicationContext (pasar en el contexto principal) y cargar beans desde el archivo XML
Paso 1:
//create parent BeanFactory
DefaultListableBeanFactory parentBeanFactory = new DefaultListableBeanFactory();
//register your pre-fabricated object in it
parentBeanFactory.registerSingleton("dataSource", dataSource);
//wrap BeanFactory inside ApplicationContext
GenericApplicationContext parentContext =
new GenericApplicationContext(parentBeanFactory);
parentContext.refresh(); //as suggested "itzgeoff", to overcome a warning about events
Paso 2:
//create your "child" ApplicationContext that contains the beans from "beans.xml"
//note that we are passing previously made parent ApplicationContext as parent
ApplicationContext context = new ClassPathXmlApplicationContext(
new String[] {"beans.xml"}, parentContext);
Puede crear una clase contenedora para un DataSource
que simplemente delegue a un DataSource
contenido
public class DataSourceWrapper implements DataSource {
DataSource dataSource;
public void setDataSource(DataSource dataSource) {
this.dataSource = dataSource;
}
@Override
public Connection getConnection() throws SQLException {
return dataSource.getConnection();
}
@Override
public Connection getConnection(String username, String password)
throws SQLException {
return dataSource.getConnection(username, password);
}
//delegate to all the other DataSource methods
}
Luego, en su archivo de contexto Spring, declara DataSourceWrapper
y lo DataSourceWrapper
a todos sus beans. Luego, en su método, obtiene una referencia a DataSourceWrapper y configura el DataSource envuelto al que ingresó en su método.
Todo este trabajo depende en gran medida de lo que suceda en el archivo de contexto de Spring cuando se cargue. Si un bean requiere que DataSource ya esté disponible cuando se carga el contexto, puede que tenga que escribir un BeanFactoryPostProcessor
que BeanFactoryPostProcessor
el archivo de contexto Spring mientras se carga, en lugar de hacer cosas después de la carga (aunque quizás un init flojo podría resolver este problema) )
Si crea un objeto llamando a "nuevo", no está bajo el control de la fábrica de Spring.
¿Por qué no hacer que Spring inyecte DataSource en el objeto en lugar de pasarlo a run ()?
Descubrí que se pueden usar dos interfaces Spring para implementar lo que necesito. La interfaz BeanNameAware le permite a Spring decirle a un objeto su nombre dentro de un contexto de aplicación llamando al método setBeanName (String) . La interfaz FactoryBean le dice a Spring que no use el objeto en sí, sino el objeto devuelto cuando se invoca el método getObject () . Póngalos juntos y obtendrá:
public class PlaceholderBean implements BeanNameAware, FactoryBean {
public static Map<String, Object> beansByName = new HashMap<String, Object>();
private String beanName;
@Override
public void setBeanName(String beanName) {
this.beanName = beanName;
}
@Override
public Object getObject() {
return beansByName.get(beanName);
}
@Override
public Class<?> getObjectType() {
return beansByName.get(beanName).getClass();
}
@Override
public boolean isSingleton() {
return true;
}
}
La definición de bean ahora se reduce a:
<bean id="dataSource" class="PlaceholderBean" />
El marcador de posición recibe su valor antes de crear el contexto de la aplicación.
public void run(DataSource externalDataSource) {
PlaceholderBean.beansByName.put("dataSource", externalDataSource);
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
assert externalDataSource == context.getBean("dataSource");
}
¡Las cosas parecen estar funcionando con éxito!
La segunda solución causa una excepción debido a un problema de actualización. Una forma más elegante será agregar objetos al contexto y luego cargar definiciones xml usando el xmlreader. Así:
ObjectToBeAddedDynamically objectInst = new ObjectToBeAddedDynamically();
DefaultListableBeanFactory parentBeanFactory = new DefaultListableBeanFactory();
parentBeanFactory.registerSingleton("parameterObject", objectInst);
GenericApplicationContext parentContext = new GenericApplicationContext(parentBeanFactory);
XmlBeanDefinitionReader xmlReader = new XmlBeanDefinitionReader(parentContext);
xmlReader.loadBeanDefinitions(new FileSystemResource("beandefinitions.xml"));
parentContext.refresh();
ObjectUsingDynamicallyAddedObject userObjectInst= (ObjectUsingDynamicallyAddedObject )parentContext.getBean("userObject");
y
<?xml version="1.0" encoding="UTF-8"?>
<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="userObject" class="com.beanwiring.ObjectUsingDynamicallyAddedObject"
>
<constructor-arg ref="parameterObject" />
</bean>
</beans>
funciona perfecto!
Hay una manera más elegante en la que puede usar un archivo xml externo y cargarlo con un recurso del sistema de archivos, luego inyectar los beans configurados en el contexto de la aplicación. Así:
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.xml.XmlBeanDefinitionReader;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.support.GenericApplicationContext;
import org.springframework.core.annotation.Order;
import org.springframework.core.io.FileSystemResource;
import org.springframework.stereotype.Service;
@Service
@Order(-100)
public class XmlBeanInitializationService implements ApplicationContextAware, InitializingBean {
private ApplicationContext applicationContext;
@Value("${xmlConfigFileLocation}")
private String xmlConfigFileLocation;
@Override
public void afterPropertiesSet() throws Exception {
XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader((BeanDefinitionRegistry)applicationContext);
reader.loadBeanDefinitions(new FileSystemResource(xmlConfigFileLocation));
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
}
donde $ {xmlConfigFileLocation} es una propiedad especificada en su archivo application.properties que apunta a la ubicación del archivo en su sistema de esta manera:
xmlConfigFileLocation="your-file-path-anywhere-in-your-system"
y su archivo xml puede contener:
<?xml version="1.0" encoding="UTF-8"?>
<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 class="com.yourpackage.YourBean1Class"></bean>
<bean class="com.yourpackage.YourBean2Class"></bean>
<bean class="com.yourpackage.YourBean3Class"></bean>
</beans>
por lo tanto, cuando la aplicación comienza, la primavera carga la clase y carga el grano en el contexto de la aplicación.
Espero que esto ayude a alguien.