|
From: Colin S. <col...@ex...> - 2003-11-28 03:23:12
|
Ok, I've been meaning to write for weeks about a solution I have for
doing hierarchical, on-demand loading of contexts (to be used by the
glue code between application layers), but I kept wanting to work on the
code a bit more. However, I'm going on vacation for a week now, and it
seems like people are interested in the subject, so I'll post what I have...
I have an app that's made up of multiple layers, basically some utility
jars, a data-access layer, a services layer, and on top of this 3
separate web-apps as WARs, all living in an EAR. At some point, all the
legacy EJB stuff will be gone, and we can move away from the ejb server,
but for the time being it's a mix of newer Hibernate persistence and
POJO services, and older Session EJBs and Entity Beans.
Because there are 3 webapps sitting at the top, I can't use the typical
Spring scenario, one application context at the top, living in the one
web app (whether assembled from one XML file or multiple XML files). The
tree structure forces me to load multiple contexts in a hierarchy (at
the very least, one context for the bottom layers, which is the parent
of 3 separate web app contexts), and stuff has to load on demand or at
least be findable by some sort of glue code to assemble them.
I had a solution I wasn't that happy with, which I posted here in
September. What I am doing now instead is using a Spring context itself
to load other contexts, on demand, in a tree fashion. So, the basic
premise is that you work with a Context group, which consists of one or
more ApplicationContexts, loaded on demand. The definition of the
context group is an ApplicationContext itself. You have a locator
interface, to which you feed a context group id (a name), and the
context (a name) within that group you want.
package com.whatever.coreserv.util.context;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextException;
/**
* Defines interface for an ApplicationContext factory.
*
* @version $Revision: 1.1 $
* @author colin
*/
public interface ContextLocator {
/**
* Use the ApplicationContext specified by the key parameter. The
context is possibly
* loaded/created as needed.
*
* @param key a value specifying which context to use
* @return the ApplicationContext instance
* @throws ApplicationContextException if there is an error loading
one or more contexts
*/
ApplicationContext useContext(String group, String contextId) throws
ApplicationContextException;
/**
* Indicate that the specified ApplicationContext instance is not
needed by a user of it,
* who has previsouly obtained it via {@link useContext}. It is an
error for releaseContext
* to be called a greater number of times than useContext. Calling
this release method may
* cause close() to be called on the specified context, if this is the
last user of it.
*
* @param ac the ApplicationContext instance
* @throws ApplicationContextException
*/
void releaseContext(ApplicationContext ac) throws
ApplicationContextException;
}
Here's a factory to get a ContextLocator:
package com.whatever.coreserv.util.context;
import org.springframework.beans.factory.support.BootstrapException;
import org.springframework.context.ApplicationContextException;
/**
* @version $Revision: 1.1 $
* @author colin sampaleanu
*/
public class ContextLocatorFactory {
private static KeyedSingletonGroupContextLocator instance;
// Do initialization when this class is loaded to avoid potential
// concurrency issues or the need to synchronize later
static {
initializeSingleton();
}
private static void initializeSingleton() {
instance = new KeyedSingletonGroupContextLocator();;
}
/**
* Return the singleton instance of the ContextLocator factory
* @return ContextLocator
* @throws BeansException
*/
public static ContextLocator getInstance() throws
ApplicationContextException {
// introduce mechanism (property) to get another implementation instead
if (instance == null)
throw new BootstrapException("Anomaly: instance is null", null);
return instance;
}
}
Now here's an implementation of ContextLocator, called
KeyedSingletonGroupContextLocator. What it does is treat the group name
as a keyed singleton value which represents an ApplicationContext to be
loaded once, which contains other contexts. Please see the JavaDoc.
package com.whatever.coreserv.util.context;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import org.apache.log4j.Logger;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextException;
import org.springframework.context.config.ConfigurableApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
/**
* Implementation of ContextLocator. <br />In this implementation, the
key is
* actually the name of an application context definition, accessed as a
resource.
* This is loaded once on demand (at the first useContext() call with
that group key),
* as a singleton. It is expected that within this application context
is defined a
* hierarchy of other application contexts. When useContext() is called
with a
* specified contextId to get from the group, the group's singleton
application
* context is queried via getBean for the specified application context
within it.
* Each application context in the parent group context is just a bean
definition, and
* points to a parent context. Depending on the singleton setting, they
will all load
* initially, or on demamd.
*
* @version $Revision: 1.1 $
* @author colin
* @see ContextFactory
*/
public class KeyedSingletonGroupContextLocator implements ContextLocator {
// --- statics
public static final Logger _log =
Logger.getLogger(KeyedSingletonGroupContextLocator.class);
// we map ContextInfo objects by String keys, and by Contexts
private static HashMap instancesByKey = new HashMap();
private static HashMap instancesByObj = new HashMap();
// --- methods
/*
* (non-Javadoc)
*
* @see
com.whatever.coreserv.webutil.ContextFactory#useContext(java.lang.String)
*/
public ApplicationContext useContext(String groupKey, String contextId)
throws ApplicationContextException {
synchronized (instancesByKey) {
ContextInfo ci = (ContextInfo) instancesByKey.get(groupKey);
if (ci != null) {
_log.debug("Context with key '" + groupKey + "' requested.
Returning existing instances");
ci.refcount++;
}
else {
_log.debug("Context group '" + groupKey + "' requested. Creating
new instance.");
// this context doesn't exist, we need to try to load it
InputStream is = getClass().getResourceAsStream(groupKey);
ClassPathXmlApplicationContext groupContext = null;
if (is == null)
throw new ApplicationContextException(
"Unable to load context(s). Context group key does not point
to a valid resource: "
+ groupKey);
try {
is.close();
groupContext = new ClassPathXmlApplicationContext(groupKey);
}
catch (IOException e) {
throw new ApplicationContextException(
"Unable to loaded(s) specified by context group key: " +
groupKey,
e);
}
ci = new ContextInfo();
ci.context = groupContext;
ci.key = groupKey;
ci.refcount = 1;
instancesByKey.put(groupKey, ci);
instancesByObj.put(groupContext, ci);
}
ApplicationContext groupContext = ci.context;
ApplicationContext appContext;
try {
appContext = (ApplicationContext) groupContext.getBean(contextId);
}
catch (BeansException e) {
throw new ApplicationContextException(
"Unable to return specified context. Group:" + groupKey + ",
contextId:" + contextId,
e);
}
return appContext;
}
}
/**
* Releases the specified ApplicationContext instance. If there are no
more
* users of this context, close() will be called on it. Note that
close will
* be called on its parents as well, recursively.
*
* @see
com.whatever.coreserv.webutil.ContextFactory#ReleaseContext(org.springframework.context.ApplicationContext)
*/
public void releaseContext(ApplicationContext ac) throws
ApplicationContextException {
//TODO: implement
}
// we track contexts with this class
private class ContextInfo {
public ConfigurableApplicationContext context;
public String key;
public int refcount = 0;
}
}
Now here's an actual context group definition. In the group are 4
contexts. A client of the group can ask for any of the contexts within
it. Depending on the singleton settings, all the contexts will have
loaded at the beginning, or will load on demand.
<?xml version="1.0" encoding="UTF-8"?>
<!-- $Id: core-context-hierarchy.xml,v 1.2 2003/11/14 23:15:20 colin Exp
$ -->
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN"
"http://www.springframework.org/dtd/spring-beans.dtd">
<!-- core Application Contexts -->
<beans>
<bean id="data-access-context"
class="org.springframework.context.support.ClassPathXmlApplicationContext">
<constructor-arg>
<value>/data-access-applicationContext.xml</value>
</constructor-arg>
</bean>
<bean id="packaging-context"
class="org.springframework.context.support.ClassPathXmlApplicationContext">
<constructor-arg index="0">
<list><value>/packaging-applicationContext.xml</value></list>
</constructor-arg>
<constructor-arg index="1">
<ref bean="data-access-context"/>
</constructor-arg>
</bean>
<bean id="qa-util-context"
class="org.springframework.context.support.ClassPathXmlApplicationContext">
<constructor-arg index="0">
<list><value>/qa-util-applicationContext.xml</value></list>
</constructor-arg>
<constructor-arg index="1">
<ref bean="packaging-context"/>
</constructor-arg>
</bean>
<bean id="core-services-context"
class="org.springframework.context.support.ClassPathXmlApplicationContext">
<constructor-arg index="0">
<list><value>/core-services-applicationContext.xml</value></list>
</constructor-arg>
<constructor-arg index="1">
<ref bean="qa-util-context"/>
</constructor-arg>
</bean>
</beans>
Now this takes care of my lower layers. What I have in my 3 web apps is
a small variation of Spring's standard ContextLoader, which is aware of
context groups. When it is constructs the web app's application context,
it uses an extra couple of params as a context group key and context
from that context group, to set as the parent of the web-app's context.
As you can see, I never finished rewriting the releaseContext method
after I switched to this code. My initial implementation did reference
counting on the returned contexts, and I want to do it here too at some
point.
Although there are a lot of ways you could do this, I think that some
sort of mechanism needs to exist in Spring to allow glue code inside an
application stack to glue contexts together in one fashion or another,
and to do it on demand. As such, if anybody agrees, I'd like to work on
a solution that everybody's happy with. Using the contexts within a
context approach is pretty cool, because it leverages Spring in several
ways, and allows the end user to extend as needed via interceptors and
the like...
Regards,
Colin
Mike Cannon-Brookes wrote:
>OK - but can't we have an include syntax that's automatically processed? It
>would be much simpler for those writing unit tests etc - you then wire your
>files together explicitly.
>
><include resource="/foobar.xml" />
><include file="c:\foobar.xml" />
>
>(for classpath loading and file loading respectively)
>
>Cheers,
>Mike
>
>PS This is also the same syntax xwork.xml uses and it works quite well
>
>On 28/11/03 10:26 AM, "jürgen höller [werk3AT]"
>(jue...@we...) penned the words:
>
>
>
>>You can't load one file from another (except for standard XML entity
>>includes), but you can simply define multiple XML file locations as
>>"contextConfigLocation" context-param:
>>
>><context-param>
>><param-name>contextConfigLocation</param-name>
>><param-value>
>> /WEB-INF/applicationContext1.xml
>> /WEB-INF/applicationContext2.xml
>></param-value>
>></context-param>
>>
>>All those bean definition files will be loaded into one single root
>>application context instance. You can also separate the locations with spaces
>>or commas - the parsing is pretty lenient.
>>
>>Any bean references between those files can be resolved, but we recommend to
>>keep dependencies one way, e.g. applicationContext1 references beans from
>>applicationContext2 but not the other way round.
>>
>>The obvious benefit of such separation is that the individual files can be
>>reused in other environments, like unit tests or standalone apps, in a
>>fine-granular fashion.
>>
>>Juergen
>>
>>
>>________________________________
>>
>>Von: spr...@li... im Auftrag von Mike
>>Cannon-Brookes
>>Gesendet: Fr 28.11.2003 00:19
>>An: Spring User ML
>>Betreff: Re: [Springframework-user] BeanFactories loadable from classpath?
>>
>>
>>
>>Maybe I missed it, but how can you load one file from another? This would be
>>_very_ useful for us, especially in the area of breaking up unit test .xml
>>files.
>>
>>Mike
>>
>>On 28/11/03 9:09 AM, "jürgen höller [werk3AT]" (jue...@we...)
>>penned the words:
>>
>>
>>
>>>Neill,
>>>
>>>We've already considered support for loading web application context
>>>definitions from the class path, possibly via a "classpath:" prefix for
>>>context config locations, analogous to "file:" and "http:" which are already
>>>supported. Non-prefix paths will still be interpreted as web app resource,
>>>like currently. I'll address this tomorrow, as it is straightforward to
>>>implement.
>>>
>>>Note that an XmlBeanFactory itself can be loaded from any InputStream. It's
>>>just application context implementations like XmlWebApplicationContext,
>>>FileSystemXmlApplicationContext and ClassPathXmlApplicationContext that are
>>>customized for a specific environment.
>>>
>>>Juergen
>>>
>>>
>>>________________________________
>>>
>>>Von: spr...@li... im Auftrag von Neill
>>>Robbins
>>>Gesendet: Do 27.11.2003 15:48
>>>An: spr...@li...
>>>Betreff: [Springframework-user] BeanFactories loadable from classpath?
>>>
>>>
>>>I have the following scenario. I have a couple of applications that operate
>>>on a core set of domain objects and business functions. To this end I have a
>>>core.jar that contains the domain objects, associated core business objects,
>>>and an applicationContextCore.xml that wire the business objects to DAOs and
>>>such like.
>>>
>>>One of my apps that requires access the business objects in this core.jar is
>>>a
>>>web app, the other is a client app that calls an ejb that uses core.jar
>>>
>>>What I would like to do is distribute applicationContextCore.xml with
>>>core.jar
>>>and then reference it when creating the contexts for the ejb and web.
>>>
>>>In particular for the war then, in web.xml it would be good to be able to
>>>specify applicationContextCore.xml (and any other web specific application
>>>context) for the war context listener.
>>>
>>>But at the moment it appears as though XmlWebApplicationContext only uses
>>>paths relative to the webapp root and does not search the web app classloader
>>>classpath for the file.
>>>
>>>Are there any plans to add this functionality? Currenly I am copying the
>>>applicationContextCore.xml into the war at build time, which works but feels
>>>a
>>>bit messy...
>>>
>>>Cheers,
>>>N
>>>
>>>
>>>
>>>-------------------------------------------------------
>>>This SF.net email is sponsored by: SF.net Giveback Program.
>>>Does SourceForge.net help you be more productive? Does it
>>>help you create better code? SHARE THE LOVE, and help us help
>>>YOU! Click Here: http://sourceforge.net/donate/
>>>_______________________________________________
>>>Springframework-user mailing list
>>>Spr...@li...
>>>https://lists.sourceforge.net/lists/listinfo/springframework-user
>>>
>>>
>>
>>-------------------------------------------------------
>>This SF.net email is sponsored by: SF.net Giveback Program.
>>Does SourceForge.net help you be more productive? Does it
>>help you create better code? SHARE THE LOVE, and help us help
>>YOU! Click Here: http://sourceforge.net/donate/
>>_______________________________________________
>>Springframework-user mailing list
>>Spr...@li...
>>https://lists.sourceforge.net/lists/listinfo/springframework-user
>>
>>
>>
>>
>>-------------------------------------------------------
>>This SF.net email is sponsored by: SF.net Giveback Program.
>>Does SourceForge.net help you be more productive? Does it
>>help you create better code? SHARE THE LOVE, and help us help
>>YOU! Click Here: http://sourceforge.net/donate/
>>_______________________________________________
>>Springframework-user mailing list
>>Spr...@li...
>>https://lists.sourceforge.net/lists/listinfo/springframework-user
>>
>>
|