1. /*
  2. * Copyright 2002-2004 the original author or authors.
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. */
  16. package org.springframework.beans.factory.access;
  17. import java.io.IOException;
  18. import java.net.MalformedURLException;
  19. import java.net.URL;
  20. import java.util.ArrayList;
  21. import java.util.Collection;
  22. import java.util.Enumeration;
  23. import java.util.HashMap;
  24. import java.util.Iterator;
  25. import java.util.Map;
  26. import org.apache.commons.logging.Log;
  27. import org.apache.commons.logging.LogFactory;
  28. import org.springframework.beans.BeansException;
  29. import org.springframework.beans.FatalBeanException;
  30. import org.springframework.beans.factory.BeanDefinitionStoreException;
  31. import org.springframework.beans.factory.BeanFactory;
  32. import org.springframework.beans.factory.config.ConfigurableBeanFactory;
  33. import org.springframework.beans.factory.support.DefaultListableBeanFactory;
  34. import org.springframework.beans.factory.xml.XmlBeanDefinitionReader;
  35. import org.springframework.core.io.UrlResource;
  36. /**
  37. * <p>Keyed-singleton implementation of BeanFactoryLocator, which leverages existing
  38. * Spring constructs. This is normally accessed through DefaultLocatorFactory, but may also
  39. * be used directly.</p>
  40. *
  41. * <p>Please see the warning in BeanFactoryLocator's JavaDoc about appropriate usage
  42. * of singleton style BeanFactoryLocator implementations. It is the opinion of the
  43. * Spring team that the use of this class and similar classes is unecessary except
  44. * (sometimes) for a small amount of glue code. Excessive usage will lead to code
  45. * that is more tightly coupled, and harder to modify or test.</p>
  46. *
  47. * <p>In this implementation, a BeanFactory is built up from one or more XML
  48. * definition files, accessed as resources. The default name of the resource file(s)
  49. * is 'beanRefFactory.xml', which is used when the instance is obtained with the no-arg
  50. * {@link #getInstance()} method. Using {@link #getInstance(String selector)} will
  51. * return a singleton instance which will use a name for the resource file(s) which
  52. * is the specificed selector argument, instead of the default. The purpose of this
  53. * BeanFactory is to create and hold a copy of one or more 'real' BeanFactory
  54. * or Application Context instances, and allow those to be obtained either directly or
  55. * via an alias. As such, it provides a level of indirection, and allows multiple
  56. * pieces of code, which are not able to work in a Dependency Injection fashion, to
  57. * refer to and use the same target BeanFactory/ApplicationContext instance(s), by
  58. * different names.<p>
  59. *
  60. * <p>Consider an example application scenario:<br><br>
  61. * <code>com.mycompany.myapp.util.applicationContext.xml</code> - ApplicationContext
  62. * definition file which defines beans for 'util' layer.<br>
  63. * <code>com.mycompany.myapp.dataaccess-applicationContext.xml</code> -
  64. * ApplicationContext definition file which defines beans for 'data access' layer.
  65. * Depends on the above<br>
  66. * <code>com.mycompany.myapp.services.applicationContext.xml</code> -
  67. * ApplicationContext definition file which defines beans for 'services' layer.
  68. * Depends on the above.
  69. *
  70. * <p>In an ideal scenario, these would be combined to create one ApplicationContext,
  71. * or created as three hierarchical ApplicationContexts, by one piece of code
  72. * somewhere at application startup (perhaps a Servlet filter), from which all other
  73. * code in the application would flow, obtained as beans from the context(s). However
  74. * when third party code enters into the picture, things can get problematic. If the
  75. * third party code needs to create user classes, which should normally be obtained
  76. * from a Spring BeanFactory/ApplicationContext, but can handle only newInstance()
  77. * style object creation, then some extra work is required to actually access and
  78. * use object from a BeanFactory/ApplicationContext. One solutions is to make the
  79. * class created by the third party code be just a stub or proxy, which gets the
  80. * real object from a BeanFactory/ApplicationContext, and delegates to it. However,
  81. * it is is not normally workable for the stub to create the BeanFactory on each
  82. * use, as depending on what is inside it, that can be an expensive operation.
  83. * Additionally, there is a fairly tight coupling between the stub and the name of
  84. * the definition resource for the BeanFactory/ApplicationContext. This is where
  85. * SingletonBeanFactoryLocator comes in. The stub can obtain a
  86. * SingletonBeanFactoryLocator instance, which is effectively a singleton, and
  87. * ask it for an appropriate BeanFactory. A subsequent invocation (assuming the
  88. * same classloader is involved) by the stub or another piece of code, will obtain
  89. * the same instance. The simple aliasing mechanism allows the context to be asked
  90. * for by a name which is appropriate for (or describes) the user. The deployer can
  91. * match alias names to actual context names.
  92. *
  93. * <p>Another use of SingletonBeanFactoryLocator, is to demand-load/use one or more
  94. * BeanFactories/ApplicationContexts. Because the definiiton can contain one of more
  95. * BeanFactories/ApplicationContexts, which can be independent or in a hierarchy, if
  96. * they are set to lazy-initialize, they will only be created when actually requested
  97. * for use.
  98. *
  99. * <p>Given the above-mentioned three ApplicationContexts, consider the simplest
  100. * SingletonBeanFactoryLocator usage scenario, where there is only one single
  101. * <code>beanRefFactory.xml</code> definition file:<br>
  102. * <pre>
  103. * <?xml version="1.0" encoding="UTF-8"?>
  104. * <!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">
  105. *
  106. * <beans>
  107. *
  108. * <bean id="com.mycompany.myapp"
  109. * class="org.springframework.context.support.ClassPathXmlApplicationContext">
  110. * <constructor-arg>
  111. * <list>
  112. * <value>com.mycompany.myapp.util.applicationContext.xml</value>
  113. * <value>com.mycompany.myapp.dataaccess.applicationContext.xml</value>
  114. * <value>com.mycompany.myapp.dataaccess.services.xml</value>
  115. * </list>
  116. * </constructor-arg>
  117. * </bean>
  118. *
  119. * </beans>
  120. * </pre>
  121. * The client code is as simple as:
  122. * <pre>
  123. * BeanFactoryLocator bfl = SingletonBeanFactoryLocator.getInstance();
  124. * BeanFactoryReference bf = bfl.useFactory("com.mycompany.myapp");
  125. * // now use some bean from factory
  126. * MyClass zed = bf.getFactory().getBean("mybean");
  127. * </pre>
  128. * Another relatively simple variation of the <code>beanRefFactory.xml</code> definition file could be:
  129. * <br>
  130. * <pre>
  131. * <?xml version="1.0" encoding="UTF-8"?>
  132. * <!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">
  133. *
  134. * <beans>
  135. *
  136. * <bean id="com.mycompany.myapp.util" lazy-init="true"
  137. * class="org.springframework.context.support.ClassPathXmlApplicationContext">
  138. * <constructor-arg>
  139. * <value>com.mycompany.myapp.util.applicationContext.xml</value>
  140. * </constructor-arg>
  141. * </bean>
  142. *
  143. * <!-- child of above -->
  144. * <bean id="com.mycompany.myapp.dataaccess" lazy-init="true"
  145. * class="org.springframework.context.support.ClassPathXmlApplicationContext">
  146. * <constructor-arg>
  147. * <list><value>com.mycompany.myapp.dataaccess.applicationContext.xml</value></list>
  148. * </constructor-arg>
  149. * <constructor-arg>
  150. * <ref bean="com.mycompany.myapp.util"/>
  151. * </constructor-arg>
  152. * </bean>
  153. *
  154. * <!-- child of above -->
  155. * <bean id="com.mycompany.myapp.services" lazy-init="true"
  156. * class="org.springframework.context.support.ClassPathXmlApplicationContext">
  157. * <constructor-arg>
  158. * <list><value>com.mycompany.myapp.dataaccess.services.xml</value></value>
  159. * </constructor-arg>
  160. * <constructor-arg>
  161. * <ref bean="com.mycompany.myapp.dataaccess"/>
  162. * </constructor-arg>
  163. * </bean>
  164. *
  165. * <!-- define an alias -->
  166. * <bean id="com.mycompany.myapp.mypackage"
  167. * class="java.lang.String">
  168. * <constructor-arg>
  169. * <value>com.mycompany.myapp.services</value>
  170. * </constructor-arg>
  171. * </bean>
  172. *
  173. * </beans>
  174. * </pre>
  175. *
  176. * <p>In this example, there is a hierarchy of three contexts created. The (potential)
  177. * advantage is that if the lazy flag is set to true, a context will only be created
  178. * if it's actually used. If there is some code that is only needed some of the time,
  179. * this mechanism can save some resources. Additionally, an alias to the last context
  180. * has been created. Aliases allow usage of the idiom where client code asks for a
  181. * context with an id which represents the package or module the code is in, and the
  182. * actual definition file(s) for the SingletonBeanFactoryLocator maps that id to
  183. * a real context id.
  184. *
  185. * <p>A final example is more complex, with a <code>beanRefFactory.xml</code> for every module.
  186. * All the files are automatically combined to create the final definition.<br>
  187. * <code>beanRefFactory.xml</code> file inside jar for util module:
  188. *
  189. * <p><pre>
  190. * <?xml version="1.0" encoding="UTF-8"?>
  191. * <!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">
  192. *
  193. * <beans>
  194. * <bean id="com.mycompany.myapp.util" lazy-init="true"
  195. * class="org.springframework.context.support.ClassPathXmlApplicationContext">
  196. * <constructor-arg>
  197. * <value>com.mycompany.myapp.util.applicationContext.xml</value>
  198. * </constructor-arg>
  199. * </bean>
  200. * </beans>
  201. * </pre>
  202. *
  203. * <code>beanRefFactory.xml</code> file inside jar for data-access module:<br>
  204. * <pre>
  205. * <?xml version="1.0" encoding="UTF-8"?>
  206. * <!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">
  207. *
  208. * <beans>
  209. * <!-- child of util -->
  210. * <bean id="com.mycompany.myapp.dataaccess" lazy-init="true"
  211. * class="org.springframework.context.support.ClassPathXmlApplicationContext">
  212. * <constructor-arg>
  213. * <list><value>com.mycompany.myapp.dataaccess.applicationContext.xml</value></list>
  214. * </constructor-arg>
  215. * <constructor-arg>
  216. * <ref bean="com.mycompany.myapp.util"/>
  217. * </constructor-arg>
  218. * </bean>
  219. * </beans>
  220. * </pre>
  221. *
  222. * <code>beanRefFactory.xml</code> file inside jar for services module:<br>
  223. * <pre>
  224. * <?xml version="1.0" encoding="UTF-8"?>
  225. * <!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">
  226. *
  227. * <beans>
  228. * <!-- child of data-access -->
  229. * <bean id="com.mycompany.myapp.services" lazy-init="true"
  230. * class="org.springframework.context.support.ClassPathXmlApplicationContext">
  231. * <constructor-arg>
  232. * <list><value>com.mycompany.myapp.dataaccess.services.xml</value></list>
  233. * </constructor-arg>
  234. * <constructor-arg>
  235. * <ref bean="com.mycompany.myapp.dataaccess"/>
  236. * </constructor-arg>
  237. * </bean>
  238. * </beans>
  239. * </pre>
  240. *
  241. * <code>beanRefFactory.xml</code> file inside jar for mypackage module. This doesn't
  242. * create any of its own contexts, but allows the other ones to be referred to be
  243. * a name known to this module:<br>
  244. * <pre>
  245. * <?xml version="1.0" encoding="UTF-8"?>
  246. * <!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">
  247. *
  248. * <beans>
  249. * <!-- define an alias -->
  250. * <bean id="com.mycompany.myapp.mypackage"
  251. * class="java.lang.String">
  252. * <constructor-arg>
  253. * <value>com.mycompany.myapp.services</value>
  254. * </constructor-arg>
  255. * </bean>
  256. *
  257. * </beans>
  258. * </pre>
  259. *
  260. * @author Colin Sampaleanu
  261. * @version $Revision: 1.13 $
  262. * @see org.springframework.context.access.DefaultLocatorFactory
  263. */
  264. public class SingletonBeanFactoryLocator implements BeanFactoryLocator {
  265. public static final String BEANS_REFS_XML_NAME = "beanRefFactory.xml";
  266. protected static final Log logger = LogFactory.getLog(SingletonBeanFactoryLocator.class);
  267. // the keyed singleton instances
  268. private static Map instances = new HashMap();
  269. // we map BeanFactoryGroup objects by String keys, and by the definition object
  270. private Map bfgInstancesByKey = new HashMap();
  271. private Map bfgInstancesByObj = new HashMap();
  272. private String resourceName;
  273. /**
  274. * Returns an instance which uses the default "beanRefFactory.xml", as the name of the
  275. * definition file(s). All resources returned by the current thread's context
  276. * classloader's getResources() method with this name will be combined to create a
  277. * definition, which is just a BeanFactory.
  278. */
  279. public static BeanFactoryLocator getInstance() throws FatalBeanException {
  280. return getInstance(BEANS_REFS_XML_NAME);
  281. }
  282. /**
  283. * Returns an instance which uses the the specified selector, as the name of the
  284. * definition file(s). All resources returned by the current thread's context
  285. * classloader's getResources() method with this name will be combined to create a
  286. * definition, which is just a a BeanFactory.
  287. * @param selector the name of the resource(s) which will be read and combine to
  288. * form the definition for the SingletonBeanFactoryLocator instance
  289. */
  290. public static BeanFactoryLocator getInstance(String selector) throws FatalBeanException {
  291. synchronized (instances) {
  292. // debugging trace only
  293. //if (logger.isDebugEnabled()) {
  294. // logger.debug("SingletonBeanFactoryLocator.getInstance(): instances.hashCode=" +
  295. // instances.hashCode() + ", instances=" + instances);
  296. //}
  297. BeanFactoryLocator bfl = (BeanFactoryLocator) instances.get(selector);
  298. if (bfl == null) {
  299. bfl = new SingletonBeanFactoryLocator(selector);
  300. instances.put(selector, bfl);
  301. }
  302. return bfl;
  303. }
  304. }
  305. /**
  306. * Constructor which uses the default "beanRefFactory.xml", as the name of the
  307. * definition file(s). All resources returned by the definition classloader's
  308. * getResources() method with this name will be combined to create a definition.
  309. */
  310. protected SingletonBeanFactoryLocator() {
  311. this.resourceName = BEANS_REFS_XML_NAME;
  312. }
  313. /**
  314. * Constructor which uses the the specified name as the name of the
  315. * definition file(s). All resources returned by the definition classloader's
  316. * getResources() method with this name will be combined to create a definition
  317. * definition.
  318. */
  319. protected SingletonBeanFactoryLocator(String resourceName) {
  320. this.resourceName = resourceName;
  321. }
  322. // see superclass JavaDoc: org.springframework.beans.factory.access.BeanFactoryLocator#useFactory(java.lang.String)
  323. public BeanFactoryReference useBeanFactory(String factoryKey) throws BeansException {
  324. synchronized (this.bfgInstancesByKey) {
  325. BeanFactoryGroup bfg = (BeanFactoryGroup) this.bfgInstancesByKey
  326. .get(this.resourceName);
  327. if (bfg != null) {
  328. // debugging trace only
  329. //if (logger.isDebugEnabled()) {
  330. // logger.debug("Factory group with resourceName '" + this.resourceName
  331. // + "' requested. Using existing instance.");
  332. //}
  333. bfg.refCount++;
  334. }
  335. else {
  336. // this group definition doesn't exist, we need to try to load it
  337. if (logger.isDebugEnabled()) {
  338. logger.debug("Factory group with resourceName '" + this.resourceName
  339. + "' requested. Creating new instance.");
  340. }
  341. Collection resourceUrls;
  342. try {
  343. resourceUrls = getAllDefinitionResources(this.resourceName);
  344. }
  345. catch (IOException ex) {
  346. throw new FatalBeanException(
  347. "Unable to load group definition. Group resource name:"
  348. + this.resourceName + ", factoryKey:" + factoryKey,
  349. ex);
  350. }
  351. int numResources = resourceUrls.size();
  352. if (numResources == 0)
  353. throw new FatalBeanException(
  354. "Unable to find definition for specified definition. Group:"
  355. + this.resourceName + ", contextId:" + factoryKey);
  356. String[] resources = new String[numResources];
  357. Iterator it = resourceUrls.iterator();
  358. for (int i = 0; i < numResources; ++i) {
  359. URL url = (URL) it.next();
  360. resources[i] = url.toExternalForm();
  361. }
  362. BeanFactory groupContext = createDefinition(resources);
  363. bfg = new BeanFactoryGroup();
  364. bfg.definition = groupContext;
  365. bfg.refCount = 1;
  366. this.bfgInstancesByKey.put(this.resourceName, bfg);
  367. this.bfgInstancesByObj.put(groupContext, bfg);
  368. }
  369. final BeanFactory groupContext = bfg.definition;
  370. String lookupId = factoryKey;
  371. Object bean;
  372. try {
  373. bean = groupContext.getBean(lookupId);
  374. }
  375. catch (BeansException e) {
  376. throw new FatalBeanException(
  377. "Unable to return specified BeanFactory instance: factoryKey="
  378. + factoryKey + ", from group with resourceName: "
  379. + this.resourceName, e);
  380. }
  381. if (bean instanceof String) {
  382. // we have some indirection
  383. lookupId = (String) bean;
  384. try {
  385. bean = groupContext.getBean(lookupId);
  386. }
  387. catch (BeansException e) {
  388. throw new FatalBeanException(
  389. "Unable to return specified BeanFactory instance: lookupId="
  390. + lookupId + ", factoryKey=" + factoryKey
  391. + ", from group with resourceName: "
  392. + this.resourceName, e);
  393. }
  394. }
  395. if (!(bean instanceof BeanFactory))
  396. throw new FatalBeanException(
  397. "Returned bean is not BeanFactory or its subclass. lookupId="
  398. + lookupId + ", factoryKey=" + factoryKey
  399. + ", from group with resourceName: " + this.resourceName
  400. + ". Returned cbject class is: " + bean.getClass());
  401. final BeanFactory beanFactory = (BeanFactory) bean;
  402. return new BeanFactoryReference() {
  403. BeanFactory groupContextRef;
  404. // constructor
  405. {
  406. this.groupContextRef = groupContext;
  407. }
  408. public BeanFactory getFactory() {
  409. return beanFactory;
  410. }
  411. public void release() throws FatalBeanException {
  412. synchronized (bfgInstancesByKey) {
  413. BeanFactoryGroup bfg = (BeanFactoryGroup) bfgInstancesByObj.get(this.groupContextRef);
  414. if (bfg != null) {
  415. bfg.refCount--;
  416. if (bfg.refCount == 0) {
  417. destroyDefinition(this.groupContextRef, resourceName);
  418. bfgInstancesByKey.remove(resourceName);
  419. bfgInstancesByObj.remove(this.groupContextRef);
  420. }
  421. }
  422. else {
  423. logger.warn("Tried to release a SingletonBeanFactoryLocator (or subclass) group definition " +
  424. "more times than it has actually been used. resourceName='" + resourceName + "'");
  425. }
  426. }
  427. }
  428. };
  429. }
  430. }
  431. /**
  432. * Actually creates definition in the form of a BeanFactory, given an array of URLs
  433. * representing resources which should be combined. This is split out as a separate
  434. * method so that subclasses can override the actual type uses (to be an
  435. * ApplicationContext, for example).
  436. */
  437. protected BeanFactory createDefinition(String[] resources) throws BeansException {
  438. DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
  439. XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(factory);
  440. for (int i = 0; i < resources.length; ++i) {
  441. try {
  442. reader.loadBeanDefinitions(new UrlResource(resources[i]));
  443. }
  444. catch (MalformedURLException ex) {
  445. throw new BeanDefinitionStoreException("Bad URL when loading definition", ex);
  446. }
  447. }
  448. factory.preInstantiateSingletons();
  449. return factory;
  450. }
  451. /**
  452. * Destroy definition in separate method so subclass may work with other definition types
  453. */
  454. protected void destroyDefinition(BeanFactory groupDef, String resourceName) throws BeansException {
  455. if (groupDef instanceof ConfigurableBeanFactory) {
  456. // debugging trace only
  457. if (logger.isDebugEnabled()) {
  458. logger.debug("Factory group with resourceName '"
  459. + resourceName
  460. + "' being released, as no more references.");
  461. }
  462. ((ConfigurableBeanFactory) groupDef).destroySingletons();
  463. }
  464. }
  465. /**
  466. * Method which returns resources (as URLs) which make up the definition of one
  467. * bean factory/application context.
  468. * <p>Protected so that test cases may subclass this class and override this
  469. * method to avoid the need for multiple classloaders to test multi-file
  470. * capability in the rest of the class.
  471. */
  472. protected Collection getAllDefinitionResources(String resourceName) throws IOException {
  473. ClassLoader cl = Thread.currentThread().getContextClassLoader();
  474. // don't depend on JDK 1.4, do our own conversion
  475. Enumeration resourceEnum = cl.getResources(resourceName);
  476. Collection list = new ArrayList();
  477. while (resourceEnum.hasMoreElements()) {
  478. list.add(resourceEnum.nextElement());
  479. }
  480. return list;
  481. }
  482. // We track BeanFactory instances with this class
  483. private static class BeanFactoryGroup {
  484. private BeanFactory definition;
  485. private int refCount = 0;
  486. }
  487. }