001: /*
002: * Copyright 2002-2007 the original author or authors.
003: *
004: * Licensed under the Apache License, Version 2.0 (the "License");
005: * you may not use this file except in compliance with the License.
006: * You may obtain a copy of the License at
007: *
008: * http://www.apache.org/licenses/LICENSE-2.0
009: *
010: * Unless required by applicable law or agreed to in writing, software
011: * distributed under the License is distributed on an "AS IS" BASIS,
012: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013: * See the License for the specific language governing permissions and
014: * limitations under the License.
015: */
016:
017: package org.springframework.beans.factory.access;
018:
019: import java.io.IOException;
020: import java.util.HashMap;
021: import java.util.Map;
022:
023: import org.apache.commons.logging.Log;
024: import org.apache.commons.logging.LogFactory;
025:
026: import org.springframework.beans.BeansException;
027: import org.springframework.beans.FatalBeanException;
028: import org.springframework.beans.factory.BeanDefinitionStoreException;
029: import org.springframework.beans.factory.BeanFactory;
030: import org.springframework.beans.factory.config.ConfigurableBeanFactory;
031: import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
032: import org.springframework.beans.factory.support.DefaultListableBeanFactory;
033: import org.springframework.beans.factory.xml.XmlBeanDefinitionReader;
034: import org.springframework.core.io.Resource;
035: import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
036: import org.springframework.core.io.support.ResourcePatternResolver;
037: import org.springframework.core.io.support.ResourcePatternUtils;
038:
039: /**
040: * <p>Keyed-singleton implementation of BeanFactoryLocator, which accesses shared
041: * Spring factory instances.</p>
042: *
043: * <p>Please see the warning in BeanFactoryLocator's javadoc about appropriate usage
044: * of singleton style BeanFactoryLocator implementations. It is the opinion of the
045: * Spring team that the use of this class and similar classes is unnecessary except
046: * (sometimes) for a small amount of glue code. Excessive usage will lead to code
047: * that is more tightly coupled, and harder to modify or test.</p>
048: *
049: * <p>In this implementation, a BeanFactory is built up from one or more XML
050: * definition file fragments, accessed as resources. The default resource name
051: * searched for is 'classpath*:beanRefFactory.xml', with the Spring-standard
052: * 'classpath*:' prefix ensuring that if the classpath contains multiple copies
053: * of this file (perhaps one in each component jar) they will be combined. To
054: * override the default resource name, instead of using the no-arg
055: * {@link #getInstance()} method, use the {@link #getInstance(String selector)}
056: * variant, which will treat the 'selector' argument as the resource name to
057: * search for.</p>
058: *
059: * <p>The purpose of this 'outer' BeanFactory is to create and hold a copy of one
060: * or more 'inner' BeanFactory or ApplicationContext instances, and allow those
061: * to be obtained either directly or via an alias. As such, this class provides
062: * both singleton style access to one or more BeanFactories/ApplicationContexts,
063: * and also a level of indirection, allowing multiple pieces of code, which are
064: * not able to work in a Dependency Injection fashion, to refer to and use the
065: * same target BeanFactory/ApplicationContext instance(s), by different names.<p>
066: *
067: * <p>Consider an example application scenario:
068: *
069: * <ul>
070: * <li><code>com.mycompany.myapp.util.applicationContext.xml</code> -
071: * ApplicationContext definition file which defines beans for 'util' layer.
072: * <li><code>com.mycompany.myapp.dataaccess-applicationContext.xml</code> -
073: * ApplicationContext definition file which defines beans for 'data access' layer.
074: * Depends on the above.
075: * <li><code>com.mycompany.myapp.services.applicationContext.xml</code> -
076: * ApplicationContext definition file which defines beans for 'services' layer.
077: * Depends on the above.
078: * </ul>
079: *
080: * <p>In an ideal scenario, these would be combined to create one ApplicationContext,
081: * or created as three hierarchical ApplicationContexts, by one piece of code
082: * somewhere at application startup (perhaps a Servlet filter), from which all other
083: * code in the application would flow, obtained as beans from the context(s). However
084: * when third party code enters into the picture, things can get problematic. If the
085: * third party code needs to create user classes, which should normally be obtained
086: * from a Spring BeanFactory/ApplicationContext, but can handle only newInstance()
087: * style object creation, then some extra work is required to actually access and
088: * use object from a BeanFactory/ApplicationContext. One solutions is to make the
089: * class created by the third party code be just a stub or proxy, which gets the
090: * real object from a BeanFactory/ApplicationContext, and delegates to it. However,
091: * it is is not normally workable for the stub to create the BeanFactory on each
092: * use, as depending on what is inside it, that can be an expensive operation.
093: * Additionally, there is a fairly tight coupling between the stub and the name of
094: * the definition resource for the BeanFactory/ApplicationContext. This is where
095: * SingletonBeanFactoryLocator comes in. The stub can obtain a
096: * SingletonBeanFactoryLocator instance, which is effectively a singleton, and
097: * ask it for an appropriate BeanFactory. A subsequent invocation (assuming the
098: * same class loader is involved) by the stub or another piece of code, will obtain
099: * the same instance. The simple aliasing mechanism allows the context to be asked
100: * for by a name which is appropriate for (or describes) the user. The deployer can
101: * match alias names to actual context names.
102: *
103: * <p>Another use of SingletonBeanFactoryLocator, is to demand-load/use one or more
104: * BeanFactories/ApplicationContexts. Because the definiiton can contain one of more
105: * BeanFactories/ApplicationContexts, which can be independent or in a hierarchy, if
106: * they are set to lazy-initialize, they will only be created when actually requested
107: * for use.
108: *
109: * <p>Given the above-mentioned three ApplicationContexts, consider the simplest
110: * SingletonBeanFactoryLocator usage scenario, where there is only one single
111: * <code>beanRefFactory.xml</code> definition file:
112: *
113: * <pre class="code"><?xml version="1.0" encoding="UTF-8"?>
114: * <!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN 2.0//EN" "http://www.springframework.org/dtd/spring-beans_2_0.dtd">
115: *
116: * <beans>
117: *
118: * <bean id="com.mycompany.myapp"
119: * class="org.springframework.context.support.ClassPathXmlApplicationContext">
120: * <constructor-arg>
121: * <list>
122: * <value>com/mycompany/myapp/util/applicationContext.xml</value>
123: * <value>com/mycompany/myapp/dataaccess/applicationContext.xml</value>
124: * <value>com/mycompany/myapp/dataaccess/services.xml</value>
125: * </list>
126: * </constructor-arg>
127: * </bean>
128: *
129: * </beans>
130: * </pre>
131: *
132: * The client code is as simple as:
133: *
134: * <pre class="code">
135: * BeanFactoryLocator bfl = SingletonBeanFactoryLocator.getInstance();
136: * BeanFactoryReference bf = bfl.useBeanFactory("com.mycompany.myapp");
137: * // now use some bean from factory
138: * MyClass zed = bf.getFactory().getBean("mybean");
139: * </pre>
140: *
141: * Another relatively simple variation of the <code>beanRefFactory.xml</code> definition file could be:
142: *
143: * <pre class="code"><?xml version="1.0" encoding="UTF-8"?>
144: * <!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN 2.0//EN" "http://www.springframework.org/dtd/spring-beans_2_0.dtd">
145: *
146: * <beans>
147: *
148: * <bean id="com.mycompany.myapp.util" lazy-init="true"
149: * class="org.springframework.context.support.ClassPathXmlApplicationContext">
150: * <constructor-arg>
151: * <value>com/mycompany/myapp/util/applicationContext.xml</value>
152: * </constructor-arg>
153: * </bean>
154: *
155: * <!-- child of above -->
156: * <bean id="com.mycompany.myapp.dataaccess" lazy-init="true"
157: * class="org.springframework.context.support.ClassPathXmlApplicationContext">
158: * <constructor-arg>
159: * <list><value>com/mycompany/myapp/dataaccess/applicationContext.xml</value></list>
160: * </constructor-arg>
161: * <constructor-arg>
162: * <ref bean="com.mycompany.myapp.util"/>
163: * </constructor-arg>
164: * </bean>
165: *
166: * <!-- child of above -->
167: * <bean id="com.mycompany.myapp.services" lazy-init="true"
168: * class="org.springframework.context.support.ClassPathXmlApplicationContext">
169: * <constructor-arg>
170: * <list><value>com/mycompany/myapp/dataaccess.services.xml</value></value>
171: * </constructor-arg>
172: * <constructor-arg>
173: * <ref bean="com.mycompany.myapp.dataaccess"/>
174: * </constructor-arg>
175: * </bean>
176: *
177: * <!-- define an alias -->
178: * <bean id="com.mycompany.myapp.mypackage"
179: * class="java.lang.String">
180: * <constructor-arg>
181: * <value>com.mycompany.myapp.services</value>
182: * </constructor-arg>
183: * </bean>
184: *
185: * </beans>
186: * </pre>
187: *
188: * <p>In this example, there is a hierarchy of three contexts created. The (potential)
189: * advantage is that if the lazy flag is set to true, a context will only be created
190: * if it's actually used. If there is some code that is only needed some of the time,
191: * this mechanism can save some resources. Additionally, an alias to the last context
192: * has been created. Aliases allow usage of the idiom where client code asks for a
193: * context with an id which represents the package or module the code is in, and the
194: * actual definition file(s) for the SingletonBeanFactoryLocator maps that id to
195: * a real context id.
196: *
197: * <p>A final example is more complex, with a <code>beanRefFactory.xml</code> for every module.
198: * All the files are automatically combined to create the final definition.
199: *
200: * <p><code>beanRefFactory.xml</code> file inside jar for util module:
201: *
202: * <pre class="code"><?xml version="1.0" encoding="UTF-8"?>
203: * <!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN 2.0//EN" "http://www.springframework.org/dtd/spring-beans_2_0.dtd">
204: *
205: * <beans>
206: * <bean id="com.mycompany.myapp.util" lazy-init="true"
207: * class="org.springframework.context.support.ClassPathXmlApplicationContext">
208: * <constructor-arg>
209: * <value>com/mycompany/myapp/util/applicationContext.xml</value>
210: * </constructor-arg>
211: * </bean>
212: * </beans>
213: * </pre>
214: *
215: * <code>beanRefFactory.xml</code> file inside jar for data-access module:<br>
216: *
217: * <pre class="code"><?xml version="1.0" encoding="UTF-8"?>
218: * <!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN 2.0//EN" "http://www.springframework.org/dtd/spring-beans_2_0.dtd">
219: *
220: * <beans>
221: * <!-- child of util -->
222: * <bean id="com.mycompany.myapp.dataaccess" lazy-init="true"
223: * class="org.springframework.context.support.ClassPathXmlApplicationContext">
224: * <constructor-arg>
225: * <list><value>com/mycompany/myapp/dataaccess/applicationContext.xml</value></list>
226: * </constructor-arg>
227: * <constructor-arg>
228: * <ref bean="com.mycompany.myapp.util"/>
229: * </constructor-arg>
230: * </bean>
231: * </beans>
232: * </pre>
233: *
234: * <code>beanRefFactory.xml</code> file inside jar for services module:
235: *
236: * <pre class="code"><?xml version="1.0" encoding="UTF-8"?>
237: * <!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN 2.0//EN" "http://www.springframework.org/dtd/spring-beans_2_0.dtd">
238: *
239: * <beans>
240: * <!-- child of data-access -->
241: * <bean id="com.mycompany.myapp.services" lazy-init="true"
242: * class="org.springframework.context.support.ClassPathXmlApplicationContext">
243: * <constructor-arg>
244: * <list><value>com/mycompany/myapp/dataaccess/services.xml</value></list>
245: * </constructor-arg>
246: * <constructor-arg>
247: * <ref bean="com.mycompany.myapp.dataaccess"/>
248: * </constructor-arg>
249: * </bean>
250: * </beans>
251: * </pre>
252: *
253: * <code>beanRefFactory.xml</code> file inside jar for mypackage module. This doesn't
254: * create any of its own contexts, but allows the other ones to be referred to be
255: * a name known to this module:
256: *
257: * <pre class="code"><?xml version="1.0" encoding="UTF-8"?>
258: * <!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN 2.0//EN" "http://www.springframework.org/dtd/spring-beans_2_0.dtd">
259: *
260: * <beans>
261: * <!-- define an alias for "com.mycompany.myapp.services" -->
262: * <alias name="com.mycompany.myapp.services" alias="com.mycompany.myapp.mypackage"/>
263: * </beans>
264: * </pre>
265: *
266: * @author Colin Sampaleanu
267: * @author Juergen Hoeller
268: * @see org.springframework.context.access.ContextSingletonBeanFactoryLocator
269: * @see org.springframework.context.access.DefaultLocatorFactory
270: */
271: public class SingletonBeanFactoryLocator implements BeanFactoryLocator {
272:
273: private static final String DEFAULT_RESOURCE_LOCATION = "classpath*:beanRefFactory.xml";
274:
275: protected static final Log logger = LogFactory
276: .getLog(SingletonBeanFactoryLocator.class);
277:
278: // the keyed singleton instances
279: private static Map instances = new HashMap();
280:
281: /**
282: * Returns an instance which uses the default "classpath*:beanRefFactory.xml",
283: * as the name of the definition file(s). All resources returned by calling the
284: * current thread's context classloader's getResources() method with this name
285: * will be combined to create a definition, which is just a BeanFactory.
286: * @return the corresponding BeanFactoryLocator instance
287: * @throws BeansException in case of factory loading failure
288: */
289: public static BeanFactoryLocator getInstance()
290: throws BeansException {
291: return getInstance(null);
292: }
293:
294: /**
295: * Returns an instance which uses the the specified selector, as the name of the
296: * definition file(s). In the case of a name with a Spring 'classpath*:' prefix,
297: * or with no prefix, which is treated the same, the current thread's context
298: * classloader's getResources() method will be called with this value to get all
299: * resources having that name. These resources will then be combined to form a
300: * definition. In the case where the name uses a Spring 'classpath:' prefix, or
301: * a standard URL prefix, then only one resource file will be loaded as the
302: * definition.
303: * @param selector the name of the resource(s) which will be read and
304: * combined to form the definition for the BeanFactoryLocator instance.
305: * Any such files must form a valid BeanFactory definition.
306: * @return the corresponding BeanFactoryLocator instance
307: * @throws BeansException in case of factory loading failure
308: */
309: public static BeanFactoryLocator getInstance(String selector)
310: throws BeansException {
311: String resourceLocation = selector;
312: if (resourceLocation == null) {
313: resourceLocation = DEFAULT_RESOURCE_LOCATION;
314: }
315:
316: // For backwards compatibility, we prepend 'classpath*:' to the selector name if there
317: // is no other prefix (i.e. classpath*:, classpath:, or some URL prefix.
318: if (!ResourcePatternUtils.isUrl(resourceLocation)) {
319: resourceLocation = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX
320: + resourceLocation;
321: }
322:
323: synchronized (instances) {
324: if (logger.isTraceEnabled()) {
325: logger
326: .trace("SingletonBeanFactoryLocator.getInstance(): instances.hashCode="
327: + instances.hashCode()
328: + ", instances="
329: + instances);
330: }
331: BeanFactoryLocator bfl = (BeanFactoryLocator) instances
332: .get(resourceLocation);
333: if (bfl == null) {
334: bfl = new SingletonBeanFactoryLocator(resourceLocation);
335: instances.put(resourceLocation, bfl);
336: }
337: return bfl;
338: }
339: }
340:
341: // We map BeanFactoryGroup objects by String keys, and by the definition object.
342: private final Map bfgInstancesByKey = new HashMap();
343:
344: private final Map bfgInstancesByObj = new HashMap();
345:
346: private final String resourceLocation;
347:
348: /**
349: * Constructor which uses the the specified name as the resource name
350: * of the definition file(s).
351: * @param resourceLocation the Spring resource location to use
352: * (either a URL or a "classpath:" / "classpath*:" pseudo URL)
353: */
354: protected SingletonBeanFactoryLocator(String resourceLocation) {
355: this .resourceLocation = resourceLocation;
356: }
357:
358: public BeanFactoryReference useBeanFactory(String factoryKey)
359: throws BeansException {
360: synchronized (this .bfgInstancesByKey) {
361: BeanFactoryGroup bfg = (BeanFactoryGroup) this .bfgInstancesByKey
362: .get(this .resourceLocation);
363:
364: if (bfg != null) {
365: bfg.refCount++;
366: } else {
367: // This group definition doesn't exist, we need to try to load it.
368: if (logger.isTraceEnabled()) {
369: logger.trace("Factory group with resource name ["
370: + this .resourceLocation
371: + "] requested. Creating new instance.");
372: }
373:
374: // Create the BeanFactory but don't initialize it.
375: BeanFactory groupContext = createDefinition(
376: this .resourceLocation, factoryKey);
377:
378: // Record its existence now, before instantiating any singletons.
379: bfg = new BeanFactoryGroup();
380: bfg.definition = groupContext;
381: bfg.refCount = 1;
382: this .bfgInstancesByKey.put(this .resourceLocation, bfg);
383: this .bfgInstancesByObj.put(groupContext, bfg);
384:
385: // Now initialize the BeanFactory. This may cause a re-entrant invocation
386: // of this method, but since we've already added the BeanFactory to our
387: // mappings, the next time it will be found and simply have its
388: // reference count incremented.
389: try {
390: initializeDefinition(groupContext);
391: } catch (BeansException ex) {
392: throw new BootstrapException(
393: "Unable to initialize group definition. "
394: + "Group resource name ["
395: + this .resourceLocation
396: + "], factory key [" + factoryKey
397: + "]", ex);
398: }
399: }
400:
401: final BeanFactory groupContext = bfg.definition;
402:
403: String beanName = factoryKey;
404: Object bean;
405: try {
406: bean = groupContext.getBean(beanName);
407: if (bean instanceof String) {
408: logger
409: .warn("You're using the deprecated alias-through-String-bean feature, "
410: + "which will be removed as of Spring 2.1. It is recommended to replace this "
411: + "with an <alias> tag (see SingletonBeanFactoryLocator javadoc).");
412: beanName = (String) bean;
413: bean = groupContext.getBean(beanName);
414: }
415: } catch (BeansException ex) {
416: throw new BootstrapException(
417: "Unable to return specified BeanFactory instance: factory key ["
418: + factoryKey
419: + "], from group with resource name ["
420: + this .resourceLocation + "]", ex);
421: }
422:
423: if (!(bean instanceof BeanFactory)) {
424: throw new BootstrapException("Bean '" + beanName
425: + "' is not a BeanFactory: factory key ["
426: + factoryKey
427: + "], from group with resource name ["
428: + this .resourceLocation + "]");
429: }
430:
431: final BeanFactory beanFactory = (BeanFactory) bean;
432:
433: return new BeanFactoryReference() {
434: BeanFactory groupContextRef;
435: {
436: this .groupContextRef = groupContext;
437: }
438:
439: public BeanFactory getFactory() {
440: return beanFactory;
441: }
442:
443: // Note that it's legal to call release more than once!
444: public void release() throws FatalBeanException {
445: synchronized (bfgInstancesByKey) {
446: BeanFactory savedRef = this .groupContextRef;
447: if (savedRef != null) {
448: this .groupContextRef = null;
449: BeanFactoryGroup bfg = (BeanFactoryGroup) bfgInstancesByObj
450: .get(savedRef);
451: if (bfg != null) {
452: bfg.refCount--;
453: if (bfg.refCount == 0) {
454: destroyDefinition(savedRef,
455: resourceLocation);
456: bfgInstancesByKey
457: .remove(resourceLocation);
458: bfgInstancesByObj.remove(savedRef);
459: }
460: } else {
461: // This should be impossible.
462: logger
463: .warn("Tried to release a SingletonBeanFactoryLocator group definition "
464: + "more times than it has actually been used. Resource name ["
465: + resourceLocation
466: + "]");
467: }
468: }
469: }
470: }
471: };
472: }
473: }
474:
475: /**
476: * Actually creates definition in the form of a BeanFactory, given a resource name
477: * which supports standard Spring resource prefixes ('classpath:', 'classpath*:', etc.)
478: * This is split out as a separate method so that subclasses can override the actual
479: * type used (to be an ApplicationContext, for example).
480: * <p>The default implementation simply builds a
481: * {@link org.springframework.beans.factory.support.DefaultListableBeanFactory}
482: * and populates it using an
483: * {@link org.springframework.beans.factory.xml.XmlBeanDefinitionReader}.
484: * <p>This method should not instantiate any singletons. That function is performed
485: * by {@link #initializeDefinition initializeDefinition()}, which should also be
486: * overridden if this method is.
487: * @param resourceLocation the resource location for this factory group
488: * @param factoryKey the bean name of the factory to obtain
489: * @return the corresponding BeanFactory reference
490: */
491: protected BeanFactory createDefinition(String resourceLocation,
492: String factoryKey) {
493: DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
494: XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(
495: factory);
496: ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver();
497:
498: try {
499: Resource[] configResources = resourcePatternResolver
500: .getResources(resourceLocation);
501: if (configResources.length == 0) {
502: throw new FatalBeanException(
503: "Unable to find resource for specified definition. "
504: + "Group resource name ["
505: + this .resourceLocation
506: + "], factory key [" + factoryKey + "]");
507: }
508: reader.loadBeanDefinitions(configResources);
509: } catch (IOException ex) {
510: throw new BeanDefinitionStoreException(
511: "Error accessing bean definition resource ["
512: + this .resourceLocation + "]", ex);
513: } catch (BeanDefinitionStoreException ex) {
514: throw new FatalBeanException(
515: "Unable to load group definition: "
516: + "group resource name ["
517: + this .resourceLocation
518: + "], factory key [" + factoryKey + "]", ex);
519: }
520:
521: return factory;
522: }
523:
524: /**
525: * Instantiate singletons and do any other normal initialization of the factory.
526: * Subclasses that override {@link #createDefinition createDefinition()} should
527: * also override this method.
528: * @param groupDef the factory returned by {@link #createDefinition createDefinition()}
529: */
530: protected void initializeDefinition(BeanFactory groupDef) {
531: if (groupDef instanceof ConfigurableListableBeanFactory) {
532: ((ConfigurableListableBeanFactory) groupDef)
533: .preInstantiateSingletons();
534: }
535: }
536:
537: /**
538: * Destroy definition in separate method so subclass may work with other definition types.
539: * @param groupDef the factory returned by {@link #createDefinition createDefinition()}
540: * @param selector the resource location for this factory group
541: */
542: protected void destroyDefinition(BeanFactory groupDef,
543: String selector) {
544: if (groupDef instanceof ConfigurableBeanFactory) {
545: if (logger.isTraceEnabled()) {
546: logger
547: .trace("Factory group with selector '"
548: + selector
549: + "' being released, as there are no more references to it");
550: }
551: ((ConfigurableBeanFactory) groupDef).destroySingletons();
552: }
553: }
554:
555: /**
556: * We track BeanFactory instances with this class.
557: */
558: private static class BeanFactoryGroup {
559:
560: private BeanFactory definition;
561:
562: private int refCount = 0;
563: }
564:
565: }
|