ConfigurationClassParser.java 18.6 KB
Newer Older
1
/*
2
 * Copyright 2002-2012 the original author or authors.
3 4 5 6 7 8 9 10 11 12 13 14 15
 *
 * 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.
 */
C
Chris Beams 已提交
16

17
package org.springframework.context.annotation;
18

19
import java.io.IOException;
20
import java.util.Arrays;
21 22
import java.util.Collections;
import java.util.Comparator;
C
Chris Beams 已提交
23
import java.util.HashMap;
24
import java.util.HashSet;
25
import java.util.Iterator;
26
import java.util.LinkedHashSet;
27
import java.util.Map;
28
import java.util.Set;
29
import java.util.Stack;
30

C
Chris Beams 已提交
31
import org.springframework.beans.BeanUtils;
32 33 34 35
import org.springframework.beans.factory.Aware;
import org.springframework.beans.factory.BeanClassLoaderAware;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
36
import org.springframework.beans.factory.config.BeanDefinitionHolder;
37
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
38 39
import org.springframework.beans.factory.parsing.Location;
import org.springframework.beans.factory.parsing.Problem;
40
import org.springframework.beans.factory.parsing.ProblemReporter;
41
import org.springframework.beans.factory.support.BeanDefinitionReader;
42
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
43
import org.springframework.beans.factory.support.BeanNameGenerator;
44
import org.springframework.context.ResourceLoaderAware;
45
import org.springframework.core.annotation.AnnotationAttributes;
46
import org.springframework.core.env.CompositePropertySource;
47
import org.springframework.core.env.Environment;
C
Chris Beams 已提交
48
import org.springframework.core.env.PropertySource;
49
import org.springframework.core.io.ResourceLoader;
50
import org.springframework.core.io.support.ResourcePropertySource;
51 52 53 54 55
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.core.type.MethodMetadata;
import org.springframework.core.type.StandardAnnotationMetadata;
import org.springframework.core.type.classreading.MetadataReader;
import org.springframework.core.type.classreading.MetadataReaderFactory;
C
Chris Beams 已提交
56
import org.springframework.core.type.filter.AssignableTypeFilter;
57
import org.springframework.util.StringUtils;
58

59 60
import static org.springframework.context.annotation.MetadataUtils.*;

61
/**
62
 * Parses a {@link Configuration} class definition, populating a collection of
63 64 65
 * {@link ConfigurationClass} objects (parsing a single Configuration class may result in
 * any number of ConfigurationClass objects because one Configuration class may import
 * another using the {@link Import} annotation).
66
 *
67
 * <p>This class helps separate the concern of parsing the structure of a Configuration
68
 * class from the concern of registering BeanDefinition objects based on the
69
 * content of that model.
70
 *
71 72 73
 * <p>This ASM-based implementation avoids reflection and eager class loading in order to
 * interoperate effectively with lazy class loading in a Spring ApplicationContext.
 *
74
 * @author Chris Beams
75 76 77
 * @author Juergen Hoeller
 * @since 3.0
 * @see ConfigurationClassBeanDefinitionReader
C
Chris Beams 已提交
78
 */
79
class ConfigurationClassParser {
80

81
	private final MetadataReaderFactory metadataReaderFactory;
82

83
	private final ProblemReporter problemReporter;
84

C
Chris Beams 已提交
85
	private final ImportStack importStack = new ImportStack();
86

87 88
	private final Set<String> knownSuperclasses = new LinkedHashSet<String>();

89 90
	private final Set<ConfigurationClass> configurationClasses =
		new LinkedHashSet<ConfigurationClass>();
91

C
Chris Beams 已提交
92 93 94
	private final Stack<PropertySource<?>> propertySources =
		new Stack<PropertySource<?>>();

95 96
	private final Environment environment;

97 98
	private final ResourceLoader resourceLoader;

99 100
	private final BeanDefinitionRegistry registry;

101 102
	private final ComponentScanAnnotationParser componentScanParser;

103

104
	/**
105
	 * Create a new {@link ConfigurationClassParser} instance that will be used
106
	 * to populate the set of configuration classes.
107
	 */
108
	public ConfigurationClassParser(MetadataReaderFactory metadataReaderFactory,
109 110
			ProblemReporter problemReporter, Environment environment, ResourceLoader resourceLoader,
			BeanNameGenerator componentScanBeanNameGenerator, BeanDefinitionRegistry registry) {
111

112
		this.metadataReaderFactory = metadataReaderFactory;
113
		this.problemReporter = problemReporter;
114
		this.environment = environment;
115
		this.resourceLoader = resourceLoader;
116
		this.registry = registry;
117
		this.componentScanParser = new ComponentScanAnnotationParser(
118
				resourceLoader, environment, componentScanBeanNameGenerator, registry);
119 120
	}

121

122
	/**
123 124 125 126
	 * Parse the specified {@link Configuration @Configuration} class.
	 * @param className the name of the class to parse
	 * @param beanName may be null, but if populated represents the bean id
	 * (assumes that this configuration class was configured via XML)
127
	 */
128
	public void parse(String className, String beanName) throws IOException {
129 130 131 132 133 134
		MetadataReader reader = this.metadataReaderFactory.getMetadataReader(className);
		processConfigurationClass(new ConfigurationClass(reader, beanName));
	}

	/**
	 * Parse the specified {@link Configuration @Configuration} class.
135
	 * @param clazz the Class to parse
136
	 * @param beanName must not be null (as of Spring 3.1.1)
137
	 */
138
	public void parse(Class<?> clazz, String beanName) throws IOException {
139 140 141 142
		processConfigurationClass(new ConfigurationClass(clazz, beanName));
	}

	protected void processConfigurationClass(ConfigurationClass configClass) throws IOException {
143
		AnnotationMetadata metadata = configClass.getMetadata();
144 145 146
		if (this.environment != null && metadata.isAnnotated(Profile.class.getName())) {
			AnnotationAttributes profile = MetadataUtils.attributesFor(metadata, Profile.class);
			if (!this.environment.acceptsProfiles(profile.getStringArray("value"))) {
147
				return;
148 149 150
			}
		}

151 152 153
		// recursively process the configuration class and its superclass hierarchy
		do {
			metadata = doProcessConfigurationClass(configClass, metadata);
154
		}
155 156
		while (metadata != null);

157
		if (this.configurationClasses.contains(configClass) && configClass.getBeanName() != null) {
158 159
			// Explicit bean definition found, probably replacing an import.
			// Let's remove the old one and go with the new one.
160
			this.configurationClasses.remove(configClass);
161
		}
162

163
		this.configurationClasses.add(configClass);
164
	}
165

166 167 168 169 170
	/**
	 * @return annotation metadata of superclass, null if none found or previously processed
	 */
	protected AnnotationMetadata doProcessConfigurationClass(
			ConfigurationClass configClass, AnnotationMetadata metadata) throws IOException {
171 172 173 174 175

		// recursively process any member (nested) classes first
		for (String memberClassName : metadata.getMemberClassNames()) {
			MetadataReader reader = this.metadataReaderFactory.getMetadataReader(memberClassName);
			AnnotationMetadata memberClassMetadata = reader.getAnnotationMetadata();
176
			if (ConfigurationClassUtils.isConfigurationCandidate(memberClassMetadata)) {
177
				processConfigurationClass(new ConfigurationClass(reader, true));
178 179 180 181
			}
		}

		// process any @PropertySource annotations
182 183 184 185 186
		AnnotationAttributes propertySource =
				attributesFor(metadata, org.springframework.context.annotation.PropertySource.class);
		if (propertySource != null) {
			String name = propertySource.getString("name");
			String[] locations = propertySource.getStringArray("value");
187
			int nLocations = locations.length;
188 189 190
			if (nLocations == 0) {
				throw new IllegalArgumentException("At least one @PropertySource(value) location is required");
			}
191
			for (int i = 0; i < nLocations; i++) {
192
				locations[i] = this.environment.resolveRequiredPlaceholders(locations[i]);
193
			}
C
Chris Beams 已提交
194
			ClassLoader classLoader = this.resourceLoader.getClassLoader();
195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210
			if (!StringUtils.hasText(name)) {
				for (String location : locations) {
					this.propertySources.push(new ResourcePropertySource(location, classLoader));
				}
			}
			else {
				if (nLocations == 1) {
					this.propertySources.push(new ResourcePropertySource(name, locations[0], classLoader));
				}
				else {
					CompositePropertySource ps = new CompositePropertySource(name);
					for (String location : locations) {
						ps.addPropertySource(new ResourcePropertySource(location, classLoader));
					}
					this.propertySources.push(ps);
				}
211
			}
C
Chris Beams 已提交
212 213
		}

214
		// process any @ComponentScan annotions
215
		AnnotationAttributes componentScan = attributesFor(metadata, ComponentScan.class);
216
		if (componentScan != null) {
217
			// the config class is annotated with @ComponentScan -> perform the scan immediately
218 219
			Set<BeanDefinitionHolder> scannedBeanDefinitions =
					this.componentScanParser.parse(componentScan, metadata.getClassName());
220 221 222

			// check the set of scanned definitions for any further config classes and parse recursively if necessary
			for (BeanDefinitionHolder holder : scannedBeanDefinitions) {
223
				if (ConfigurationClassUtils.checkConfigurationClassCandidate(holder.getBeanDefinition(), this.metadataReaderFactory)) {
224
					this.parse(holder.getBeanDefinition().getBeanClassName(), holder.getBeanName());
225 226 227 228
				}
			}
		}

229
		// process any @Import annotations
P
Phillip Webb 已提交
230 231 232 233
		Set<String> imports = getImports(metadata.getClassName(), null, new HashSet<String>());
		if (imports != null && !imports.isEmpty()) {
			processImport(configClass, imports.toArray(new String[imports.size()]), true);
		}
234

235
		// process any @ImportResource annotations
236
		if (metadata.isAnnotated(ImportResource.class.getName())) {
237 238
			AnnotationAttributes importResource = attributesFor(metadata, ImportResource.class);
			String[] resources = importResource.getStringArray("value");
239
			Class<? extends BeanDefinitionReader> readerClass = importResource.getClass("reader");
240 241
			for (String resource : resources) {
				configClass.addImportedResource(resource, readerClass);
242 243
			}
		}
244 245

		// process individual @Bean methods
246
		Set<MethodMetadata> beanMethods = metadata.getAnnotatedMethods(Bean.class.getName());
247 248
		for (MethodMetadata methodMetadata : beanMethods) {
			configClass.addBeanMethod(new BeanMethod(methodMetadata, configClass));
249 250
		}

251 252 253 254 255 256 257 258 259
		// process superclass, if any
		if (metadata.hasSuperClass()) {
			String superclass = metadata.getSuperClassName();
			if (this.knownSuperclasses.add(superclass)) {
				// superclass found, return its annotation metadata and recurse
				if (metadata instanceof StandardAnnotationMetadata) {
					Class<?> clazz = ((StandardAnnotationMetadata) metadata).getIntrospectedClass();
					return new StandardAnnotationMetadata(clazz.getSuperclass(), true);
				}
260 261 262 263 264 265 266 267 268 269
				else if (superclass.startsWith("java")) {
					// never load core JDK classes via ASM, in particular not java.lang.Object!
					try {
						return new StandardAnnotationMetadata(
								this.resourceLoader.getClassLoader().loadClass(superclass), true);
					}
					catch (ClassNotFoundException ex) {
						throw new IllegalStateException(ex);
					}
				}
270 271 272 273 274 275 276 277 278 279
				else {
					MetadataReader reader = this.metadataReaderFactory.getMetadataReader(superclass);
					return reader.getAnnotationMetadata();
				}
			}
		}

		// no superclass, processing is complete
		return null;
	}
280 281

	/**
282 283 284 285 286 287 288 289 290 291 292 293
	 * Recursively collect all declared {@code @Import} values. Unlike most
	 * meta-annotations it is valid to have several {@code @Import}s declared with
	 * different values, the usual process or returning values from the first
	 * meta-annotation on a class is not sufficient.
	 * <p>For example, it is common for a {@code @Configuration} class to declare direct
	 * {@code @Import}s in addition to meta-imports originating from an {@code @Enable}
	 * annotation.
	 * @param className the class name to search
	 * @param imports the imports collected so far or {@code null}
	 * @param visited used to track visited classes to prevent infinite recursion (must not be null)
	 * @return a set of all {@link Import#value() import values} or {@code null}
	 * @throws IOException if there is any problem reading metadata from the named class
294
	 */
P
Phillip Webb 已提交
295
	private Set<String> getImports(String className, Set<String> imports,
296 297 298
			Set<String> visited) throws IOException {
		if (visited.add(className)) {
			AnnotationMetadata metadata = metadataReaderFactory.getMetadataReader(className).getAnnotationMetadata();
299
			for (String annotationType : metadata.getAnnotationTypes()) {
P
Phillip Webb 已提交
300
				imports = getImports(annotationType, imports, visited);
301
			}
302 303 304 305
			Map<String, Object> attributes = metadata.getAnnotationAttributes(Import.class.getName(), true);
			if (attributes != null) {
				String[] value = (String[]) attributes.get("value");
				if (value != null && value.length > 0) {
P
Phillip Webb 已提交
306
					imports = (imports == null ? new LinkedHashSet<String>() : imports);
307 308
					imports.addAll(Arrays.asList(value));
				}
309 310
			}
		}
P
Phillip Webb 已提交
311
		return imports;
312 313
	}

C
Chris Beams 已提交
314 315
	private void processImport(ConfigurationClass configClass, String[] classesToImport, boolean checkForCircularImports) throws IOException {
		if (checkForCircularImports && this.importStack.contains(configClass)) {
316
			this.problemReporter.error(new CircularImportProblem(configClass, this.importStack, configClass.getMetadata()));
317 318 319
		}
		else {
			this.importStack.push(configClass);
C
Chris Beams 已提交
320 321 322
			AnnotationMetadata importingClassMetadata = configClass.getMetadata();
			for (String candidate : classesToImport) {
				MetadataReader reader = this.metadataReaderFactory.getMetadataReader(candidate);
J
Juergen Hoeller 已提交
323
				if (new AssignableTypeFilter(ImportSelector.class).match(reader, this.metadataReaderFactory)) {
C
Chris Beams 已提交
324 325
					// the candidate class is an ImportSelector -> delegate to it to determine imports
					try {
326 327
						ImportSelector selector = BeanUtils.instantiateClass(
								this.resourceLoader.getClassLoader().loadClass(candidate), ImportSelector.class);
C
Chris Beams 已提交
328
						processImport(configClass, selector.selectImports(importingClassMetadata), false);
J
Juergen Hoeller 已提交
329 330
					}
					catch (ClassNotFoundException ex) {
C
Chris Beams 已提交
331 332 333 334 335 336
						throw new IllegalStateException(ex);
					}
				}
				else if (new AssignableTypeFilter(ImportBeanDefinitionRegistrar.class).match(reader, metadataReaderFactory)) {
					// the candidate class is an ImportBeanDefinitionRegistrar -> delegate to it to register additional bean definitions
					try {
337 338
						ImportBeanDefinitionRegistrar registrar = BeanUtils.instantiateClass(
								this.resourceLoader.getClassLoader().loadClass(candidate), ImportBeanDefinitionRegistrar.class);
339
						invokeAwareMethods(registrar);
C
Chris Beams 已提交
340
						registrar.registerBeanDefinitions(importingClassMetadata, registry);
J
Juergen Hoeller 已提交
341 342
					}
					catch (ClassNotFoundException ex) {
C
Chris Beams 已提交
343 344 345 346
						throw new IllegalStateException(ex);
					}
				}
				else {
C
Chris Beams 已提交
347
					// the candidate class not an ImportSelector or ImportBeanDefinitionRegistrar -> process it as a @Configuration class
C
Chris Beams 已提交
348
					this.importStack.registerImport(importingClassMetadata.getClassName(), candidate);
349
					processConfigurationClass(new ConfigurationClass(reader, true));
C
Chris Beams 已提交
350
				}
351 352 353 354 355
			}
			this.importStack.pop();
		}
	}

356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378
	/**
	 * Invoke {@link ResourceLoaderAware}, {@link BeanClassLoaderAware} and
	 * {@link BeanFactoryAware} contracts if implemented by the given {@code registrar}.
	 */
	private void invokeAwareMethods(ImportBeanDefinitionRegistrar registrar) {
		if (registrar instanceof Aware) {
			if (registrar instanceof ResourceLoaderAware) {
				((ResourceLoaderAware) registrar).setResourceLoader(resourceLoader);
			}
			if (registrar instanceof BeanClassLoaderAware) {
				ClassLoader classLoader =
						registry instanceof ConfigurableBeanFactory ?
						((ConfigurableBeanFactory) registry).getBeanClassLoader() :
						resourceLoader.getClassLoader();
				((BeanClassLoaderAware) registrar).setBeanClassLoader(classLoader);
			}
			if (registrar instanceof BeanFactoryAware && registry instanceof BeanFactory) {
				((BeanFactoryAware) registrar).setBeanFactory((BeanFactory) registry);
			}
		}
	}


379
	/**
380
	 * Validate each {@link ConfigurationClass} object.
381
	 * @see ConfigurationClass#validate
382
	 */
383
	public void validate() {
384
		for (ConfigurationClass configClass : this.configurationClasses) {
385 386 387 388
			configClass.validate(this.problemReporter);
		}
	}

389 390
	public Set<ConfigurationClass> getConfigurationClasses() {
		return this.configurationClasses;
C
Chris Beams 已提交
391
	}
392

C
Chris Beams 已提交
393 394 395 396
	public Stack<PropertySource<?>> getPropertySources() {
		return this.propertySources;
	}

C
Chris Beams 已提交
397 398 399 400 401
	public ImportRegistry getImportRegistry() {
		return this.importStack;
	}

	interface ImportRegistry {
J
Juergen Hoeller 已提交
402

C
Chris Beams 已提交
403 404 405
		String getImportingClassFor(String importedClass);
	}

406 407

	@SuppressWarnings("serial")
C
Chris Beams 已提交
408 409
	private static class ImportStack extends Stack<ConfigurationClass> implements ImportRegistry {

J
Juergen Hoeller 已提交
410
		private final Map<String, String> imports = new HashMap<String, String>();
C
Chris Beams 已提交
411

J
Juergen Hoeller 已提交
412 413
		public void registerImport(String importingClass, String importedClass) {
			this.imports.put(importedClass, importingClass);
C
Chris Beams 已提交
414 415
		}

J
Juergen Hoeller 已提交
416 417
		public String getImportingClassFor(String importedClass) {
			return this.imports.get(importedClass);
C
Chris Beams 已提交
418
		}
419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437

		/**
		 * Simplified contains() implementation that tests to see if any {@link ConfigurationClass}
		 * exists within this stack that has the same name as <var>elem</var>. Elem must be of
		 * type ConfigurationClass.
		 */
		@Override
		public boolean contains(Object elem) {
			ConfigurationClass configClass = (ConfigurationClass) elem;
			Comparator<ConfigurationClass> comparator = new Comparator<ConfigurationClass>() {
				public int compare(ConfigurationClass first, ConfigurationClass second) {
					return first.getMetadata().getClassName().equals(second.getMetadata().getClassName()) ? 0 : 1;
				}
			};
			return (Collections.binarySearch(this, configClass, comparator) != -1);
		}

		/**
		 * Given a stack containing (in order)
C
Chris Beams 已提交
438
		 * <ul>
439 440 441
		 * <li>com.acme.Foo</li>
		 * <li>com.acme.Bar</li>
		 * <li>com.acme.Baz</li>
C
Chris Beams 已提交
442 443
		 * </ul>
		 * return "ImportStack: [Foo->Bar->Baz]".
444 445 446
		 */
		@Override
		public String toString() {
C
Chris Beams 已提交
447
			StringBuilder builder = new StringBuilder("ImportStack: [");
448 449 450 451 452 453 454
			Iterator<ConfigurationClass> iterator = iterator();
			while (iterator.hasNext()) {
				builder.append(iterator.next().getSimpleName());
				if (iterator.hasNext()) {
					builder.append("->");
				}
			}
C
Chris Beams 已提交
455
			return builder.append(']').toString();
456 457 458 459 460 461 462 463 464
		}
	}


	/**
	 * {@link Problem} registered upon detection of a circular {@link Import}.
	 */
	private static class CircularImportProblem extends Problem {

465
		public CircularImportProblem(ConfigurationClass attemptedImport, Stack<ConfigurationClass> importStack, AnnotationMetadata metadata) {
466
			super(String.format("A circular @Import has been detected: " +
467 468 469 470
					"Illegal attempt by @Configuration class '%s' to import class '%s' as '%s' is " +
					"already present in the current import stack [%s]", importStack.peek().getSimpleName(),
					attemptedImport.getSimpleName(), attemptedImport.getSimpleName(), importStack),
					new Location(importStack.peek().getResource(), metadata));
471 472
		}
	}
473
}