001/* 002 * Copyright 2002-2020 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.annotation; 018 019import java.beans.PropertyDescriptor; 020import java.lang.reflect.Field; 021import java.lang.reflect.InvocationTargetException; 022import java.lang.reflect.Member; 023import java.lang.reflect.Method; 024import java.util.Collection; 025import java.util.Collections; 026import java.util.LinkedHashSet; 027import java.util.Set; 028 029import org.springframework.beans.MutablePropertyValues; 030import org.springframework.beans.PropertyValues; 031import org.springframework.beans.factory.support.RootBeanDefinition; 032import org.springframework.lang.Nullable; 033import org.springframework.util.ReflectionUtils; 034 035/** 036 * Internal class for managing injection metadata. 037 * Not intended for direct use in applications. 038 * 039 * <p>Used by {@link AutowiredAnnotationBeanPostProcessor}, 040 * {@link org.springframework.context.annotation.CommonAnnotationBeanPostProcessor} and 041 * {@link org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor}. 042 * 043 * @author Juergen Hoeller 044 * @since 2.5 045 */ 046public class InjectionMetadata { 047 048 /** 049 * An empty {@code InjectionMetadata} instance with no-op callbacks. 050 * @since 5.2 051 */ 052 public static final InjectionMetadata EMPTY = new InjectionMetadata(Object.class, Collections.emptyList()) { 053 @Override 054 protected boolean needsRefresh(Class<?> clazz) { 055 return false; 056 } 057 @Override 058 public void checkConfigMembers(RootBeanDefinition beanDefinition) { 059 } 060 @Override 061 public void inject(Object target, @Nullable String beanName, @Nullable PropertyValues pvs) { 062 } 063 @Override 064 public void clear(@Nullable PropertyValues pvs) { 065 } 066 }; 067 068 069 private final Class<?> targetClass; 070 071 private final Collection<InjectedElement> injectedElements; 072 073 @Nullable 074 private volatile Set<InjectedElement> checkedElements; 075 076 077 /** 078 * Create a new {@code InjectionMetadata instance}. 079 * <p>Preferably use {@link #forElements} for reusing the {@link #EMPTY} 080 * instance in case of no elements. 081 * @param targetClass the target class 082 * @param elements the associated elements to inject 083 * @see #forElements 084 */ 085 public InjectionMetadata(Class<?> targetClass, Collection<InjectedElement> elements) { 086 this.targetClass = targetClass; 087 this.injectedElements = elements; 088 } 089 090 091 /** 092 * Determine whether this metadata instance needs to be refreshed. 093 * @param clazz the current target class 094 * @return {@code true} indicating a refresh, {@code false} otherwise 095 * @since 5.2.4 096 */ 097 protected boolean needsRefresh(Class<?> clazz) { 098 return this.targetClass != clazz; 099 } 100 101 public void checkConfigMembers(RootBeanDefinition beanDefinition) { 102 Set<InjectedElement> checkedElements = new LinkedHashSet<>(this.injectedElements.size()); 103 for (InjectedElement element : this.injectedElements) { 104 Member member = element.getMember(); 105 if (!beanDefinition.isExternallyManagedConfigMember(member)) { 106 beanDefinition.registerExternallyManagedConfigMember(member); 107 checkedElements.add(element); 108 } 109 } 110 this.checkedElements = checkedElements; 111 } 112 113 public void inject(Object target, @Nullable String beanName, @Nullable PropertyValues pvs) throws Throwable { 114 Collection<InjectedElement> checkedElements = this.checkedElements; 115 Collection<InjectedElement> elementsToIterate = 116 (checkedElements != null ? checkedElements : this.injectedElements); 117 if (!elementsToIterate.isEmpty()) { 118 for (InjectedElement element : elementsToIterate) { 119 element.inject(target, beanName, pvs); 120 } 121 } 122 } 123 124 /** 125 * Clear property skipping for the contained elements. 126 * @since 3.2.13 127 */ 128 public void clear(@Nullable PropertyValues pvs) { 129 Collection<InjectedElement> checkedElements = this.checkedElements; 130 Collection<InjectedElement> elementsToIterate = 131 (checkedElements != null ? checkedElements : this.injectedElements); 132 if (!elementsToIterate.isEmpty()) { 133 for (InjectedElement element : elementsToIterate) { 134 element.clearPropertySkipping(pvs); 135 } 136 } 137 } 138 139 140 /** 141 * Return an {@code InjectionMetadata} instance, possibly for empty elements. 142 * @param elements the elements to inject (possibly empty) 143 * @param clazz the target class 144 * @return a new {@link #InjectionMetadata(Class, Collection)} instance 145 * @since 5.2 146 */ 147 public static InjectionMetadata forElements(Collection<InjectedElement> elements, Class<?> clazz) { 148 return (elements.isEmpty() ? new InjectionMetadata(clazz, Collections.emptyList()) : 149 new InjectionMetadata(clazz, elements)); 150 } 151 152 /** 153 * Check whether the given injection metadata needs to be refreshed. 154 * @param metadata the existing metadata instance 155 * @param clazz the current target class 156 * @return {@code true} indicating a refresh, {@code false} otherwise 157 * @see #needsRefresh(Class) 158 */ 159 public static boolean needsRefresh(@Nullable InjectionMetadata metadata, Class<?> clazz) { 160 return (metadata == null || metadata.needsRefresh(clazz)); 161 } 162 163 164 /** 165 * A single injected element. 166 */ 167 public abstract static class InjectedElement { 168 169 protected final Member member; 170 171 protected final boolean isField; 172 173 @Nullable 174 protected final PropertyDescriptor pd; 175 176 @Nullable 177 protected volatile Boolean skip; 178 179 protected InjectedElement(Member member, @Nullable PropertyDescriptor pd) { 180 this.member = member; 181 this.isField = (member instanceof Field); 182 this.pd = pd; 183 } 184 185 public final Member getMember() { 186 return this.member; 187 } 188 189 protected final Class<?> getResourceType() { 190 if (this.isField) { 191 return ((Field) this.member).getType(); 192 } 193 else if (this.pd != null) { 194 return this.pd.getPropertyType(); 195 } 196 else { 197 return ((Method) this.member).getParameterTypes()[0]; 198 } 199 } 200 201 protected final void checkResourceType(Class<?> resourceType) { 202 if (this.isField) { 203 Class<?> fieldType = ((Field) this.member).getType(); 204 if (!(resourceType.isAssignableFrom(fieldType) || fieldType.isAssignableFrom(resourceType))) { 205 throw new IllegalStateException("Specified field type [" + fieldType + 206 "] is incompatible with resource type [" + resourceType.getName() + "]"); 207 } 208 } 209 else { 210 Class<?> paramType = 211 (this.pd != null ? this.pd.getPropertyType() : ((Method) this.member).getParameterTypes()[0]); 212 if (!(resourceType.isAssignableFrom(paramType) || paramType.isAssignableFrom(resourceType))) { 213 throw new IllegalStateException("Specified parameter type [" + paramType + 214 "] is incompatible with resource type [" + resourceType.getName() + "]"); 215 } 216 } 217 } 218 219 /** 220 * Either this or {@link #getResourceToInject} needs to be overridden. 221 */ 222 protected void inject(Object target, @Nullable String requestingBeanName, @Nullable PropertyValues pvs) 223 throws Throwable { 224 225 if (this.isField) { 226 Field field = (Field) this.member; 227 ReflectionUtils.makeAccessible(field); 228 field.set(target, getResourceToInject(target, requestingBeanName)); 229 } 230 else { 231 if (checkPropertySkipping(pvs)) { 232 return; 233 } 234 try { 235 Method method = (Method) this.member; 236 ReflectionUtils.makeAccessible(method); 237 method.invoke(target, getResourceToInject(target, requestingBeanName)); 238 } 239 catch (InvocationTargetException ex) { 240 throw ex.getTargetException(); 241 } 242 } 243 } 244 245 /** 246 * Check whether this injector's property needs to be skipped due to 247 * an explicit property value having been specified. Also marks the 248 * affected property as processed for other processors to ignore it. 249 */ 250 protected boolean checkPropertySkipping(@Nullable PropertyValues pvs) { 251 Boolean skip = this.skip; 252 if (skip != null) { 253 return skip; 254 } 255 if (pvs == null) { 256 this.skip = false; 257 return false; 258 } 259 synchronized (pvs) { 260 skip = this.skip; 261 if (skip != null) { 262 return skip; 263 } 264 if (this.pd != null) { 265 if (pvs.contains(this.pd.getName())) { 266 // Explicit value provided as part of the bean definition. 267 this.skip = true; 268 return true; 269 } 270 else if (pvs instanceof MutablePropertyValues) { 271 ((MutablePropertyValues) pvs).registerProcessedProperty(this.pd.getName()); 272 } 273 } 274 this.skip = false; 275 return false; 276 } 277 } 278 279 /** 280 * Clear property skipping for this element. 281 * @since 3.2.13 282 */ 283 protected void clearPropertySkipping(@Nullable PropertyValues pvs) { 284 if (pvs == null) { 285 return; 286 } 287 synchronized (pvs) { 288 if (Boolean.FALSE.equals(this.skip) && this.pd != null && pvs instanceof MutablePropertyValues) { 289 ((MutablePropertyValues) pvs).clearProcessedProperty(this.pd.getName()); 290 } 291 } 292 } 293 294 /** 295 * Either this or {@link #inject} needs to be overridden. 296 */ 297 @Nullable 298 protected Object getResourceToInject(Object target, @Nullable String requestingBeanName) { 299 return null; 300 } 301 302 @Override 303 public boolean equals(@Nullable Object other) { 304 if (this == other) { 305 return true; 306 } 307 if (!(other instanceof InjectedElement)) { 308 return false; 309 } 310 InjectedElement otherElement = (InjectedElement) other; 311 return this.member.equals(otherElement.member); 312 } 313 314 @Override 315 public int hashCode() { 316 return this.member.getClass().hashCode() * 29 + this.member.getName().hashCode(); 317 } 318 319 @Override 320 public String toString() { 321 return getClass().getSimpleName() + " for " + this.member; 322 } 323 } 324 325}