ConfigurationClassBeanDefinitionReader.java 17.9 KB
Newer Older
1
/*
2
 * Copyright 2002-2011 the original author or authors.
3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
 *
 * 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.context.annotation;

19
import java.io.IOException;
20
import java.lang.reflect.Method;
21 22
import java.util.ArrayList;
import java.util.Arrays;
23
import java.util.HashMap;
24
import java.util.List;
25
import java.util.Map;
26 27 28 29
import java.util.Set;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
30
import org.springframework.beans.BeanUtils;
31
import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition;
32
import org.springframework.beans.factory.annotation.Autowire;
33
import org.springframework.beans.factory.annotation.RequiredAnnotationBeanPostProcessor;
34 35
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanDefinitionHolder;
36
import org.springframework.beans.factory.parsing.BeanDefinitionParsingException;
37 38 39
import org.springframework.beans.factory.parsing.Location;
import org.springframework.beans.factory.parsing.Problem;
import org.springframework.beans.factory.parsing.ProblemReporter;
J
Juergen Hoeller 已提交
40
import org.springframework.beans.factory.parsing.SourceExtractor;
41
import org.springframework.beans.factory.support.AbstractBeanDefinition;
42
import org.springframework.beans.factory.support.AbstractBeanDefinitionReader;
43 44 45 46 47
import org.springframework.beans.factory.support.BeanDefinitionReader;
import org.springframework.beans.factory.support.BeanDefinitionReaderUtils;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.GenericBeanDefinition;
import org.springframework.beans.factory.support.RootBeanDefinition;
48
import org.springframework.context.config.FeatureSpecification;
49
import org.springframework.context.config.SpecificationContext;
50
import org.springframework.core.Conventions;
51
import org.springframework.core.env.Environment;
52
import org.springframework.core.io.Resource;
53
import org.springframework.core.io.ResourceLoader;
54
import org.springframework.core.type.AnnotationMetadata;
55
import org.springframework.core.type.MethodMetadata;
56 57 58
import org.springframework.core.type.StandardAnnotationMetadata;
import org.springframework.core.type.classreading.MetadataReader;
import org.springframework.core.type.classreading.MetadataReaderFactory;
59
import org.springframework.core.type.classreading.SimpleMetadataReaderFactory;
60
import org.springframework.stereotype.Component;
61 62 63
import org.springframework.util.StringUtils;

/**
64 65
 * Reads a given fully-populated set of ConfigurationClass instances, registering bean
 * definitions with the given {@link BeanDefinitionRegistry} based on its contents.
66 67
 *
 * <p>This class was modeled after the {@link BeanDefinitionReader} hierarchy, but does
68 69
 * not implement/extend any of its artifacts as a set of configuration classes is not a
 * {@link Resource}.
70 71 72 73
 *
 * @author Chris Beams
 * @author Juergen Hoeller
 * @since 3.0
74
 * @see ConfigurationClassParser
75
 */
76
public class ConfigurationClassBeanDefinitionReader {
77

78
	private static final String CONFIGURATION_CLASS_FULL = "full";
79

80
	private static final String CONFIGURATION_CLASS_LITE = "lite";
81

82
	private static final String CONFIGURATION_CLASS_ATTRIBUTE =
83 84 85 86
		Conventions.getQualifiedAttributeName(ConfigurationClassPostProcessor.class, "configurationClass");

	private static final Log logger = LogFactory.getLog(ConfigurationClassBeanDefinitionReader.class);
	
87 88
	private final BeanDefinitionRegistry registry;

J
Juergen Hoeller 已提交
89
	private final SourceExtractor sourceExtractor;
90

91 92 93 94
	private final ProblemReporter problemReporter;

	private final MetadataReaderFactory metadataReaderFactory;

95 96
	private ResourceLoader resourceLoader;

97
	private SpecificationContext specificationContext;
98

J
Juergen Hoeller 已提交
99 100 101
	/**
	 * Create a new {@link ConfigurationClassBeanDefinitionReader} instance that will be used
	 * to populate the given {@link BeanDefinitionRegistry}.
102 103
	 * @param problemReporter 
	 * @param metadataReaderFactory 
J
Juergen Hoeller 已提交
104
	 */
105 106 107
	public ConfigurationClassBeanDefinitionReader(final BeanDefinitionRegistry registry, SourceExtractor sourceExtractor,
			ProblemReporter problemReporter, MetadataReaderFactory metadataReaderFactory,
			ResourceLoader resourceLoader, Environment environment) {
108

109
		this.registry = registry;
J
Juergen Hoeller 已提交
110
		this.sourceExtractor = sourceExtractor;
111 112
		this.problemReporter = problemReporter;
		this.metadataReaderFactory = metadataReaderFactory;
113
		this.resourceLoader = resourceLoader;
114
		// TODO SPR-7420: see about passing in the SpecificationContext created in ConfigurationClassPostProcessor
115 116 117
		this.specificationContext = new SpecificationContext();
		this.specificationContext.setRegistry(this.registry);
		this.specificationContext.setRegistrar(new SimpleComponentRegistrar(this.registry));
118
		this.specificationContext.setResourceLoader(this.resourceLoader);
119 120
		this.specificationContext.setEnvironment(environment);
		this.specificationContext.setProblemReporter(problemReporter);
121 122 123 124
	}


	/**
C
Chris Beams 已提交
125
	 * Read {@code configurationModel}, registering bean definitions with {@link #registry}
126 127 128 129 130 131 132 133 134
	 * based on its contents.
	 */
	public void loadBeanDefinitions(Set<ConfigurationClass> configurationModel) {
		for (ConfigurationClass configClass : configurationModel) {
			loadBeanDefinitionsForConfigurationClass(configClass);
		}
	}

	/**
C
Chris Beams 已提交
135
	 * Read a particular {@link ConfigurationClass}, registering bean definitions for the
136 137 138
	 * class itself, all its {@link Bean} methods
	 */
	private void loadBeanDefinitionsForConfigurationClass(ConfigurationClass configClass) {
139 140
		AnnotationMetadata metadata = configClass.getMetadata();
		processFeatureAnnotations(metadata);
141
		doLoadBeanDefinitionForConfigurationClassIfNecessary(configClass);
142 143
		for (ConfigurationClassMethod beanMethod : configClass.getMethods()) {
			loadBeanDefinitionsForBeanMethod(beanMethod);
144
		}
145
		loadBeanDefinitionsFromImportedResources(configClass.getImportedResources());
146 147
	}

148 149 150 151 152 153 154 155 156
	private void processFeatureAnnotations(AnnotationMetadata metadata) {
		try {
			for (String annotationType : metadata.getAnnotationTypes()) {
				MetadataReader metadataReader = new SimpleMetadataReaderFactory().getMetadataReader(annotationType);
				if (metadataReader.getAnnotationMetadata().isAnnotated(FeatureAnnotation.class.getName())) {
					Map<String, Object> annotationAttributes = metadataReader.getAnnotationMetadata().getAnnotationAttributes(FeatureAnnotation.class.getName(), true);
					// TODO SPR-7420: this is where we can catch user-defined types and avoid instantiating them for STS purposes
					FeatureAnnotationParser processor = (FeatureAnnotationParser) BeanUtils.instantiateClass(Class.forName((String)annotationAttributes.get("parser")));
					FeatureSpecification spec = processor.process(metadata);
157
					spec.execute(this.specificationContext);
158 159 160 161 162 163 164 165 166 167 168
				}
			}
		} catch (BeanDefinitionParsingException ex) {
			throw ex;
		}
		catch (Exception ex) {
			// TODO SPR-7420: what exception to throw?
			throw new RuntimeException(ex);
		}
	}

169
	/**
170
	 * Register the {@link Configuration} class itself as a bean definition.
171
	 */
172 173 174 175 176 177 178
	private void doLoadBeanDefinitionForConfigurationClassIfNecessary(ConfigurationClass configClass) {
		if (configClass.getBeanName() != null) {
			// a bean definition already exists for this configuration class -> nothing to do
			return;
		}

		// no bean definition exists yet -> this must be an imported configuration class (@Import).
179
		BeanDefinition configBeanDef = new GenericBeanDefinition();
180 181
		String className = configClass.getMetadata().getClassName();
		configBeanDef.setBeanClassName(className);
J
Juergen Hoeller 已提交
182
		if (checkConfigurationClassCandidate(configBeanDef, this.metadataReaderFactory)) {
183
			String configBeanName = BeanDefinitionReaderUtils.registerWithGeneratedName(configBeanDef, this.registry);
184 185 186 187
			configClass.setBeanName(configBeanName);
			if (logger.isDebugEnabled()) {
				logger.debug(String.format("Registered bean definition for imported @Configuration class %s", configBeanName));
			}
188 189
		}
		else {
190 191 192 193 194
			try {
				MetadataReader reader = this.metadataReaderFactory.getMetadataReader(className);
				AnnotationMetadata metadata = reader.getAnnotationMetadata();
				this.problemReporter.error(
						new InvalidConfigurationImportProblem(className, reader.getResource(), metadata));
195 196
			}
			catch (IOException ex) {
197 198
				throw new IllegalStateException("Could not create MetadataReader for class " + className);
			}
199 200 201 202
		}
	}

	/**
C
Chris Beams 已提交
203
	 * Read a particular {@link ConfigurationClassMethod}, registering bean definitions
J
Juergen Hoeller 已提交
204
	 * with the BeanDefinitionRegistry based on its contents.
205
	 */
206 207 208
	private void loadBeanDefinitionsForBeanMethod(ConfigurationClassMethod beanMethod) {
		ConfigurationClass configClass = beanMethod.getConfigurationClass();
		MethodMetadata metadata = beanMethod.getMetadata();
209

210 211
		RootBeanDefinition beanDef = new ConfigurationClassBeanDefinition(configClass);
		beanDef.setResource(configClass.getResource());
J
Juergen Hoeller 已提交
212
		beanDef.setSource(this.sourceExtractor.extractSource(metadata, configClass.getResource()));
213
		beanDef.setFactoryBeanName(configClass.getBeanName());
214
		beanDef.setUniqueFactoryMethodName(metadata.getMethodName());
215
		beanDef.setAutowireMode(RootBeanDefinition.AUTOWIRE_CONSTRUCTOR);
216
		beanDef.setAttribute(RequiredAnnotationBeanPostProcessor.SKIP_REQUIRED_CHECK_ATTRIBUTE, Boolean.TRUE);
217 218

		// consider name and any aliases
219 220
		Map<String, Object> beanAttributes = metadata.getAnnotationAttributes(Bean.class.getName());
		List<String> names = new ArrayList<String>(Arrays.asList((String[]) beanAttributes.get("name")));
221
		String beanName = (names.size() > 0 ? names.remove(0) : beanMethod.getMetadata().getMethodName());
222
		for (String alias : names) {
223
			this.registry.registerAlias(beanName, alias);
224 225
		}

226
		// has this already been overridden (e.g. via XML)?
227
		if (this.registry.containsBeanDefinition(beanName)) {
228
			BeanDefinition existingBeanDef = registry.getBeanDefinition(beanName);
229
			// is the existing bean definition one that was created from a configuration class?
230 231 232 233 234
			if (!(existingBeanDef instanceof ConfigurationClassBeanDefinition)) {
				// no -> then it's an external override, probably XML
				// overriding is legal, return immediately
				if (logger.isDebugEnabled()) {
					logger.debug(String.format("Skipping loading bean definition for %s: a definition for bean " +
235
							"'%s' already exists. This is likely due to an override in XML.", beanMethod, beanName));
236 237 238 239 240
				}
				return;
			}
		}

241
		if (metadata.isAnnotated(Primary.class.getName())) {
242 243 244 245
			beanDef.setPrimary(true);
		}

		// is this bean to be instantiated lazily?
246
		if (metadata.isAnnotated(Lazy.class.getName())) {
247 248
			beanDef.setLazyInit((Boolean) metadata.getAnnotationAttributes(Lazy.class.getName()).get("value"));
		}
249
		else if (configClass.getMetadata().isAnnotated(Lazy.class.getName())){
250
			beanDef.setLazyInit((Boolean) configClass.getMetadata().getAnnotationAttributes(Lazy.class.getName()).get("value"));
251 252
		}

253
		if (metadata.isAnnotated(DependsOn.class.getName())) {
254 255 256 257
			String[] dependsOn = (String[]) metadata.getAnnotationAttributes(DependsOn.class.getName()).get("value");
			if (dependsOn.length > 0) {
				beanDef.setDependsOn(dependsOn);
			}
258 259
		}

260
		Autowire autowire = (Autowire) beanAttributes.get("autowire");
261 262 263 264
		if (autowire.isAutowire()) {
			beanDef.setAutowireMode(autowire.value());
		}

265
		String initMethodName = (String) beanAttributes.get("initMethod");
266 267 268 269
		if (StringUtils.hasText(initMethodName)) {
			beanDef.setInitMethodName(initMethodName);
		}

270
		String destroyMethodName = (String) beanAttributes.get("destroyMethod");
271 272 273 274 275 276
		if (StringUtils.hasText(destroyMethodName)) {
			beanDef.setDestroyMethodName(destroyMethodName);
		}

		// consider scoping
		ScopedProxyMode proxyMode = ScopedProxyMode.NO;
277 278
		Map<String, Object> scopeAttributes = metadata.getAnnotationAttributes(Scope.class.getName());
		if (scopeAttributes != null) {
279 280
			beanDef.setScope((String) scopeAttributes.get("value"));
			proxyMode = (ScopedProxyMode) scopeAttributes.get("proxyMode");
281 282 283 284 285 286 287 288
			if (proxyMode == ScopedProxyMode.DEFAULT) {
				proxyMode = ScopedProxyMode.NO;
			}
		}

		// replace the original bean definition with the target one, if necessary
		BeanDefinition beanDefToRegister = beanDef;
		if (proxyMode != ScopedProxyMode.NO) {
289
			BeanDefinitionHolder proxyDef = ScopedProxyCreator.createScopedProxy(
290
					new BeanDefinitionHolder(beanDef, beanName), this.registry, proxyMode == ScopedProxyMode.TARGET_CLASS);
291 292 293 294
			beanDefToRegister = proxyDef.getBeanDefinition();
		}

		if (logger.isDebugEnabled()) {
295
			logger.debug(String.format("Registering bean definition for @Bean method %s.%s()", configClass.getMetadata().getClassName(), beanName));
296 297 298 299
		}

		registry.registerBeanDefinition(beanName, beanDefToRegister);
	}
300 301


302 303 304
	private void loadBeanDefinitionsFromImportedResources(Map<String, Class<?>> importedResources) {
		Map<Class<?>, BeanDefinitionReader> readerInstanceCache = new HashMap<Class<?>, BeanDefinitionReader>();
		for (Map.Entry<String, Class<?>> entry : importedResources.entrySet()) {
305
			String resource = entry.getKey();
306
			Class<?> readerClass = entry.getValue();
307
			if (!readerInstanceCache.containsKey(readerClass)) {
308
				try {
309
					// Instantiate the specified BeanDefinitionReader
310 311
					BeanDefinitionReader readerInstance = (BeanDefinitionReader)
							readerClass.getConstructor(BeanDefinitionRegistry.class).newInstance(this.registry);
312 313 314 315 316 317

					// Delegate the current ResourceLoader to it if possible
					if (readerInstance instanceof AbstractBeanDefinitionReader) {
						((AbstractBeanDefinitionReader)readerInstance).setResourceLoader(this.resourceLoader);
					}

318 319 320 321
					readerInstanceCache.put(readerClass, readerInstance);
				}
				catch (Exception ex) {
					throw new IllegalStateException("Could not instantiate BeanDefinitionReader class [" + readerClass.getName() + "]");
322 323
				}
			}
324
			BeanDefinitionReader reader = readerInstanceCache.get(readerClass);
S
Sam Brannen 已提交
325
			// TODO SPR-6310: qualify relative path locations as done in AbstractContextLoader.modifyLocations
326
			reader.loadBeanDefinitions(resource);
327
		}
328
	}
329

330

331 332 333 334 335 336 337
	/**
	 * Check whether the given bean definition is a candidate for a configuration class,
	 * and mark it accordingly.
	 * @param beanDef the bean definition to check
	 * @param metadataReaderFactory the current factory in use by the caller
	 * @return whether the candidate qualifies as (any kind of) configuration class
	 */
338
	public static boolean checkConfigurationClassCandidate(BeanDefinition beanDef, MetadataReaderFactory metadataReaderFactory) {
339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375
		AnnotationMetadata metadata = null;
	
		// Check already loaded Class if present...
		// since we possibly can't even load the class file for this Class.
		if (beanDef instanceof AbstractBeanDefinition && ((AbstractBeanDefinition) beanDef).hasBeanClass()) {
			metadata = new StandardAnnotationMetadata(((AbstractBeanDefinition) beanDef).getBeanClass());
		}
		else {
			String className = beanDef.getBeanClassName();
			if (className != null) {
				try {
					MetadataReader metadataReader = metadataReaderFactory.getMetadataReader(className);
					metadata = metadataReader.getAnnotationMetadata();
				}
				catch (IOException ex) {
					if (logger.isDebugEnabled()) {
						logger.debug("Could not find class file for introspecting factory methods: " + className, ex);
					}
					return false;
				}
			}
		}
	
		if (metadata != null) {
			if (metadata.isAnnotated(Configuration.class.getName())) {
				beanDef.setAttribute(CONFIGURATION_CLASS_ATTRIBUTE, CONFIGURATION_CLASS_FULL);
				return true;
			}
			else if (metadata.isAnnotated(Component.class.getName()) ||
					metadata.hasAnnotatedMethods(Bean.class.getName())) {
				beanDef.setAttribute(CONFIGURATION_CLASS_ATTRIBUTE, CONFIGURATION_CLASS_LITE);
				return true;
			}
		}
		return false;
	}

376 377 378 379 380 381 382 383
	/**
	 * Determine whether the given bean definition indicates a full @Configuration class.
	 */
	public static boolean isFullConfigurationClass(BeanDefinition beanDef) {
		return CONFIGURATION_CLASS_FULL.equals(beanDef.getAttribute(CONFIGURATION_CLASS_ATTRIBUTE));
	}


384
	/**
385
	 * {@link RootBeanDefinition} marker subclass used to signify that a bean definition
S
Sam Brannen 已提交
386
	 * was created from a configuration class as opposed to any other configuration source.
387 388
	 * Used in bean overriding cases where it's necessary to determine whether the bean
	 * definition was created externally.
389
	 */
390
	@SuppressWarnings("serial")
391
	private static class ConfigurationClassBeanDefinition extends RootBeanDefinition implements AnnotatedBeanDefinition {
392 393

		private AnnotationMetadata annotationMetadata;
394

395 396
		public ConfigurationClassBeanDefinition(ConfigurationClass configClass) {
			this.annotationMetadata = configClass.getMetadata();
397 398 399 400
		}

		private ConfigurationClassBeanDefinition(ConfigurationClassBeanDefinition original) {
			super(original);
401 402 403 404 405
			this.annotationMetadata = original.annotationMetadata;
		}

		public AnnotationMetadata getMetadata() {
			return this.annotationMetadata;
406 407 408 409
		}

		@Override
		public boolean isFactoryMethod(Method candidate) {
410
			return (super.isFactoryMethod(candidate) && BeanAnnotationHelper.isBeanAnnotated(candidate));
411 412 413 414 415 416
		}

		@Override
		public ConfigurationClassBeanDefinition cloneBeanDefinition() {
			return new ConfigurationClassBeanDefinition(this);
		}
417 418
	}

419 420 421 422 423 424 425
	
	/**
	 * Configuration classes must be annotated with {@link Configuration @Configuration} or
	 * declare at least one {@link Bean @Bean} method.
	 */
	private static class InvalidConfigurationImportProblem extends Problem {
		public InvalidConfigurationImportProblem(String className, Resource resource, AnnotationMetadata metadata) {
S
Sam Brannen 已提交
426
			super(String.format("%s was @Import'ed but is not annotated with @FeatureConfiguration or " +
427 428
					"@Configuration nor does it declare any @Bean methods. Update the class to " +
					"meet one of these requirements or do not attempt to @Import it.", className),
429 430 431 432
					new Location(resource, metadata));
		}
	}

433
}