diff --git a/spring-context/src/main/java/org/springframework/context/annotation/AnnotationConfigApplicationContext.java b/spring-context/src/main/java/org/springframework/context/annotation/AnnotationConfigApplicationContext.java index eac46b98e6..61957aec47 100644 --- a/spring-context/src/main/java/org/springframework/context/annotation/AnnotationConfigApplicationContext.java +++ b/spring-context/src/main/java/org/springframework/context/annotation/AnnotationConfigApplicationContext.java @@ -38,10 +38,10 @@ import org.springframework.util.Assert; * deliberately override certain bean definitions via an extra {@code @Configuration} * class. * - *

See @{@link Configuration} Javadoc for usage examples. + *

See @{@link Configuration}'s javadoc for usage examples. * - * @author Chris Beams * @author Juergen Hoeller + * @author Chris Beams * @since 3.0 * @see #register * @see #scan @@ -138,12 +138,6 @@ public class AnnotationConfigApplicationContext extends GenericApplicationContex this.scanner.setScopeMetadataResolver(scopeMetadataResolver); } - @Override - protected void prepareRefresh() { - this.scanner.clearCache(); - super.prepareRefresh(); - } - //--------------------------------------------------------------------- // Implementation of AnnotationConfigRegistry diff --git a/spring-context/src/main/java/org/springframework/context/annotation/ClassPathBeanDefinitionScanner.java b/spring-context/src/main/java/org/springframework/context/annotation/ClassPathBeanDefinitionScanner.java index 89d004212e..53721d6b6f 100644 --- a/spring-context/src/main/java/org/springframework/context/annotation/ClassPathBeanDefinitionScanner.java +++ b/spring-context/src/main/java/org/springframework/context/annotation/ClassPathBeanDefinitionScanner.java @@ -132,16 +132,39 @@ public class ClassPathBeanDefinitionScanner extends ClassPathScanningCandidateCo * @since 3.1 * @see #setResourceLoader */ - public ClassPathBeanDefinitionScanner(BeanDefinitionRegistry registry, boolean useDefaultFilters, Environment environment) { - super(useDefaultFilters, environment); + public ClassPathBeanDefinitionScanner(BeanDefinitionRegistry registry, boolean useDefaultFilters, + Environment environment) { + + this(registry, useDefaultFilters, environment, + (registry instanceof ResourceLoader ? (ResourceLoader) registry : null)); + } + + /** + * Create a new {@code ClassPathBeanDefinitionScanner} for the given bean factory and + * using the given {@link Environment} when evaluating bean definition profile metadata. + * @param registry the {@code BeanFactory} to load bean definitions into, in the form + * of a {@code BeanDefinitionRegistry} + * @param useDefaultFilters whether to include the default filters for the + * {@link org.springframework.stereotype.Component @Component}, + * {@link org.springframework.stereotype.Repository @Repository}, + * {@link org.springframework.stereotype.Service @Service}, and + * {@link org.springframework.stereotype.Controller @Controller} stereotype annotations + * @param environment the Spring {@link Environment} to use when evaluating bean + * definition profile metadata + * @param resourceLoader the {@link ResourceLoader} to use + * @since 4.3.6 + */ + public ClassPathBeanDefinitionScanner(BeanDefinitionRegistry registry, boolean useDefaultFilters, + Environment environment, ResourceLoader resourceLoader) { Assert.notNull(registry, "BeanDefinitionRegistry must not be null"); this.registry = registry; - // Determine ResourceLoader to use. - if (this.registry instanceof ResourceLoader) { - setResourceLoader((ResourceLoader) this.registry); + if (useDefaultFilters) { + registerDefaultFilters(); } + setEnvironment(environment); + setResourceLoader(resourceLoader); } @@ -192,7 +215,8 @@ public class ClassPathBeanDefinitionScanner extends ClassPathScanningCandidateCo * @see #setScopedProxyMode */ public void setScopeMetadataResolver(ScopeMetadataResolver scopeMetadataResolver) { - this.scopeMetadataResolver = (scopeMetadataResolver != null ? scopeMetadataResolver : new AnnotationScopeMetadataResolver()); + this.scopeMetadataResolver = + (scopeMetadataResolver != null ? scopeMetadataResolver : new AnnotationScopeMetadataResolver()); } /** @@ -258,7 +282,8 @@ public class ClassPathBeanDefinitionScanner extends ClassPathScanningCandidateCo } if (checkCandidate(beanName, candidate)) { BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName); - definitionHolder = AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry); + definitionHolder = + AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry); beanDefinitions.add(definitionHolder); registerBeanDefinition(definitionHolder, this.registry); } diff --git a/spring-context/src/main/java/org/springframework/context/annotation/ClassPathScanningCandidateComponentProvider.java b/spring-context/src/main/java/org/springframework/context/annotation/ClassPathScanningCandidateComponentProvider.java index 37a8f34b7d..55171cb90f 100644 --- a/spring-context/src/main/java/org/springframework/context/annotation/ClassPathScanningCandidateComponentProvider.java +++ b/spring-context/src/main/java/org/springframework/context/annotation/ClassPathScanningCandidateComponentProvider.java @@ -41,7 +41,6 @@ import org.springframework.core.env.EnvironmentCapable; import org.springframework.core.env.StandardEnvironment; import org.springframework.core.io.Resource; import org.springframework.core.io.ResourceLoader; -import org.springframework.core.io.support.PathMatchingResourcePatternResolver; import org.springframework.core.io.support.ResourcePatternResolver; import org.springframework.core.io.support.ResourcePatternUtils; import org.springframework.core.type.classreading.CachingMetadataReaderFactory; @@ -63,7 +62,7 @@ import org.springframework.util.ClassUtils; * use {@link CandidateComponentsIndex the index} if it is available of scans the * classpath otherwise. Candidate components are identified by applying exclude and * include filters. {@link AnnotationTypeFilter}, {@link AssignableTypeFilter} include - * filters on an annotation/super-class that are annotated with {@link Indexed} are + * filters on an annotation/superclass that are annotated with {@link Indexed} are * supported: if any other include filter is specified, the index is ignored and * classpath scanning is used instead. * @@ -86,25 +85,32 @@ public class ClassPathScanningCandidateComponentProvider implements EnvironmentC static final String DEFAULT_RESOURCE_PATTERN = "**/*.class"; + protected final Log logger = LogFactory.getLog(getClass()); - private Environment environment; - - private ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver(); - - private MetadataReaderFactory metadataReaderFactory = - new CachingMetadataReaderFactory(this.resourcePatternResolver); - - private CandidateComponentsIndex componentsIndex; - private String resourcePattern = DEFAULT_RESOURCE_PATTERN; private final List includeFilters = new LinkedList<>(); private final List excludeFilters = new LinkedList<>(); + private Environment environment; + private ConditionEvaluator conditionEvaluator; + private ResourcePatternResolver resourcePatternResolver; + + private MetadataReaderFactory metadataReaderFactory; + + private CandidateComponentsIndex componentsIndex; + + + /** + * Protected constructor for flexible subclass initialization. + * @since 4.3.6 + */ + protected ClassPathScanningCandidateComponentProvider() { + } /** * Create a ClassPathScanningCandidateComponentProvider with a {@link StandardEnvironment}. @@ -131,75 +137,11 @@ public class ClassPathScanningCandidateComponentProvider implements EnvironmentC if (useDefaultFilters) { registerDefaultFilters(); } - Assert.notNull(environment, "Environment must not be null"); - this.environment = environment; + setEnvironment(environment); + setResourceLoader(null); } - /** - * Set the ResourceLoader to use for resource locations. - * This will typically be a ResourcePatternResolver implementation. - *

Default is PathMatchingResourcePatternResolver, also capable of - * resource pattern resolving through the ResourcePatternResolver interface. - * @see org.springframework.core.io.support.ResourcePatternResolver - * @see org.springframework.core.io.support.PathMatchingResourcePatternResolver - */ - @Override - public void setResourceLoader(ResourceLoader resourceLoader) { - this.resourcePatternResolver = ResourcePatternUtils.getResourcePatternResolver(resourceLoader); - this.metadataReaderFactory = new CachingMetadataReaderFactory(resourceLoader); - this.componentsIndex = CandidateComponentsIndexLoader.loadIndex(resourceLoader.getClassLoader()); - } - - /** - * Return the ResourceLoader that this component provider uses. - */ - public final ResourceLoader getResourceLoader() { - return this.resourcePatternResolver; - } - - /** - * Set the {@link MetadataReaderFactory} to use. - *

Default is a {@link CachingMetadataReaderFactory} for the specified - * {@linkplain #setResourceLoader resource loader}. - *

Call this setter method after {@link #setResourceLoader} in order - * for the given MetadataReaderFactory to override the default factory. - */ - public void setMetadataReaderFactory(MetadataReaderFactory metadataReaderFactory) { - this.metadataReaderFactory = metadataReaderFactory; - } - - /** - * Return the MetadataReaderFactory used by this component provider. - */ - public final MetadataReaderFactory getMetadataReaderFactory() { - return this.metadataReaderFactory; - } - - /** - * Set the Environment to use when resolving placeholders and evaluating - * {@link Conditional @Conditional}-annotated component classes. - *

The default is a {@link StandardEnvironment}. - * @param environment the Environment to use - */ - public void setEnvironment(Environment environment) { - Assert.notNull(environment, "Environment must not be null"); - this.environment = environment; - this.conditionEvaluator = null; - } - - @Override - public final Environment getEnvironment() { - return this.environment; - } - - /** - * Returns the {@link BeanDefinitionRegistry} used by this scanner, if any. - */ - protected BeanDefinitionRegistry getRegistry() { - return null; - } - /** * Set the resource pattern to use when scanning the classpath. * This value will be appended to each base package name. @@ -273,6 +215,70 @@ public class ClassPathScanningCandidateComponentProvider implements EnvironmentC } } + /** + * Set the Environment to use when resolving placeholders and evaluating + * {@link Conditional @Conditional}-annotated component classes. + *

The default is a {@link StandardEnvironment}. + * @param environment the Environment to use + */ + public void setEnvironment(Environment environment) { + Assert.notNull(environment, "Environment must not be null"); + this.environment = environment; + this.conditionEvaluator = null; + } + + @Override + public final Environment getEnvironment() { + return this.environment; + } + + /** + * Return the {@link BeanDefinitionRegistry} used by this scanner, if any. + */ + protected BeanDefinitionRegistry getRegistry() { + return null; + } + + /** + * Set the {@link ResourceLoader} to use for resource locations. + * This will typically be a {@link ResourcePatternResolver} implementation. + *

Default is a {@code PathMatchingResourcePatternResolver}, also capable of + * resource pattern resolving through the {@code ResourcePatternResolver} interface. + * @see org.springframework.core.io.support.ResourcePatternResolver + * @see org.springframework.core.io.support.PathMatchingResourcePatternResolver + */ + @Override + public void setResourceLoader(ResourceLoader resourceLoader) { + this.resourcePatternResolver = ResourcePatternUtils.getResourcePatternResolver(resourceLoader); + this.metadataReaderFactory = new CachingMetadataReaderFactory(resourceLoader); + this.componentsIndex = CandidateComponentsIndexLoader.loadIndex(this.resourcePatternResolver.getClassLoader()); + } + + /** + * Return the ResourceLoader that this component provider uses. + */ + public final ResourceLoader getResourceLoader() { + return this.resourcePatternResolver; + } + + /** + * Set the {@link MetadataReaderFactory} to use. + *

Default is a {@link CachingMetadataReaderFactory} for the specified + * {@linkplain #setResourceLoader resource loader}. + *

Call this setter method after {@link #setResourceLoader} in order + * for the given MetadataReaderFactory to override the default factory. + */ + public void setMetadataReaderFactory(MetadataReaderFactory metadataReaderFactory) { + this.metadataReaderFactory = metadataReaderFactory; + } + + /** + * Return the MetadataReaderFactory used by this component provider. + */ + public final MetadataReaderFactory getMetadataReaderFactory() { + return this.metadataReaderFactory; + } + /** * Scan the class path for candidate components. @@ -496,10 +502,12 @@ public class ClassPathScanningCandidateComponentProvider implements EnvironmentC } /** - * Clear the underlying metadata cache, removing all cached class metadata. + * Clear the local metadata cache, if any, removing all cached class metadata. */ public void clearCache() { if (this.metadataReaderFactory instanceof CachingMetadataReaderFactory) { + // Clear cache in externally provided MetadataReaderFactory; this is a no-op + // for a shared cache since it'll be cleared by the ApplicationContext. ((CachingMetadataReaderFactory) this.metadataReaderFactory).clearCache(); } } diff --git a/spring-context/src/main/java/org/springframework/context/annotation/ComponentScanAnnotationParser.java b/spring-context/src/main/java/org/springframework/context/annotation/ComponentScanAnnotationParser.java index fe1caf44b4..fc095c9a07 100644 --- a/spring-context/src/main/java/org/springframework/context/annotation/ComponentScanAnnotationParser.java +++ b/spring-context/src/main/java/org/springframework/context/annotation/ComponentScanAnnotationParser.java @@ -77,10 +77,8 @@ class ComponentScanAnnotationParser { Assert.state(this.environment != null, "Environment must not be null"); Assert.state(this.resourceLoader != null, "ResourceLoader must not be null"); - ClassPathBeanDefinitionScanner scanner = - new ClassPathBeanDefinitionScanner(this.registry, componentScan.getBoolean("useDefaultFilters")); - scanner.setEnvironment(this.environment); - scanner.setResourceLoader(this.resourceLoader); + ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(this.registry, + componentScan.getBoolean("useDefaultFilters"), this.environment, this.resourceLoader); Class generatorClass = componentScan.getClass("nameGenerator"); boolean useInheritedGenerator = (BeanNameGenerator.class == generatorClass); diff --git a/spring-context/src/main/java/org/springframework/context/annotation/ComponentScanBeanDefinitionParser.java b/spring-context/src/main/java/org/springframework/context/annotation/ComponentScanBeanDefinitionParser.java index d7c5afd336..96dad592e1 100644 --- a/spring-context/src/main/java/org/springframework/context/annotation/ComponentScanBeanDefinitionParser.java +++ b/spring-context/src/main/java/org/springframework/context/annotation/ComponentScanBeanDefinitionParser.java @@ -99,8 +99,6 @@ public class ComponentScanBeanDefinitionParser implements BeanDefinitionParser { // Delegate bean definition registration to scanner class. ClassPathBeanDefinitionScanner scanner = createScanner(parserContext.getReaderContext(), useDefaultFilters); - scanner.setResourceLoader(parserContext.getReaderContext().getResourceLoader()); - scanner.setEnvironment(parserContext.getReaderContext().getEnvironment()); scanner.setBeanDefinitionDefaults(parserContext.getDelegate().getBeanDefinitionDefaults()); scanner.setAutowireCandidatePatterns(parserContext.getDelegate().getAutowireCandidatePatterns()); @@ -128,7 +126,8 @@ public class ComponentScanBeanDefinitionParser implements BeanDefinitionParser { } protected ClassPathBeanDefinitionScanner createScanner(XmlReaderContext readerContext, boolean useDefaultFilters) { - return new ClassPathBeanDefinitionScanner(readerContext.getRegistry(), useDefaultFilters); + return new ClassPathBeanDefinitionScanner(readerContext.getRegistry(), useDefaultFilters, + readerContext.getEnvironment(), readerContext.getResourceLoader()); } protected void registerComponents( diff --git a/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassPostProcessor.java b/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassPostProcessor.java index 67a62f91ee..7c82cc02a1 100644 --- a/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassPostProcessor.java +++ b/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassPostProcessor.java @@ -350,6 +350,8 @@ public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPo } if (this.metadataReaderFactory instanceof CachingMetadataReaderFactory) { + // Clear cache in externally provided MetadataReaderFactory; this is a no-op + // for a shared cache since it'll be cleared by the ApplicationContext. ((CachingMetadataReaderFactory) this.metadataReaderFactory).clearCache(); } } diff --git a/spring-context/src/main/java/org/springframework/context/index/CandidateComponentsIndexLoader.java b/spring-context/src/main/java/org/springframework/context/index/CandidateComponentsIndexLoader.java index 103df5f9c2..5df96add55 100644 --- a/spring-context/src/main/java/org/springframework/context/index/CandidateComponentsIndexLoader.java +++ b/spring-context/src/main/java/org/springframework/context/index/CandidateComponentsIndexLoader.java @@ -40,8 +40,11 @@ import org.springframework.util.ConcurrentReferenceHashMap; */ public class CandidateComponentsIndexLoader { - - private static final Log logger = LogFactory.getLog(CandidateComponentsIndexLoader.class); + /** + * The location to look for components. + *

Can be present in multiple JAR files. + */ + public static final String COMPONENTS_RESOURCE_LOCATION = "META-INF/spring.components"; /** * System property that instructs Spring to ignore the index, i.e. @@ -54,18 +57,13 @@ public class CandidateComponentsIndexLoader { */ public static final String IGNORE_INDEX = "spring.index.ignore"; - private static final boolean shouldIgnoreIndex = - SpringProperties.getFlag(IGNORE_INDEX); + private static final boolean shouldIgnoreIndex = SpringProperties.getFlag(IGNORE_INDEX); - /** - * The location to look for components. - *

Can be present in multiple JAR files. - */ - public static final String COMPONENTS_RESOURCE_LOCATION = "META-INF/spring.components"; + private static final Log logger = LogFactory.getLog(CandidateComponentsIndexLoader.class); - private static final ConcurrentMap cache - = new ConcurrentReferenceHashMap<>(); + private static final ConcurrentMap cache = + new ConcurrentReferenceHashMap<>(); /** @@ -107,8 +105,8 @@ public class CandidateComponentsIndexLoader { return (totalCount > 0 ? new CandidateComponentsIndex(result) : null); } catch (IOException ex) { - throw new IllegalArgumentException("Unable to load indexes from location [" - + COMPONENTS_RESOURCE_LOCATION + "]", ex); + throw new IllegalArgumentException("Unable to load indexes from location [" + + COMPONENTS_RESOURCE_LOCATION + "]", ex); } } diff --git a/spring-context/src/main/java/org/springframework/context/support/AbstractApplicationContext.java b/spring-context/src/main/java/org/springframework/context/support/AbstractApplicationContext.java index 22fd285427..46417c985d 100644 --- a/spring-context/src/main/java/org/springframework/context/support/AbstractApplicationContext.java +++ b/spring-context/src/main/java/org/springframework/context/support/AbstractApplicationContext.java @@ -872,6 +872,9 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader * {@link org.springframework.context.event.ContextRefreshedEvent}. */ protected void finishRefresh() { + // Clear context-level resource caches (such as ASM metadata). + clearResourceCaches(); + // Initialize lifecycle processor for this context. initLifecycleProcessor(); diff --git a/spring-core/src/main/java/org/springframework/core/io/DefaultResourceLoader.java b/spring-core/src/main/java/org/springframework/core/io/DefaultResourceLoader.java index 34a15b7c7c..390ed21bdc 100644 --- a/spring-core/src/main/java/org/springframework/core/io/DefaultResourceLoader.java +++ b/spring-core/src/main/java/org/springframework/core/io/DefaultResourceLoader.java @@ -20,7 +20,9 @@ import java.net.MalformedURLException; import java.net.URL; import java.util.Collection; import java.util.LinkedHashSet; +import java.util.Map; import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; import org.springframework.util.Assert; import org.springframework.util.ClassUtils; @@ -47,6 +49,8 @@ public class DefaultResourceLoader implements ResourceLoader { private final Set protocolResolvers = new LinkedHashSet<>(4); + private final Map, Map> resourceCaches = new ConcurrentHashMap<>(4); + /** * Create a new DefaultResourceLoader. @@ -111,6 +115,26 @@ public class DefaultResourceLoader implements ResourceLoader { return this.protocolResolvers; } + /** + * Obtain a cache for the given value type, keyed by {@link Resource}. + * @param valueType the value type, e.g. an ASM {@code MetadataReader} + * @return the cache {@link Map}, shared at the {@code ResourceLoader} level + * @since 5.0 + */ + @SuppressWarnings("unchecked") + public Map getResourceCache(Class valueType) { + return (Map) this.resourceCaches.computeIfAbsent(valueType, key -> new ConcurrentHashMap<>()); + } + + /** + * Clear all resource caches in this resource loader. + * @since 5.0 + * @see #getResourceCache + */ + public void clearResourceCaches() { + this.resourceCaches.clear(); + } + @Override public Resource getResource(String location) { diff --git a/spring-core/src/main/java/org/springframework/core/type/classreading/CachingMetadataReaderFactory.java b/spring-core/src/main/java/org/springframework/core/type/classreading/CachingMetadataReaderFactory.java index ef8201d6b5..3745c16eee 100644 --- a/spring-core/src/main/java/org/springframework/core/type/classreading/CachingMetadataReaderFactory.java +++ b/spring-core/src/main/java/org/springframework/core/type/classreading/CachingMetadataReaderFactory.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,13 +19,15 @@ package org.springframework.core.type.classreading; import java.io.IOException; import java.util.LinkedHashMap; import java.util.Map; +import java.util.concurrent.ConcurrentMap; +import org.springframework.core.io.DefaultResourceLoader; import org.springframework.core.io.Resource; import org.springframework.core.io.ResourceLoader; /** * Caching implementation of the {@link MetadataReaderFactory} interface, - * caching {@link MetadataReader} per Spring {@link Resource} handle + * caching a {@link MetadataReader} instance per Spring {@link Resource} handle * (i.e. per ".class" file). * * @author Juergen Hoeller @@ -34,69 +36,86 @@ import org.springframework.core.io.ResourceLoader; */ public class CachingMetadataReaderFactory extends SimpleMetadataReaderFactory { - /** Default maximum number of entries for the MetadataReader cache: 256 */ + /** Default maximum number of entries for a local MetadataReader cache: 256 */ public static final int DEFAULT_CACHE_LIMIT = 256; - - private volatile int cacheLimit = DEFAULT_CACHE_LIMIT; - - @SuppressWarnings("serial") - private final Map metadataReaderCache = - new LinkedHashMap(DEFAULT_CACHE_LIMIT, 0.75f, true) { - @Override - protected boolean removeEldestEntry(Map.Entry eldest) { - return size() > getCacheLimit(); - } - }; + /** MetadataReader cache: either local or shared at the ResourceLoader level */ + private Map metadataReaderCache; /** - * Create a new CachingMetadataReaderFactory for the default class loader. + * Create a new CachingMetadataReaderFactory for the default class loader, + * using a local resource cache. */ public CachingMetadataReaderFactory() { super(); + setCacheLimit(DEFAULT_CACHE_LIMIT); } /** - * Create a new CachingMetadataReaderFactory for the given resource loader. - * @param resourceLoader the Spring ResourceLoader to use - * (also determines the ClassLoader to use) - */ - public CachingMetadataReaderFactory(ResourceLoader resourceLoader) { - super(resourceLoader); - } - - /** - * Create a new CachingMetadataReaderFactory for the given class loader. + * Create a new CachingMetadataReaderFactory for the given {@link ClassLoader}, + * using a local resource cache. * @param classLoader the ClassLoader to use */ public CachingMetadataReaderFactory(ClassLoader classLoader) { super(classLoader); + setCacheLimit(DEFAULT_CACHE_LIMIT); + } + + /** + * Create a new CachingMetadataReaderFactory for the given {@link ResourceLoader}, + * using a shared resource cache if supported or a local resource cache otherwise. + * @param resourceLoader the Spring ResourceLoader to use + * (also determines the ClassLoader to use) + * @see DefaultResourceLoader#getResourceCache + */ + public CachingMetadataReaderFactory(ResourceLoader resourceLoader) { + super(resourceLoader); + if (resourceLoader instanceof DefaultResourceLoader) { + this.metadataReaderCache = + ((DefaultResourceLoader) resourceLoader).getResourceCache(MetadataReader.class); + } + else { + setCacheLimit(DEFAULT_CACHE_LIMIT); + } } /** * Specify the maximum number of entries for the MetadataReader cache. - * Default is 256. + *

Default is 256 for a local cache, whereas a shared cache is + * typically unbounded. This method enforces a local resource cache, + * even if the {@link ResourceLoader} supports a shared resource cache. */ public void setCacheLimit(int cacheLimit) { - this.cacheLimit = cacheLimit; + if (cacheLimit <= 0) { + this.metadataReaderCache = null; + } + else if (this.metadataReaderCache instanceof LocalResourceCache) { + ((LocalResourceCache) this.metadataReaderCache).setCacheLimit(cacheLimit); + } + else { + this.metadataReaderCache = new LocalResourceCache(cacheLimit); + } } /** * Return the maximum number of entries for the MetadataReader cache. */ public int getCacheLimit() { - return this.cacheLimit; + if (this.metadataReaderCache instanceof LocalResourceCache) { + return ((LocalResourceCache) this.metadataReaderCache).getCacheLimit(); + } + else { + return (this.metadataReaderCache != null ? Integer.MAX_VALUE : 0); + } } @Override public MetadataReader getMetadataReader(Resource resource) throws IOException { - if (getCacheLimit() <= 0) { - return super.getMetadataReader(resource); - } - synchronized (this.metadataReaderCache) { + if (this.metadataReaderCache instanceof ConcurrentMap) { + // No synchronization necessary... MetadataReader metadataReader = this.metadataReaderCache.get(resource); if (metadataReader == null) { metadataReader = super.getMetadataReader(resource); @@ -104,14 +123,53 @@ public class CachingMetadataReaderFactory extends SimpleMetadataReaderFactory { } return metadataReader; } + else if (this.metadataReaderCache != null) { + synchronized (this.metadataReaderCache) { + MetadataReader metadataReader = this.metadataReaderCache.get(resource); + if (metadataReader == null) { + metadataReader = super.getMetadataReader(resource); + this.metadataReaderCache.put(resource, metadataReader); + } + return metadataReader; + } + } + else { + return super.getMetadataReader(resource); + } } /** - * Clear the entire MetadataReader cache, removing all cached class metadata. + * Clear the local MetadataReader cache, if any, removing all cached class metadata. */ public void clearCache() { - synchronized (this.metadataReaderCache) { - this.metadataReaderCache.clear(); + if (this.metadataReaderCache instanceof LocalResourceCache) { + synchronized (this.metadataReaderCache) { + this.metadataReaderCache.clear(); + } + } + } + + + @SuppressWarnings("serial") + private static class LocalResourceCache extends LinkedHashMap { + + private volatile int cacheLimit; + + public LocalResourceCache(int cacheLimit) { + super(cacheLimit, 0.75f, true); + } + + public void setCacheLimit(int cacheLimit) { + this.cacheLimit = cacheLimit; + } + + public int getCacheLimit() { + return this.cacheLimit; + } + + @Override + protected boolean removeEldestEntry(Map.Entry eldest) { + return size() > this.cacheLimit; } }