001/* 002 * Copyright 2002-2018 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.support; 018 019import java.io.IOException; 020import java.io.InputStream; 021import java.io.InputStreamReader; 022import java.util.Enumeration; 023import java.util.HashMap; 024import java.util.Map; 025import java.util.Properties; 026import java.util.ResourceBundle; 027 028import org.springframework.beans.BeansException; 029import org.springframework.beans.MutablePropertyValues; 030import org.springframework.beans.PropertyAccessor; 031import org.springframework.beans.factory.BeanDefinitionStoreException; 032import org.springframework.beans.factory.CannotLoadBeanClassException; 033import org.springframework.beans.factory.config.ConstructorArgumentValues; 034import org.springframework.beans.factory.config.RuntimeBeanReference; 035import org.springframework.core.io.Resource; 036import org.springframework.core.io.support.EncodedResource; 037import org.springframework.util.DefaultPropertiesPersister; 038import org.springframework.util.PropertiesPersister; 039import org.springframework.util.StringUtils; 040 041/** 042 * Bean definition reader for a simple properties format. 043 * 044 * <p>Provides bean definition registration methods for Map/Properties and 045 * ResourceBundle. Typically applied to a DefaultListableBeanFactory. 046 * 047 * <p><b>Example:</b> 048 * 049 * <pre class="code"> 050 * employee.(class)=MyClass // bean is of class MyClass 051 * employee.(abstract)=true // this bean can't be instantiated directly 052 * employee.group=Insurance // real property 053 * employee.usesDialUp=false // real property (potentially overridden) 054 * 055 * salesrep.(parent)=employee // derives from "employee" bean definition 056 * salesrep.(lazy-init)=true // lazily initialize this singleton bean 057 * salesrep.manager(ref)=tony // reference to another bean 058 * salesrep.department=Sales // real property 059 * 060 * techie.(parent)=employee // derives from "employee" bean definition 061 * techie.(scope)=prototype // bean is a prototype (not a shared instance) 062 * techie.manager(ref)=jeff // reference to another bean 063 * techie.department=Engineering // real property 064 * techie.usesDialUp=true // real property (overriding parent value) 065 * 066 * ceo.$0(ref)=secretary // inject 'secretary' bean as 0th constructor arg 067 * ceo.$1=1000000 // inject value '1000000' at 1st constructor arg 068 * </pre> 069 * 070 * @author Rod Johnson 071 * @author Juergen Hoeller 072 * @author Rob Harrop 073 * @since 26.11.2003 074 * @see DefaultListableBeanFactory 075 */ 076public class PropertiesBeanDefinitionReader extends AbstractBeanDefinitionReader { 077 078 /** 079 * Value of a T/F attribute that represents true. 080 * Anything else represents false. Case seNsItive. 081 */ 082 public static final String TRUE_VALUE = "true"; 083 084 /** 085 * Separator between bean name and property name. 086 * We follow normal Java conventions. 087 */ 088 public static final String SEPARATOR = "."; 089 090 /** 091 * Special key to distinguish {@code owner.(class)=com.myapp.MyClass}. 092 */ 093 public static final String CLASS_KEY = "(class)"; 094 095 /** 096 * Special key to distinguish {@code owner.(parent)=parentBeanName}. 097 */ 098 public static final String PARENT_KEY = "(parent)"; 099 100 /** 101 * Special key to distinguish {@code owner.(scope)=prototype}. 102 * Default is "true". 103 */ 104 public static final String SCOPE_KEY = "(scope)"; 105 106 /** 107 * Special key to distinguish {@code owner.(singleton)=false}. 108 * Default is "true". 109 */ 110 public static final String SINGLETON_KEY = "(singleton)"; 111 112 /** 113 * Special key to distinguish {@code owner.(abstract)=true} 114 * Default is "false". 115 */ 116 public static final String ABSTRACT_KEY = "(abstract)"; 117 118 /** 119 * Special key to distinguish {@code owner.(lazy-init)=true} 120 * Default is "false". 121 */ 122 public static final String LAZY_INIT_KEY = "(lazy-init)"; 123 124 /** 125 * Property suffix for references to other beans in the current 126 * BeanFactory: e.g. {@code owner.dog(ref)=fido}. 127 * Whether this is a reference to a singleton or a prototype 128 * will depend on the definition of the target bean. 129 */ 130 public static final String REF_SUFFIX = "(ref)"; 131 132 /** 133 * Prefix before values referencing other beans. 134 */ 135 public static final String REF_PREFIX = "*"; 136 137 /** 138 * Prefix used to denote a constructor argument definition. 139 */ 140 public static final String CONSTRUCTOR_ARG_PREFIX = "$"; 141 142 143 private String defaultParentBean; 144 145 private PropertiesPersister propertiesPersister = new DefaultPropertiesPersister(); 146 147 148 /** 149 * Create new PropertiesBeanDefinitionReader for the given bean factory. 150 * @param registry the BeanFactory to load bean definitions into, 151 * in the form of a BeanDefinitionRegistry 152 */ 153 public PropertiesBeanDefinitionReader(BeanDefinitionRegistry registry) { 154 super(registry); 155 } 156 157 158 /** 159 * Set the default parent bean for this bean factory. 160 * If a child bean definition handled by this factory provides neither 161 * a parent nor a class attribute, this default value gets used. 162 * <p>Can be used e.g. for view definition files, to define a parent 163 * with a default view class and common attributes for all views. 164 * View definitions that define their own parent or carry their own 165 * class can still override this. 166 * <p>Strictly speaking, the rule that a default parent setting does 167 * not apply to a bean definition that carries a class is there for 168 * backwards compatibility reasons. It still matches the typical use case. 169 */ 170 public void setDefaultParentBean(String defaultParentBean) { 171 this.defaultParentBean = defaultParentBean; 172 } 173 174 /** 175 * Return the default parent bean for this bean factory. 176 */ 177 public String getDefaultParentBean() { 178 return this.defaultParentBean; 179 } 180 181 /** 182 * Set the PropertiesPersister to use for parsing properties files. 183 * The default is DefaultPropertiesPersister. 184 * @see org.springframework.util.DefaultPropertiesPersister 185 */ 186 public void setPropertiesPersister(PropertiesPersister propertiesPersister) { 187 this.propertiesPersister = 188 (propertiesPersister != null ? propertiesPersister : new DefaultPropertiesPersister()); 189 } 190 191 /** 192 * Return the PropertiesPersister to use for parsing properties files. 193 */ 194 public PropertiesPersister getPropertiesPersister() { 195 return this.propertiesPersister; 196 } 197 198 199 /** 200 * Load bean definitions from the specified properties file, 201 * using all property keys (i.e. not filtering by prefix). 202 * @param resource the resource descriptor for the properties file 203 * @return the number of bean definitions found 204 * @throws BeanDefinitionStoreException in case of loading or parsing errors 205 * @see #loadBeanDefinitions(org.springframework.core.io.Resource, String) 206 */ 207 @Override 208 public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException { 209 return loadBeanDefinitions(new EncodedResource(resource), null); 210 } 211 212 /** 213 * Load bean definitions from the specified properties file. 214 * @param resource the resource descriptor for the properties file 215 * @param prefix a filter within the keys in the map: e.g. 'beans.' 216 * (can be empty or {@code null}) 217 * @return the number of bean definitions found 218 * @throws BeanDefinitionStoreException in case of loading or parsing errors 219 */ 220 public int loadBeanDefinitions(Resource resource, String prefix) throws BeanDefinitionStoreException { 221 return loadBeanDefinitions(new EncodedResource(resource), prefix); 222 } 223 224 /** 225 * Load bean definitions from the specified properties file. 226 * @param encodedResource the resource descriptor for the properties file, 227 * allowing to specify an encoding to use for parsing the file 228 * @return the number of bean definitions found 229 * @throws BeanDefinitionStoreException in case of loading or parsing errors 230 */ 231 public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException { 232 return loadBeanDefinitions(encodedResource, null); 233 } 234 235 /** 236 * Load bean definitions from the specified properties file. 237 * @param encodedResource the resource descriptor for the properties file, 238 * allowing to specify an encoding to use for parsing the file 239 * @param prefix a filter within the keys in the map: e.g. 'beans.' 240 * (can be empty or {@code null}) 241 * @return the number of bean definitions found 242 * @throws BeanDefinitionStoreException in case of loading or parsing errors 243 */ 244 public int loadBeanDefinitions(EncodedResource encodedResource, String prefix) 245 throws BeanDefinitionStoreException { 246 247 Properties props = new Properties(); 248 try { 249 InputStream is = encodedResource.getResource().getInputStream(); 250 try { 251 if (encodedResource.getEncoding() != null) { 252 getPropertiesPersister().load(props, new InputStreamReader(is, encodedResource.getEncoding())); 253 } 254 else { 255 getPropertiesPersister().load(props, is); 256 } 257 } 258 finally { 259 is.close(); 260 } 261 return registerBeanDefinitions(props, prefix, encodedResource.getResource().getDescription()); 262 } 263 catch (IOException ex) { 264 throw new BeanDefinitionStoreException("Could not parse properties from " + encodedResource.getResource(), ex); 265 } 266 } 267 268 /** 269 * Register bean definitions contained in a resource bundle, 270 * using all property keys (i.e. not filtering by prefix). 271 * @param rb the ResourceBundle to load from 272 * @return the number of bean definitions found 273 * @throws BeanDefinitionStoreException in case of loading or parsing errors 274 * @see #registerBeanDefinitions(java.util.ResourceBundle, String) 275 */ 276 public int registerBeanDefinitions(ResourceBundle rb) throws BeanDefinitionStoreException { 277 return registerBeanDefinitions(rb, null); 278 } 279 280 /** 281 * Register bean definitions contained in a ResourceBundle. 282 * <p>Similar syntax as for a Map. This method is useful to enable 283 * standard Java internationalization support. 284 * @param rb the ResourceBundle to load from 285 * @param prefix a filter within the keys in the map: e.g. 'beans.' 286 * (can be empty or {@code null}) 287 * @return the number of bean definitions found 288 * @throws BeanDefinitionStoreException in case of loading or parsing errors 289 */ 290 public int registerBeanDefinitions(ResourceBundle rb, String prefix) throws BeanDefinitionStoreException { 291 // Simply create a map and call overloaded method. 292 Map<String, Object> map = new HashMap<String, Object>(); 293 Enumeration<String> keys = rb.getKeys(); 294 while (keys.hasMoreElements()) { 295 String key = keys.nextElement(); 296 map.put(key, rb.getObject(key)); 297 } 298 return registerBeanDefinitions(map, prefix); 299 } 300 301 302 /** 303 * Register bean definitions contained in a Map, using all property keys (i.e. not 304 * filtering by prefix). 305 * @param map a map of {@code name} to {@code property} (String or Object). Property 306 * values will be strings if coming from a Properties file etc. Property names 307 * (keys) <b>must</b> be Strings. Class keys must be Strings. 308 * @return the number of bean definitions found 309 * @throws BeansException in case of loading or parsing errors 310 * @see #registerBeanDefinitions(java.util.Map, String, String) 311 */ 312 public int registerBeanDefinitions(Map<?, ?> map) throws BeansException { 313 return registerBeanDefinitions(map, null); 314 } 315 316 /** 317 * Register bean definitions contained in a Map. 318 * Ignore ineligible properties. 319 * @param map a map of {@code name} to {@code property} (String or Object). Property 320 * values will be strings if coming from a Properties file etc. Property names 321 * (keys) <b>must</b> be Strings. Class keys must be Strings. 322 * @param prefix a filter within the keys in the map: e.g. 'beans.' 323 * (can be empty or {@code null}) 324 * @return the number of bean definitions found 325 * @throws BeansException in case of loading or parsing errors 326 */ 327 public int registerBeanDefinitions(Map<?, ?> map, String prefix) throws BeansException { 328 return registerBeanDefinitions(map, prefix, "Map " + map); 329 } 330 331 /** 332 * Register bean definitions contained in a Map. 333 * Ignore ineligible properties. 334 * @param map a map of {@code name} to {@code property} (String or Object). Property 335 * values will be strings if coming from a Properties file etc. Property names 336 * (keys) <b>must</b> be Strings. Class keys must be Strings. 337 * @param prefix a filter within the keys in the map: e.g. 'beans.' 338 * (can be empty or {@code null}) 339 * @param resourceDescription description of the resource that the 340 * Map came from (for logging purposes) 341 * @return the number of bean definitions found 342 * @throws BeansException in case of loading or parsing errors 343 * @see #registerBeanDefinitions(Map, String) 344 */ 345 public int registerBeanDefinitions(Map<?, ?> map, String prefix, String resourceDescription) 346 throws BeansException { 347 348 if (prefix == null) { 349 prefix = ""; 350 } 351 int beanCount = 0; 352 353 for (Object key : map.keySet()) { 354 if (!(key instanceof String)) { 355 throw new IllegalArgumentException("Illegal key [" + key + "]: only Strings allowed"); 356 } 357 String keyString = (String) key; 358 if (keyString.startsWith(prefix)) { 359 // Key is of form: prefix<name>.property 360 String nameAndProperty = keyString.substring(prefix.length()); 361 // Find dot before property name, ignoring dots in property keys. 362 int sepIdx = -1; 363 int propKeyIdx = nameAndProperty.indexOf(PropertyAccessor.PROPERTY_KEY_PREFIX); 364 if (propKeyIdx != -1) { 365 sepIdx = nameAndProperty.lastIndexOf(SEPARATOR, propKeyIdx); 366 } 367 else { 368 sepIdx = nameAndProperty.lastIndexOf(SEPARATOR); 369 } 370 if (sepIdx != -1) { 371 String beanName = nameAndProperty.substring(0, sepIdx); 372 if (logger.isDebugEnabled()) { 373 logger.debug("Found bean name '" + beanName + "'"); 374 } 375 if (!getRegistry().containsBeanDefinition(beanName)) { 376 // If we haven't already registered it... 377 registerBeanDefinition(beanName, map, prefix + beanName, resourceDescription); 378 ++beanCount; 379 } 380 } 381 else { 382 // Ignore it: It wasn't a valid bean name and property, 383 // although it did start with the required prefix. 384 if (logger.isDebugEnabled()) { 385 logger.debug("Invalid bean name and property [" + nameAndProperty + "]"); 386 } 387 } 388 } 389 } 390 391 return beanCount; 392 } 393 394 /** 395 * Get all property values, given a prefix (which will be stripped) 396 * and add the bean they define to the factory with the given name. 397 * @param beanName name of the bean to define 398 * @param map a Map containing string pairs 399 * @param prefix prefix of each entry, which will be stripped 400 * @param resourceDescription description of the resource that the 401 * Map came from (for logging purposes) 402 * @throws BeansException if the bean definition could not be parsed or registered 403 */ 404 protected void registerBeanDefinition(String beanName, Map<?, ?> map, String prefix, String resourceDescription) 405 throws BeansException { 406 407 String className = null; 408 String parent = null; 409 String scope = GenericBeanDefinition.SCOPE_SINGLETON; 410 boolean isAbstract = false; 411 boolean lazyInit = false; 412 413 ConstructorArgumentValues cas = new ConstructorArgumentValues(); 414 MutablePropertyValues pvs = new MutablePropertyValues(); 415 416 for (Map.Entry<?, ?> entry : map.entrySet()) { 417 String key = StringUtils.trimWhitespace((String) entry.getKey()); 418 if (key.startsWith(prefix + SEPARATOR)) { 419 String property = key.substring(prefix.length() + SEPARATOR.length()); 420 if (CLASS_KEY.equals(property)) { 421 className = StringUtils.trimWhitespace((String) entry.getValue()); 422 } 423 else if (PARENT_KEY.equals(property)) { 424 parent = StringUtils.trimWhitespace((String) entry.getValue()); 425 } 426 else if (ABSTRACT_KEY.equals(property)) { 427 String val = StringUtils.trimWhitespace((String) entry.getValue()); 428 isAbstract = TRUE_VALUE.equals(val); 429 } 430 else if (SCOPE_KEY.equals(property)) { 431 // Spring 2.0 style 432 scope = StringUtils.trimWhitespace((String) entry.getValue()); 433 } 434 else if (SINGLETON_KEY.equals(property)) { 435 // Spring 1.2 style 436 String val = StringUtils.trimWhitespace((String) entry.getValue()); 437 scope = ((val == null || TRUE_VALUE.equals(val) ? GenericBeanDefinition.SCOPE_SINGLETON : 438 GenericBeanDefinition.SCOPE_PROTOTYPE)); 439 } 440 else if (LAZY_INIT_KEY.equals(property)) { 441 String val = StringUtils.trimWhitespace((String) entry.getValue()); 442 lazyInit = TRUE_VALUE.equals(val); 443 } 444 else if (property.startsWith(CONSTRUCTOR_ARG_PREFIX)) { 445 if (property.endsWith(REF_SUFFIX)) { 446 int index = Integer.parseInt(property.substring(1, property.length() - REF_SUFFIX.length())); 447 cas.addIndexedArgumentValue(index, new RuntimeBeanReference(entry.getValue().toString())); 448 } 449 else { 450 int index = Integer.parseInt(property.substring(1)); 451 cas.addIndexedArgumentValue(index, readValue(entry)); 452 } 453 } 454 else if (property.endsWith(REF_SUFFIX)) { 455 // This isn't a real property, but a reference to another prototype 456 // Extract property name: property is of form dog(ref) 457 property = property.substring(0, property.length() - REF_SUFFIX.length()); 458 String ref = StringUtils.trimWhitespace((String) entry.getValue()); 459 460 // It doesn't matter if the referenced bean hasn't yet been registered: 461 // this will ensure that the reference is resolved at runtime. 462 Object val = new RuntimeBeanReference(ref); 463 pvs.add(property, val); 464 } 465 else { 466 // It's a normal bean property. 467 pvs.add(property, readValue(entry)); 468 } 469 } 470 } 471 472 if (logger.isDebugEnabled()) { 473 logger.debug("Registering bean definition for bean name '" + beanName + "' with " + pvs); 474 } 475 476 // Just use default parent if we're not dealing with the parent itself, 477 // and if there's no class name specified. The latter has to happen for 478 // backwards compatibility reasons. 479 if (parent == null && className == null && !beanName.equals(this.defaultParentBean)) { 480 parent = this.defaultParentBean; 481 } 482 483 try { 484 AbstractBeanDefinition bd = BeanDefinitionReaderUtils.createBeanDefinition( 485 parent, className, getBeanClassLoader()); 486 bd.setScope(scope); 487 bd.setAbstract(isAbstract); 488 bd.setLazyInit(lazyInit); 489 bd.setConstructorArgumentValues(cas); 490 bd.setPropertyValues(pvs); 491 getRegistry().registerBeanDefinition(beanName, bd); 492 } 493 catch (ClassNotFoundException ex) { 494 throw new CannotLoadBeanClassException(resourceDescription, beanName, className, ex); 495 } 496 catch (LinkageError err) { 497 throw new CannotLoadBeanClassException(resourceDescription, beanName, className, err); 498 } 499 } 500 501 /** 502 * Reads the value of the entry. Correctly interprets bean references for 503 * values that are prefixed with an asterisk. 504 */ 505 private Object readValue(Map.Entry<?, ?> entry) { 506 Object val = entry.getValue(); 507 if (val instanceof String) { 508 String strVal = (String) val; 509 // If it starts with a reference prefix... 510 if (strVal.startsWith(REF_PREFIX)) { 511 // Expand the reference. 512 String targetName = strVal.substring(1); 513 if (targetName.startsWith(REF_PREFIX)) { 514 // Escaped prefix -> use plain value. 515 val = targetName; 516 } 517 else { 518 val = new RuntimeBeanReference(targetName); 519 } 520 } 521 } 522 return val; 523 } 524 525}