1 /* 2 * Copyright 2002-2013 the original author or authors. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package org.springframework.beans; 18 19 import java.beans.BeanInfo; 20 import java.beans.IntrospectionException; 21 import java.beans.Introspector; 22 import java.beans.PropertyDescriptor; 23 import java.lang.ref.Reference; 24 import java.lang.ref.WeakReference; 25 import java.lang.reflect.Method; 26 import java.util.Collections; 27 import java.util.HashSet; 28 import java.util.Iterator; 29 import java.util.LinkedHashMap; 30 import java.util.Map; 31 import java.util.Set; 32 import java.util.WeakHashMap; 33 34 import org.apache.commons.logging.Log; 35 import org.apache.commons.logging.LogFactory; 36 import org.springframework.util.ClassUtils; 37 import org.springframework.util.StringUtils; 38 39 /** 40 * Internal class that caches JavaBeans {@link java.beans.PropertyDescriptor} 41 * information for a Java class. Not intended for direct use by application code. 42 * 43 * <p>Necessary for own caching of descriptors within the application's 44 * ClassLoader, rather than rely on the JDK's system-wide BeanInfo cache 45 * (in order to avoid leaks on ClassLoader shutdown). 46 * 47 * <p>Information is cached statically, so we don't need to create new 48 * objects of this class for every JavaBean we manipulate. Hence, this class 49 * implements the factory design pattern, using a private constructor and 50 * a static {@link #forClass(Class)} factory method to obtain instances. 51 * 52 * @author Rod Johnson 53 * @author Juergen Hoeller 54 * @since 05 May 2001 55 * @see #acceptClassLoader(ClassLoader) 56 * @see #clearClassLoader(ClassLoader) 57 * @see #forClass(Class) 58 */ 59 public class CachedIntrospectionResults { 60 61 private static final Log logger = LogFactory.getLog(CachedIntrospectionResults.class); 62 63 /** 64 * Set of ClassLoaders that this CachedIntrospectionResults class will always 65 * accept classes from, even if the classes do not qualify as cache-safe. 66 */ 67 static final Set<ClassLoader> acceptedClassLoaders = Collections.synchronizedSet(new HashSet<ClassLoader>()); 68 69 /** 70 * Map keyed by class containing CachedIntrospectionResults. 71 * Needs to be a WeakHashMap with WeakReferences as values to allow 72 * for proper garbage collection in case of multiple class loaders. 73 */ 74 static final Map<Class, Object> classCache = Collections.synchronizedMap(new WeakHashMap<Class, Object>()); 75 76 77 /** 78 * Accept the given ClassLoader as cache-safe, even if its classes would 79 * not qualify as cache-safe in this CachedIntrospectionResults class. 80 * <p>This configuration method is only relevant in scenarios where the Spring 81 * classes reside in a 'common' ClassLoader (e.g. the system ClassLoader) 82 * whose lifecycle is not coupled to the application. In such a scenario, 83 * CachedIntrospectionResults would by default not cache any of the application's 84 * classes, since they would create a leak in the common ClassLoader. 85 * <p>Any <code>acceptClassLoader</code> call at application startup should 86 * be paired with a {@link #clearClassLoader} call at application shutdown. 87 * @param classLoader the ClassLoader to accept 88 */ acceptClassLoader(ClassLoader classLoader)89 public static void acceptClassLoader(ClassLoader classLoader) { 90 if (classLoader != null) { 91 acceptedClassLoaders.add(classLoader); 92 } 93 } 94 95 /** 96 * Clear the introspection cache for the given ClassLoader, removing the 97 * introspection results for all classes underneath that ClassLoader, 98 * and deregistering the ClassLoader (and any of its children) from the 99 * acceptance list. 100 * @param classLoader the ClassLoader to clear the cache for 101 */ clearClassLoader(ClassLoader classLoader)102 public static void clearClassLoader(ClassLoader classLoader) { 103 synchronized (classCache) { 104 for (Iterator<Class> it = classCache.keySet().iterator(); it.hasNext();) { 105 Class beanClass = it.next(); 106 if (isUnderneathClassLoader(beanClass.getClassLoader(), classLoader)) { 107 it.remove(); 108 } 109 } 110 } 111 synchronized (acceptedClassLoaders) { 112 for (Iterator<ClassLoader> it = acceptedClassLoaders.iterator(); it.hasNext();) { 113 ClassLoader registeredLoader = it.next(); 114 if (isUnderneathClassLoader(registeredLoader, classLoader)) { 115 it.remove(); 116 } 117 } 118 } 119 } 120 121 /** 122 * Create CachedIntrospectionResults for the given bean class. 123 * <P>We don't want to use synchronization here. Object references are atomic, 124 * so we can live with doing the occasional unnecessary lookup at startup only. 125 * @param beanClass the bean class to analyze 126 * @return the corresponding CachedIntrospectionResults 127 * @throws BeansException in case of introspection failure 128 */ forClass(Class beanClass)129 static CachedIntrospectionResults forClass(Class beanClass) throws BeansException { 130 CachedIntrospectionResults results; 131 Object value = classCache.get(beanClass); 132 if (value instanceof Reference) { 133 Reference ref = (Reference) value; 134 results = (CachedIntrospectionResults) ref.get(); 135 } 136 else { 137 results = (CachedIntrospectionResults) value; 138 } 139 if (results == null) { 140 if (ClassUtils.isCacheSafe(beanClass, CachedIntrospectionResults.class.getClassLoader()) || 141 isClassLoaderAccepted(beanClass.getClassLoader())) { 142 results = new CachedIntrospectionResults(beanClass); 143 classCache.put(beanClass, results); 144 } 145 else { 146 if (logger.isDebugEnabled()) { 147 logger.debug("Not strongly caching class [" + beanClass.getName() + "] because it is not cache-safe"); 148 } 149 results = new CachedIntrospectionResults(beanClass); 150 classCache.put(beanClass, new WeakReference<CachedIntrospectionResults>(results)); 151 } 152 } 153 return results; 154 } 155 156 /** 157 * Check whether this CachedIntrospectionResults class is configured 158 * to accept the given ClassLoader. 159 * @param classLoader the ClassLoader to check 160 * @return whether the given ClassLoader is accepted 161 * @see #acceptClassLoader 162 */ isClassLoaderAccepted(ClassLoader classLoader)163 private static boolean isClassLoaderAccepted(ClassLoader classLoader) { 164 // Iterate over array copy in order to avoid synchronization for the entire 165 // ClassLoader check (avoiding a synchronized acceptedClassLoaders Iterator). 166 ClassLoader[] acceptedLoaderArray = 167 acceptedClassLoaders.toArray(new ClassLoader[acceptedClassLoaders.size()]); 168 for (ClassLoader registeredLoader : acceptedLoaderArray) { 169 if (isUnderneathClassLoader(classLoader, registeredLoader)) { 170 return true; 171 } 172 } 173 return false; 174 } 175 176 /** 177 * Check whether the given ClassLoader is underneath the given parent, 178 * that is, whether the parent is within the candidate's hierarchy. 179 * @param candidate the candidate ClassLoader to check 180 * @param parent the parent ClassLoader to check for 181 */ isUnderneathClassLoader(ClassLoader candidate, ClassLoader parent)182 private static boolean isUnderneathClassLoader(ClassLoader candidate, ClassLoader parent) { 183 if (candidate == parent) { 184 return true; 185 } 186 if (candidate == null) { 187 return false; 188 } 189 ClassLoader classLoaderToCheck = candidate; 190 while (classLoaderToCheck != null) { 191 classLoaderToCheck = classLoaderToCheck.getParent(); 192 if (classLoaderToCheck == parent) { 193 return true; 194 } 195 } 196 return false; 197 } 198 199 200 /** The BeanInfo object for the introspected bean class */ 201 private final BeanInfo beanInfo; 202 203 /** PropertyDescriptor objects keyed by property name String */ 204 private final Map<String, PropertyDescriptor> propertyDescriptorCache; 205 206 207 /** 208 * Create a new CachedIntrospectionResults instance for the given class. 209 * @param beanClass the bean class to analyze 210 * @throws BeansException in case of introspection failure 211 */ CachedIntrospectionResults(Class beanClass)212 private CachedIntrospectionResults(Class beanClass) throws BeansException { 213 try { 214 if (logger.isTraceEnabled()) { 215 logger.trace("Getting BeanInfo for class [" + beanClass.getName() + "]"); 216 } 217 BeanInfo originalBeanInfo = Introspector.getBeanInfo(beanClass); 218 BeanInfo extendedBeanInfo = null; 219 for (Method method : beanClass.getMethods()) { 220 if (ExtendedBeanInfo.isCandidateWriteMethod(method)) { 221 extendedBeanInfo = new ExtendedBeanInfo(originalBeanInfo); 222 break; 223 } 224 } 225 this.beanInfo = extendedBeanInfo != null ? extendedBeanInfo : originalBeanInfo; 226 227 // Immediately remove class from Introspector cache, to allow for proper 228 // garbage collection on class loader shutdown - we cache it here anyway, 229 // in a GC-friendly manner. In contrast to CachedIntrospectionResults, 230 // Introspector does not use WeakReferences as values of its WeakHashMap! 231 Class classToFlush = beanClass; 232 do { 233 Introspector.flushFromCaches(classToFlush); 234 classToFlush = classToFlush.getSuperclass(); 235 } 236 while (classToFlush != null); 237 238 if (logger.isTraceEnabled()) { 239 logger.trace("Caching PropertyDescriptors for class [" + beanClass.getName() + "]"); 240 } 241 this.propertyDescriptorCache = new LinkedHashMap<String, PropertyDescriptor>(); 242 243 // This call is slow so we do it once. 244 PropertyDescriptor[] pds = this.beanInfo.getPropertyDescriptors(); 245 for (PropertyDescriptor pd : pds) { 246 if (Class.class.equals(beanClass) && "classLoader".equals(pd.getName())) { 247 // Ignore Class.getClassLoader() method - nobody needs to bind to that 248 continue; 249 } 250 if (logger.isTraceEnabled()) { 251 logger.trace("Found bean property '" + pd.getName() + "'" + 252 (pd.getPropertyType() != null ? " of type [" + pd.getPropertyType().getName() + "]" : "") + 253 (pd.getPropertyEditorClass() != null ? 254 "; editor [" + pd.getPropertyEditorClass().getName() + "]" : "")); 255 } 256 pd = buildGenericTypeAwarePropertyDescriptor(beanClass, pd); 257 this.propertyDescriptorCache.put(pd.getName(), pd); 258 } 259 } 260 catch (IntrospectionException ex) { 261 throw new FatalBeanException("Failed to obtain BeanInfo for class [" + beanClass.getName() + "]", ex); 262 } 263 } 264 getBeanInfo()265 BeanInfo getBeanInfo() { 266 return this.beanInfo; 267 } 268 getBeanClass()269 Class getBeanClass() { 270 return this.beanInfo.getBeanDescriptor().getBeanClass(); 271 } 272 getPropertyDescriptor(String name)273 PropertyDescriptor getPropertyDescriptor(String name) { 274 PropertyDescriptor pd = this.propertyDescriptorCache.get(name); 275 if (pd == null && StringUtils.hasLength(name)) { 276 // Same lenient fallback checking as in PropertyTypeDescriptor... 277 pd = this.propertyDescriptorCache.get(name.substring(0, 1).toLowerCase() + name.substring(1)); 278 if (pd == null) { 279 pd = this.propertyDescriptorCache.get(name.substring(0, 1).toUpperCase() + name.substring(1)); 280 } 281 } 282 return (pd == null || pd instanceof GenericTypeAwarePropertyDescriptor ? pd : 283 buildGenericTypeAwarePropertyDescriptor(getBeanClass(), pd)); 284 } 285 getPropertyDescriptors()286 PropertyDescriptor[] getPropertyDescriptors() { 287 PropertyDescriptor[] pds = new PropertyDescriptor[this.propertyDescriptorCache.size()]; 288 int i = 0; 289 for (PropertyDescriptor pd : this.propertyDescriptorCache.values()) { 290 pds[i] = (pd instanceof GenericTypeAwarePropertyDescriptor ? pd : 291 buildGenericTypeAwarePropertyDescriptor(getBeanClass(), pd)); 292 i++; 293 } 294 return pds; 295 } 296 buildGenericTypeAwarePropertyDescriptor(Class beanClass, PropertyDescriptor pd)297 private PropertyDescriptor buildGenericTypeAwarePropertyDescriptor(Class beanClass, PropertyDescriptor pd) { 298 try { 299 return new GenericTypeAwarePropertyDescriptor(beanClass, pd.getName(), pd.getReadMethod(), 300 pd.getWriteMethod(), pd.getPropertyEditorClass()); 301 } 302 catch (IntrospectionException ex) { 303 throw new FatalBeanException("Failed to re-introspect class [" + beanClass.getName() + "]", ex); 304 } 305 } 306 307 } 308