1 /* 2 * Copyright 2002-2008 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 17 package org.springframework.beans.factory.access; 18 19 import java.io.IOException; 20 import java.util.HashMap; 21 import java.util.Map; 22 23 import org.apache.commons.logging.Log; 24 import org.apache.commons.logging.LogFactory; 25 26 import org.springframework.beans.BeansException; 27 import org.springframework.beans.FatalBeanException; 28 import org.springframework.beans.factory.BeanDefinitionStoreException; 29 import org.springframework.beans.factory.BeanFactory; 30 import org.springframework.beans.factory.BeanFactoryUtils; 31 import org.springframework.beans.factory.ListableBeanFactory; 32 import org.springframework.beans.factory.config.ConfigurableBeanFactory; 33 import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; 34 import org.springframework.beans.factory.support.DefaultListableBeanFactory; 35 import org.springframework.beans.factory.xml.XmlBeanDefinitionReader; 36 import org.springframework.core.io.Resource; 37 import org.springframework.core.io.support.PathMatchingResourcePatternResolver; 38 import org.springframework.core.io.support.ResourcePatternResolver; 39 import org.springframework.core.io.support.ResourcePatternUtils; 40 41 /** 42 * <p>Keyed-singleton implementation of {@link BeanFactoryLocator}, 43 * which accesses shared Spring {@link BeanFactory} instances.</p> 44 * 45 * <p>Please see the warning in BeanFactoryLocator's javadoc about appropriate usage 46 * of singleton style BeanFactoryLocator implementations. It is the opinion of the 47 * Spring team that the use of this class and similar classes is unnecessary except 48 * (sometimes) for a small amount of glue code. Excessive usage will lead to code 49 * that is more tightly coupled, and harder to modify or test.</p> 50 * 51 * <p>In this implementation, a BeanFactory is built up from one or more XML 52 * definition file fragments, accessed as resources. The default resource name 53 * searched for is 'classpath*:beanRefFactory.xml', with the Spring-standard 54 * 'classpath*:' prefix ensuring that if the classpath contains multiple copies 55 * of this file (perhaps one in each component jar) they will be combined. To 56 * override the default resource name, instead of using the no-arg 57 * {@link #getInstance()} method, use the {@link #getInstance(String selector)} 58 * variant, which will treat the 'selector' argument as the resource name to 59 * search for.</p> 60 * 61 * <p>The purpose of this 'outer' BeanFactory is to create and hold a copy of one 62 * or more 'inner' BeanFactory or ApplicationContext instances, and allow those 63 * to be obtained either directly or via an alias. As such, this class provides 64 * both singleton style access to one or more BeanFactories/ApplicationContexts, 65 * and also a level of indirection, allowing multiple pieces of code, which are 66 * not able to work in a Dependency Injection fashion, to refer to and use the 67 * same target BeanFactory/ApplicationContext instance(s), by different names.<p> 68 * 69 * <p>Consider an example application scenario: 70 * 71 * <ul> 72 * <li><code>com.mycompany.myapp.util.applicationContext.xml</code> - 73 * ApplicationContext definition file which defines beans for 'util' layer. 74 * <li><code>com.mycompany.myapp.dataaccess-applicationContext.xml</code> - 75 * ApplicationContext definition file which defines beans for 'data access' layer. 76 * Depends on the above. 77 * <li><code>com.mycompany.myapp.services.applicationContext.xml</code> - 78 * ApplicationContext definition file which defines beans for 'services' layer. 79 * Depends on the above. 80 * </ul> 81 * 82 * <p>In an ideal scenario, these would be combined to create one ApplicationContext, 83 * or created as three hierarchical ApplicationContexts, by one piece of code 84 * somewhere at application startup (perhaps a Servlet filter), from which all other 85 * code in the application would flow, obtained as beans from the context(s). However 86 * when third party code enters into the picture, things can get problematic. If the 87 * third party code needs to create user classes, which should normally be obtained 88 * from a Spring BeanFactory/ApplicationContext, but can handle only newInstance() 89 * style object creation, then some extra work is required to actually access and 90 * use object from a BeanFactory/ApplicationContext. One solutions is to make the 91 * class created by the third party code be just a stub or proxy, which gets the 92 * real object from a BeanFactory/ApplicationContext, and delegates to it. However, 93 * it is is not normally workable for the stub to create the BeanFactory on each 94 * use, as depending on what is inside it, that can be an expensive operation. 95 * Additionally, there is a fairly tight coupling between the stub and the name of 96 * the definition resource for the BeanFactory/ApplicationContext. This is where 97 * SingletonBeanFactoryLocator comes in. The stub can obtain a 98 * SingletonBeanFactoryLocator instance, which is effectively a singleton, and 99 * ask it for an appropriate BeanFactory. A subsequent invocation (assuming the 100 * same class loader is involved) by the stub or another piece of code, will obtain 101 * the same instance. The simple aliasing mechanism allows the context to be asked 102 * for by a name which is appropriate for (or describes) the user. The deployer can 103 * match alias names to actual context names. 104 * 105 * <p>Another use of SingletonBeanFactoryLocator, is to demand-load/use one or more 106 * BeanFactories/ApplicationContexts. Because the definition can contain one of more 107 * BeanFactories/ApplicationContexts, which can be independent or in a hierarchy, if 108 * they are set to lazy-initialize, they will only be created when actually requested 109 * for use. 110 * 111 * <p>Given the above-mentioned three ApplicationContexts, consider the simplest 112 * SingletonBeanFactoryLocator usage scenario, where there is only one single 113 * <code>beanRefFactory.xml</code> definition file: 114 * 115 * <pre class="code"><?xml version="1.0" encoding="UTF-8"?> 116 * <!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN 2.0//EN" "http://www.springframework.org/dtd/spring-beans-2.0.dtd"> 117 * 118 * <beans> 119 * 120 * <bean id="com.mycompany.myapp" 121 * class="org.springframework.context.support.ClassPathXmlApplicationContext"> 122 * <constructor-arg> 123 * <list> 124 * <value>com/mycompany/myapp/util/applicationContext.xml</value> 125 * <value>com/mycompany/myapp/dataaccess/applicationContext.xml</value> 126 * <value>com/mycompany/myapp/dataaccess/services.xml</value> 127 * </list> 128 * </constructor-arg> 129 * </bean> 130 * 131 * </beans> 132 * </pre> 133 * 134 * The client code is as simple as: 135 * 136 * <pre class="code"> 137 * BeanFactoryLocator bfl = SingletonBeanFactoryLocator.getInstance(); 138 * BeanFactoryReference bf = bfl.useBeanFactory("com.mycompany.myapp"); 139 * // now use some bean from factory 140 * MyClass zed = bf.getFactory().getBean("mybean"); 141 * </pre> 142 * 143 * Another relatively simple variation of the <code>beanRefFactory.xml</code> definition file could be: 144 * 145 * <pre class="code"><?xml version="1.0" encoding="UTF-8"?> 146 * <!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN 2.0//EN" "http://www.springframework.org/dtd/spring-beans-2.0.dtd"> 147 * 148 * <beans> 149 * 150 * <bean id="com.mycompany.myapp.util" lazy-init="true" 151 * class="org.springframework.context.support.ClassPathXmlApplicationContext"> 152 * <constructor-arg> 153 * <value>com/mycompany/myapp/util/applicationContext.xml</value> 154 * </constructor-arg> 155 * </bean> 156 * 157 * <!-- child of above --> 158 * <bean id="com.mycompany.myapp.dataaccess" lazy-init="true" 159 * class="org.springframework.context.support.ClassPathXmlApplicationContext"> 160 * <constructor-arg> 161 * <list><value>com/mycompany/myapp/dataaccess/applicationContext.xml</value></list> 162 * </constructor-arg> 163 * <constructor-arg> 164 * <ref bean="com.mycompany.myapp.util"/> 165 * </constructor-arg> 166 * </bean> 167 * 168 * <!-- child of above --> 169 * <bean id="com.mycompany.myapp.services" lazy-init="true" 170 * class="org.springframework.context.support.ClassPathXmlApplicationContext"> 171 * <constructor-arg> 172 * <list><value>com/mycompany/myapp/dataaccess.services.xml</value></value> 173 * </constructor-arg> 174 * <constructor-arg> 175 * <ref bean="com.mycompany.myapp.dataaccess"/> 176 * </constructor-arg> 177 * </bean> 178 * 179 * <!-- define an alias --> 180 * <bean id="com.mycompany.myapp.mypackage" 181 * class="java.lang.String"> 182 * <constructor-arg> 183 * <value>com.mycompany.myapp.services</value> 184 * </constructor-arg> 185 * </bean> 186 * 187 * </beans> 188 * </pre> 189 * 190 * <p>In this example, there is a hierarchy of three contexts created. The (potential) 191 * advantage is that if the lazy flag is set to true, a context will only be created 192 * if it's actually used. If there is some code that is only needed some of the time, 193 * this mechanism can save some resources. Additionally, an alias to the last context 194 * has been created. Aliases allow usage of the idiom where client code asks for a 195 * context with an id which represents the package or module the code is in, and the 196 * actual definition file(s) for the SingletonBeanFactoryLocator maps that id to 197 * a real context id. 198 * 199 * <p>A final example is more complex, with a <code>beanRefFactory.xml</code> for every module. 200 * All the files are automatically combined to create the final definition. 201 * 202 * <p><code>beanRefFactory.xml</code> file inside jar for util module: 203 * 204 * <pre class="code"><?xml version="1.0" encoding="UTF-8"?> 205 * <!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN 2.0//EN" "http://www.springframework.org/dtd/spring-beans-2.0.dtd"> 206 * 207 * <beans> 208 * <bean id="com.mycompany.myapp.util" lazy-init="true" 209 * class="org.springframework.context.support.ClassPathXmlApplicationContext"> 210 * <constructor-arg> 211 * <value>com/mycompany/myapp/util/applicationContext.xml</value> 212 * </constructor-arg> 213 * </bean> 214 * </beans> 215 * </pre> 216 * 217 * <code>beanRefFactory.xml</code> file inside jar for data-access module:<br> 218 * 219 * <pre class="code"><?xml version="1.0" encoding="UTF-8"?> 220 * <!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN 2.0//EN" "http://www.springframework.org/dtd/spring-beans-2.0.dtd"> 221 * 222 * <beans> 223 * <!-- child of util --> 224 * <bean id="com.mycompany.myapp.dataaccess" lazy-init="true" 225 * class="org.springframework.context.support.ClassPathXmlApplicationContext"> 226 * <constructor-arg> 227 * <list><value>com/mycompany/myapp/dataaccess/applicationContext.xml</value></list> 228 * </constructor-arg> 229 * <constructor-arg> 230 * <ref bean="com.mycompany.myapp.util"/> 231 * </constructor-arg> 232 * </bean> 233 * </beans> 234 * </pre> 235 * 236 * <code>beanRefFactory.xml</code> file inside jar for services module: 237 * 238 * <pre class="code"><?xml version="1.0" encoding="UTF-8"?> 239 * <!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN 2.0//EN" "http://www.springframework.org/dtd/spring-beans-2.0.dtd"> 240 * 241 * <beans> 242 * <!-- child of data-access --> 243 * <bean id="com.mycompany.myapp.services" lazy-init="true" 244 * class="org.springframework.context.support.ClassPathXmlApplicationContext"> 245 * <constructor-arg> 246 * <list><value>com/mycompany/myapp/dataaccess/services.xml</value></list> 247 * </constructor-arg> 248 * <constructor-arg> 249 * <ref bean="com.mycompany.myapp.dataaccess"/> 250 * </constructor-arg> 251 * </bean> 252 * </beans> 253 * </pre> 254 * 255 * <code>beanRefFactory.xml</code> file inside jar for mypackage module. This doesn't 256 * create any of its own contexts, but allows the other ones to be referred to be 257 * a name known to this module: 258 * 259 * <pre class="code"><?xml version="1.0" encoding="UTF-8"?> 260 * <!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN 2.0//EN" "http://www.springframework.org/dtd/spring-beans-2.0.dtd"> 261 * 262 * <beans> 263 * <!-- define an alias for "com.mycompany.myapp.services" --> 264 * <alias name="com.mycompany.myapp.services" alias="com.mycompany.myapp.mypackage"/> 265 * </beans> 266 * </pre> 267 * 268 * @author Colin Sampaleanu 269 * @author Juergen Hoeller 270 * @see org.springframework.context.access.ContextSingletonBeanFactoryLocator 271 * @see org.springframework.context.access.DefaultLocatorFactory 272 */ 273 public class SingletonBeanFactoryLocator implements BeanFactoryLocator { 274 275 private static final String DEFAULT_RESOURCE_LOCATION = "classpath*:beanRefFactory.xml"; 276 277 protected static final Log logger = LogFactory.getLog(SingletonBeanFactoryLocator.class); 278 279 /** The keyed BeanFactory instances */ 280 private static Map instances = new HashMap(); 281 282 283 /** 284 * Returns an instance which uses the default "classpath*:beanRefFactory.xml", 285 * as the name of the definition file(s). All resources returned by calling the 286 * current thread context ClassLoader's <code>getResources</code> method with 287 * this name will be combined to create a BeanFactory definition set. 288 * @return the corresponding BeanFactoryLocator instance 289 * @throws BeansException in case of factory loading failure 290 */ 291 public static BeanFactoryLocator getInstance() throws BeansException { 292 return getInstance(null); 293 } 294 295 /** 296 * Returns an instance which uses the the specified selector, as the name of the 297 * definition file(s). In the case of a name with a Spring 'classpath*:' prefix, 298 * or with no prefix, which is treated the same, the current thread context 299 * ClassLoader's <code>getResources</code> method will be called with this value 300 * to get all resources having that name. These resources will then be combined to 301 * form a definition. In the case where the name uses a Spring 'classpath:' prefix, 302 * or a standard URL prefix, then only one resource file will be loaded as the 303 * definition. 304 * @param selector the name of the resource(s) which will be read and 305 * combined to form the definition for the BeanFactoryLocator instance. 306 * Any such files must form a valid BeanFactory definition. 307 * @return the corresponding BeanFactoryLocator instance 308 * @throws BeansException in case of factory loading failure 309 */ 310 public static BeanFactoryLocator getInstance(String selector) 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 + resourceLocation; 320 } 321 322 synchronized (instances) { 323 if (logger.isTraceEnabled()) { 324 logger.trace("SingletonBeanFactoryLocator.getInstance(): instances.hashCode=" + 325 instances.hashCode() + ", instances=" + instances); 326 } 327 BeanFactoryLocator bfl = (BeanFactoryLocator) instances.get(resourceLocation); 328 if (bfl == null) { 329 bfl = new SingletonBeanFactoryLocator(resourceLocation); 330 instances.put(resourceLocation, bfl); 331 } 332 return bfl; 333 } 334 } 335 336 337 // We map BeanFactoryGroup objects by String keys, and by the definition object. 338 private final Map bfgInstancesByKey = new HashMap(); 339 340 private final Map bfgInstancesByObj = new HashMap(); 341 342 private final String resourceLocation; 343 344 345 /** 346 * Constructor which uses the the specified name as the resource name 347 * of the definition file(s). 348 * @param resourceLocation the Spring resource location to use 349 * (either a URL or a "classpath:" / "classpath*:" pseudo URL) 350 */ 351 protected SingletonBeanFactoryLocator(String resourceLocation) { 352 this.resourceLocation = resourceLocation; 353 } 354 355 public BeanFactoryReference useBeanFactory(String factoryKey) throws BeansException { 356 synchronized (this.bfgInstancesByKey) { 357 BeanFactoryGroup bfg = (BeanFactoryGroup) this.bfgInstancesByKey.get(this.resourceLocation); 358 359 if (bfg != null) { 360 bfg.refCount++; 361 } 362 else { 363 // This group definition doesn't exist, we need to try to load it. 364 if (logger.isTraceEnabled()) { 365 logger.trace("Factory group with resource name [" + this.resourceLocation + 366 "] requested. Creating new instance."); 367 } 368 369 // Create the BeanFactory but don't initialize it. 370 BeanFactory groupContext = createDefinition(this.resourceLocation, factoryKey); 371 372 // Record its existence now, before instantiating any singletons. 373 bfg = new BeanFactoryGroup(); 374 bfg.definition = groupContext; 375 bfg.refCount = 1; 376 this.bfgInstancesByKey.put(this.resourceLocation, bfg); 377 this.bfgInstancesByObj.put(groupContext, bfg); 378 379 // Now initialize the BeanFactory. This may cause a re-entrant invocation 380 // of this method, but since we've already added the BeanFactory to our 381 // mappings, the next time it will be found and simply have its 382 // reference count incremented. 383 try { 384 initializeDefinition(groupContext); 385 } 386 catch (BeansException ex) { 387 this.bfgInstancesByKey.remove(this.resourceLocation); 388 this.bfgInstancesByObj.remove(groupContext); 389 throw new BootstrapException("Unable to initialize group definition. " + 390 "Group resource name [" + this.resourceLocation + "], factory key [" + factoryKey + "]", ex); 391 } 392 } 393 394 try { 395 BeanFactory beanFactory = null; 396 if (factoryKey != null) { 397 beanFactory = (BeanFactory) bfg.definition.getBean(factoryKey, BeanFactory.class); 398 } 399 else if (bfg.definition instanceof ListableBeanFactory) { 400 beanFactory = (BeanFactory) 401 BeanFactoryUtils.beanOfType((ListableBeanFactory) bfg.definition, BeanFactory.class); 402 } 403 else { 404 throw new IllegalStateException( 405 "Factory key is null, and underlying factory is not a ListableBeanFactory: " + bfg.definition); 406 } 407 return new CountingBeanFactoryReference(beanFactory, bfg.definition); 408 } 409 catch (BeansException ex) { 410 throw new BootstrapException("Unable to return specified BeanFactory instance: factory key [" + 411 factoryKey + "], from group with resource name [" + this.resourceLocation + "]", ex); 412 } 413 414 } 415 } 416 417 /** 418 * Actually creates definition in the form of a BeanFactory, given a resource name 419 * which supports standard Spring resource prefixes ('classpath:', 'classpath*:', etc.) 420 * This is split out as a separate method so that subclasses can override the actual 421 * type used (to be an ApplicationContext, for example). 422 * <p>The default implementation simply builds a 423 * {@link org.springframework.beans.factory.support.DefaultListableBeanFactory} 424 * and populates it using an 425 * {@link org.springframework.beans.factory.xml.XmlBeanDefinitionReader}. 426 * <p>This method should not instantiate any singletons. That function is performed 427 * by {@link #initializeDefinition initializeDefinition()}, which should also be 428 * overridden if this method is. 429 * @param resourceLocation the resource location for this factory group 430 * @param factoryKey the bean name of the factory to obtain 431 * @return the corresponding BeanFactory reference 432 */ 433 protected BeanFactory createDefinition(String resourceLocation, String factoryKey) { 434 DefaultListableBeanFactory factory = new DefaultListableBeanFactory(); 435 XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(factory); 436 ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver(); 437 438 try { 439 Resource[] configResources = resourcePatternResolver.getResources(resourceLocation); 440 if (configResources.length == 0) { 441 throw new FatalBeanException("Unable to find resource for specified definition. " + 442 "Group resource name [" + this.resourceLocation + "], factory key [" + factoryKey + "]"); 443 } 444 reader.loadBeanDefinitions(configResources); 445 } 446 catch (IOException ex) { 447 throw new BeanDefinitionStoreException( 448 "Error accessing bean definition resource [" + this.resourceLocation + "]", ex); 449 } 450 catch (BeanDefinitionStoreException ex) { 451 throw new FatalBeanException("Unable to load group definition: " + 452 "group resource name [" + this.resourceLocation + "], factory key [" + factoryKey + "]", ex); 453 } 454 455 return factory; 456 } 457 458 /** 459 * Instantiate singletons and do any other normal initialization of the factory. 460 * Subclasses that override {@link #createDefinition createDefinition()} should 461 * also override this method. 462 * @param groupDef the factory returned by {@link #createDefinition createDefinition()} 463 */ 464 protected void initializeDefinition(BeanFactory groupDef) { 465 if (groupDef instanceof ConfigurableListableBeanFactory) { 466 ((ConfigurableListableBeanFactory) groupDef).preInstantiateSingletons(); 467 } 468 } 469 470 /** 471 * Destroy definition in separate method so subclass may work with other definition types. 472 * @param groupDef the factory returned by {@link #createDefinition createDefinition()} 473 * @param selector the resource location for this factory group 474 */ 475 protected void destroyDefinition(BeanFactory groupDef, String selector) { 476 if (groupDef instanceof ConfigurableBeanFactory) { 477 if (logger.isTraceEnabled()) { 478 logger.trace("Factory group with selector '" + selector + 479 "' being released, as there are no more references to it"); 480 } 481 ((ConfigurableBeanFactory) groupDef).destroySingletons(); 482 } 483 } 484 485 486 /** 487 * We track BeanFactory instances with this class. 488 */ 489 private static class BeanFactoryGroup { 490 491 private BeanFactory definition; 492 493 private int refCount = 0; 494 } 495 496 497 /** 498 * BeanFactoryReference implementation for this locator. 499 */ 500 private class CountingBeanFactoryReference implements BeanFactoryReference { 501 502 private BeanFactory beanFactory; 503 504 private BeanFactory groupContextRef; 505 506 public CountingBeanFactoryReference(BeanFactory beanFactory, BeanFactory groupContext) { 507 this.beanFactory = beanFactory; 508 this.groupContextRef = groupContext; 509 } 510 511 public BeanFactory getFactory() { 512 return this.beanFactory; 513 } 514 515 // Note that it's legal to call release more than once! 516 public void release() throws FatalBeanException { 517 synchronized (bfgInstancesByKey) { 518 BeanFactory savedRef = this.groupContextRef; 519 if (savedRef != null) { 520 this.groupContextRef = null; 521 BeanFactoryGroup bfg = (BeanFactoryGroup) bfgInstancesByObj.get(savedRef); 522 if (bfg != null) { 523 bfg.refCount--; 524 if (bfg.refCount == 0) { 525 destroyDefinition(savedRef, resourceLocation); 526 bfgInstancesByKey.remove(resourceLocation); 527 bfgInstancesByObj.remove(savedRef); 528 } 529 } 530 else { 531 // This should be impossible. 532 logger.warn("Tried to release a SingletonBeanFactoryLocator group definition " + 533 "more times than it has actually been used. Resource name [" + resourceLocation + "]"); 534 } 535 } 536 } 537 } 538 } 539 540 }