Share MetadataReaderFactory

Add SharedMetadataReaderFactoryContextInitializer to ensure that a
shared caching MetadataReaderFactory is used between configuration
classes and auto-configure sorting.

Fixes gh-4993
pull/5060/head
Phillip Webb 9 years ago
parent 26e0807d33
commit 26dfbeb8f4

@ -28,9 +28,7 @@ import java.util.Map;
import java.util.Set;
import org.springframework.core.Ordered;
import org.springframework.core.io.ResourceLoader;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.core.type.classreading.CachingMetadataReaderFactory;
import org.springframework.core.type.classreading.MetadataReader;
import org.springframework.core.type.classreading.MetadataReaderFactory;
import org.springframework.util.Assert;
@ -44,11 +42,11 @@ import org.springframework.util.Assert;
*/
class AutoConfigurationSorter {
private final CachingMetadataReaderFactory metadataReaderFactory;
private final MetadataReaderFactory metadataReaderFactory;
AutoConfigurationSorter(ResourceLoader resourceLoader) {
Assert.notNull(resourceLoader, "ResourceLoader must not be null");
this.metadataReaderFactory = new CachingMetadataReaderFactory(resourceLoader);
AutoConfigurationSorter(MetadataReaderFactory metadataReaderFactory) {
Assert.notNull(metadataReaderFactory, "MetadataReaderFactory must not be null");
this.metadataReaderFactory = metadataReaderFactory;
}
public List<String> getInPriorityOrder(Collection<String> classNames)

@ -28,6 +28,7 @@ import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanClassLoaderAware;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.boot.autoconfigure.condition.ConditionEvaluationReport;
import org.springframework.boot.bind.PropertySourcesPropertyValues;
@ -44,6 +45,8 @@ import org.springframework.core.env.Environment;
import org.springframework.core.io.ResourceLoader;
import org.springframework.core.io.support.SpringFactoriesLoader;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.core.type.classreading.CachingMetadataReaderFactory;
import org.springframework.core.type.classreading.MetadataReaderFactory;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
@ -169,11 +172,22 @@ public class EnableAutoConfigurationImportSelector implements DeferredImportSele
}
private List<String> sort(List<String> configurations) throws IOException {
configurations = new AutoConfigurationSorter(getResourceLoader())
configurations = new AutoConfigurationSorter(getMetadataReaderFactory())
.getInPriorityOrder(configurations);
return configurations;
}
private MetadataReaderFactory getMetadataReaderFactory() {
try {
return getBeanFactory().getBean(
SharedMetadataReaderFactoryContextInitializer.BEAN_NAME,
MetadataReaderFactory.class);
}
catch (NoSuchBeanDefinitionException ex) {
return new CachingMetadataReaderFactory(this.resourceLoader);
}
}
private void recordWithConditionEvaluationReport(List<String> configurations,
Collection<String> exclusions) throws IOException {
ConditionEvaluationReport report = ConditionEvaluationReport

@ -0,0 +1,145 @@
/*
* Copyright 2012-2015 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.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.autoconfigure;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanClassLoaderAware;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.config.RuntimeBeanReference;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.boot.type.classreading.ConcurrentReferenceCachingMetadataReaderFactory;
import org.springframework.context.ApplicationContextInitializer;
import org.springframework.context.ApplicationListener;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.AnnotationConfigUtils;
import org.springframework.context.annotation.ConfigurationClassPostProcessor;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.core.Ordered;
import org.springframework.core.PriorityOrdered;
import org.springframework.core.type.classreading.CachingMetadataReaderFactory;
import org.springframework.core.type.classreading.MetadataReaderFactory;
/**
* {@link ApplicationContextInitializer} to create a shared
* {@link CachingMetadataReaderFactory} between the
* {@link ConfigurationClassPostProcessor} and Spring Boot.
*
* @author Phillip Webb
* @since 1.4.0
*/
class SharedMetadataReaderFactoryContextInitializer
implements ApplicationContextInitializer<ConfigurableApplicationContext> {
public static final String BEAN_NAME = "org.springframework.boot.autoconfigure."
+ "internalCachingMetadataReaderFactory";
@Override
public void initialize(ConfigurableApplicationContext applicationContext) {
applicationContext.addBeanFactoryPostProcessor(
new CachingMetadataReaderFactoryPostProcessor());
}
/**
* {@link BeanDefinitionRegistryPostProcessor} to register the
* {@link CachingMetadataReaderFactory} and configure the
* {@link ConfigurationClassPostProcessor}.
*/
private static class CachingMetadataReaderFactoryPostProcessor
implements BeanDefinitionRegistryPostProcessor, PriorityOrdered {
@Override
public int getOrder() {
// Must happen before the ConfigurationClassPostProcessor is created
return Ordered.HIGHEST_PRECEDENCE;
}
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory)
throws BeansException {
}
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry)
throws BeansException {
register(registry);
configureConfigurationClassPostProcessor(registry);
}
private void register(BeanDefinitionRegistry registry) {
RootBeanDefinition definition = new RootBeanDefinition(
SharedMetadataReaderFactoryBean.class);
registry.registerBeanDefinition(BEAN_NAME, definition);
}
private void configureConfigurationClassPostProcessor(
BeanDefinitionRegistry registry) {
try {
BeanDefinition definition = registry.getBeanDefinition(
AnnotationConfigUtils.CONFIGURATION_ANNOTATION_PROCESSOR_BEAN_NAME);
definition.getPropertyValues().add("metadataReaderFactory",
new RuntimeBeanReference(BEAN_NAME));
}
catch (NoSuchBeanDefinitionException ex) {
}
}
}
/**
* {@link FactoryBean} to create the shared {@link MetadataReaderFactory}.
*/
static class SharedMetadataReaderFactoryBean
implements FactoryBean<ConcurrentReferenceCachingMetadataReaderFactory>,
BeanClassLoaderAware, ApplicationListener<ContextRefreshedEvent> {
private ConcurrentReferenceCachingMetadataReaderFactory metadataReaderFactory;
@Override
public void setBeanClassLoader(ClassLoader classLoader) {
this.metadataReaderFactory = new ConcurrentReferenceCachingMetadataReaderFactory(
classLoader);
}
@Override
public ConcurrentReferenceCachingMetadataReaderFactory getObject()
throws Exception {
return this.metadataReaderFactory;
}
@Override
public Class<?> getObjectType() {
return CachingMetadataReaderFactory.class;
}
@Override
public boolean isSingleton() {
return true;
}
@Override
public void onApplicationEvent(ContextRefreshedEvent event) {
this.metadataReaderFactory.clearCache();
}
}
}

@ -1,5 +1,6 @@
# Initializers
org.springframework.context.ApplicationContextInitializer=\
org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer,\
org.springframework.boot.autoconfigure.logging.AutoConfigurationReportLoggingInitializer
# Application Listeners

@ -29,7 +29,7 @@ import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.springframework.core.Ordered;
import org.springframework.core.io.DefaultResourceLoader;
import org.springframework.core.type.classreading.CachingMetadataReaderFactory;
import static org.junit.Assert.assertThat;
@ -61,7 +61,7 @@ public class AutoConfigurationSorterTests {
@Before
public void setup() {
this.sorter = new AutoConfigurationSorter(new DefaultResourceLoader());
this.sorter = new AutoConfigurationSorter(new CachingMetadataReaderFactory());
}
@Test

@ -125,7 +125,6 @@ public class EnableAutoConfigurationImportSelectorTests {
configureExclusions(new String[0], new String[0],
new String[] { FreeMarkerAutoConfiguration.class.getName(),
VelocityAutoConfiguration.class.getName() });
String[] imports = this.importSelector.selectImports(this.annotationMetadata);
assertThat(imports.length,
is(equalTo(getAutoConfigurationClassNames().size() - 2)));

@ -16,7 +16,7 @@
package org.springframework.boot.autoconfigure;
import org.springframework.core.io.ResourceLoader;
import org.springframework.core.type.classreading.MetadataReaderFactory;
/**
* Public version of {@link AutoConfigurationSorter} for use in tests.
@ -25,8 +25,8 @@ import org.springframework.core.io.ResourceLoader;
*/
public class TestAutoConfigurationSorter extends AutoConfigurationSorter {
public TestAutoConfigurationSorter(ResourceLoader resourceLoader) {
super(resourceLoader);
public TestAutoConfigurationSorter(MetadataReaderFactory metadataReaderFactory) {
super(metadataReaderFactory);
}
}

@ -27,8 +27,7 @@ import org.springframework.boot.autoconfigure.data.jpa.JpaRepositoriesAutoConfig
import org.springframework.boot.autoconfigure.data.mongo.MongoRepositoriesAutoConfiguration;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.boot.autoconfigure.mongo.MongoAutoConfiguration;
import org.springframework.core.io.DefaultResourceLoader;
import org.springframework.core.io.ResourceLoader;
import org.springframework.core.type.classreading.CachingMetadataReaderFactory;
import static org.hamcrest.Matchers.equalTo;
import static org.junit.Assert.assertThat;
@ -42,8 +41,8 @@ public class CloudAutoConfigurationTests {
@Test
public void testOrder() throws Exception {
ResourceLoader loader = new DefaultResourceLoader();
TestAutoConfigurationSorter sorter = new TestAutoConfigurationSorter(loader);
TestAutoConfigurationSorter sorter = new TestAutoConfigurationSorter(
new CachingMetadataReaderFactory());
Collection<String> classNames = new ArrayList<String>();
classNames.add(MongoAutoConfiguration.class.getName());
classNames.add(DataSourceAutoConfiguration.class.getName());

@ -0,0 +1,99 @@
/*
* Copyright 2012-2015 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.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.type.classreading;
import java.io.IOException;
import java.util.Map;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
import org.springframework.core.type.classreading.CachingMetadataReaderFactory;
import org.springframework.core.type.classreading.MetadataReader;
import org.springframework.core.type.classreading.MetadataReaderFactory;
import org.springframework.core.type.classreading.SimpleMetadataReaderFactory;
import org.springframework.util.ConcurrentReferenceHashMap;
/**
* Caching implementation of the {@link MetadataReaderFactory} interface backed by a
* {@link ConcurrentReferenceHashMap} , caching {@link MetadataReader} per Spring
* {@link Resource} handle (i.e. per ".class" file).
*
* @author Phillip Webb
* @since 1.4.0
* @see CachingMetadataReaderFactory
*/
public class ConcurrentReferenceCachingMetadataReaderFactory
extends SimpleMetadataReaderFactory {
private final Map<Resource, MetadataReader> cache = new ConcurrentReferenceHashMap<Resource, MetadataReader>();
/**
* Create a new {@link ConcurrentReferenceCachingMetadataReaderFactory} instance for
* the default class loader.
*/
public ConcurrentReferenceCachingMetadataReaderFactory() {
super();
}
/**
* Create a new {@link ConcurrentReferenceCachingMetadataReaderFactory} instance for
* the given resource loader.
* @param resourceLoader the Spring ResourceLoader to use (also determines the
* ClassLoader to use)
*/
public ConcurrentReferenceCachingMetadataReaderFactory(
ResourceLoader resourceLoader) {
super(resourceLoader);
}
/**
* Create a new {@link ConcurrentReferenceCachingMetadataReaderFactory} instance for
* the given class loader.
* @param classLoader the ClassLoader to use
*/
public ConcurrentReferenceCachingMetadataReaderFactory(ClassLoader classLoader) {
super(classLoader);
}
@Override
public MetadataReader getMetadataReader(Resource resource) throws IOException {
MetadataReader metadataReader = this.cache.get(resource);
if (metadataReader == null) {
metadataReader = createMetadataReader(resource);
this.cache.put(resource, metadataReader);
}
return metadataReader;
}
/**
* Create the meta-data reader.
* @param resource the source resource.
* @return the meta-data reader
* @throws IOException on error
*/
protected MetadataReader createMetadataReader(Resource resource) throws IOException {
return super.getMetadataReader(resource);
}
/**
* Clear the entire MetadataReader cache, removing all cached class metadata.
*/
public void clearCache() {
this.cache.clear();
}
}

@ -0,0 +1,73 @@
/*
* Copyright 2012-2015 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.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.type.classreading;
import java.io.IOException;
import org.junit.Test;
import org.springframework.core.io.Resource;
import org.springframework.core.type.classreading.MetadataReader;
import static org.hamcrest.Matchers.not;
import static org.hamcrest.Matchers.sameInstance;
import static org.junit.Assert.assertThat;
import static org.mockito.Matchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
/**
* Tests for {@link ConcurrentReferenceCachingMetadataReaderFactory}.
*
* @author Phillip Webb
*/
public class ConcurrentReferenceCachingMetadataReaderFactoryTests {
@Test
public void getMetadataReaderUsesCache() throws Exception {
TestConcurrentReferenceCachingMetadataReaderFactory factory = spy(
new TestConcurrentReferenceCachingMetadataReaderFactory());
MetadataReader metadataReader1 = factory.getMetadataReader(getClass().getName());
MetadataReader metadataReader2 = factory.getMetadataReader(getClass().getName());
assertThat(metadataReader1, sameInstance(metadataReader2));
verify(factory, times(1)).createMetadataReader((Resource) any());
}
@Test
public void clearResetsCache() throws Exception {
TestConcurrentReferenceCachingMetadataReaderFactory factory = spy(
new TestConcurrentReferenceCachingMetadataReaderFactory());
MetadataReader metadataReader1 = factory.getMetadataReader(getClass().getName());
factory.clearCache();
MetadataReader metadataReader2 = factory.getMetadataReader(getClass().getName());
assertThat(metadataReader1, not(sameInstance(metadataReader2)));
verify(factory, times(2)).createMetadataReader((Resource) any());
}
private static class TestConcurrentReferenceCachingMetadataReaderFactory
extends ConcurrentReferenceCachingMetadataReaderFactory {
@Override
public MetadataReader createMetadataReader(Resource resource) throws IOException {
return mock(MetadataReader.class);
}
}
}
Loading…
Cancel
Save