Comments to this way of implementing Spring into ejbs
I wanted to have access to my POJO's and local interfaces to ejb's from other ejb's using one single Spring application context within the Jboss container..
I have created a Spring MBean,- running a standalone MBean service....
Code:
package no.aftenposten.framework.spring;
import org.jboss.naming.NonSerializableFactory;
import org.jboss.system.ServiceMBeanSupport;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.FileSystemXmlApplicationContext;
import javax.naming.*;
import java.io.*;
import java.util.Properties;
/**
* JMX - MBean which initiates the Spring framework by reading the spring.xml, exposing
* the Spring ApplicationContext from withing the ejb-container via jndi lookup.
* see spring-service.xml
*
* <p/>
* User: regearne
* Date: 03.des.2004
* Time: 15:02:51
*
* @author regearne
* @version $Revision: 1.2 $Date: 2005/01/01 19:31:52 $
*/
public class SpringService extends ServiceMBeanSupport implements SpringServiceMBean {
private Properties properties;
private String jndiName;
private String propertiesFile;
private boolean useProperties;
private boolean usePropertiesFile;
ApplicationContext context = null;
private static final String SPRING_CONFIG_KEY = "spring.config";
/*
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*
* Constructors.
*
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*/
public SpringService() {
usePropertiesFile = false;
propertiesFile = "";
useProperties = false;
properties = new Properties();
jndiName = "Spring";
}
/*
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*
* Interface.
*
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*/
public void setJndiName(String jndiName) throws Exception {
String oldName = this.jndiName;
this.jndiName = jndiName;
if (super.getState() == STARTED){
try {
unbind(oldName);
} catch (NamingException ne) {
log.error(captureStackTrace(ne));
throw new Exception("Failed to unbind Spring - ", ne);
}
try {
rebind();
} catch (NamingException ne) {
log.error(captureStackTrace(ne));
throw new Exception("Failed to rebind Spring - ", ne);
}
}
}
/** Getter for jndiName
*
* @return
*/
public String getJndiName() {
return jndiName;
}
/** Getter for name
*
* @return
*/
public String getName() {
return "SpringService(" + jndiName + ")";
}
/** Setter for properties, - used when assign properties via the
* spring-service.xml
*
* @param properties
*/
public void setProperties(String properties) {
if (usePropertiesFile){
log.error("Must specify only one of 'Properties' or 'PropertiesFile'");
return;
}
useProperties = true;
try {
ByteArrayInputStream bais = new ByteArrayInputStream(properties.getBytes());
this.properties = new Properties();
this.properties.load(bais);
} catch (IOException ioe) {
// should not happen
}
}
/** Get properties
*
* @return
*/
public String getProperties() {
try {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
properties.store(baos, "");
return new String(baos.toByteArray());
} catch (IOException ioe) {
// should not happen
return "";
}
}
/** Setter for propertiesFile
*
* @param propertiesFile
*/
public void setPropertiesFile(String propertiesFile) {
if (useProperties){
log.error("Must specify only one of 'Properties' or 'PropertiesFile'");
return;
}
usePropertiesFile = true;
this.propertiesFile = propertiesFile;
}
public String getPropertiesFile() {
return propertiesFile;
}
public void createService() throws Exception {
}
public void destroyService() throws Exception {
log.info("Destroy SpringService(" + jndiName + ")...");
}
/** Starting the jmx-service
*
* @throws Exception
*/
public void startService() throws Exception {
log.info("Start SpringService(" + jndiName + ")...");
String configFile;
if ((configFile = properties.getProperty(SPRING_CONFIG_KEY)) != null){
if (new File(configFile).exists()){
log.info("Initializing Spring framework with " + configFile);
context = new FileSystemXmlApplicationContext(configFile);
log.info("Spring framework initialized");
} else {
throw new FileNotFoundException("File: "+configFile+"doesn't exist");
}
}
try {
rebind();
} catch (NamingException ne) {
log.error(captureStackTrace(ne));
throw new Exception("Failed to rebind Spring - ",
ne);
}
log.info("SpringService(" + jndiName + ") started.");
}
public void stopService() throws Exception {
log.info("Stop SpringService(" + jndiName + ")...");
log.info("Spring(" + jndiName + ") stopped.");
}
private String captureStackTrace(Throwable throwable) {
StringWriter sw = new StringWriter();
throwable.printStackTrace(new PrintWriter(sw, true));
return sw.toString();
}
private static Context createContext(Context rootCtx, Name name)
throws NamingException {
Context subctx = rootCtx;
for (int n = 0; n < name.size(); n++){
String atom = name.get(n);
try {
Object obj = subctx.lookup(atom);
subctx = (Context) obj;
} catch (NamingException e) {
// No binding exists, create a subcontext
subctx = subctx.createSubcontext(atom);
}
}
return subctx;
}
private void rebind() throws Exception {
InitialContext rootCtx = new InitialContext();
// Get the parent context into which we are to bind
Name fullName = rootCtx.getNameParser("").parse(jndiName);
Name parentName = fullName;
if (fullName.size() > 1){
parentName = fullName.getPrefix(fullName.size() - 1);
} else {
parentName = new CompositeName();
}
Context parentCtx = createContext(rootCtx, parentName);
Name atomName = fullName.getSuffix(fullName.size() - 1);
String atom = atomName.get(0);
// Scheduler scheduler = schedulerFactory.getScheduler();
NonSerializableFactory.rebind(parentCtx, atom, context);
}
private void unbind(String jndiName) throws NamingException {
Context rootCtx = new InitialContext();
Name fullName = rootCtx.getNameParser("").parse(jndiName);
Name atomName = fullName.getSuffix(fullName.size() - 1);
String atom = atomName.get(0);
rootCtx.unbind(jndiName);
NonSerializableFactory.unbind(atom);
}
}
// deployed the class within a jar into the jboss lib folder...
and a spring-service.xml into the deploy folder, firing up the the service into the Jboss....
Code:
<?xml version="1.0" encoding="UTF-8"?>
<server>
<classpath codebase="." archives="a-framework.jar"/>
<mbean code="no.aftenposten.framework.spring.SpringService"
name="user:service=SpringService,name=SpringService">
<attribute name="JndiName">Spring</attribute>
<attribute name="Properties">
spring.config=/jboss-3.2.2/server/default/deploy/spring.xml
</attribute>
</mbean>
</server>
This enables me through this static method from any ejb to access the Spring application context within the container...
Code:
public static Object getBean(String name) throws NamingException, ComponentNotAvailableException {
//Initializing the spring configuration
InitialContext jndiContext = new InitialContext();
ApplicationContext context;
if((context = (ApplicationContext) jndiContext.lookup("Spring")) != null){
return context.getBean(name);
} else {
throw new ComponentNotAvailableException("Could get Spring-bean-"+name+"," +
" due to missing BeanFactory (jndi-Spring)");
}
}
From my ejb, - the only code I need to access a Spring configured bean is:
Code:
HibernateDAO sf = (HibernateDAO) SpringBeanFactory.getBean("DAOImplementation");
sf.create(bean);
What about this way of implementing the Spring into ejb container. Have I overlooked something. I haven't found any standard Spring classes helping my to achieve the same solution, - which I would have preferred. Or should I solve it another way??
Geir
The solution i was looking for!... for another problem ;)
Hey,
Thx for the code I was just about to implement the same thing to get a better beanFactory deployment on the JBoss server. The only difference is that my problem is another.
I already use the provided AbstractEjb methods from spring but i find a problem in the interaction with the EJB.
BeanFactoryLocator holds a counter that is incremented everytime someone requests a SingletonFactory. Whenever someone is interested in releasing its hold he can call the singleton method release() which in time, when the counter gets to 0 destroys the bean context forcing someone accesing the factory again to recreate the Bean Factory.
The above exposed (which i find perfect for some situations) is a real drawback when working with EJB in JBoss due to the fact that JBoss destroys and recreates the EJB (counter-- 0 destroy context, next access = create bean factory) everytime the EJB throws an unexpected runtime exception.
In this scenary i need another anchor to enforce the lifecicle i want for the spring context (full application container life, not ejb, that is the reason it is singleton) and the MBean deployment descriptor is just the perfect place for it :).