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.core.type.classreading; 018 019import java.io.IOException; 020import java.util.LinkedHashMap; 021import java.util.Map; 022import java.util.concurrent.ConcurrentMap; 023 024import org.springframework.core.io.DefaultResourceLoader; 025import org.springframework.core.io.Resource; 026import org.springframework.core.io.ResourceLoader; 027import org.springframework.lang.Nullable; 028 029/** 030 * Caching implementation of the {@link MetadataReaderFactory} interface, 031 * caching a {@link MetadataReader} instance per Spring {@link Resource} handle 032 * (i.e. per ".class" file). 033 * 034 * @author Juergen Hoeller 035 * @author Costin Leau 036 * @since 2.5 037 */ 038public class CachingMetadataReaderFactory extends SimpleMetadataReaderFactory { 039 040 /** Default maximum number of entries for a local MetadataReader cache: 256. */ 041 public static final int DEFAULT_CACHE_LIMIT = 256; 042 043 /** MetadataReader cache: either local or shared at the ResourceLoader level. */ 044 @Nullable 045 private Map<Resource, MetadataReader> metadataReaderCache; 046 047 048 /** 049 * Create a new CachingMetadataReaderFactory for the default class loader, 050 * using a local resource cache. 051 */ 052 public CachingMetadataReaderFactory() { 053 super(); 054 setCacheLimit(DEFAULT_CACHE_LIMIT); 055 } 056 057 /** 058 * Create a new CachingMetadataReaderFactory for the given {@link ClassLoader}, 059 * using a local resource cache. 060 * @param classLoader the ClassLoader to use 061 */ 062 public CachingMetadataReaderFactory(@Nullable ClassLoader classLoader) { 063 super(classLoader); 064 setCacheLimit(DEFAULT_CACHE_LIMIT); 065 } 066 067 /** 068 * Create a new CachingMetadataReaderFactory for the given {@link ResourceLoader}, 069 * using a shared resource cache if supported or a local resource cache otherwise. 070 * @param resourceLoader the Spring ResourceLoader to use 071 * (also determines the ClassLoader to use) 072 * @see DefaultResourceLoader#getResourceCache 073 */ 074 public CachingMetadataReaderFactory(@Nullable ResourceLoader resourceLoader) { 075 super(resourceLoader); 076 if (resourceLoader instanceof DefaultResourceLoader) { 077 this.metadataReaderCache = 078 ((DefaultResourceLoader) resourceLoader).getResourceCache(MetadataReader.class); 079 } 080 else { 081 setCacheLimit(DEFAULT_CACHE_LIMIT); 082 } 083 } 084 085 086 /** 087 * Specify the maximum number of entries for the MetadataReader cache. 088 * <p>Default is 256 for a local cache, whereas a shared cache is 089 * typically unbounded. This method enforces a local resource cache, 090 * even if the {@link ResourceLoader} supports a shared resource cache. 091 */ 092 public void setCacheLimit(int cacheLimit) { 093 if (cacheLimit <= 0) { 094 this.metadataReaderCache = null; 095 } 096 else if (this.metadataReaderCache instanceof LocalResourceCache) { 097 ((LocalResourceCache) this.metadataReaderCache).setCacheLimit(cacheLimit); 098 } 099 else { 100 this.metadataReaderCache = new LocalResourceCache(cacheLimit); 101 } 102 } 103 104 /** 105 * Return the maximum number of entries for the MetadataReader cache. 106 */ 107 public int getCacheLimit() { 108 if (this.metadataReaderCache instanceof LocalResourceCache) { 109 return ((LocalResourceCache) this.metadataReaderCache).getCacheLimit(); 110 } 111 else { 112 return (this.metadataReaderCache != null ? Integer.MAX_VALUE : 0); 113 } 114 } 115 116 117 @Override 118 public MetadataReader getMetadataReader(Resource resource) throws IOException { 119 if (this.metadataReaderCache instanceof ConcurrentMap) { 120 // No synchronization necessary... 121 MetadataReader metadataReader = this.metadataReaderCache.get(resource); 122 if (metadataReader == null) { 123 metadataReader = super.getMetadataReader(resource); 124 this.metadataReaderCache.put(resource, metadataReader); 125 } 126 return metadataReader; 127 } 128 else if (this.metadataReaderCache != null) { 129 synchronized (this.metadataReaderCache) { 130 MetadataReader metadataReader = this.metadataReaderCache.get(resource); 131 if (metadataReader == null) { 132 metadataReader = super.getMetadataReader(resource); 133 this.metadataReaderCache.put(resource, metadataReader); 134 } 135 return metadataReader; 136 } 137 } 138 else { 139 return super.getMetadataReader(resource); 140 } 141 } 142 143 /** 144 * Clear the local MetadataReader cache, if any, removing all cached class metadata. 145 */ 146 public void clearCache() { 147 if (this.metadataReaderCache instanceof LocalResourceCache) { 148 synchronized (this.metadataReaderCache) { 149 this.metadataReaderCache.clear(); 150 } 151 } 152 else if (this.metadataReaderCache != null) { 153 // Shared resource cache -> reset to local cache. 154 setCacheLimit(DEFAULT_CACHE_LIMIT); 155 } 156 } 157 158 159 @SuppressWarnings("serial") 160 private static class LocalResourceCache extends LinkedHashMap<Resource, MetadataReader> { 161 162 private volatile int cacheLimit; 163 164 public LocalResourceCache(int cacheLimit) { 165 super(cacheLimit, 0.75f, true); 166 this.cacheLimit = cacheLimit; 167 } 168 169 public void setCacheLimit(int cacheLimit) { 170 this.cacheLimit = cacheLimit; 171 } 172 173 public int getCacheLimit() { 174 return this.cacheLimit; 175 } 176 177 @Override 178 protected boolean removeEldestEntry(Map.Entry<Resource, MetadataReader> eldest) { 179 return size() > this.cacheLimit; 180 } 181 } 182 183}