001/* 002 * Copyright 2002-2012 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 * https://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 017package org.springframework.beans.factory.access; 018 019import java.io.IOException; 020import java.util.HashMap; 021import java.util.Map; 022 023import org.apache.commons.logging.Log; 024import org.apache.commons.logging.LogFactory; 025 026import org.springframework.beans.BeansException; 027import org.springframework.beans.FatalBeanException; 028import org.springframework.beans.factory.BeanDefinitionStoreException; 029import org.springframework.beans.factory.BeanFactory; 030import org.springframework.beans.factory.config.ConfigurableBeanFactory; 031import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; 032import org.springframework.beans.factory.support.DefaultListableBeanFactory; 033import org.springframework.beans.factory.xml.XmlBeanDefinitionReader; 034import org.springframework.core.io.Resource; 035import org.springframework.core.io.support.PathMatchingResourcePatternResolver; 036import org.springframework.core.io.support.ResourcePatternResolver; 037import org.springframework.core.io.support.ResourcePatternUtils; 038 039/** 040 * <p>Keyed-singleton implementation of {@link BeanFactoryLocator}, 041 * which accesses shared Spring {@link BeanFactory} 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} - 071 * ApplicationContext definition file which defines beans for 'util' layer. 072 * <li>{@code com.mycompany.myapp.dataaccess-applicationContext.xml} - 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} - 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 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 definition 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} definition file: 112 * 113 * <pre class="code"><?xml version="1.0" encoding="UTF-8"?> 114 * <!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN 2.0//EN" "https://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} 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" "https://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} for every module. 198 * All the files are automatically combined to create the final definition. 199 * 200 * <p>{@code beanRefFactory.xml} 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" "https://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} 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" "https://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} 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" "https://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} 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" "https://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 */ 271public class SingletonBeanFactoryLocator implements BeanFactoryLocator { 272 273 private static final String DEFAULT_RESOURCE_LOCATION = "classpath*:beanRefFactory.xml"; 274 275 protected static final Log logger = LogFactory.getLog(SingletonBeanFactoryLocator.class); 276 277 /** The keyed BeanFactory instances */ 278 private static final Map<String, BeanFactoryLocator> instances = new HashMap<String, BeanFactoryLocator>(); 279 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 context ClassLoader's {@code getResources} method with 285 * this name will be combined to create a BeanFactory definition set. 286 * @return the corresponding BeanFactoryLocator instance 287 * @throws BeansException in case of factory loading failure 288 */ 289 public static BeanFactoryLocator getInstance() throws BeansException { 290 return getInstance(null); 291 } 292 293 /** 294 * Returns an instance which uses the specified selector, as the name of the 295 * definition file(s). In the case of a name with a Spring 'classpath*:' prefix, 296 * or with no prefix, which is treated the same, the current thread context 297 * ClassLoader's {@code getResources} method will be called with this value 298 * to get all resources having that name. These resources will then be combined to 299 * form a definition. In the case where the name uses a Spring 'classpath:' prefix, 300 * or a standard URL prefix, then only one resource file will be loaded as the 301 * definition. 302 * @param selector the name of the resource(s) which will be read and 303 * combined to form the definition for the BeanFactoryLocator instance. 304 * Any such files must form a valid BeanFactory definition. 305 * @return the corresponding BeanFactoryLocator instance 306 * @throws BeansException in case of factory loading failure 307 */ 308 public static BeanFactoryLocator getInstance(String selector) throws BeansException { 309 String resourceLocation = selector; 310 if (resourceLocation == null) { 311 resourceLocation = DEFAULT_RESOURCE_LOCATION; 312 } 313 314 // For backwards compatibility, we prepend 'classpath*:' to the selector name if there 315 // is no other prefix (i.e. classpath*:, classpath:, or some URL prefix. 316 if (!ResourcePatternUtils.isUrl(resourceLocation)) { 317 resourceLocation = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX + resourceLocation; 318 } 319 320 synchronized (instances) { 321 if (logger.isTraceEnabled()) { 322 logger.trace("SingletonBeanFactoryLocator.getInstance(): instances.hashCode=" + 323 instances.hashCode() + ", instances=" + instances); 324 } 325 BeanFactoryLocator bfl = instances.get(resourceLocation); 326 if (bfl == null) { 327 bfl = new SingletonBeanFactoryLocator(resourceLocation); 328 instances.put(resourceLocation, bfl); 329 } 330 return bfl; 331 } 332 } 333 334 335 // We map BeanFactoryGroup objects by String keys, and by the definition object. 336 private final Map<String, BeanFactoryGroup> bfgInstancesByKey = new HashMap<String, BeanFactoryGroup>(); 337 338 private final Map<BeanFactory, BeanFactoryGroup> bfgInstancesByObj = new HashMap<BeanFactory, BeanFactoryGroup>(); 339 340 private final String resourceLocation; 341 342 343 /** 344 * Constructor which uses the specified name as the resource name 345 * of the definition file(s). 346 * @param resourceLocation the Spring resource location to use 347 * (either a URL or a "classpath:" / "classpath*:" pseudo URL) 348 */ 349 protected SingletonBeanFactoryLocator(String resourceLocation) { 350 this.resourceLocation = resourceLocation; 351 } 352 353 @Override 354 public BeanFactoryReference useBeanFactory(String factoryKey) throws BeansException { 355 synchronized (this.bfgInstancesByKey) { 356 BeanFactoryGroup bfg = this.bfgInstancesByKey.get(this.resourceLocation); 357 358 if (bfg != null) { 359 bfg.refCount++; 360 } 361 else { 362 // This group definition doesn't exist, we need to try to load it. 363 if (logger.isTraceEnabled()) { 364 logger.trace("Factory group with resource name [" + this.resourceLocation + 365 "] requested. Creating new instance."); 366 } 367 368 // Create the BeanFactory but don't initialize it. 369 BeanFactory groupContext = createDefinition(this.resourceLocation, factoryKey); 370 371 // Record its existence now, before instantiating any singletons. 372 bfg = new BeanFactoryGroup(); 373 bfg.definition = groupContext; 374 bfg.refCount = 1; 375 this.bfgInstancesByKey.put(this.resourceLocation, bfg); 376 this.bfgInstancesByObj.put(groupContext, bfg); 377 378 // Now initialize the BeanFactory. This may cause a re-entrant invocation 379 // of this method, but since we've already added the BeanFactory to our 380 // mappings, the next time it will be found and simply have its 381 // reference count incremented. 382 try { 383 initializeDefinition(groupContext); 384 } 385 catch (BeansException ex) { 386 this.bfgInstancesByKey.remove(this.resourceLocation); 387 this.bfgInstancesByObj.remove(groupContext); 388 throw new BootstrapException("Unable to initialize group definition. " + 389 "Group resource name [" + this.resourceLocation + "], factory key [" + factoryKey + "]", ex); 390 } 391 } 392 393 try { 394 BeanFactory beanFactory; 395 if (factoryKey != null) { 396 beanFactory = bfg.definition.getBean(factoryKey, BeanFactory.class); 397 } 398 else { 399 beanFactory = bfg.definition.getBean(BeanFactory.class); 400 } 401 return new CountingBeanFactoryReference(beanFactory, bfg.definition); 402 } 403 catch (BeansException ex) { 404 throw new BootstrapException("Unable to return specified BeanFactory instance: factory key [" + 405 factoryKey + "], from group with resource name [" + this.resourceLocation + "]", ex); 406 } 407 408 } 409 } 410 411 /** 412 * Actually creates definition in the form of a BeanFactory, given a resource name 413 * which supports standard Spring resource prefixes ('classpath:', 'classpath*:', etc.) 414 * This is split out as a separate method so that subclasses can override the actual 415 * type used (to be an ApplicationContext, for example). 416 * <p>The default implementation simply builds a 417 * {@link org.springframework.beans.factory.support.DefaultListableBeanFactory} 418 * and populates it using an 419 * {@link org.springframework.beans.factory.xml.XmlBeanDefinitionReader}. 420 * <p>This method should not instantiate any singletons. That function is performed 421 * by {@link #initializeDefinition initializeDefinition()}, which should also be 422 * overridden if this method is. 423 * @param resourceLocation the resource location for this factory group 424 * @param factoryKey the bean name of the factory to obtain 425 * @return the corresponding BeanFactory reference 426 */ 427 protected BeanFactory createDefinition(String resourceLocation, String factoryKey) { 428 DefaultListableBeanFactory factory = new DefaultListableBeanFactory(); 429 XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(factory); 430 ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver(); 431 432 try { 433 Resource[] configResources = resourcePatternResolver.getResources(resourceLocation); 434 if (configResources.length == 0) { 435 throw new FatalBeanException("Unable to find resource for specified definition. " + 436 "Group resource name [" + this.resourceLocation + "], factory key [" + factoryKey + "]"); 437 } 438 reader.loadBeanDefinitions(configResources); 439 } 440 catch (IOException ex) { 441 throw new BeanDefinitionStoreException( 442 "Error accessing bean definition resource [" + this.resourceLocation + "]", ex); 443 } 444 catch (BeanDefinitionStoreException ex) { 445 throw new FatalBeanException("Unable to load group definition: " + 446 "group resource name [" + this.resourceLocation + "], factory key [" + factoryKey + "]", ex); 447 } 448 449 return factory; 450 } 451 452 /** 453 * Instantiate singletons and do any other normal initialization of the factory. 454 * Subclasses that override {@link #createDefinition createDefinition()} should 455 * also override this method. 456 * @param groupDef the factory returned by {@link #createDefinition createDefinition()} 457 */ 458 protected void initializeDefinition(BeanFactory groupDef) { 459 if (groupDef instanceof ConfigurableListableBeanFactory) { 460 ((ConfigurableListableBeanFactory) groupDef).preInstantiateSingletons(); 461 } 462 } 463 464 /** 465 * Destroy definition in separate method so subclass may work with other definition types. 466 * @param groupDef the factory returned by {@link #createDefinition createDefinition()} 467 * @param selector the resource location for this factory group 468 */ 469 protected void destroyDefinition(BeanFactory groupDef, String selector) { 470 if (groupDef instanceof ConfigurableBeanFactory) { 471 if (logger.isTraceEnabled()) { 472 logger.trace("Factory group with selector '" + selector + 473 "' being released, as there are no more references to it"); 474 } 475 ((ConfigurableBeanFactory) groupDef).destroySingletons(); 476 } 477 } 478 479 480 /** 481 * We track BeanFactory instances with this class. 482 */ 483 private static class BeanFactoryGroup { 484 485 private BeanFactory definition; 486 487 private int refCount = 0; 488 } 489 490 491 /** 492 * BeanFactoryReference implementation for this locator. 493 */ 494 private class CountingBeanFactoryReference implements BeanFactoryReference { 495 496 private BeanFactory beanFactory; 497 498 private BeanFactory groupContextRef; 499 500 public CountingBeanFactoryReference(BeanFactory beanFactory, BeanFactory groupContext) { 501 this.beanFactory = beanFactory; 502 this.groupContextRef = groupContext; 503 } 504 505 @Override 506 public BeanFactory getFactory() { 507 return this.beanFactory; 508 } 509 510 // Note that it's legal to call release more than once! 511 @Override 512 public void release() throws FatalBeanException { 513 synchronized (bfgInstancesByKey) { 514 BeanFactory savedRef = this.groupContextRef; 515 if (savedRef != null) { 516 this.groupContextRef = null; 517 BeanFactoryGroup bfg = bfgInstancesByObj.get(savedRef); 518 if (bfg != null) { 519 bfg.refCount--; 520 if (bfg.refCount == 0) { 521 destroyDefinition(savedRef, resourceLocation); 522 bfgInstancesByKey.remove(resourceLocation); 523 bfgInstancesByObj.remove(savedRef); 524 } 525 } 526 else { 527 // This should be impossible. 528 logger.warn("Tried to release a SingletonBeanFactoryLocator group definition " + 529 "more times than it has actually been used. Resource name [" + resourceLocation + "]"); 530 } 531 } 532 } 533 } 534 } 535 536}