001/* 002 * Copyright 2002-2017 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.config; 018 019import org.apache.commons.logging.Log; 020import org.apache.commons.logging.LogFactory; 021 022import org.springframework.beans.BeanWrapper; 023import org.springframework.beans.BeansException; 024import org.springframework.beans.PropertyAccessorFactory; 025import org.springframework.beans.factory.BeanFactory; 026import org.springframework.beans.factory.BeanFactoryAware; 027import org.springframework.beans.factory.BeanFactoryUtils; 028import org.springframework.beans.factory.BeanNameAware; 029import org.springframework.beans.factory.FactoryBean; 030import org.springframework.lang.Nullable; 031import org.springframework.util.Assert; 032import org.springframework.util.StringUtils; 033 034/** 035 * {@link FactoryBean} that evaluates a property path on a given target object. 036 * 037 * <p>The target object can be specified directly or via a bean name. 038 * 039 * <p>Usage examples: 040 * 041 * <pre class="code"><!-- target bean to be referenced by name --> 042 * <bean id="tb" class="org.springframework.beans.TestBean" singleton="false"> 043 * <property name="age" value="10"/> 044 * <property name="spouse"> 045 * <bean class="org.springframework.beans.TestBean"> 046 * <property name="age" value="11"/> 047 * </bean> 048 * </property> 049 * </bean> 050 * 051 * <!-- will result in 12, which is the value of property 'age' of the inner bean --> 052 * <bean id="propertyPath1" class="org.springframework.beans.factory.config.PropertyPathFactoryBean"> 053 * <property name="targetObject"> 054 * <bean class="org.springframework.beans.TestBean"> 055 * <property name="age" value="12"/> 056 * </bean> 057 * </property> 058 * <property name="propertyPath" value="age"/> 059 * </bean> 060 * 061 * <!-- will result in 11, which is the value of property 'spouse.age' of bean 'tb' --> 062 * <bean id="propertyPath2" class="org.springframework.beans.factory.config.PropertyPathFactoryBean"> 063 * <property name="targetBeanName" value="tb"/> 064 * <property name="propertyPath" value="spouse.age"/> 065 * </bean> 066 * 067 * <!-- will result in 10, which is the value of property 'age' of bean 'tb' --> 068 * <bean id="tb.age" class="org.springframework.beans.factory.config.PropertyPathFactoryBean"/></pre> 069 * 070 * <p>If you are using Spring 2.0 and XML Schema support in your configuration file(s), 071 * you can also use the following style of configuration for property path access. 072 * (See also the appendix entitled 'XML Schema-based configuration' in the Spring 073 * reference manual for more examples.) 074 * 075 * <pre class="code"> <!-- will result in 10, which is the value of property 'age' of bean 'tb' --> 076 * <util:property-path id="name" path="testBean.age"/></pre> 077 * 078 * Thanks to Matthias Ernst for the suggestion and initial prototype! 079 * 080 * @author Juergen Hoeller 081 * @since 1.1.2 082 * @see #setTargetObject 083 * @see #setTargetBeanName 084 * @see #setPropertyPath 085 */ 086public class PropertyPathFactoryBean implements FactoryBean<Object>, BeanNameAware, BeanFactoryAware { 087 088 private static final Log logger = LogFactory.getLog(PropertyPathFactoryBean.class); 089 090 @Nullable 091 private BeanWrapper targetBeanWrapper; 092 093 @Nullable 094 private String targetBeanName; 095 096 @Nullable 097 private String propertyPath; 098 099 @Nullable 100 private Class<?> resultType; 101 102 @Nullable 103 private String beanName; 104 105 @Nullable 106 private BeanFactory beanFactory; 107 108 109 /** 110 * Specify a target object to apply the property path to. 111 * Alternatively, specify a target bean name. 112 * @param targetObject a target object, for example a bean reference 113 * or an inner bean 114 * @see #setTargetBeanName 115 */ 116 public void setTargetObject(Object targetObject) { 117 this.targetBeanWrapper = PropertyAccessorFactory.forBeanPropertyAccess(targetObject); 118 } 119 120 /** 121 * Specify the name of a target bean to apply the property path to. 122 * Alternatively, specify a target object directly. 123 * @param targetBeanName the bean name to be looked up in the 124 * containing bean factory (e.g. "testBean") 125 * @see #setTargetObject 126 */ 127 public void setTargetBeanName(String targetBeanName) { 128 this.targetBeanName = StringUtils.trimAllWhitespace(targetBeanName); 129 } 130 131 /** 132 * Specify the property path to apply to the target. 133 * @param propertyPath the property path, potentially nested 134 * (e.g. "age" or "spouse.age") 135 */ 136 public void setPropertyPath(String propertyPath) { 137 this.propertyPath = StringUtils.trimAllWhitespace(propertyPath); 138 } 139 140 /** 141 * Specify the type of the result from evaluating the property path. 142 * <p>Note: This is not necessary for directly specified target objects 143 * or singleton target beans, where the type can be determined through 144 * introspection. Just specify this in case of a prototype target, 145 * provided that you need matching by type (for example, for autowiring). 146 * @param resultType the result type, for example "java.lang.Integer" 147 */ 148 public void setResultType(Class<?> resultType) { 149 this.resultType = resultType; 150 } 151 152 /** 153 * The bean name of this PropertyPathFactoryBean will be interpreted 154 * as "beanName.property" pattern, if neither "targetObject" nor 155 * "targetBeanName" nor "propertyPath" have been specified. 156 * This allows for concise bean definitions with just an id/name. 157 */ 158 @Override 159 public void setBeanName(String beanName) { 160 this.beanName = StringUtils.trimAllWhitespace(BeanFactoryUtils.originalBeanName(beanName)); 161 } 162 163 164 @Override 165 public void setBeanFactory(BeanFactory beanFactory) { 166 this.beanFactory = beanFactory; 167 168 if (this.targetBeanWrapper != null && this.targetBeanName != null) { 169 throw new IllegalArgumentException("Specify either 'targetObject' or 'targetBeanName', not both"); 170 } 171 172 if (this.targetBeanWrapper == null && this.targetBeanName == null) { 173 if (this.propertyPath != null) { 174 throw new IllegalArgumentException( 175 "Specify 'targetObject' or 'targetBeanName' in combination with 'propertyPath'"); 176 } 177 178 // No other properties specified: check bean name. 179 int dotIndex = (this.beanName != null ? this.beanName.indexOf('.') : -1); 180 if (dotIndex == -1) { 181 throw new IllegalArgumentException( 182 "Neither 'targetObject' nor 'targetBeanName' specified, and PropertyPathFactoryBean " + 183 "bean name '" + this.beanName + "' does not follow 'beanName.property' syntax"); 184 } 185 this.targetBeanName = this.beanName.substring(0, dotIndex); 186 this.propertyPath = this.beanName.substring(dotIndex + 1); 187 } 188 189 else if (this.propertyPath == null) { 190 // either targetObject or targetBeanName specified 191 throw new IllegalArgumentException("'propertyPath' is required"); 192 } 193 194 if (this.targetBeanWrapper == null && this.beanFactory.isSingleton(this.targetBeanName)) { 195 // Eagerly fetch singleton target bean, and determine result type. 196 Object bean = this.beanFactory.getBean(this.targetBeanName); 197 this.targetBeanWrapper = PropertyAccessorFactory.forBeanPropertyAccess(bean); 198 this.resultType = this.targetBeanWrapper.getPropertyType(this.propertyPath); 199 } 200 } 201 202 203 @Override 204 @Nullable 205 public Object getObject() throws BeansException { 206 BeanWrapper target = this.targetBeanWrapper; 207 if (target != null) { 208 if (logger.isWarnEnabled() && this.targetBeanName != null && 209 this.beanFactory instanceof ConfigurableBeanFactory && 210 ((ConfigurableBeanFactory) this.beanFactory).isCurrentlyInCreation(this.targetBeanName)) { 211 logger.warn("Target bean '" + this.targetBeanName + "' is still in creation due to a circular " + 212 "reference - obtained value for property '" + this.propertyPath + "' may be outdated!"); 213 } 214 } 215 else { 216 // Fetch prototype target bean... 217 Assert.state(this.beanFactory != null, "No BeanFactory available"); 218 Assert.state(this.targetBeanName != null, "No target bean name specified"); 219 Object bean = this.beanFactory.getBean(this.targetBeanName); 220 target = PropertyAccessorFactory.forBeanPropertyAccess(bean); 221 } 222 Assert.state(this.propertyPath != null, "No property path specified"); 223 return target.getPropertyValue(this.propertyPath); 224 } 225 226 @Override 227 public Class<?> getObjectType() { 228 return this.resultType; 229 } 230 231 /** 232 * While this FactoryBean will often be used for singleton targets, 233 * the invoked getters for the property path might return a new object 234 * for each call, so we have to assume that we're not returning the 235 * same object for each {@link #getObject()} call. 236 */ 237 @Override 238 public boolean isSingleton() { 239 return false; 240 } 241 242}