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}