View Javadoc
1   /*
2    * Copyright 2002-2015 the original author or authors.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *      http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  
17  package org.springframework.context.annotation;
18  
19  import java.lang.reflect.Method;
20  import java.util.ArrayList;
21  import java.util.Arrays;
22  import java.util.HashMap;
23  import java.util.List;
24  import java.util.Map;
25  import java.util.Set;
26  
27  import org.apache.commons.logging.Log;
28  import org.apache.commons.logging.LogFactory;
29  
30  import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition;
31  import org.springframework.beans.factory.annotation.AnnotatedGenericBeanDefinition;
32  import org.springframework.beans.factory.annotation.Autowire;
33  import org.springframework.beans.factory.annotation.RequiredAnnotationBeanPostProcessor;
34  import org.springframework.beans.factory.config.BeanDefinition;
35  import org.springframework.beans.factory.config.BeanDefinitionHolder;
36  import org.springframework.beans.factory.groovy.GroovyBeanDefinitionReader;
37  import org.springframework.beans.factory.parsing.Location;
38  import org.springframework.beans.factory.parsing.Problem;
39  import org.springframework.beans.factory.parsing.ProblemReporter;
40  import org.springframework.beans.factory.parsing.SourceExtractor;
41  import org.springframework.beans.factory.support.AbstractBeanDefinitionReader;
42  import org.springframework.beans.factory.support.BeanDefinitionReader;
43  import org.springframework.beans.factory.support.BeanDefinitionRegistry;
44  import org.springframework.beans.factory.support.BeanNameGenerator;
45  import org.springframework.beans.factory.support.RootBeanDefinition;
46  import org.springframework.beans.factory.xml.XmlBeanDefinitionReader;
47  import org.springframework.context.annotation.ConfigurationCondition.ConfigurationPhase;
48  import org.springframework.core.annotation.AnnotationAttributes;
49  import org.springframework.core.env.Environment;
50  import org.springframework.core.io.Resource;
51  import org.springframework.core.io.ResourceLoader;
52  import org.springframework.core.type.AnnotationMetadata;
53  import org.springframework.core.type.MethodMetadata;
54  import org.springframework.core.type.classreading.MetadataReaderFactory;
55  import org.springframework.util.StringUtils;
56  
57  /**
58   * Reads a given fully-populated set of ConfigurationClass instances, registering bean
59   * definitions with the given {@link BeanDefinitionRegistry} based on its contents.
60   *
61   * <p>This class was modeled after the {@link BeanDefinitionReader} hierarchy, but does
62   * not implement/extend any of its artifacts as a set of configuration classes is not a
63   * {@link Resource}.
64   *
65   * @author Chris Beams
66   * @author Juergen Hoeller
67   * @author Phillip Webb
68   * @since 3.0
69   * @see ConfigurationClassParser
70   */
71  class ConfigurationClassBeanDefinitionReader {
72  
73  	private static final Log logger = LogFactory.getLog(ConfigurationClassBeanDefinitionReader.class);
74  
75  	private static final ScopeMetadataResolver scopeMetadataResolver = new AnnotationScopeMetadataResolver();
76  
77  	private final BeanDefinitionRegistry registry;
78  
79  	private final SourceExtractor sourceExtractor;
80  
81  	private final ProblemReporter problemReporter;
82  
83  	private final MetadataReaderFactory metadataReaderFactory;
84  
85  	private final ResourceLoader resourceLoader;
86  
87  	private final Environment environment;
88  
89  	private final BeanNameGenerator importBeanNameGenerator;
90  
91  	private final ImportRegistry importRegistry;
92  
93  	private final ConditionEvaluator conditionEvaluator;
94  
95  
96  	/**
97  	 * Create a new {@link ConfigurationClassBeanDefinitionReader} instance that will be used
98  	 * to populate the given {@link BeanDefinitionRegistry}.
99  	 */
100 	ConfigurationClassBeanDefinitionReader(BeanDefinitionRegistry registry, SourceExtractor sourceExtractor,
101 			ProblemReporter problemReporter, MetadataReaderFactory metadataReaderFactory,
102 			ResourceLoader resourceLoader, Environment environment, BeanNameGenerator importBeanNameGenerator,
103 			ImportRegistry importRegistry) {
104 
105 		this.registry = registry;
106 		this.sourceExtractor = sourceExtractor;
107 		this.problemReporter = problemReporter;
108 		this.metadataReaderFactory = metadataReaderFactory;
109 		this.resourceLoader = resourceLoader;
110 		this.environment = environment;
111 		this.importBeanNameGenerator = importBeanNameGenerator;
112 		this.importRegistry = importRegistry;
113 		this.conditionEvaluator = new ConditionEvaluator(registry, environment, resourceLoader);
114 	}
115 
116 
117 	/**
118 	 * Read {@code configurationModel}, registering bean definitions
119 	 * with the registry based on its contents.
120 	 */
121 	public void loadBeanDefinitions(Set<ConfigurationClass> configurationModel) {
122 		TrackedConditionEvaluator trackedConditionEvaluator = new TrackedConditionEvaluator();
123 		for (ConfigurationClass configClass : configurationModel) {
124 			loadBeanDefinitionsForConfigurationClass(configClass, trackedConditionEvaluator);
125 		}
126 	}
127 
128 	/**
129 	 * Read a particular {@link ConfigurationClass}, registering bean definitions
130 	 * for the class itself and all of its {@link Bean} methods.
131 	 */
132 	private void loadBeanDefinitionsForConfigurationClass(ConfigurationClass configClass,
133 			TrackedConditionEvaluator trackedConditionEvaluator) {
134 
135 		if (trackedConditionEvaluator.shouldSkip(configClass)) {
136 			String beanName = configClass.getBeanName();
137 			if (StringUtils.hasLength(beanName) && this.registry.containsBeanDefinition(beanName)) {
138 				this.registry.removeBeanDefinition(beanName);
139 			}
140 			this.importRegistry.removeImportingClassFor(configClass.getMetadata().getClassName());
141 			return;
142 		}
143 
144 		if (configClass.isImported()) {
145 			registerBeanDefinitionForImportedConfigurationClass(configClass);
146 		}
147 		for (BeanMethod beanMethod : configClass.getBeanMethods()) {
148 			loadBeanDefinitionsForBeanMethod(beanMethod);
149 		}
150 		loadBeanDefinitionsFromImportedResources(configClass.getImportedResources());
151 		loadBeanDefinitionsFromRegistrars(configClass.getImportBeanDefinitionRegistrars());
152 	}
153 
154 	/**
155 	 * Register the {@link Configuration} class itself as a bean definition.
156 	 */
157 	private void registerBeanDefinitionForImportedConfigurationClass(ConfigurationClass configClass) {
158 		AnnotationMetadata metadata = configClass.getMetadata();
159 		AnnotatedGenericBeanDefinition configBeanDef = new AnnotatedGenericBeanDefinition(metadata);
160 
161 		if (ConfigurationClassUtils.checkConfigurationClassCandidate(configBeanDef, this.metadataReaderFactory)) {
162 			ScopeMetadata scopeMetadata = scopeMetadataResolver.resolveScopeMetadata(configBeanDef);
163 			configBeanDef.setScope(scopeMetadata.getScopeName());
164 			String configBeanName = this.importBeanNameGenerator.generateBeanName(configBeanDef, this.registry);
165 			AnnotationConfigUtils.processCommonDefinitionAnnotations(configBeanDef, metadata);
166 			BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(configBeanDef, configBeanName);
167 			definitionHolder = AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
168 			this.registry.registerBeanDefinition(definitionHolder.getBeanName(), definitionHolder.getBeanDefinition());
169 			configClass.setBeanName(configBeanName);
170 			if (logger.isDebugEnabled()) {
171 				logger.debug(String.format("Registered bean definition for imported @Configuration class %s", configBeanName));
172 			}
173 		}
174 		else {
175 			this.problemReporter.error(
176 					new InvalidConfigurationImportProblem(metadata.getClassName(), configClass.getResource(), metadata));
177 		}
178 	}
179 
180 	/**
181 	 * Read the given {@link BeanMethod}, registering bean definitions
182 	 * with the BeanDefinitionRegistry based on its contents.
183 	 */
184 	private void loadBeanDefinitionsForBeanMethod(BeanMethod beanMethod) {
185 		ConfigurationClass configClass = beanMethod.getConfigurationClass();
186 		MethodMetadata metadata = beanMethod.getMetadata();
187 		String methodName = metadata.getMethodName();
188 
189 		// Do we need to mark the bean as skipped by its condition?
190 		if (this.conditionEvaluator.shouldSkip(metadata, ConfigurationPhase.REGISTER_BEAN)) {
191 			configClass.skippedBeanMethods.add(methodName);
192 			return;
193 		}
194 		if (configClass.skippedBeanMethods.contains(methodName)) {
195 			return;
196 		}
197 
198 		// Consider name and any aliases
199 		AnnotationAttributes bean = AnnotationConfigUtils.attributesFor(metadata, Bean.class);
200 		List<String> names = new ArrayList<String>(Arrays.asList(bean.getStringArray("name")));
201 		String beanName = (names.size() > 0 ? names.remove(0) : methodName);
202 
203 		// Register aliases even when overridden
204 		for (String alias : names) {
205 			this.registry.registerAlias(beanName, alias);
206 		}
207 
208 		// Has this effectively been overridden before (e.g. via XML)?
209 		if (isOverriddenByExistingDefinition(beanMethod, beanName)) {
210 			return;
211 		}
212 
213 		ConfigurationClassBeanDefinition beanDef = new ConfigurationClassBeanDefinition(configClass, metadata);
214 		beanDef.setResource(configClass.getResource());
215 		beanDef.setSource(this.sourceExtractor.extractSource(metadata, configClass.getResource()));
216 
217 		if (metadata.isStatic()) {
218 			// static @Bean method
219 			beanDef.setBeanClassName(configClass.getMetadata().getClassName());
220 			beanDef.setFactoryMethodName(methodName);
221 		}
222 		else {
223 			// instance @Bean method
224 			beanDef.setFactoryBeanName(configClass.getBeanName());
225 			beanDef.setUniqueFactoryMethodName(methodName);
226 		}
227 		beanDef.setAutowireMode(RootBeanDefinition.AUTOWIRE_CONSTRUCTOR);
228 		beanDef.setAttribute(RequiredAnnotationBeanPostProcessor.SKIP_REQUIRED_CHECK_ATTRIBUTE, Boolean.TRUE);
229 
230 		AnnotationConfigUtils.processCommonDefinitionAnnotations(beanDef, metadata);
231 
232 		Autowire autowire = bean.getEnum("autowire");
233 		if (autowire.isAutowire()) {
234 			beanDef.setAutowireMode(autowire.value());
235 		}
236 
237 		String initMethodName = bean.getString("initMethod");
238 		if (StringUtils.hasText(initMethodName)) {
239 			beanDef.setInitMethodName(initMethodName);
240 		}
241 
242 		String destroyMethodName = bean.getString("destroyMethod");
243 		if (StringUtils.hasText(destroyMethodName)) {
244 			beanDef.setDestroyMethodName(destroyMethodName);
245 		}
246 
247 		// Consider scoping
248 		ScopedProxyMode proxyMode = ScopedProxyMode.NO;
249 		AnnotationAttributes scope = AnnotationConfigUtils.attributesFor(metadata, Scope.class);
250 		if (scope != null) {
251 			beanDef.setScope(scope.getString("value"));
252 			proxyMode = scope.getEnum("proxyMode");
253 			if (proxyMode == ScopedProxyMode.DEFAULT) {
254 				proxyMode = ScopedProxyMode.NO;
255 			}
256 		}
257 
258 		// Replace the original bean definition with the target one, if necessary
259 		BeanDefinition beanDefToRegister = beanDef;
260 		if (proxyMode != ScopedProxyMode.NO) {
261 			BeanDefinitionHolder proxyDef = ScopedProxyCreator.createScopedProxy(
262 					new BeanDefinitionHolder(beanDef, beanName), this.registry, proxyMode == ScopedProxyMode.TARGET_CLASS);
263 			beanDefToRegister = new ConfigurationClassBeanDefinition(
264 					(RootBeanDefinition) proxyDef.getBeanDefinition(), configClass, metadata);
265 		}
266 
267 		if (logger.isDebugEnabled()) {
268 			logger.debug(String.format("Registering bean definition for @Bean method %s.%s()",
269 					configClass.getMetadata().getClassName(), beanName));
270 		}
271 
272 		this.registry.registerBeanDefinition(beanName, beanDefToRegister);
273 	}
274 
275 	protected boolean isOverriddenByExistingDefinition(BeanMethod beanMethod, String beanName) {
276 		if (!this.registry.containsBeanDefinition(beanName)) {
277 			return false;
278 		}
279 		BeanDefinition existingBeanDef = this.registry.getBeanDefinition(beanName);
280 
281 		// Is the existing bean definition one that was created from a configuration class?
282 		// -> allow the current bean method to override, since both are at second-pass level.
283 		// However, if the bean method is an overloaded case on the same configuration class,
284 		// preserve the existing bean definition.
285 		if (existingBeanDef instanceof ConfigurationClassBeanDefinition) {
286 			ConfigurationClassBeanDefinition ccbd = (ConfigurationClassBeanDefinition) existingBeanDef;
287 			return (ccbd.getMetadata().getClassName().equals(beanMethod.getConfigurationClass().getMetadata().getClassName()));
288 		}
289 
290 		// Has the existing bean definition bean marked as a framework-generated bean?
291 		// -> allow the current bean method to override it, since it is application-level
292 		if (existingBeanDef.getRole() > BeanDefinition.ROLE_APPLICATION) {
293 			return false;
294 		}
295 
296 		// At this point, it's a top-level override (probably XML), just having been parsed
297 		// before configuration class processing kicks in...
298 		if (logger.isInfoEnabled()) {
299 			logger.info(String.format("Skipping bean definition for %s: a definition for bean '%s' " +
300 					"already exists. This top-level bean definition is considered as an override.",
301 					beanMethod, beanName));
302 		}
303 		return true;
304 	}
305 
306 	private void loadBeanDefinitionsFromImportedResources(
307 			Map<String, Class<? extends BeanDefinitionReader>> importedResources) {
308 
309 		Map<Class<?>, BeanDefinitionReader> readerInstanceCache = new HashMap<Class<?>, BeanDefinitionReader>();
310 
311 		for (Map.Entry<String, Class<? extends BeanDefinitionReader>> entry : importedResources.entrySet()) {
312 			String resource = entry.getKey();
313 			Class<? extends BeanDefinitionReader> readerClass = entry.getValue();
314 
315 			// Default reader selection necessary?
316 			if (readerClass.equals(BeanDefinitionReader.class)) {
317 				if (StringUtils.endsWithIgnoreCase(resource, ".groovy")) {
318 					// When clearly asking for Groovy, that's what they'll get...
319 					readerClass = GroovyBeanDefinitionReader.class;
320 				}
321 				else {
322 					// Primarily ".xml" files but for any other extension as well
323 					readerClass = XmlBeanDefinitionReader.class;
324 				}
325 			}
326 
327 			BeanDefinitionReader reader = readerInstanceCache.get(readerClass);
328 			if (reader == null) {
329 				try {
330 					// Instantiate the specified BeanDefinitionReader
331 					reader = readerClass.getConstructor(BeanDefinitionRegistry.class).newInstance(this.registry);
332 					// Delegate the current ResourceLoader to it if possible
333 					if (reader instanceof AbstractBeanDefinitionReader) {
334 						AbstractBeanDefinitionReader abdr = ((AbstractBeanDefinitionReader) reader);
335 						abdr.setResourceLoader(this.resourceLoader);
336 						abdr.setEnvironment(this.environment);
337 					}
338 					readerInstanceCache.put(readerClass, reader);
339 				}
340 				catch (Exception ex) {
341 					throw new IllegalStateException(
342 							"Could not instantiate BeanDefinitionReader class [" + readerClass.getName() + "]");
343 				}
344 			}
345 
346 			// TODO SPR-6310: qualify relative path locations as done in AbstractContextLoader.modifyLocations
347 			reader.loadBeanDefinitions(resource);
348 		}
349 	}
350 
351 	private void loadBeanDefinitionsFromRegistrars(Map<ImportBeanDefinitionRegistrar, AnnotationMetadata> registrars) {
352 		for (Map.Entry<ImportBeanDefinitionRegistrar, AnnotationMetadata> entry : registrars.entrySet()) {
353 			entry.getKey().registerBeanDefinitions(entry.getValue(), this.registry);
354 		}
355 	}
356 
357 
358 	/**
359 	 * {@link RootBeanDefinition} marker subclass used to signify that a bean definition
360 	 * was created from a configuration class as opposed to any other configuration source.
361 	 * Used in bean overriding cases where it's necessary to determine whether the bean
362 	 * definition was created externally.
363 	 */
364 	@SuppressWarnings("serial")
365 	private static class ConfigurationClassBeanDefinition extends RootBeanDefinition implements AnnotatedBeanDefinition {
366 
367 		private final AnnotationMetadata annotationMetadata;
368 
369 		private final MethodMetadata factoryMethodMetadata;
370 
371 		public ConfigurationClassBeanDefinition(ConfigurationClass configClass, MethodMetadata beanMethodMetadata) {
372 			this.annotationMetadata = configClass.getMetadata();
373 			this.factoryMethodMetadata = beanMethodMetadata;
374 			setLenientConstructorResolution(false);
375 		}
376 
377 		public ConfigurationClassBeanDefinition(
378 				RootBeanDefinition original, ConfigurationClass configClass, MethodMetadata beanMethodMetadata) {
379 			super(original);
380 			this.annotationMetadata = configClass.getMetadata();
381 			this.factoryMethodMetadata = beanMethodMetadata;
382 		}
383 
384 		private ConfigurationClassBeanDefinition(ConfigurationClassBeanDefinition original) {
385 			super(original);
386 			this.annotationMetadata = original.annotationMetadata;
387 			this.factoryMethodMetadata = original.factoryMethodMetadata;
388 		}
389 
390 		@Override
391 		public AnnotationMetadata getMetadata() {
392 			return this.annotationMetadata;
393 		}
394 
395 		@Override
396 		public MethodMetadata getFactoryMethodMetadata() {
397 			return this.factoryMethodMetadata;
398 		}
399 
400 		@Override
401 		public boolean isFactoryMethod(Method candidate) {
402 			return (super.isFactoryMethod(candidate) && BeanAnnotationHelper.isBeanAnnotated(candidate));
403 		}
404 
405 		@Override
406 		public ConfigurationClassBeanDefinition cloneBeanDefinition() {
407 			return new ConfigurationClassBeanDefinition(this);
408 		}
409 	}
410 
411 
412 	/**
413 	 * Configuration classes must be annotated with {@link Configuration @Configuration} or
414 	 * declare at least one {@link Bean @Bean} method.
415 	 */
416 	private static class InvalidConfigurationImportProblem extends Problem {
417 
418 		public InvalidConfigurationImportProblem(String className, Resource resource, AnnotationMetadata metadata) {
419 			super(String.format("%s was @Import'ed but is not annotated with @Configuration " +
420 					"nor does it declare any @Bean methods; it does not implement ImportSelector " +
421 					"or extend ImportBeanDefinitionRegistrar. Update the class to meet one of these requirements " +
422 					"or do not attempt to @Import it.", className), new Location(resource, metadata));
423 		}
424 	}
425 
426 
427 	/**
428 	 * Evaluate {@code @Conditional} annotations, tracking results and taking into
429 	 * account 'imported by'.
430 	 */
431 	private class TrackedConditionEvaluator {
432 
433 		private final Map<ConfigurationClass, Boolean> skipped = new HashMap<ConfigurationClass, Boolean>();
434 
435 		public boolean shouldSkip(ConfigurationClass configClass) {
436 			Boolean skip = this.skipped.get(configClass);
437 			if (skip == null) {
438 				if (configClass.isImported()) {
439 					boolean allSkipped = true;
440 					for (ConfigurationClass importedBy : configClass.getImportedBy()) {
441 						if (!shouldSkip(importedBy)) {
442 							allSkipped = false;
443 							break;
444 						}
445 					}
446 					if (allSkipped) {
447 						// The config classes that imported this one were all skipped, therefore we are skipped...
448 						skip = true;
449 					}
450 				}
451 				if (skip == null) {
452 					skip = conditionEvaluator.shouldSkip(configClass.getMetadata(), ConfigurationPhase.REGISTER_BEAN);
453 				}
454 				this.skipped.put(configClass, skip);
455 			}
456 			return skip;
457 		}
458 	}
459 
460 }