|
From: Rod J. <rod...@in...> - 2003-10-10 15:32:30
|
I think this is very useful.
It would be good to have this available for the XmlBeanFactory as well.
Often, especially for integration testing, I tend to use a BeanFactory
unless my code has specific dependencies on an application context for
sourcing messages etc.
Regards,
Rod
----- Original Message -----
From: "jürgen höller [werk3AT]" <jue...@we...>
To: <spr...@li...>
Sent: Friday, October 10, 2003 3:30 PM
Subject: [Springframework-developer] new bean factory and application
context features
Everybody,
Related to the discussion about hierarchical loading, I've just committed
support for assembling an application context from more than one XML file,
just available via XmlWebApplicationContext for the moment. It allows to
specify config locations with multiple paths in it, like in the web case:
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>
WEB-INF/applicationContext1.xml
WEB-INF/applicationContext2.xml
</param-value>
</context-param>
Any number of the characters ";, " between paths are ignored, to allow for
lenient parsing. The result is a *single* root web application context that
has been loaded from multiple XML files, each conforming to the spring-beans
DTD. Bean references between the individual files need to be <ref
external="..."> instead of <ref bean="..."> that references XML entities in
the same file.
This allows to split a large definition of a middle tier application context
into multiple XML files, either separating by system modules or by sub
layers. Obviously this doesn't address the EJB/multiple web app case, but it
should be well suited for typical large web applications.
BTW, I've also added support for multiple resource bundle basenames in
ResourceBundleMessageSource, via the "basenames" property (in addition to
the existing "basename" one). Those resource bundles get checked
sequentially within a *single* MessageSource, allowing to split large
message bundles into multiple files.
Furthermore, I've revised the notation of name aliases like <bean ...
name="myalias1,myalias2"> to match the rules above: Any number of the
characters ";, " between bean names are ignored. This particularly allows
BeanNameUrlHandlerMapping to interpret multiple aliases as multiple mappings
without custom parsing, no matter if separated by a comma or space (the
latter looks cleaner with URLs).
The drawback of the latter is that XML bean aliases are not allowed to
contains spaces or commas anymore -- for the benefit of being able to
specify multiple aliases. Does anyone object to that tradeoff?
-----
I've also added a new post processor type: BeanPostProcessor, in addition to
the existing BeanFactoryPostProcessor. Note that the former is in the
beans.factory.support package, with AbstractBeanFactory offering support for
it -- while the latter is in context.config, as it allows application
contexts to override property values *after* their bean factory (a
ListableBeanFactoryImpl, to be exact) has loaded.
A BeanFactoryPostProcessor gets invoked once per bean factory loading;
BeanPostProcessor once per bean creation. Both types of post processor can
be defined as normal beans in an application context, getting automatically
detected at startup, and applied before the rest of the beans gets created.
With a plain bean factory, only BeanPostProcessor is applicable, but not
defined as a normal bean but rather set via AbstractBeanFactory's
setBeanPostProcessors method.
I've applied the principle that bean factories are not supposed to
understand special beans in the bean definitions that influence the behavior
of the factory itself -- just application contexts are, like with the
message source, options beans, and post processors. Bean factories can be
customized through their implementation class, though - e.g.
setEntityResolver, setValidating, and setBeanPostProcessors on
XmlBeanFactory.
A special implementation of BeanPostProcessor is applied under the hood by
AbstractApplicationContext: ApplicationContextAwareProcessor is implictly
registered with the underlying bean factory to automatically pass the
application context to ApplicationContextAware beans. This allowed me to get
rid of the rather ugly manual check for ApplicationContextAware with a cache
of already managed instances to avoid double setting of the application
context -- the current solution is much cleaner.
Note that a custom BeanPostProcessor definition in an application context
can not only be used to check for marker interfaces but also to wrap certain
bean instances with proxies, as it can return a different bean instance than
the one that came in. Look at the respective test case in
StaticApplicationContextTestSuite that implicitly wraps each bean instance
with a CGLIB proxy.
Now imagine a BeanPostProcessor implementation that checks for certain
attributes in the bean class file and applies respective interceptors --
like transaction attributes that trigger a CGLIB proxy and an implicit
TransactionInterceptor! I consider this as the perfect hook for such
implicit wrapping at bean creation time.
Juergen
-----Original Message-----
From: Colin Sampaleanu [mailto:col...@ex...]
Sent: Thursday, September 18, 2003 5:15 PM
To: spr...@li...
Subject: [Springframework-user] Hierarchical loading of Application
Contexts
I wanted to spur some discussion of best practices for hierachical
loading of ApplicationContexts. ApplicationContext implementations can
of course be created with a parent context specified. Currently, there
is some code support for actually managing this loading in the web ui
layer (where it's easy to have a setup with a root
WebApplicationContext, and multiple child contexts, per servlet.
Most Spring usage examples assume (unless I'm missing something) that
the root WebApplicationContext will contain the entire layer stack, that
is, data-access beans and service layer beans will be defined in there.
This does work great when there there is only one web-app.
Now in my case, I need something a little bit different. I have a J2EE
application as an EAR file, which contains 3 web-apps (as WARs) at the
top layer. Among other layers, below the web ui layer there is a
services layer, and a data-access layer. Now I could get by with three
different, parallel, application contexts defined, one for each of the
web-apps, where all the bean definitions for the data-access and
services layer was duplicated in each application context. I do not want
to do this however, since I am using Hibernate, and do not want to force
the Hibernate meta-data to be read in 3 times (it's a slow process). I
much prefer to have a hierachical setup for the whole j2ee app, where I
have an application context definition for the data-access layer, which
needs to be the parent of an application context definition for the
services layer, which needs to be the parent of three separate web
application context definitions, one in each web-app.
I have been able to achieve this as follows (and want to get feedback if
somebody can think of a better way): I have an application context
defintion for my data access layer
data-access-applicationContext.xml
I have an application context definition for my services layer, which is
a child of the data-access context, and refers to beans in it.:
core-servies-applicationContext.xml
I then have the three application contexts for the web apps, which are
children of the services context, and refer to beans within it.
Now what I do, is (in a somewhat lazy fashion) trigger loading (once) of
the services and data-access contexts, when I load the first web
application context. The 2nd and 3rd web application context end up
using the previously loaded service context instance. I have an
interface, ContextFactory, as follows:
/**
* Defines interface for an ApplicationContext factory.
*
* @version $Revision: 1.40 $
* @author colin
*/
public interface ContextFactory {
/**
* 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 key) 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;
}
Now I have an actual implementation, which can be used to get access to
a reference counted context, triggering loading as needed, and loading
parents of that context, if needed:
/**
* Implementation of ContextFactory.<br />
* In this implementation, the key is actually the name of a properties
file accessed as
* a resource, each line of which specifies an ApplicationContext to
load. Each line in
* the file is the parent of the subsequent line. The last context is
the one returned.
*
* @version $Revision: $
* @author colin
*/
public class ContextFactoryImpl implements ContextFactory {
// --- statics
public static final Logger _log =
Logger.getLogger(ContextFactoryImpl.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.tira.coreserv.webutil.ContextFactory#useContext(java.lang.String)
*/
public ApplicationContext useContext(String key) throws
ApplicationContextException {
synchronized(instancesByKey) {
ContextInfo ci = (ContextInfo) instancesByKey.get(key);
if (ci != null) {
_log.debug("Context with key '" + key + "' requested. Returning
existing instances");
ci.refcount++;
return ci.context;
}
_log.debug("Context with key '" + key + "' requested. Creating new
instance");
// this context doesn't exist, we need to try to load it
InputStream is = getClass().getResourceAsStream(key);
if (is == null)
throw new ApplicationContextException("Unable to load
context(s). Key does not point to a valid resource: " + key);
Properties props = new Properties();
try {
props.load(is);
is.close();
}
catch (IOException e) {
throw new ApplicationContextException("Error reading application
context pointer definition.", e);
}
ArrayList contexts = new ArrayList();
for (int i = 0; ; ++i) {
String context = props.getProperty(Integer.toString(i));
if (context == null)
break;
contexts.add(context);
}
if (contexts.size() == 0)
throw new ApplicationContextException("No application context
definitions specified in definition file: " + key);
ClassPathXmlApplicationContext ac;
try {
ac = new
ClassPathXmlApplicationContext((String[])contexts.toArray(new String[0]));
}
catch (IOException e) {
throw new ApplicationContextException("Error reading application
context definition", e);
}
ci = new ContextInfo();
ci.context = ac;
ci.key = key;
ci.refcount = 1;
instancesByKey.put(key, ci);
instancesByObj.put(ac, ci);
return ac;
}
}
/**
* 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.tira.coreserv.webutil.ContextFactory#ReleaseContext(org.springframework.
context.ApplicationContext)
*/
public void releaseContext(ApplicationContext ac) throws
ApplicationContextException {
synchronized(instancesByKey) {
ContextInfo ci = (ContextInfo) instancesByObj.get(ac);
if (ci != null) {
ci.refcount--;
if (ci.refcount < 0)
throw new ApplicationContextException("Illegal state: context
released more times than acquired");
if (ci.refcount == 0) {
_log.debug("Release requested on Context with key '" + ci.key
+ "'. Last reference, so closing along with parents");
instancesByObj.remove(ac);
instancesByKey.remove(ci.key);
ApplicationContext child = ci.context;
ApplicationContext parent;
while (child != null) {
parent = child.getParent();
child.close();
child = parent;
}
}
else
_log.debug("Release requested on Context with key '" + ci.key
+ "'. References remain, so not closing.");
}
else
throw new ApplicationContextException("Attempted to release
context reference for context not known to this factory");
}
}
// we track contexts with this class
private class ContextInfo {
public ApplicationContext context;
public String key;
public int refcount = 0;
}
}
So my ContextFactory impl can load the services application context, and
first its parent, the data-access context, on demand, and subsequent
request will just return a reference to the same context. The 'key'
specified to the context factory is just the name of a file specifying
which contexts to load:
File core-services-applicationContexts.properties:
# defines one or more hierarchical xml ApplicationContext instances
which are considered
# to make up the application context stack for core-services. Each line
is a parent of the
# subsequent line.
0=/data-access-applicationContext.xml
1=/core-services-applicationContext.xml
Now I just need to have a modified version of ContextLoader from Spring
which along with loading a specified WebApplicationContext instance,
will optioinally use the ContextFactory to load a parent context first.
Here's the code:
/**
* Class used to load a web application context, including possibly
triggering loading of a parent
* context through a ContextFactory instance
*
* @version $Revision: $
*/
public class ContextLoader {
// --- statics
/**?
* Config param for the root WebApplicationContext implementation
class to use.
*/
public static final String CONTEXT_CLASS_PARAM = "contextClass";
public static final Class DEFAULT_CONTEXT_CLASS =
XmlWebApplicationContext.class;
public static final String PARENT_CONTEXT_FACTORY_CLASS_PARAM =
"parentContextFactoryClass";
public static final String PARENT_CONTEXT_FACTORY_PARAM1_PARAM =
"parentContextFactoryParam";
public static final String PARENT_CONTEXT_KEY_PARAM = "parentContextKey";
public static final Logger _log = Logger.getLogger(ContextLoader.class);
/**
* Initialize Spring's web application context for the given servlet
context,
* regarding the "contextClass" servlet context init parameter.
* @param servletContext current servlet context
* @return the new WebApplicationContext
*/
public static WebApplicationContext initContext(ServletContext
servletContext)
throws ApplicationContextException {
servletContext.log("Loading root WebApplicationContext");
String contextClass =
servletContext.getInitParameter(CONTEXT_CLASS_PARAM);
String className = null;
try {
className =
servletContext.getInitParameter(PARENT_CONTEXT_FACTORY_CLASS_PARAM);
ContextFactory parentContextFactory =
getParentContextFactory(servletContext);
ApplicationContext parentContext = null;
if (parentContextFactory != null) {
String parentContextKey =
servletContext.getInitParameter(PARENT_CONTEXT_KEY_PARAM);
_log.info(
"Getting parent context: using context factory class '" +
className + "', key '" +
parentContextKey + "'");
parentContext = parentContextFactory.useContext(parentContextKey);
}
// Now we must load the WebApplicationContext.
// It configures itself: all we need to do is construct the class
with the proper
// constructor, and invoke setServletContext.
Class clazz = (contextClass != null ? Class.forName(contextClass)
: DEFAULT_CONTEXT_CLASS);
_log.info(
"Loading root WebApplicationContext: using context class '" +
clazz.getName() + "'");
if (!WebApplicationContext.class.isAssignableFrom(clazz)) {
throw new ApplicationContextException(
"Context class is no WebApplicationContext: " + contextClass);
}
Class[] parameterTypes;
Object[] params;
if (parentContext == null) {
parameterTypes = new Class[0];
params = new Object[0];
}
else {
parameterTypes = new Class[] {ApplicationContext.class,
String.class};
params = new Object[] {parentContext, null};
}
Constructor constructor = clazz.getConstructor(parameterTypes);
WebApplicationContext webApplicationContext =
(WebApplicationContext) constructor.newInstance(params);
webApplicationContext.setServletContext(servletContext);
return webApplicationContext;
}
catch (ApplicationContextException ex) {
handleException("Failed to initialize application context", ex);
}
catch (BeansException ex) {
handleException("Failed to initialize beans in application
context", ex);
}
catch (ClassNotFoundException ex) {
handleException("Failed to load config class '" + className + "'",
ex);
}
catch (InstantiationException ex) {
handleException(
"Failed to instantiate config class '"
+ className
+ "': does it have the proper constructor?",
ex);
}
catch (IllegalAccessException ex) {
handleException(
"Illegal access while finding or instantiating config class '"
+ className
+ "': does it have the proper constructor?",
ex);
}
catch (Throwable ex) {
handleException("Unexpected error loading context configuration", ex);
}
return null;
}
/**
* Log and throw an appropriate exception.
*/
private static void handleException(String msg, Throwable ex)
throws ApplicationContextException {
String thrownMsg = msg + ": " + ex.getMessage();
_log.error(thrownMsg, ex);
if (ex instanceof Error) {
throw (Error) ex;
}
else if (ex instanceof ApplicationContextException) {
throw (ApplicationContextException) ex;
}
else {
throw new ApplicationContextException(thrownMsg, ex);
}
}
/**
* Close Spring's web application context for the given servlet context.
* @param servletContext current servlet context
*/
public static void closeContext(ServletContext servletContext) {
servletContext.log("Closing root WebApplicationContext");
ApplicationContext ac =
WebApplicationContextUtils.getWebApplicationContext(servletContext);
ApplicationContext parent = ac.getParent();
try {
ac.close();
}
finally {
if (parent != null) {
ContextFactory cf = null;
String className =
servletContext.getInitParameter(PARENT_CONTEXT_FACTORY_CLASS_PARAM);
// should we check this for null. for now, let's not, as it has
to be there if there is a parent
try {
cf = getParentContextFactory(servletContext);
}
catch (Exception ex) {
handleException("Unable to obtain context factory to release
parent context. Context factory className '" + className + "'", ex);
}
cf.releaseContext(parent);
}
}
}
/**
* Returns a ContextFactory, if any, to be used to obtain and release
a parent context
*/
private static ContextFactory getParentContextFactory(ServletContext
servletContext) throws ClassNotFoundException, SecurityException,
NoSuchMethodException, IllegalArgumentException, InstantiationException,
IllegalAccessException, InvocationTargetException {
String parentContextFactoryClassName =
servletContext.getInitParameter(PARENT_CONTEXT_FACTORY_CLASS_PARAM);
String parentContextFactoryParam1 =
servletContext.getInitParameter(PARENT_CONTEXT_FACTORY_PARAM1_PARAM);
String className = null;
Class[] parameterTypes;
Object[] params;
ContextFactory parentContextFactory = null;
if (parentContextFactoryClassName != null) {
className = parentContextFactoryClassName;
Class clazz = Class.forName(className);
parameterTypes = new Class[0];
params = new Object[0];
if (parentContextFactoryParam1 != null) {
parameterTypes = new Class[] {String.class};
params = new Object[] {parentContextFactoryParam1};
}
Constructor constructor = clazz.getConstructor(parameterTypes);
parentContextFactory = (ContextFactory)
constructor.newInstance(params);
}
return parentContextFactory;
}
}
So this mechanism works fine. It allows me to layer application contexts
all the way down the layer stack, not just in the web-ui layer. Can
anybody think of a better or simpler way to handle this need?
Is it worth adding some of this code into Spring itself? I don't think
I'm the only person who will need to do something like this. Admittedly,
in the absence of the large start-up time for Hibernate, it would have
been acceptible to handle this via just three application contexts at
the web level, with duplicate definitions for the content from the
data-access and services layer brought in via xml includes.
Regards,
Colin
-------------------------------------------------------
This sf.net email is sponsored by:ThinkGeek
Welcome to geek heaven.
http://thinkgeek.com/sf
_______________________________________________
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.
SourceForge.net hosts over 70,000 Open Source Projects.
See the people who have HELPED US provide better services:
Click here: http://sourceforge.net/supporters.php
_______________________________________________
Springframework-developer mailing list
Spr...@li...
https://lists.sourceforge.net/lists/listinfo/springframework-developer
|