001/* 002 * Copyright 2002-2019 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 java.util.LinkedHashMap; 020import java.util.LinkedHashSet; 021import java.util.List; 022import java.util.Map; 023import java.util.Set; 024 025import org.springframework.beans.MutablePropertyValues; 026import org.springframework.beans.PropertyValue; 027import org.springframework.lang.Nullable; 028import org.springframework.util.Assert; 029import org.springframework.util.ObjectUtils; 030import org.springframework.util.StringValueResolver; 031 032/** 033 * Visitor class for traversing {@link BeanDefinition} objects, in particular 034 * the property values and constructor argument values contained in them, 035 * resolving bean metadata values. 036 * 037 * <p>Used by {@link PlaceholderConfigurerSupport} to parse all String values 038 * contained in a BeanDefinition, resolving any placeholders found. 039 * 040 * @author Juergen Hoeller 041 * @author Sam Brannen 042 * @since 1.2 043 * @see BeanDefinition 044 * @see BeanDefinition#getPropertyValues 045 * @see BeanDefinition#getConstructorArgumentValues 046 * @see PlaceholderConfigurerSupport 047 */ 048public class BeanDefinitionVisitor { 049 050 @Nullable 051 private StringValueResolver valueResolver; 052 053 054 /** 055 * Create a new BeanDefinitionVisitor, applying the specified 056 * value resolver to all bean metadata values. 057 * @param valueResolver the StringValueResolver to apply 058 */ 059 public BeanDefinitionVisitor(StringValueResolver valueResolver) { 060 Assert.notNull(valueResolver, "StringValueResolver must not be null"); 061 this.valueResolver = valueResolver; 062 } 063 064 /** 065 * Create a new BeanDefinitionVisitor for subclassing. 066 * Subclasses need to override the {@link #resolveStringValue} method. 067 */ 068 protected BeanDefinitionVisitor() { 069 } 070 071 072 /** 073 * Traverse the given BeanDefinition object and the MutablePropertyValues 074 * and ConstructorArgumentValues contained in them. 075 * @param beanDefinition the BeanDefinition object to traverse 076 * @see #resolveStringValue(String) 077 */ 078 public void visitBeanDefinition(BeanDefinition beanDefinition) { 079 visitParentName(beanDefinition); 080 visitBeanClassName(beanDefinition); 081 visitFactoryBeanName(beanDefinition); 082 visitFactoryMethodName(beanDefinition); 083 visitScope(beanDefinition); 084 if (beanDefinition.hasPropertyValues()) { 085 visitPropertyValues(beanDefinition.getPropertyValues()); 086 } 087 if (beanDefinition.hasConstructorArgumentValues()) { 088 ConstructorArgumentValues cas = beanDefinition.getConstructorArgumentValues(); 089 visitIndexedArgumentValues(cas.getIndexedArgumentValues()); 090 visitGenericArgumentValues(cas.getGenericArgumentValues()); 091 } 092 } 093 094 protected void visitParentName(BeanDefinition beanDefinition) { 095 String parentName = beanDefinition.getParentName(); 096 if (parentName != null) { 097 String resolvedName = resolveStringValue(parentName); 098 if (!parentName.equals(resolvedName)) { 099 beanDefinition.setParentName(resolvedName); 100 } 101 } 102 } 103 104 protected void visitBeanClassName(BeanDefinition beanDefinition) { 105 String beanClassName = beanDefinition.getBeanClassName(); 106 if (beanClassName != null) { 107 String resolvedName = resolveStringValue(beanClassName); 108 if (!beanClassName.equals(resolvedName)) { 109 beanDefinition.setBeanClassName(resolvedName); 110 } 111 } 112 } 113 114 protected void visitFactoryBeanName(BeanDefinition beanDefinition) { 115 String factoryBeanName = beanDefinition.getFactoryBeanName(); 116 if (factoryBeanName != null) { 117 String resolvedName = resolveStringValue(factoryBeanName); 118 if (!factoryBeanName.equals(resolvedName)) { 119 beanDefinition.setFactoryBeanName(resolvedName); 120 } 121 } 122 } 123 124 protected void visitFactoryMethodName(BeanDefinition beanDefinition) { 125 String factoryMethodName = beanDefinition.getFactoryMethodName(); 126 if (factoryMethodName != null) { 127 String resolvedName = resolveStringValue(factoryMethodName); 128 if (!factoryMethodName.equals(resolvedName)) { 129 beanDefinition.setFactoryMethodName(resolvedName); 130 } 131 } 132 } 133 134 protected void visitScope(BeanDefinition beanDefinition) { 135 String scope = beanDefinition.getScope(); 136 if (scope != null) { 137 String resolvedScope = resolveStringValue(scope); 138 if (!scope.equals(resolvedScope)) { 139 beanDefinition.setScope(resolvedScope); 140 } 141 } 142 } 143 144 protected void visitPropertyValues(MutablePropertyValues pvs) { 145 PropertyValue[] pvArray = pvs.getPropertyValues(); 146 for (PropertyValue pv : pvArray) { 147 Object newVal = resolveValue(pv.getValue()); 148 if (!ObjectUtils.nullSafeEquals(newVal, pv.getValue())) { 149 pvs.add(pv.getName(), newVal); 150 } 151 } 152 } 153 154 protected void visitIndexedArgumentValues(Map<Integer, ConstructorArgumentValues.ValueHolder> ias) { 155 for (ConstructorArgumentValues.ValueHolder valueHolder : ias.values()) { 156 Object newVal = resolveValue(valueHolder.getValue()); 157 if (!ObjectUtils.nullSafeEquals(newVal, valueHolder.getValue())) { 158 valueHolder.setValue(newVal); 159 } 160 } 161 } 162 163 protected void visitGenericArgumentValues(List<ConstructorArgumentValues.ValueHolder> gas) { 164 for (ConstructorArgumentValues.ValueHolder valueHolder : gas) { 165 Object newVal = resolveValue(valueHolder.getValue()); 166 if (!ObjectUtils.nullSafeEquals(newVal, valueHolder.getValue())) { 167 valueHolder.setValue(newVal); 168 } 169 } 170 } 171 172 @SuppressWarnings("rawtypes") 173 @Nullable 174 protected Object resolveValue(@Nullable Object value) { 175 if (value instanceof BeanDefinition) { 176 visitBeanDefinition((BeanDefinition) value); 177 } 178 else if (value instanceof BeanDefinitionHolder) { 179 visitBeanDefinition(((BeanDefinitionHolder) value).getBeanDefinition()); 180 } 181 else if (value instanceof RuntimeBeanReference) { 182 RuntimeBeanReference ref = (RuntimeBeanReference) value; 183 String newBeanName = resolveStringValue(ref.getBeanName()); 184 if (newBeanName == null) { 185 return null; 186 } 187 if (!newBeanName.equals(ref.getBeanName())) { 188 return new RuntimeBeanReference(newBeanName); 189 } 190 } 191 else if (value instanceof RuntimeBeanNameReference) { 192 RuntimeBeanNameReference ref = (RuntimeBeanNameReference) value; 193 String newBeanName = resolveStringValue(ref.getBeanName()); 194 if (newBeanName == null) { 195 return null; 196 } 197 if (!newBeanName.equals(ref.getBeanName())) { 198 return new RuntimeBeanNameReference(newBeanName); 199 } 200 } 201 else if (value instanceof Object[]) { 202 visitArray((Object[]) value); 203 } 204 else if (value instanceof List) { 205 visitList((List) value); 206 } 207 else if (value instanceof Set) { 208 visitSet((Set) value); 209 } 210 else if (value instanceof Map) { 211 visitMap((Map) value); 212 } 213 else if (value instanceof TypedStringValue) { 214 TypedStringValue typedStringValue = (TypedStringValue) value; 215 String stringValue = typedStringValue.getValue(); 216 if (stringValue != null) { 217 String visitedString = resolveStringValue(stringValue); 218 typedStringValue.setValue(visitedString); 219 } 220 } 221 else if (value instanceof String) { 222 return resolveStringValue((String) value); 223 } 224 return value; 225 } 226 227 protected void visitArray(Object[] arrayVal) { 228 for (int i = 0; i < arrayVal.length; i++) { 229 Object elem = arrayVal[i]; 230 Object newVal = resolveValue(elem); 231 if (!ObjectUtils.nullSafeEquals(newVal, elem)) { 232 arrayVal[i] = newVal; 233 } 234 } 235 } 236 237 @SuppressWarnings({"rawtypes", "unchecked"}) 238 protected void visitList(List listVal) { 239 for (int i = 0; i < listVal.size(); i++) { 240 Object elem = listVal.get(i); 241 Object newVal = resolveValue(elem); 242 if (!ObjectUtils.nullSafeEquals(newVal, elem)) { 243 listVal.set(i, newVal); 244 } 245 } 246 } 247 248 @SuppressWarnings({"rawtypes", "unchecked"}) 249 protected void visitSet(Set setVal) { 250 Set newContent = new LinkedHashSet(); 251 boolean entriesModified = false; 252 for (Object elem : setVal) { 253 int elemHash = (elem != null ? elem.hashCode() : 0); 254 Object newVal = resolveValue(elem); 255 int newValHash = (newVal != null ? newVal.hashCode() : 0); 256 newContent.add(newVal); 257 entriesModified = entriesModified || (newVal != elem || newValHash != elemHash); 258 } 259 if (entriesModified) { 260 setVal.clear(); 261 setVal.addAll(newContent); 262 } 263 } 264 265 @SuppressWarnings({"rawtypes", "unchecked"}) 266 protected void visitMap(Map<?, ?> mapVal) { 267 Map newContent = new LinkedHashMap(); 268 boolean entriesModified = false; 269 for (Map.Entry entry : mapVal.entrySet()) { 270 Object key = entry.getKey(); 271 int keyHash = (key != null ? key.hashCode() : 0); 272 Object newKey = resolveValue(key); 273 int newKeyHash = (newKey != null ? newKey.hashCode() : 0); 274 Object val = entry.getValue(); 275 Object newVal = resolveValue(val); 276 newContent.put(newKey, newVal); 277 entriesModified = entriesModified || (newVal != val || newKey != key || newKeyHash != keyHash); 278 } 279 if (entriesModified) { 280 mapVal.clear(); 281 mapVal.putAll(newContent); 282 } 283 } 284 285 /** 286 * Resolve the given String value, for example parsing placeholders. 287 * @param strVal the original String value 288 * @return the resolved String value 289 */ 290 @Nullable 291 protected String resolveStringValue(String strVal) { 292 if (this.valueResolver == null) { 293 throw new IllegalStateException("No StringValueResolver specified - pass a resolver " + 294 "object into the constructor or override the 'resolveStringValue' method"); 295 } 296 String resolvedValue = this.valueResolver.resolveStringValue(strVal); 297 // Return original String if not modified. 298 return (strVal.equals(resolvedValue) ? strVal : resolvedValue); 299 } 300 301}