PDA

View Full Version : ApplicationContext hierarchy loaded at application startup



ehardy
Sep 18th, 2004, 11:08 AM
Hi all,

We have a web application with several jar modules. Each module has it's own context. We wanted those context to be loaded automatically at application startup to form a context hierarchy. Looking at the docs, I didn't see anything that could do this out of the box. Searching through this forum, I found a couple of posts related to this same problem, among others:

http://forum.springframework.org/showthread.php?t=10228
and
http://forum.springframework.org/showthread.php?t=10335

So I started from these posts and looked at the javadocs to find out that everything was there in the Spring APIs to load the context hierarchy at application startup. All that I needed to do was to create my own ContextLoader class and a ContextLoaderListener that instantiated this custom ContextLoader. My custom ContextLoader overrides loadParentContext() to load the hierarchy, using SingletonBeanFactoryLocator.

I would like to know if this is the way it should be done? Or if anything already exists in Spring for this matter? Here is the source code. Hope it's usefull for other Spring users!

Source for context loader:


package com.pyxis.spring.web.context;

import javax.servlet.ServletContext;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.access.BeanFacto ryLocator;
import org.springframework.beans.factory.access.BeanFacto ryReference;
import org.springframework.beans.factory.access.Singleton BeanFactoryLocator;
import org.springframework.context.ApplicationContext;
import org.springframework.web.context.ContextLoader;

/**
* Context loader class that loads a parent context, using
* <code>SingletonBeanFactoryLocator</code>. Uses
* <code>SingletonBeanFactoryLocator</code> default definition
* (beanRefFactory.xml) file as the default parent context definition file. It
* is possible to override this definition by adding the
* <i>parentContextConfigLocation</i> context-parame in the web.xml file.
*
* @author E. Hardy
* @version $Revision: $ $Date: $
*/
public class ContextHierarchyLoader
extends ContextLoader
{
/**
* Name of servlet context parameter that can specify the parent config
* location for the root context, falling back to the implementation's
* default else (beanRefFactory.xml).
*
* <p>This constant value is: parentContextConfigLocation
*/
public static final String PARENT_CONFIG_LOCATION_PARAM =
"parentContextConfigLocation";

/**
* Name of servlet context parameter that can specify the parent bean factory
* name for the root context, falling back to the implementation's
* default else (parentBeanFactory).
*
* <p>This constant value is: parentBeanFactoryName
*/
public static final String BEAN_FACTORY_NAME_PARAM = "parentBeanFactoryName";

private static final String DEFAULT_BEAN_FACTORY_NAME = "parentBeanFactory";

protected ApplicationContext loadParentContext(ServletContext servletContext)
throws BeansException
{
String parentContextConfig = servletContext.getInitParameter(
PARENT_CONFIG_LOCATION_PARAM);
BeanFactoryLocator locator = null;

if ((parentContextConfig != null)
&& (parentContextConfig.trim().length() > 0))
{
locator = SingletonBeanFactoryLocator.getInstance(parentCont extConfig);
}
else
{
locator = SingletonBeanFactoryLocator.getInstance();
}

String beanFactoryName = servletContext.getInitParameter(
BEAN_FACTORY_NAME_PARAM);
BeanFactoryReference bfr = null;

if ((beanFactoryName != null)
&& (beanFactoryName.trim().length() >0))
{
bfr = locator.useBeanFactory(beanFactoryName);
}
else
{
bfr = locator.useBeanFactory(DEFAULT_BEAN_FACTORY_NAME);
}

return (ApplicationContext)bfr.getFactory();
}
}


And the source for the context loader listener:


package com.pyxis.spring.web.context;

import org.springframework.web.context.ContextLoader;
import org.springframework.web.context.ContextLoaderListe ner;

/**
* Bootstrap listener to start up Spring's root
* <code>WebApplicationContext</code>. This listener delegates to
* <code>ContextHierarchyLoader</code> to bootstrap the context hierarchy.
*
* @author E. Hardy
* @version $Revision: $ $Date: $
*/
public class ContextHierarchyLoaderListener
extends ContextLoaderListener
{
protected ContextLoader createContextLoader()
{
return new ContextHierarchyLoader();
}
}


Cheers,

Colin Sampaleanu
Sep 23rd, 2004, 08:19 PM
Yes, you're doing it the right way. I've actually posted code to the mailing list a few times that is basically almost identical to yours, and the ejbtest integration test/sample contains a version:

http://cvs.sourceforge.net/viewcvs.py/springframework/spring/autobuilds/apps/ejbtest/src/java/org/springframework/autobuilds/ejbtest/util/context/ContextLoader.java?rev=1.2&view=markup

ehardy
Sep 24th, 2004, 08:08 AM
Thanks for the reply! Any chance will see that piece of code integrated in the next Spring release?

Cheers,

cardsharp
Nov 29th, 2004, 03:34 PM
You should check out Matt Raible's solution to this problem. It's much simpler and uses no custom code:

http://www.jroller.com/page/raible?anchor=package_your_spring_config_files

Colin Sampaleanu
Nov 29th, 2004, 06:24 PM
You should check out Matt Raible's solution to this problem. It's much simpler and uses no custom code:

http://www.jroller.com/page/raible?anchor=package_your_spring_config_files

What Matt's post is showing is not a hierarchy of contexts though. It's just relying on the standard spring classpath*: prefix to load all fragments of the same name. That's great if this is good enough, but if you need a hierarchy that's something different...

billsalvucci
Jul 2nd, 2005, 06:43 AM
As far as I can tell using classpath: in contextConfigLocation param like Matt Raible shows does not support the classpath*: directive or wildcard characters like applicationContext*.xml and does not allow you to use a beanRefFactory.xml. All of which I need.

The solution proposed by E. Hardy is exactly what I am looking for. I had a similar implementation before I came across this post, but I could not get it to work. I tried the exact code posted and that doesn't work either. I get the following error while tomcat 5.5.9 is starting up:

[java] SEVERE: Exception sending context initialized event to listener inst
ance of class com.rjlg.commons.frmwrk.spring.SpringHelperContext LoaderListener
[java] org.springframework.beans.FatalBeanException: Unable to load group d
efinition. Group resource name [classpath:conf/spring/beanRefFactory.xml], facto
ry key [beanFactory]; nested exception is org.springframework.beans.factory.Bean
DefinitionStoreException: IOException parsing XML document from class path resou
rce [conf/spring/beanRefFactory.xml]; nested exception is java.io.FileNotFoundEx
ception: Could not open class path resource [conf/spring/beanRefFactory.xml]
[java] org.springframework.beans.factory.BeanDefinitionSt oreException: IOEx
ception parsing XML document from class path resource [conf/spring/beanRefFactor
y.xml]; nested exception is java.io.FileNotFoundException: Could not open class
path resource [conf/spring/beanRefFactory.xml]
[java] java.io.FileNotFoundException: Could not open class path resource [c
onf/spring/beanRefFactory.xml]


There is a jar in WEB-INF/lib that has a conf/spring/beanRefFactory.xml. I can get the same code working in a standalone application.

I'm using Spring 1.1.4.

billsalvucci
Jul 2nd, 2005, 08:10 AM
Actually, it works great. I have lots of problems getting log4j to initialize correctly when I redeploy to tomcat. It happened to initialize correctly on a deploy and I saw lots of spring log info that showed it was finding the conf/spring/beanRefFactory.xml. I was getting an error later on because I had removed the configContextLocation param from web.xml, so it was looking for /WEB-INF/applicationContext.xml by default and did not find one when it was doing the context initialization of the WebApplicationConext. You have to remember that this solution does not take the place of the WebApplicationConext initialization, it initializes a parent context of the WebApplicationContext using a beanRefFactory. Very nice.

It has my vote for inclusion into the framework! It is very helpful for anyone that builds both web and standalone clients and uses shares spring enabled jars between them. I wrote a SpringHelper class that acts as a facade for spring initialization for my standalong clients. So now for the web apps I just make the custom ContextLoader delegate to it, so I'm use the same SpringHelper class to initialize standalone and web based applications!


Thanks for the post E. Hardy!

Colin Sampaleanu
Jul 2nd, 2005, 09:28 AM
Just a note to anybody reading this thread that since Spring 1.1.4 ContextLoader has had support for loading a shared parent context which is done via the ContextSingletonBeanFactoryLocator mechanism. This is essentially the same code I pointed to before in this thread, but now it's built in.

See the ContextLoader JavaDocs (http://static.springframework.org/spring/docs/1.2.x/api/org/springframework/web/context/ContextLoader.html).

billsalvucci
Jul 5th, 2005, 11:13 AM
That was easy. Thanks.

Did I miss this in the documentation or should I request that it be added to the docs?

Colin Sampaleanu
Jul 10th, 2005, 05:26 PM
Yeah, this really should be documented in the bean chapter, around the section "Creating an ApplicationContext from a web application". Will add a Jira issue...

jntcwi
Jul 19th, 2005, 09:24 PM
We're using Spring 1.1.5, and this seemed like an appropriate place to ask, so here goes:

We're using a single ear, but multiple wars, ejb jars, and dao jars.
We have adopted a layered approach (dao separate from ejb, etc.), but we're running into that
"Overriding bean definition message" repeatedly.

In our packaging scheme, we use a beanRefContext.xml file in each jar. In each jar, the beanRefContext.xml file is actually a ClassPathXmlApplicationContext which pulls in the definitions of the beans, the datasource, and the hibernate sessionFactory. The ClassPathXmlApplicationContext is named according to the jar name (to avoid conflicts).

Each of the EJBs uses the classpath*:beanRefContext.xml as the BeanFactoryPath. I've toyed with web.xml and its settings for the ContextLoaderListener.

The documentation on 1.1.5 points out:
"However, if there are multiple sibling web-apps at the top of the hierarchy, it is problematic to create an ApplicationContext for each web-app which consists of mostly identical bean definitions from lower layers, as there may be issues due to increased memory usage, issues with multiple copies of beans... and possible issues due to side-effects."

The documentation then suggests using the ContextSingletonBeanFactoryLocator or the SingletonBeanFactoryLocator - but how?

I quickly tried extending the AbstractStatelessSessionBean to use the ContextSingletonBeanFactoryLocator, but, at first blush, that didn't seem to work.

Any suggestions?

Thanks so much (in advance)!

Colin Sampaleanu
Jul 19th, 2005, 10:24 PM
You can take a look at the ejbtest integration test app in the autobuilds portion of the Spring CVS tree, for an example of using a shared context from EJBs:

http://cvs.sourceforge.net/viewcvs.py/springframework/spring/autobuilds/apps/ejbtest/

Regards,