Remove "Feature" support introduced in 3.1 M1

Feature-related support such as @Feature, @FeatureConfiguration,
and FeatureSpecification types will be replaced by framework-provided
@Configuration classes and convenience annotations such as
@ComponentScan (already exists), @EnableAsync, @EnableScheduling,
@EnableTransactionManagement and others.

Issue: SPR-8012,SPR-8034,SPR-8039,SPR-8188,SPR-8206,SPR-8223,
SPR-8225,SPR-8226,SPR-8227
This commit is contained in:
Chris Beams
2011-05-06 19:03:52 +00:00
parent 0a790c143f
commit 111fb71fe1
80 changed files with 857 additions and 6737 deletions

View File

@@ -16,13 +16,13 @@
package org.springframework.context.annotation;
import org.springframework.beans.factory.parsing.Location;
import org.springframework.beans.factory.parsing.Problem;
import org.springframework.beans.factory.parsing.ProblemReporter;
import org.springframework.core.type.MethodMetadata;
/**
* Represents a {@link Configuration} class method marked with the {@link Bean} annotation.
* Represents a {@link Configuration} class method marked with the
* {@link Bean} annotation.
*
* @author Chris Beams
* @author Juergen Hoeller
@@ -31,30 +31,13 @@ import org.springframework.core.type.MethodMetadata;
* @see ConfigurationClassParser
* @see ConfigurationClassBeanDefinitionReader
*/
final class BeanMethod {
private final MethodMetadata metadata;
private final ConfigurationClass configurationClass;
final class BeanMethod extends ConfigurationMethod {
public BeanMethod(MethodMetadata metadata, ConfigurationClass configurationClass) {
this.metadata = metadata;
this.configurationClass = configurationClass;
}
public MethodMetadata getMetadata() {
return this.metadata;
}
public ConfigurationClass getConfigurationClass() {
return this.configurationClass;
}
public Location getResourceLocation() {
return new Location(this.configurationClass.getResource(), this.metadata);
super(metadata, configurationClass);
}
@Override
public void validate(ProblemReporter problemReporter) {
if (this.configurationClass.getMetadata().isAnnotated(Configuration.class.getName())) {
if (!getMetadata().isOverridable()) {
@@ -68,13 +51,6 @@ final class BeanMethod {
}
}
@Override
public String toString() {
return String.format("[%s:name=%s,declaringClass=%s]",
this.getClass().getSimpleName(), this.getMetadata().getMethodName(), this.getMetadata().getDeclaringClassName());
}
/**
* {@link Bean} methods must be overridable in order to accommodate CGLIB.
*/

View File

@@ -1,97 +0,0 @@
/*
* Copyright 2002-2011 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.context.annotation;
import java.util.Map;
import org.springframework.context.annotation.ComponentScan.Filter;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
/**
* {@link FeatureAnnotationParser} implementation that reads attributes from a
* {@link ComponentScan @ComponentScan} annotation into a {@link ComponentScanSpec}
* which can in turn be executed by {@link ComponentScanExecutor}.
* {@link ComponentScanBeanDefinitionParser} serves the same role for
* the {@code <context:component-scan>} XML element.
*
* <p>Note that {@link ComponentScanSpec} objects may be directly
* instantiated and returned from {@link Feature @Feature} methods as an
* alternative to using the {@link ComponentScan @ComponentScan} annotation.
*
* @author Chris Beams
* @since 3.1
* @see ComponentScan
* @see ComponentScanSpec
* @see ComponentScanExecutor
* @see ComponentScanBeanDefinitionParser
* @see ConfigurationClassBeanDefinitionReader
*/
final class ComponentScanAnnotationParser implements FeatureAnnotationParser {
/**
* Create and return a new {@link ComponentScanSpec} from the given
* {@link ComponentScan} annotation metadata.
* @throws IllegalArgumentException if ComponentScan attributes are not present in metadata
*/
public ComponentScanSpec process(AnnotationMetadata metadata) {
Map<String, Object> attribs = metadata.getAnnotationAttributes(ComponentScan.class.getName(), true);
Assert.notNull(attribs, String.format("@ComponentScan annotation not found " +
"while parsing metadata for class [%s].", metadata.getClassName()));
ClassLoader classLoader = ClassUtils.getDefaultClassLoader();
ComponentScanSpec spec = new ComponentScanSpec();
for (String pkg : (String[])attribs.get("value")) {
spec.addBasePackage(pkg);
}
for (String pkg : (String[])attribs.get("basePackages")) {
spec.addBasePackage(pkg);
}
for (String className : (String[])attribs.get("basePackageClasses")) {
spec.addBasePackage(className.substring(0, className.lastIndexOf('.')));
}
String resolverAttribute = "scopeResolver";
if (!((String)attribs.get(resolverAttribute)).equals(((Class<?>)AnnotationUtils.getDefaultValue(ComponentScan.class, resolverAttribute)).getName())) {
spec.scopeMetadataResolver((String)attribs.get(resolverAttribute), classLoader);
}
String scopedProxyAttribute = "scopedProxy";
ScopedProxyMode scopedProxyMode = (ScopedProxyMode) attribs.get(scopedProxyAttribute);
if (scopedProxyMode != ((ScopedProxyMode)AnnotationUtils.getDefaultValue(ComponentScan.class, scopedProxyAttribute))) {
spec.scopedProxyMode(scopedProxyMode);
}
for (Filter filter : (Filter[]) attribs.get("includeFilters")) {
spec.addIncludeFilter(filter.type().toString(), filter.value().getName(), classLoader);
}
for (Filter filter : (Filter[]) attribs.get("excludeFilters")) {
spec.addExcludeFilter(filter.type().toString(), filter.value().getName(), classLoader);
}
spec.resourcePattern((String)attribs.get("resourcePattern"))
.useDefaultFilters((Boolean)attribs.get("useDefaultFilters"))
.beanNameGenerator((String)attribs.get("nameGenerator"), classLoader)
.source(metadata.getClassName())
.sourceName(metadata.getClassName());
return spec;
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2011 the original author or authors.
* Copyright 2002-2009 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.
@@ -16,60 +16,263 @@
package org.springframework.context.annotation;
import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.context.config.AbstractSpecificationBeanDefinitionParser;
import org.springframework.context.config.FeatureSpecification;
import java.lang.annotation.Annotation;
import java.util.Set;
import java.util.regex.Pattern;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.FatalBeanException;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanDefinitionHolder;
import org.springframework.beans.factory.parsing.BeanComponentDefinition;
import org.springframework.beans.factory.parsing.CompositeComponentDefinition;
import org.springframework.beans.factory.support.BeanNameGenerator;
import org.springframework.beans.factory.xml.BeanDefinitionParser;
import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.beans.factory.xml.XmlReaderContext;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.type.filter.AnnotationTypeFilter;
import org.springframework.core.type.filter.AspectJTypeFilter;
import org.springframework.core.type.filter.AssignableTypeFilter;
import org.springframework.core.type.filter.RegexPatternTypeFilter;
import org.springframework.core.type.filter.TypeFilter;
import org.springframework.util.StringUtils;
/**
* Parser for the {@code <context:component-scan/>} element. Parsed metadata is
* used to populate and execute a {@link ComponentScanSpec} instance.
* Parser for the {@code <context:component-scan/>} element.
*
* @author Mark Fisher
* @author Ramnivas Laddad
* @author Juergen Hoeller
* @author Chris Beams
* @since 2.5
* @see ComponentScan
* @see ComponentScanSpec
* @see ComponentScanExecutor
*/
public class ComponentScanBeanDefinitionParser extends AbstractSpecificationBeanDefinitionParser {
public class ComponentScanBeanDefinitionParser implements BeanDefinitionParser {
public FeatureSpecification doParse(Element element, ParserContext parserContext) {
ClassLoader classLoader = parserContext.getReaderContext().getResourceLoader().getClassLoader();
private static final String BASE_PACKAGE_ATTRIBUTE = "base-package";
ComponentScanSpec spec =
ComponentScanSpec.forDelimitedPackages(element.getAttribute("base-package"))
.includeAnnotationConfig(element.getAttribute("annotation-config"))
.useDefaultFilters(element.getAttribute("use-default-filters"))
.resourcePattern(element.getAttribute("resource-pattern"))
.beanNameGenerator(element.getAttribute("name-generator"), classLoader)
.scopeMetadataResolver(element.getAttribute("scope-resolver"), classLoader)
.scopedProxyMode(element.getAttribute("scoped-proxy"))
.beanDefinitionDefaults(parserContext.getDelegate().getBeanDefinitionDefaults())
.autowireCandidatePatterns(parserContext.getDelegate().getAutowireCandidatePatterns());
private static final String RESOURCE_PATTERN_ATTRIBUTE = "resource-pattern";
private static final String USE_DEFAULT_FILTERS_ATTRIBUTE = "use-default-filters";
private static final String ANNOTATION_CONFIG_ATTRIBUTE = "annotation-config";
private static final String NAME_GENERATOR_ATTRIBUTE = "name-generator";
private static final String SCOPE_RESOLVER_ATTRIBUTE = "scope-resolver";
private static final String SCOPED_PROXY_ATTRIBUTE = "scoped-proxy";
private static final String EXCLUDE_FILTER_ELEMENT = "exclude-filter";
private static final String INCLUDE_FILTER_ELEMENT = "include-filter";
private static final String FILTER_TYPE_ATTRIBUTE = "type";
private static final String FILTER_EXPRESSION_ATTRIBUTE = "expression";
public BeanDefinition parse(Element element, ParserContext parserContext) {
String[] basePackages = StringUtils.tokenizeToStringArray(element.getAttribute(BASE_PACKAGE_ATTRIBUTE),
ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
// Actually scan for bean definitions and register them.
ClassPathBeanDefinitionScanner scanner = configureScanner(parserContext, element);
Set<BeanDefinitionHolder> beanDefinitions = scanner.doScan(basePackages);
registerComponents(parserContext.getReaderContext(), beanDefinitions, element);
return null;
}
protected ClassPathBeanDefinitionScanner configureScanner(ParserContext parserContext, Element element) {
XmlReaderContext readerContext = parserContext.getReaderContext();
boolean useDefaultFilters = true;
if (element.hasAttribute(USE_DEFAULT_FILTERS_ATTRIBUTE)) {
useDefaultFilters = Boolean.valueOf(element.getAttribute(USE_DEFAULT_FILTERS_ATTRIBUTE));
}
// Delegate bean definition registration to scanner class.
ClassPathBeanDefinitionScanner scanner = createScanner(readerContext, useDefaultFilters);
scanner.setResourceLoader(readerContext.getResourceLoader());
scanner.setBeanDefinitionDefaults(parserContext.getDelegate().getBeanDefinitionDefaults());
scanner.setAutowireCandidatePatterns(parserContext.getDelegate().getAutowireCandidatePatterns());
if (element.hasAttribute(RESOURCE_PATTERN_ATTRIBUTE)) {
scanner.setResourcePattern(element.getAttribute(RESOURCE_PATTERN_ATTRIBUTE));
}
try {
parseBeanNameGenerator(element, scanner);
}
catch (Exception ex) {
readerContext.error(ex.getMessage(), readerContext.extractSource(element), ex.getCause());
}
try {
parseScope(element, scanner);
}
catch (Exception ex) {
readerContext.error(ex.getMessage(), readerContext.extractSource(element), ex.getCause());
}
parseTypeFilters(element, scanner, readerContext, parserContext);
return scanner;
}
protected ClassPathBeanDefinitionScanner createScanner(XmlReaderContext readerContext, boolean useDefaultFilters) {
return new ClassPathBeanDefinitionScanner(readerContext.getRegistry(), useDefaultFilters);
}
protected void registerComponents(
XmlReaderContext readerContext, Set<BeanDefinitionHolder> beanDefinitions, Element element) {
Object source = readerContext.extractSource(element);
CompositeComponentDefinition compositeDef = new CompositeComponentDefinition(element.getTagName(), source);
for (BeanDefinitionHolder beanDefHolder : beanDefinitions) {
compositeDef.addNestedComponent(new BeanComponentDefinition(beanDefHolder));
}
// Register annotation config processors, if necessary.
boolean annotationConfig = true;
if (element.hasAttribute(ANNOTATION_CONFIG_ATTRIBUTE)) {
annotationConfig = Boolean.valueOf(element.getAttribute(ANNOTATION_CONFIG_ATTRIBUTE));
}
if (annotationConfig) {
Set<BeanDefinitionHolder> processorDefinitions =
AnnotationConfigUtils.registerAnnotationConfigProcessors(readerContext.getRegistry(), source);
for (BeanDefinitionHolder processorDefinition : processorDefinitions) {
compositeDef.addNestedComponent(new BeanComponentDefinition(processorDefinition));
}
}
readerContext.fireComponentRegistered(compositeDef);
}
protected void parseBeanNameGenerator(Element element, ClassPathBeanDefinitionScanner scanner) {
if (element.hasAttribute(NAME_GENERATOR_ATTRIBUTE)) {
BeanNameGenerator beanNameGenerator = (BeanNameGenerator) instantiateUserDefinedStrategy(
element.getAttribute(NAME_GENERATOR_ATTRIBUTE), BeanNameGenerator.class,
scanner.getResourceLoader().getClassLoader());
scanner.setBeanNameGenerator(beanNameGenerator);
}
}
protected void parseScope(Element element, ClassPathBeanDefinitionScanner scanner) {
// Register ScopeMetadataResolver if class name provided.
if (element.hasAttribute(SCOPE_RESOLVER_ATTRIBUTE)) {
if (element.hasAttribute(SCOPED_PROXY_ATTRIBUTE)) {
throw new IllegalArgumentException(
"Cannot define both 'scope-resolver' and 'scoped-proxy' on <component-scan> tag");
}
ScopeMetadataResolver scopeMetadataResolver = (ScopeMetadataResolver) instantiateUserDefinedStrategy(
element.getAttribute(SCOPE_RESOLVER_ATTRIBUTE), ScopeMetadataResolver.class,
scanner.getResourceLoader().getClassLoader());
scanner.setScopeMetadataResolver(scopeMetadataResolver);
}
if (element.hasAttribute(SCOPED_PROXY_ATTRIBUTE)) {
String mode = element.getAttribute(SCOPED_PROXY_ATTRIBUTE);
if ("targetClass".equals(mode)) {
scanner.setScopedProxyMode(ScopedProxyMode.TARGET_CLASS);
}
else if ("interfaces".equals(mode)) {
scanner.setScopedProxyMode(ScopedProxyMode.INTERFACES);
}
else if ("no".equals(mode)) {
scanner.setScopedProxyMode(ScopedProxyMode.NO);
}
else {
throw new IllegalArgumentException("scoped-proxy only supports 'no', 'interfaces' and 'targetClass'");
}
}
}
protected void parseTypeFilters(
Element element, ClassPathBeanDefinitionScanner scanner, XmlReaderContext readerContext, ParserContext parserContext) {
// Parse exclude and include filter elements.
ClassLoader classLoader = scanner.getResourceLoader().getClassLoader();
NodeList nodeList = element.getChildNodes();
for (int i = 0; i < nodeList.getLength(); i++) {
Node node = nodeList.item(i);
if (node.getNodeType() == Node.ELEMENT_NODE) {
String localName = parserContext.getDelegate().getLocalName(node);
String filterType = ((Element)node).getAttribute("type");
String expression = ((Element)node).getAttribute("expression");
if ("include-filter".equals(localName)) {
spec.addIncludeFilter(filterType, expression, classLoader);
try {
if (INCLUDE_FILTER_ELEMENT.equals(localName)) {
TypeFilter typeFilter = createTypeFilter((Element) node, classLoader);
scanner.addIncludeFilter(typeFilter);
}
else if (EXCLUDE_FILTER_ELEMENT.equals(localName)) {
TypeFilter typeFilter = createTypeFilter((Element) node, classLoader);
scanner.addExcludeFilter(typeFilter);
}
}
else if ("exclude-filter".equals(localName)) {
spec.addExcludeFilter(filterType, expression, classLoader);
catch (Exception ex) {
readerContext.error(ex.getMessage(), readerContext.extractSource(element), ex.getCause());
}
}
}
}
return spec;
@SuppressWarnings("unchecked")
protected TypeFilter createTypeFilter(Element element, ClassLoader classLoader) {
String filterType = element.getAttribute(FILTER_TYPE_ATTRIBUTE);
String expression = element.getAttribute(FILTER_EXPRESSION_ATTRIBUTE);
try {
if ("annotation".equals(filterType)) {
return new AnnotationTypeFilter((Class<Annotation>) classLoader.loadClass(expression));
}
else if ("assignable".equals(filterType)) {
return new AssignableTypeFilter(classLoader.loadClass(expression));
}
else if ("aspectj".equals(filterType)) {
return new AspectJTypeFilter(expression, classLoader);
}
else if ("regex".equals(filterType)) {
return new RegexPatternTypeFilter(Pattern.compile(expression));
}
else if ("custom".equals(filterType)) {
Class filterClass = classLoader.loadClass(expression);
if (!TypeFilter.class.isAssignableFrom(filterClass)) {
throw new IllegalArgumentException(
"Class is not assignable to [" + TypeFilter.class.getName() + "]: " + expression);
}
return (TypeFilter) BeanUtils.instantiateClass(filterClass);
}
else {
throw new IllegalArgumentException("Unsupported filter type: " + filterType);
}
}
catch (ClassNotFoundException ex) {
throw new FatalBeanException("Type filter class not found: " + expression, ex);
}
}
@SuppressWarnings("unchecked")
private Object instantiateUserDefinedStrategy(String className, Class strategyType, ClassLoader classLoader) {
Object result = null;
try {
result = classLoader.loadClass(className).newInstance();
}
catch (ClassNotFoundException ex) {
throw new IllegalArgumentException("Class [" + className + "] for strategy [" +
strategyType.getName() + "] not found", ex);
}
catch (Exception ex) {
throw new IllegalArgumentException("Unable to instantiate class [" + className + "] for strategy [" +
strategyType.getName() + "]. A zero-argument constructor is required", ex);
}
if (!strategyType.isAssignableFrom(result.getClass())) {
throw new IllegalArgumentException("Provided class name must be an implementation of " + strategyType);
}
return result;
}
}

View File

@@ -1,110 +0,0 @@
/*
* Copyright 2002-2011 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.context.annotation;
import java.util.Set;
import org.springframework.beans.factory.config.BeanDefinitionHolder;
import org.springframework.beans.factory.parsing.BeanComponentDefinition;
import org.springframework.beans.factory.parsing.CompositeComponentDefinition;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.context.config.AbstractSpecificationExecutor;
import org.springframework.context.config.SpecificationContext;
import org.springframework.core.env.Environment;
import org.springframework.core.io.ResourceLoader;
import org.springframework.core.type.filter.TypeFilter;
/**
* Executes the {@link ComponentScanSpec} feature specification.
*
* @author Chris Beams
* @since 3.1
* @see ComponentScanSpec
* @see ComponentScanBeanDefinitionParser
* @see ComponentScan
*/
final class ComponentScanExecutor extends AbstractSpecificationExecutor<ComponentScanSpec> {
/**
* Configure a {@link ClassPathBeanDefinitionScanner} based on the content of
* the given specification and perform actual scanning and bean definition
* registration.
*/
protected void doExecute(ComponentScanSpec spec, SpecificationContext specificationContext) {
BeanDefinitionRegistry registry = specificationContext.getRegistry();
ResourceLoader resourceLoader = specificationContext.getResourceLoader();
Environment environment = specificationContext.getEnvironment();
ClassPathBeanDefinitionScanner scanner = spec.useDefaultFilters() == null ?
new ClassPathBeanDefinitionScanner(registry) :
new ClassPathBeanDefinitionScanner(registry, spec.useDefaultFilters());
scanner.setResourceLoader(resourceLoader);
scanner.setEnvironment(environment);
if (spec.beanDefinitionDefaults() != null) {
scanner.setBeanDefinitionDefaults(spec.beanDefinitionDefaults());
}
if (spec.autowireCandidatePatterns() != null) {
scanner.setAutowireCandidatePatterns(spec.autowireCandidatePatterns());
}
if (spec.resourcePattern() != null) {
scanner.setResourcePattern(spec.resourcePattern());
}
if (spec.beanNameGenerator() != null) {
scanner.setBeanNameGenerator(spec.beanNameGenerator());
}
if (spec.includeAnnotationConfig() != null) {
scanner.setIncludeAnnotationConfig(spec.includeAnnotationConfig());
}
if (spec.scopeMetadataResolver() != null) {
scanner.setScopeMetadataResolver(spec.scopeMetadataResolver());
}
if (spec.scopedProxyMode() != null) {
scanner.setScopedProxyMode(spec.scopedProxyMode());
}
for (TypeFilter filter : spec.includeFilters()) {
scanner.addIncludeFilter(filter);
}
for (TypeFilter filter : spec.excludeFilters()) {
scanner.addExcludeFilter(filter);
}
Set<BeanDefinitionHolder> scannedBeans = scanner.doScan(spec.basePackages());
Object source = spec.source();
String sourceName = spec.sourceName();
CompositeComponentDefinition compositeDef = new CompositeComponentDefinition(sourceName, source);
for (BeanDefinitionHolder beanDefHolder : scannedBeans) {
compositeDef.addNestedComponent(new BeanComponentDefinition(beanDefHolder));
}
// Register annotation config processors, if necessary.
if ((spec.includeAnnotationConfig() != null) && spec.includeAnnotationConfig()) {
Set<BeanDefinitionHolder> processorDefinitions =
AnnotationConfigUtils.registerAnnotationConfigProcessors(registry, source);
for (BeanDefinitionHolder processorDefinition : processorDefinitions) {
compositeDef.addNestedComponent(new BeanComponentDefinition(processorDefinition));
}
}
specificationContext.getRegistrar().registerComponent(compositeDef);
}
}

View File

@@ -1,460 +0,0 @@
/*
* Copyright 2002-2011 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.context.annotation;
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Pattern;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.parsing.ProblemCollector;
import org.springframework.beans.factory.support.BeanDefinitionDefaults;
import org.springframework.beans.factory.support.BeanNameGenerator;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.config.AbstractFeatureSpecification;
import org.springframework.context.config.FeatureSpecificationExecutor;
import org.springframework.core.type.classreading.MetadataReader;
import org.springframework.core.type.classreading.MetadataReaderFactory;
import org.springframework.core.type.filter.AnnotationTypeFilter;
import org.springframework.core.type.filter.AspectJTypeFilter;
import org.springframework.core.type.filter.AssignableTypeFilter;
import org.springframework.core.type.filter.RegexPatternTypeFilter;
import org.springframework.core.type.filter.TypeFilter;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
/**
* Specifies the configuration of Spring's <em>component-scanning</em> feature.
* May be used directly within a {@link Feature @Feature} method, or indirectly
* through the {@link ComponentScan @ComponentScan} annotation.
*
* @author Chris Beams
* @since 3.1
* @see ComponentScan
* @see ComponentScanAnnotationParser
* @see ComponentScanBeanDefinitionParser
* @see ComponentScanExecutor
*/
public final class ComponentScanSpec extends AbstractFeatureSpecification {
private static final Class<? extends FeatureSpecificationExecutor> EXECUTOR_TYPE = ComponentScanExecutor.class;
private Boolean includeAnnotationConfig = null;
private String resourcePattern = null;
private List<String> basePackages = new ArrayList<String>();
private Object beanNameGenerator = null;
private Object scopeMetadataResolver = null;
private Object scopedProxyMode = null;
private Boolean useDefaultFilters = null;
private List<Object> includeFilters = new ArrayList<Object>();
private List<Object> excludeFilters = new ArrayList<Object>();
private BeanDefinitionDefaults beanDefinitionDefaults;
private String[] autowireCandidatePatterns;
private ClassLoader classLoader;
/**
* Package-visible constructor for use by {@link ComponentScanBeanDefinitionParser}.
* End users should always call String... or Class<?>... constructors to specify
* base packages.
*
* @see #validate()
*/
ComponentScanSpec() {
super(EXECUTOR_TYPE);
}
/**
*
* @param basePackages
* @see #forDelimitedPackages(String)
*/
public ComponentScanSpec(String... basePackages) {
this();
Assert.notEmpty(basePackages, "At least one base package must be specified");
for (String basePackage : basePackages) {
addBasePackage(basePackage);
}
}
public ComponentScanSpec(Class<?>... basePackageClasses) {
this(packagesFor(basePackageClasses));
}
public ComponentScanSpec includeAnnotationConfig(Boolean includeAnnotationConfig) {
this.includeAnnotationConfig = includeAnnotationConfig;
return this;
}
ComponentScanSpec includeAnnotationConfig(String includeAnnotationConfig) {
if (StringUtils.hasText(includeAnnotationConfig)) {
this.includeAnnotationConfig = Boolean.valueOf(includeAnnotationConfig);
}
return this;
}
Boolean includeAnnotationConfig() {
return this.includeAnnotationConfig;
}
public ComponentScanSpec resourcePattern(String resourcePattern) {
if (StringUtils.hasText(resourcePattern)) {
this.resourcePattern = resourcePattern;
}
return this;
}
String resourcePattern() {
return resourcePattern;
}
ComponentScanSpec addBasePackage(String basePackage) {
if (StringUtils.hasText(basePackage)) {
this.basePackages.add(basePackage);
}
return this;
}
/**
* Return the set of base packages specified, never {@code null}, never empty
* post-validation.
* @see #doValidate(SimpleProblemReporter)
*/
String[] basePackages() {
return this.basePackages.toArray(new String[this.basePackages.size()]);
}
public ComponentScanSpec beanNameGenerator(BeanNameGenerator beanNameGenerator) {
this.beanNameGenerator = beanNameGenerator;
return this;
}
/**
* Set the class name of the BeanNameGenerator to be used and the ClassLoader
* to load it.
*/
ComponentScanSpec beanNameGenerator(String beanNameGenerator, ClassLoader classLoader) {
setClassLoader(classLoader);
if (StringUtils.hasText(beanNameGenerator)) {
this.beanNameGenerator = beanNameGenerator;
}
return this;
}
BeanNameGenerator beanNameGenerator() {
return nullSafeTypedObject(this.beanNameGenerator, BeanNameGenerator.class);
}
public ComponentScanSpec scopeMetadataResolver(ScopeMetadataResolver scopeMetadataResolver) {
this.scopeMetadataResolver = scopeMetadataResolver;
return this;
}
ComponentScanSpec scopeMetadataResolver(String scopeMetadataResolver, ClassLoader classLoader) {
setClassLoader(classLoader);
if (StringUtils.hasText(scopeMetadataResolver)) {
this.scopeMetadataResolver = scopeMetadataResolver;
}
return this;
}
ScopeMetadataResolver scopeMetadataResolver() {
return nullSafeTypedObject(this.scopeMetadataResolver, ScopeMetadataResolver.class);
}
public ComponentScanSpec scopedProxyMode(ScopedProxyMode scopedProxyMode) {
this.scopedProxyMode = scopedProxyMode;
return this;
}
ComponentScanSpec scopedProxyMode(String scopedProxyMode) {
if (StringUtils.hasText(scopedProxyMode)) {
this.scopedProxyMode = scopedProxyMode;
}
return this;
}
ScopedProxyMode scopedProxyMode() {
return nullSafeTypedObject(this.scopedProxyMode, ScopedProxyMode.class);
}
public ComponentScanSpec useDefaultFilters(Boolean useDefaultFilters) {
this.useDefaultFilters = useDefaultFilters;
return this;
}
ComponentScanSpec useDefaultFilters(String useDefaultFilters) {
if (StringUtils.hasText(useDefaultFilters)) {
this.useDefaultFilters = Boolean.valueOf(useDefaultFilters);
}
return this;
}
Boolean useDefaultFilters() {
return this.useDefaultFilters;
}
public ComponentScanSpec includeFilters(TypeFilter... includeFilters) {
this.includeFilters.clear();
for (TypeFilter filter : includeFilters) {
addIncludeFilter(filter);
}
return this;
}
ComponentScanSpec addIncludeFilter(TypeFilter includeFilter) {
Assert.notNull(includeFilter, "includeFilter must not be null");
this.includeFilters.add(includeFilter);
return this;
}
ComponentScanSpec addIncludeFilter(String filterType, String expression, ClassLoader classLoader) {
this.includeFilters.add(new FilterTypeDescriptor(filterType, expression, classLoader));
return this;
}
TypeFilter[] includeFilters() {
return this.includeFilters.toArray(new TypeFilter[this.includeFilters.size()]);
}
public ComponentScanSpec excludeFilters(TypeFilter... excludeFilters) {
this.excludeFilters.clear();
for (TypeFilter filter : excludeFilters) {
addExcludeFilter(filter);
}
return this;
}
ComponentScanSpec addExcludeFilter(TypeFilter excludeFilter) {
Assert.notNull(excludeFilter, "excludeFilter must not be null");
this.excludeFilters.add(excludeFilter);
return this;
}
ComponentScanSpec addExcludeFilter(String filterType, String expression, ClassLoader classLoader) {
this.excludeFilters.add(new FilterTypeDescriptor(filterType, expression, classLoader));
return this;
}
TypeFilter[] excludeFilters() {
return this.excludeFilters.toArray(new TypeFilter[this.excludeFilters.size()]);
}
ComponentScanSpec beanDefinitionDefaults(BeanDefinitionDefaults beanDefinitionDefaults) {
this.beanDefinitionDefaults = beanDefinitionDefaults;
return this;
}
BeanDefinitionDefaults beanDefinitionDefaults() {
return this.beanDefinitionDefaults;
}
ComponentScanSpec autowireCandidatePatterns(String[] autowireCandidatePatterns) {
this.autowireCandidatePatterns = autowireCandidatePatterns;
return this;
}
String[] autowireCandidatePatterns() {
return this.autowireCandidatePatterns;
}
/**
* Create a ComponentScanSpec from a single string containing
* delimited package names.
* @see ConfigurableApplicationContext#CONFIG_LOCATION_DELIMITERS
*/
static ComponentScanSpec forDelimitedPackages(String basePackages) {
Assert.notNull(basePackages, "base packages must not be null");
return new ComponentScanSpec(
StringUtils.tokenizeToStringArray(basePackages,
ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
}
public void doValidate(ProblemCollector problems) {
if(this.basePackages.isEmpty()) {
problems.error("At least one base package must be specified");
}
if(this.beanNameGenerator instanceof String) {
this.beanNameGenerator = instantiateUserDefinedType("bean name generator", BeanNameGenerator.class, this.beanNameGenerator, this.classLoader, problems);
}
if(this.scopeMetadataResolver instanceof String) {
this.scopeMetadataResolver = instantiateUserDefinedType("scope metadata resolver", ScopeMetadataResolver.class, this.scopeMetadataResolver, this.classLoader, problems);
}
if (this.scopedProxyMode instanceof String) {
if ("targetClass".equalsIgnoreCase((String)this.scopedProxyMode)) {
this.scopedProxyMode = ScopedProxyMode.TARGET_CLASS;
}
else if ("interfaces".equalsIgnoreCase((String)this.scopedProxyMode)) {
this.scopedProxyMode = ScopedProxyMode.INTERFACES;
}
else if ("no".equalsIgnoreCase((String)this.scopedProxyMode)) {
this.scopedProxyMode = ScopedProxyMode.NO;
}
else {
problems.error("invalid scoped proxy mode [%s] supported modes are " +
"'no', 'interfaces' and 'targetClass'");
this.scopedProxyMode = null;
}
}
if (this.scopeMetadataResolver != null && this.scopedProxyMode != null) {
problems.error("Cannot define both scope metadata resolver and scoped proxy mode");
}
for (int i = 0; i < this.includeFilters.size(); i++) {
if (this.includeFilters.get(i) instanceof FilterTypeDescriptor) {
this.includeFilters.set(i, ((FilterTypeDescriptor)this.includeFilters.get(i)).createTypeFilter(problems));
}
}
for (int i = 0; i < this.excludeFilters.size(); i++) {
if (this.excludeFilters.get(i) instanceof FilterTypeDescriptor) {
this.excludeFilters.set(i, ((FilterTypeDescriptor)this.excludeFilters.get(i)).createTypeFilter(problems));
}
}
}
private static Object instantiateUserDefinedType(String description, Class<?> targetType, Object className, ClassLoader classLoader, ProblemCollector problems) {
Assert.isInstanceOf(String.class, className, "userType must be of type String");
Assert.notNull(classLoader, "classLoader must not be null");
Assert.notNull(targetType, "targetType must not be null");
Object instance = null;
try {
instance = classLoader.loadClass((String)className).newInstance();
if (!targetType.isAssignableFrom(instance.getClass())) {
problems.error(description + " class name must be assignable to " + targetType.getSimpleName());
instance = null;
}
}
catch (ClassNotFoundException ex) {
problems.error(String.format(description + " class [%s] not found", className), ex);
}
catch (Exception ex) {
problems.error(String.format("Unable to instantiate %s class [%s] for " +
"strategy [%s]. Has a no-argument constructor been provided?",
description, className, targetType.getClass().getSimpleName()), ex);
}
return instance;
}
private void setClassLoader(ClassLoader classLoader) {
Assert.notNull(classLoader, "classLoader must not be null");
if (this.classLoader == null) {
this.classLoader = classLoader;
}
else {
Assert.isTrue(this.classLoader == classLoader, "A classLoader has already been assigned " +
"and the supplied classLoader is not the same instance. Use the same classLoader " +
"for all string-based class properties.");
}
}
@SuppressWarnings("unchecked")
private static <T> T nullSafeTypedObject(Object object, Class<T> type) {
if (object != null) {
if (!(type.isAssignableFrom(object.getClass()))) {
throw new IllegalStateException(
String.format("field must be of type %s but was actually of type %s", type, object.getClass()));
}
}
return (T)object;
}
private static String[] packagesFor(Class<?>[] classes) {
ArrayList<String> packages = new ArrayList<String>();
for (Class<?> clazz : classes) {
packages.add(clazz.getPackage().getName());
}
return packages.toArray(new String[packages.size()]);
}
private static class FilterTypeDescriptor {
private String filterType;
private String expression;
private ClassLoader classLoader;
FilterTypeDescriptor(String filterType, String expression, ClassLoader classLoader) {
Assert.notNull(filterType, "filterType must not be null");
Assert.notNull(expression, "expression must not be null");
Assert.notNull(classLoader, "classLoader must not be null");
this.filterType = filterType;
this.expression = expression;
this.classLoader = classLoader;
}
@SuppressWarnings("unchecked")
TypeFilter createTypeFilter(ProblemCollector problems) {
try {
if ("annotation".equalsIgnoreCase(this.filterType)) {
return new AnnotationTypeFilter((Class<Annotation>) this.classLoader.loadClass(this.expression));
}
else if ("assignable".equalsIgnoreCase(this.filterType)
|| "assignable_type".equalsIgnoreCase(this.filterType)) {
return new AssignableTypeFilter(this.classLoader.loadClass(this.expression));
}
else if ("aspectj".equalsIgnoreCase(this.filterType)) {
return new AspectJTypeFilter(this.expression, this.classLoader);
}
else if ("regex".equalsIgnoreCase(this.filterType)) {
return new RegexPatternTypeFilter(Pattern.compile(this.expression));
}
else if ("custom".equalsIgnoreCase(this.filterType)) {
Class<?> filterClass = this.classLoader.loadClass(this.expression);
if (!TypeFilter.class.isAssignableFrom(filterClass)) {
problems.error(String.format("custom type filter class [%s] must be assignable to %s",
this.expression, TypeFilter.class));
}
return (TypeFilter) BeanUtils.instantiateClass(filterClass);
}
else {
problems.error(String.format("Unsupported filter type [%s]; supported types are: " +
"'annotation', 'assignable[_type]', 'aspectj', 'regex', 'custom'", this.filterType));
}
} catch (ClassNotFoundException ex) {
problems.error("Type filter class not found: " + this.expression, ex);
} catch (Exception ex) {
problems.error(ex.getMessage(), ex.getCause());
}
return new PlaceholderTypeFilter();
}
private class PlaceholderTypeFilter implements TypeFilter {
public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory)
throws IOException {
throw new UnsupportedOperationException(
String.format("match() method for placeholder type filter for " +
"{filterType=%s,expression=%s} should never be invoked",
filterType, expression));
}
}
}
}

View File

@@ -33,7 +33,6 @@ import org.springframework.beans.factory.annotation.Autowire;
import org.springframework.beans.factory.annotation.RequiredAnnotationBeanPostProcessor;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanDefinitionHolder;
import org.springframework.beans.factory.parsing.BeanDefinitionParsingException;
import org.springframework.beans.factory.parsing.Location;
import org.springframework.beans.factory.parsing.Problem;
import org.springframework.beans.factory.parsing.ProblemReporter;
@@ -45,8 +44,8 @@ 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;
import org.springframework.context.config.FeatureSpecification;
import org.springframework.context.config.SpecificationContext;
import org.springframework.context.EnvironmentAware;
import org.springframework.context.ResourceLoaderAware;
import org.springframework.core.Conventions;
import org.springframework.core.env.Environment;
import org.springframework.core.io.Resource;
@@ -94,7 +93,7 @@ public class ConfigurationClassBeanDefinitionReader {
private ResourceLoader resourceLoader;
private SpecificationContext specificationContext;
private Environment environment;
/**
* Create a new {@link ConfigurationClassBeanDefinitionReader} instance that will be used
@@ -111,13 +110,7 @@ public class ConfigurationClassBeanDefinitionReader {
this.problemReporter = problemReporter;
this.metadataReaderFactory = metadataReaderFactory;
this.resourceLoader = resourceLoader;
// TODO SPR-7420: see about passing in the SpecificationContext created in ConfigurationClassPostProcessor
this.specificationContext = new SpecificationContext();
this.specificationContext.setRegistry(this.registry);
this.specificationContext.setRegistrar(new SimpleComponentRegistrar(this.registry));
this.specificationContext.setResourceLoader(this.resourceLoader);
this.specificationContext.setEnvironment(environment);
this.specificationContext.setProblemReporter(problemReporter);
this.environment = environment;
}
@@ -137,7 +130,6 @@ public class ConfigurationClassBeanDefinitionReader {
*/
private void loadBeanDefinitionsForConfigurationClass(ConfigurationClass configClass) {
AnnotationMetadata metadata = configClass.getMetadata();
processFeatureAnnotations(metadata);
doLoadBeanDefinitionForConfigurationClassIfNecessary(configClass);
for (BeanMethod beanMethod : configClass.getBeanMethods()) {
loadBeanDefinitionsForBeanMethod(beanMethod);
@@ -145,27 +137,6 @@ public class ConfigurationClassBeanDefinitionReader {
loadBeanDefinitionsFromImportedResources(configClass.getImportedResources());
}
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);
spec.execute(this.specificationContext);
}
}
} catch (BeanDefinitionParsingException ex) {
throw ex;
}
catch (Exception ex) {
// TODO SPR-7420: what exception to throw?
throw new RuntimeException(ex);
}
}
/**
* Register the {@link Configuration} class itself as a bean definition.
*/
@@ -200,7 +171,7 @@ public class ConfigurationClassBeanDefinitionReader {
}
/**
* Read a particular {@link BeanMethod}, registering bean definitions
* Read the given {@link BeanMethod}, registering bean definitions
* with the BeanDefinitionRegistry based on its contents.
*/
private void loadBeanDefinitionsForBeanMethod(BeanMethod beanMethod) {
@@ -423,8 +394,8 @@ public class ConfigurationClassBeanDefinitionReader {
*/
private static class InvalidConfigurationImportProblem extends Problem {
public InvalidConfigurationImportProblem(String className, Resource resource, AnnotationMetadata metadata) {
super(String.format("%s was @Import'ed but is not annotated with @FeatureConfiguration or " +
"@Configuration nor does it declare any @Bean methods. Update the class to " +
super(String.format("%s was @Import'ed but is not annotated with @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),
new Location(resource, metadata));
}

View File

@@ -197,15 +197,12 @@ class ConfigurationClassEnhancer {
* Enhance a {@link Bean @Bean} method to check the supplied BeanFactory for the
* existence of this bean object.
*
* @throws ProxyCreationException if an early bean reference proxy should be
* created but the return type of the bean method being intercepted is not an
* interface and thus not a candidate for JDK proxy creation.
* @throws Throwable as a catch-all for any exception that may be thrown when
* invoking the super implementation of the proxied method i.e., the actual
* {@code @Bean} method.
*/
public Object intercept(Object enhancedConfigInstance, Method beanMethod, Object[] beanMethodArgs,
MethodProxy cglibMethodProxy) throws ProxyCreationException, Throwable {
MethodProxy cglibMethodProxy) throws Throwable {
String beanName = BeanAnnotationHelper.determineBeanNameFor(beanMethod);

View File

@@ -16,15 +16,9 @@
package org.springframework.context.annotation;
import static java.lang.String.format;
import java.io.IOException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -32,12 +26,10 @@ import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.BeanClassLoaderAware;
import org.springframework.beans.factory.BeanDefinitionStoreException;
import org.springframework.beans.factory.ListableBeanFactory;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanDefinitionHolder;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.config.DependencyDescriptor;
import org.springframework.beans.factory.parsing.FailFastProblemReporter;
import org.springframework.beans.factory.parsing.PassThroughSourceExtractor;
import org.springframework.beans.factory.parsing.ProblemReporter;
@@ -47,23 +39,14 @@ import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor;
import org.springframework.context.EnvironmentAware;
import org.springframework.context.ResourceLoaderAware;
import org.springframework.context.config.FeatureSpecification;
import org.springframework.context.config.SourceAwareSpecification;
import org.springframework.context.config.SpecificationContext;
import org.springframework.core.MethodParameter;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.core.env.Environment;
import org.springframework.core.io.DefaultResourceLoader;
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.core.type.classreading.SimpleMetadataReaderFactory;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.ReflectionUtils;
/**
* {@link BeanFactoryPostProcessor} used for bootstrapping processing of
@@ -194,175 +177,13 @@ public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPo
}
/**
* Find and process all @Configuration classes with @Feature methods in the given registry.
* Find and process all @Configuration classes in the given registry.
*/
private void processConfigurationClasses(BeanDefinitionRegistry registry) {
ConfigurationClassBeanDefinitionReader reader = getConfigurationClassBeanDefinitionReader(registry);
processConfigBeanDefinitions(registry, reader);
ConfigurationClassParser parser = new ConfigurationClassParser(this.metadataReaderFactory, this.problemReporter, this.environment);
processConfigBeanDefinitions(parser, reader, registry);
enhanceConfigurationClasses((ConfigurableListableBeanFactory)registry);
processFeatureConfigurationClasses((ConfigurableListableBeanFactory) registry);
}
/**
* Process any @FeatureConfiguration classes
*/
private void processFeatureConfigurationClasses(final ConfigurableListableBeanFactory beanFactory) {
Map<String, Object> featureConfigBeans = retrieveFeatureConfigurationBeans(beanFactory);
if (featureConfigBeans.size() == 0) {
return;
}
for (final Object featureConfigBean : featureConfigBeans.values()) {
checkForBeanMethods(featureConfigBean.getClass());
}
if (!cglibAvailable) {
throw new IllegalStateException("CGLIB is required to process @FeatureConfiguration classes. " +
"Either add CGLIB to the classpath or remove the following @FeatureConfiguration bean definitions: " +
featureConfigBeans.keySet());
}
final EarlyBeanReferenceProxyCreator proxyCreator = new EarlyBeanReferenceProxyCreator(beanFactory);
final SpecificationContext specificationContext = createSpecificationContext(beanFactory);
for (final Object featureConfigBean : featureConfigBeans.values()) {
ReflectionUtils.doWithMethods(featureConfigBean.getClass(),
new ReflectionUtils.MethodCallback() {
public void doWith(Method featureMethod) throws IllegalArgumentException, IllegalAccessException {
processFeatureMethod(featureMethod, featureConfigBean, specificationContext, proxyCreator);
} },
new ReflectionUtils.MethodFilter() {
public boolean matches(Method candidateMethod) {
return candidateMethod.isAnnotationPresent(Feature.class);
} });
}
}
/**
* Alternative to {@link ListableBeanFactory#getBeansWithAnnotation(Class)} that avoids
* instantiating FactoryBean objects. FeatureConfiguration types cannot be registered as
* FactoryBeans, so ignoring them won't cause a problem. On the other hand, using gBWA()
* at this early phase of the container would cause all @Bean methods to be invoked, as they
* are ultimately FactoryBeans underneath.
*/
private Map<String, Object> retrieveFeatureConfigurationBeans(ConfigurableListableBeanFactory beanFactory) {
Map<String, Object> fcBeans = new HashMap<String, Object>();
for (String beanName : beanFactory.getBeanDefinitionNames()) {
BeanDefinition beanDef = beanFactory.getBeanDefinition(beanName);
if (isFeatureConfiguration(beanDef)) {
fcBeans.put(beanName, beanFactory.getBean(beanName));
}
}
return fcBeans;
}
private boolean isFeatureConfiguration(BeanDefinition candidate) {
if (!(candidate instanceof AbstractBeanDefinition) || (candidate.getBeanClassName() == null)) {
return false;
}
AbstractBeanDefinition beanDef = (AbstractBeanDefinition) candidate;
if (beanDef.hasBeanClass()) {
Class<?> beanClass = beanDef.getBeanClass();
if (AnnotationUtils.findAnnotation(beanClass, FeatureConfiguration.class) != null) {
return true;
}
}
else {
// in the case of @FeatureConfiguration classes included with @Import the bean class name
// will still be in String form. Since we don't know whether the current bean definition
// is a @FeatureConfiguration or not, carefully check for the annotation using ASM instead
// eager classloading.
String className = null;
try {
className = beanDef.getBeanClassName();
MetadataReader metadataReader = new SimpleMetadataReaderFactory().getMetadataReader(className);
AnnotationMetadata annotationMetadata = metadataReader.getAnnotationMetadata();
if (annotationMetadata.isAnnotated(FeatureConfiguration.class.getName())) {
return true;
}
}
catch (IOException ex) {
throw new IllegalStateException("Could not create MetadataReader for class " + className, ex);
}
}
return false;
}
private void checkForBeanMethods(final Class<?> featureConfigClass) {
ReflectionUtils.doWithMethods(featureConfigClass,
new ReflectionUtils.MethodCallback() {
public void doWith(Method beanMethod) throws IllegalArgumentException, IllegalAccessException {
throw new FeatureMethodExecutionException(
format("@FeatureConfiguration classes must not contain @Bean-annotated methods. " +
"%s.%s() is annotated with @Bean and must be removed in order to proceed. " +
"Consider moving this method into a dedicated @Configuration class and " +
"injecting the bean as a parameter into any @Feature method(s) that need it.",
beanMethod.getDeclaringClass().getSimpleName(), beanMethod.getName()));
} },
new ReflectionUtils.MethodFilter() {
public boolean matches(Method candidateMethod) {
return BeanAnnotationHelper.isBeanAnnotated(candidateMethod);
} });
}
/**
* TODO SPR-7420: this method invokes user-supplied code, which is not going to fly for STS
*
* consider introducing some kind of check to see if we're in a tooling context and make guesses
* based on return type rather than actually invoking the method and processing the the specification
* object that returns.
* @param beanFactory
* @throws SecurityException
*/
private void processFeatureMethod(final Method featureMethod, Object configInstance,
SpecificationContext specificationContext, EarlyBeanReferenceProxyCreator proxyCreator) {
try {
// get the return type
if (!(FeatureSpecification.class.isAssignableFrom(featureMethod.getReturnType()))) {
// TODO SPR-7420: raise a Problem instead?
throw new IllegalArgumentException(
format("Return type for @Feature method %s.%s() must be assignable to FeatureSpecification",
featureMethod.getDeclaringClass().getSimpleName(), featureMethod.getName()));
}
List<Object> beanArgs = new ArrayList<Object>();
Class<?>[] parameterTypes = featureMethod.getParameterTypes();
for (int i = 0; i < parameterTypes.length; i++) {
MethodParameter mp = new MethodParameter(featureMethod, i);
DependencyDescriptor dd = new DependencyDescriptor(mp, true, false);
Object proxiedBean = proxyCreator.createProxyIfPossible(dd);
beanArgs.add(proxiedBean);
}
// reflectively invoke that method
FeatureSpecification spec;
featureMethod.setAccessible(true);
spec = (FeatureSpecification) featureMethod.invoke(configInstance, beanArgs.toArray(new Object[beanArgs.size()]));
Assert.notNull(spec,
format("The specification returned from @Feature method %s.%s() must not be null",
featureMethod.getDeclaringClass().getSimpleName(), featureMethod.getName()));
if (spec instanceof SourceAwareSpecification) {
((SourceAwareSpecification)spec).source(featureMethod);
((SourceAwareSpecification)spec).sourceName(featureMethod.getName());
}
spec.execute(specificationContext);
} catch (Exception ex) {
throw new FeatureMethodExecutionException(ex);
}
}
private SpecificationContext createSpecificationContext(ConfigurableListableBeanFactory beanFactory) {
final BeanDefinitionRegistry registry = (BeanDefinitionRegistry) beanFactory;
SpecificationContext specificationContext = new SpecificationContext();
specificationContext.setEnvironment(this.environment);
specificationContext.setResourceLoader(this.resourceLoader);
specificationContext.setRegistry(registry);
specificationContext.setRegistrar(new SimpleComponentRegistrar(registry));
specificationContext.setProblemReporter(this.problemReporter);
return specificationContext;
}
private ConfigurationClassBeanDefinitionReader getConfigurationClassBeanDefinitionReader(BeanDefinitionRegistry registry) {
@@ -377,7 +198,7 @@ public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPo
* Build and validate a configuration model based on the registry of
* {@link Configuration} classes.
*/
public void processConfigBeanDefinitions(BeanDefinitionRegistry registry, ConfigurationClassBeanDefinitionReader reader) {
public void processConfigBeanDefinitions(ConfigurationClassParser parser, ConfigurationClassBeanDefinitionReader reader, BeanDefinitionRegistry registry) {
Set<BeanDefinitionHolder> configCandidates = new LinkedHashSet<BeanDefinitionHolder>();
for (String beanName : registry.getBeanDefinitionNames()) {
BeanDefinition beanDef = registry.getBeanDefinition(beanName);
@@ -391,8 +212,7 @@ public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPo
return;
}
// Populate a new configuration model by parsing each @Configuration classes
ConfigurationClassParser parser = new ConfigurationClassParser(this.metadataReaderFactory, this.problemReporter, this.environment);
// Parse each @Configuration class
for (BeanDefinitionHolder holder : configCandidates) {
BeanDefinition bd = holder.getBeanDefinition();
try {

View File

@@ -0,0 +1,60 @@
/*
* Copyright 2002-2011 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.context.annotation;
import org.springframework.beans.factory.parsing.Location;
import org.springframework.beans.factory.parsing.ProblemReporter;
import org.springframework.core.type.MethodMetadata;
/**
* @author Chris Beams
* @since 3.1
*/
abstract class ConfigurationMethod {
protected final MethodMetadata metadata;
protected final ConfigurationClass configurationClass;
public ConfigurationMethod(MethodMetadata metadata, ConfigurationClass configurationClass) {
this.metadata = metadata;
this.configurationClass = configurationClass;
}
public MethodMetadata getMetadata() {
return this.metadata;
}
public ConfigurationClass getConfigurationClass() {
return this.configurationClass;
}
public Location getResourceLocation() {
return new Location(this.configurationClass.getResource(), this.metadata);
}
public void validate(ProblemReporter problemReporter) {
}
@Override
public String toString() {
return String.format("[%s:name=%s,declaringClass=%s]",
this.getClass().getSimpleName(), this.getMetadata().getMethodName(), this.getMetadata().getDeclaringClassName());
}
}

View File

@@ -1,30 +0,0 @@
/*
* Copyright 2002-2011 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.context.annotation;
/**
* Marker interface indicating that an object is a proxy for a bean referenced
* from within a {@link Feature @Feature} method.
*
* @author Chris Beams
* @since 3.1
*/
public interface EarlyBeanReferenceProxy {
Object dereferenceTargetBean();
}

View File

@@ -1,254 +0,0 @@
/*
* Copyright 2002-2011 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.context.annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import net.sf.cglib.proxy.Callback;
import net.sf.cglib.proxy.CallbackFilter;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
import org.springframework.beans.factory.config.DependencyDescriptor;
import org.springframework.util.Assert;
import org.springframework.util.ReflectionUtils;
/**
* Creates proxies for beans referenced from within @Feature methods.
*
* TODO SPR-7420: document
* - discuss why proxies are important (avoiding side effects of early instantiation)
* - discuss benefits of interface-based proxies over concrete proxies
* - make it clear that both of the above are possible
* - discuss invocation of @Bean methods and how they too return proxies.
* this 'proxy returning a proxy' approach can be confusing at first, but the
* implementation should help in making it clear.
*
* @author Chris Beams
* @since 3.1
*/
class EarlyBeanReferenceProxyCreator {
static final String MISSING_NO_ARG_CONSTRUCTOR_ERROR_MESSAGE =
"Cannot create subclass proxy for bean type %s because it does not have a no-arg constructor. " +
"Add a no-arg constructor or attempt to inject the bean by interface rather than by concrete class.";
static final String PRIVATE_NO_ARG_CONSTRUCTOR_ERROR_MESSAGE =
"Cannot create subclass proxy for bean type %s because its no-arg constructor is private. " +
"Increase the visibility of the no-arg constructor or attempt to inject the bean by interface rather " +
"than by concrete class.";
private final AutowireCapableBeanFactory beanFactory;
/**
* Create a new proxy creator that will dereference proxy target beans against
* the given bean factory.
*/
public EarlyBeanReferenceProxyCreator(AutowireCapableBeanFactory beanFactory) {
this.beanFactory = beanFactory;
}
/**
* Create a proxy that will ultimately dereference its target object using
* the given dependency descriptor. No proxy is created if the dependency type
* is final, rather the dependency is resolved immediately. This is important
* especially with regard to supporting @Value injection.
*/
public Object createProxyIfPossible(DependencyDescriptor descriptor) {
if (Modifier.isFinal(descriptor.getDependencyType().getModifiers())) {
return beanFactory.resolveDependency(descriptor, "");
}
return doCreateProxy(new ResolveDependencyTargetBeanDereferencingInterceptor(descriptor));
}
/**
* Create a proxy that looks up target beans using the given dereferencing interceptor.
*
* @see EarlyBeanReferenceProxy#dereferenceTargetBean()
*/
private Object doCreateProxy(TargetBeanDereferencingInterceptor targetBeanDereferencingInterceptor) {
Enhancer enhancer = new Enhancer();
Class<?> targetBeanType = targetBeanDereferencingInterceptor.getTargetBeanType();
if (targetBeanType.isInterface()) {
enhancer.setSuperclass(Object.class);
enhancer.setInterfaces(new Class<?>[] {targetBeanType, EarlyBeanReferenceProxy.class});
} else {
assertClassIsProxyCapable(targetBeanType);
enhancer.setSuperclass(targetBeanType);
enhancer.setInterfaces(new Class<?>[] {EarlyBeanReferenceProxy.class});
}
enhancer.setCallbacks(new Callback[] {
new BeanMethodInterceptor(),
new ObjectMethodsInterceptor(),
targetBeanDereferencingInterceptor,
new TargetBeanDelegatingMethodInterceptor()
});
enhancer.setCallbackFilter(new CallbackFilter() {
public int accept(Method method) {
if (BeanAnnotationHelper.isBeanAnnotated(method)) {
return 0;
}
if (ReflectionUtils.isObjectMethod(method)) {
return 1;
}
if (method.getName().equals("dereferenceTargetBean")) {
return 2;
}
return 3;
}
});
return enhancer.create();
}
/**
* Return whether the given class is capable of being subclass proxied by CGLIB.
*/
private static void assertClassIsProxyCapable(Class<?> clazz) {
Assert.isTrue(!clazz.isInterface(), "class parameter must be a concrete type");
try {
// attempt to retrieve the no-arg constructor for the class
Constructor<?> noArgCtor = clazz.getDeclaredConstructor();
if ((noArgCtor.getModifiers() & Modifier.PRIVATE) != 0) {
throw new ProxyCreationException(String.format(PRIVATE_NO_ARG_CONSTRUCTOR_ERROR_MESSAGE, clazz.getName()));
}
} catch (NoSuchMethodException ex) {
throw new ProxyCreationException(String.format(MISSING_NO_ARG_CONSTRUCTOR_ERROR_MESSAGE, clazz.getName()));
}
}
/**
* Interceptor for @Bean-annotated methods called from early-proxied bean instances, such as
* @Configuration class instances. Invoking instance methods on early-proxied beans usually
* causes an eager bean lookup, but in the case of @Bean methods, it is important to return
* a proxy.
*/
private class BeanMethodInterceptor implements MethodInterceptor {
public Object intercept(Object obj, final Method beanMethod, Object[] args, MethodProxy proxy) throws Throwable {
return doCreateProxy(new ByNameLookupTargetBeanDereferencingInterceptor(beanMethod));
}
}
/**
* Interceptor for methods declared by java.lang.Object()
*/
private static class ObjectMethodsInterceptor implements MethodInterceptor {
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
if (method.getName().equals("toString")) {
return String.format("EarlyBeanReferenceProxy for bean of type %s",
obj.getClass().getSuperclass().getSimpleName());
}
if (method.getName().equals("hashCode")) {
return System.identityHashCode(obj);
}
if (method.getName().equals("equals")) {
return obj == args[0];
}
if (method.getName().equals("finalize")) {
return null;
}
return proxy.invokeSuper(obj, args);
}
}
/**
* Strategy interface allowing for various approaches to dereferencing (i.e. 'looking up')
* the target bean for an early bean reference proxy.
*
* @see EarlyBeanReferenceProxy#dereferenceTargetBean()
*/
private interface TargetBeanDereferencingInterceptor extends MethodInterceptor {
Class<?> getTargetBeanType();
}
/**
* Interceptor that dereferences the target bean for the proxy by calling
* {@link AutowireCapableBeanFactory#resolveDependency(DependencyDescriptor, String)}.
*
* @see EarlyBeanReferenceProxy#dereferenceTargetBean()
*/
private class ResolveDependencyTargetBeanDereferencingInterceptor implements TargetBeanDereferencingInterceptor {
private final DependencyDescriptor descriptor;
public ResolveDependencyTargetBeanDereferencingInterceptor(DependencyDescriptor descriptor) {
this.descriptor = descriptor;
}
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
return beanFactory.resolveDependency(descriptor, null);
}
public Class<?> getTargetBeanType() {
return this.descriptor.getDependencyType();
}
}
/**
* Interceptor that dereferences the target bean for the proxy by calling BeanFactory#getBean(String).
*
* @see EarlyBeanReferenceProxy#dereferenceTargetBean()
*/
private class ByNameLookupTargetBeanDereferencingInterceptor implements TargetBeanDereferencingInterceptor {
private final Method beanMethod;
public ByNameLookupTargetBeanDereferencingInterceptor(Method beanMethod) {
this.beanMethod = beanMethod;
}
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
return beanFactory.getBean(BeanAnnotationHelper.determineBeanNameFor(beanMethod));
}
public Class<?> getTargetBeanType() {
return beanMethod.getReturnType();
}
}
/**
* Interceptor that dereferences the target bean for the proxy and delegates the
* current method call to it.
* @see TargetBeanDereferencingInterceptor
*/
private static class TargetBeanDelegatingMethodInterceptor implements MethodInterceptor {
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
Object targetBean = ((EarlyBeanReferenceProxy)obj).dereferenceTargetBean();
return method.invoke(targetBean, args);
}
}
}

View File

@@ -1,34 +0,0 @@
/*
* Copyright 2002-2011 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.context.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* TODO SPR-7420: document
*
* @author Chris Beams
* @since 3.1
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Feature {
}

View File

@@ -1,45 +0,0 @@
/*
* Copyright 2002-2011 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.context.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Meta-annotation indicating that an annotation should be processed
* to produce a {@code FeatureSpecification}.
*
* <p>See {@link ComponentScan @ComponentScan} for an implementation example.
*
* @author Chris Beams
* @since 3.1
* @see ComponentScan
* @see org.springframework.context.config.FeatureSpecification
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface FeatureAnnotation {
/**
* Indicate the class that should be used to parse this annotation
* into a {@code FeatureSpecification}.
*/
Class<? extends FeatureAnnotationParser> parser();
}

View File

@@ -1,61 +0,0 @@
/*
* Copyright 2002-2011 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.context.annotation;
import org.springframework.context.config.FeatureSpecification;
import org.springframework.context.config.FeatureSpecificationExecutor;
import org.springframework.core.type.AnnotationMetadata;
/**
* Interface for parsing {@link AnnotationMetadata} from a {@link FeatureAnnotation}
* into a {@link FeatureSpecification} object. Used in conjunction with a
* {@link FeatureSpecificationExecutor} to provide a source-agnostic approach to
* handling configuration metadata.
*
* <p>For example, Spring's component-scanning can be configured via XML using
* the {@code context:component-scan} element or via the {@link ComponentScan}
* annotation. In either case, the metadata is the same -- only the source
* format differs. {@link ComponentScanBeanDefinitionParser} is used to create
* a specification from the {@code <context:component-scan>} XML element, while
* {@link ComponentScanAnnotationParser} creates a specification from the
* the annotation style. They both produce a {@link ComponentScanSpec}
* object that is ultimately delegated to a {@link ComponentScanExecutor}
* which understands how to configure a {@link ClassPathBeanDefinitionScanner},
* perform actual scanning, and register actual bean definitions against the
* container.
*
* <p>Implementations must be instantiable via a no-arg constructor.
*
* TODO SPR-7420: documentation (clean up)
* TODO SPR-7420: rework so annotations declare their creator.
*
*
* @author Chris Beams
* @since 3.1
* @see FeatureAnnotation#parser()
* @see FeatureSpecification
* @see FeatureSpecificationExecutor
*/
public interface FeatureAnnotationParser {
/**
* Parse the given annotation metadata and populate a {@link FeatureSpecification}
* object suitable for execution by a {@link FeatureSpecificationExecutor}.
*/
FeatureSpecification process(AnnotationMetadata metadata);
}

View File

@@ -1,54 +0,0 @@
/*
* Copyright 2002-2011 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.context.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.stereotype.Component;
/**
* TODO SPR-7420: document
*
* @author Chris Beams
* @since 3.1
* @see Configuration
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface FeatureConfiguration {
/**
* Explicitly specify the name of the Spring bean definition associated
* with this FeatureConfiguration class. If left unspecified (the common case),
* a bean name will be automatically generated.
*
* <p>The custom name applies only if the FeatureConfiguration class is picked up via
* component scanning or supplied directly to a {@link AnnotationConfigApplicationContext}.
* If the FeatureConfiguration class is registered as a traditional XML bean definition,
* the name/id of the bean element will take precedence.
*
* @return the specified bean name, if any
* @see org.springframework.beans.factory.support.DefaultBeanNameGenerator
*/
String value() default "";
}

View File

@@ -1,28 +0,0 @@
/*
* Copyright 2002-2011 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.context.annotation;
@SuppressWarnings("serial")
class FeatureMethodExecutionException extends RuntimeException {
public FeatureMethodExecutionException(Throwable cause) {
super(cause);
}
public FeatureMethodExecutionException(String message) {
super(message);
}
}

View File

@@ -1,26 +0,0 @@
/*
* Copyright 2002-2011 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.context.annotation;
@SuppressWarnings("serial")
class ProxyCreationException extends RuntimeException {
public ProxyCreationException(String message) {
super(message);
}
}

View File

@@ -1,52 +0,0 @@
/*
* Copyright 2002-2011 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.context.annotation;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.parsing.BeanComponentDefinition;
import org.springframework.beans.factory.parsing.ComponentDefinition;
import org.springframework.beans.factory.parsing.ComponentRegistrar;
import org.springframework.beans.factory.support.BeanDefinitionReaderUtils;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
/**
* TODO SPR-7420: document
*
* @author Chris Beams
* @since 3.1
*/
class SimpleComponentRegistrar implements ComponentRegistrar {
private final BeanDefinitionRegistry registry;
public SimpleComponentRegistrar(BeanDefinitionRegistry registry) {
this.registry = registry;
}
public String registerWithGeneratedName(BeanDefinition beanDefinition) {
return BeanDefinitionReaderUtils.registerWithGeneratedName(beanDefinition, this.registry);
}
public void registerBeanComponent(BeanComponentDefinition component) {
BeanDefinitionReaderUtils.registerBeanDefinition(component, this.registry);
registerComponent(component);
}
public void registerComponent(ComponentDefinition component) {
// no-op
}
}

View File

@@ -1,78 +0,0 @@
/*
* Copyright 2002-2011 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.context.config;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.parsing.ProblemCollector;
import org.springframework.beans.factory.parsing.ProblemReporter;
import org.springframework.beans.factory.parsing.SimpleProblemCollector;
/**
* TODO SPR-7420: document
*
* @author Chris Beams
* @since 3.1
*/
public abstract class AbstractFeatureSpecification implements SourceAwareSpecification {
private static final Object DUMMY_SOURCE = new Object();
private static final String DUMMY_SOURCE_NAME = "dummySource";
protected Class<? extends FeatureSpecificationExecutor> executorType;
private Object source = DUMMY_SOURCE;
private String sourceName = DUMMY_SOURCE_NAME;
protected AbstractFeatureSpecification(Class<? extends FeatureSpecificationExecutor> executorType) {
this.executorType = executorType;
}
public final boolean validate(ProblemReporter problemReporter) {
ProblemCollector collector = new SimpleProblemCollector(this.source());
this.doValidate(collector);
collector.reportProblems(problemReporter);
return collector.hasErrors() ? false : true;
}
protected abstract void doValidate(ProblemCollector problems);
public AbstractFeatureSpecification source(Object source) {
this.source = source;
return this;
}
public Object source() {
return this.source;
}
public AbstractFeatureSpecification sourceName(String sourceName) {
this.sourceName = sourceName;
return this;
}
public String sourceName() {
return this.sourceName;
}
public void execute(SpecificationContext specificationContext) {
FeatureSpecificationExecutor executor =
BeanUtils.instantiateClass(this.executorType);
executor.execute(this, specificationContext);
}
}

View File

@@ -1,83 +0,0 @@
/*
* Copyright 2002-2011 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.context.config;
import java.lang.reflect.Field;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.parsing.ComponentRegistrarAdapter;
import org.springframework.beans.factory.parsing.ProblemReporter;
import org.springframework.beans.factory.parsing.ReaderContext;
import org.springframework.beans.factory.xml.BeanDefinitionParser;
import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.core.env.DefaultEnvironment;
import org.w3c.dom.Element;
/**
* TODO SPR-7420: document
*
* @author Chris Beams
* @since 3.1
*/
public abstract class AbstractSpecificationBeanDefinitionParser implements BeanDefinitionParser {
public final BeanDefinition parse(Element element, ParserContext parserContext) {
FeatureSpecification spec = doParse(element, parserContext);
if (spec instanceof SourceAwareSpecification) {
((SourceAwareSpecification)spec).source(parserContext.getReaderContext().extractSource(element));
((SourceAwareSpecification)spec).sourceName(element.getTagName());
}
spec.execute(specificationContextFrom(parserContext));
return null;
}
abstract protected FeatureSpecification doParse(Element element, ParserContext parserContext);
/**
* Adapt the given ParserContext into a SpecificationContext.
*/
private SpecificationContext specificationContextFrom(ParserContext parserContext) {
SpecificationContext specContext = new SpecificationContext();
specContext.setRegistry(parserContext.getRegistry());
specContext.setRegistrar(new ComponentRegistrarAdapter(parserContext));
specContext.setResourceLoader(parserContext.getReaderContext().getResourceLoader());
try {
// again, STS constraints around the addition of the new getEnvironment()
// method in 3.1.0 (it's not present in STS current spring version, 3.0.5)
// TODO 3.1 GA: remove this block prior to 3.1 GA
specContext.setEnvironment(parserContext.getDelegate().getEnvironment());
} catch (NoSuchMethodError ex) {
specContext.setEnvironment(new DefaultEnvironment());
}
try {
// access the reader context's problem reporter reflectively in order to
// compensate for tooling (STS) constraints around introduction of changes
// to parser context / reader context classes.
// TODO 3.1 GA: remove this block prior to 3.1 GA
Field field = ReaderContext.class.getDeclaredField("problemReporter");
field.setAccessible(true);
ProblemReporter problemReporter = (ProblemReporter)field.get(parserContext.getReaderContext());
specContext.setProblemReporter(problemReporter);
} catch (Exception ex) {
throw new IllegalStateException(
"Could not access field 'ReaderContext#problemReporter' on object " +
parserContext.getReaderContext(), ex);
}
return specContext;
}
}

View File

@@ -1,54 +0,0 @@
/*
* Copyright 2002-2011 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.context.config;
import org.springframework.core.GenericTypeResolver;
import org.springframework.util.Assert;
/**
* TODO SPR-7420: document
*
* @author Chris Beams
* @since 3.1
*/
public abstract class AbstractSpecificationExecutor<S extends FeatureSpecification> implements FeatureSpecificationExecutor {
/**
* {@inheritDoc}
* <p>This implementation {@linkplain FeatureSpecification#validate() validates} the
* given specification and delegates it to {@link #doExecute(FeatureSpecification)}
* only if valid.
*/
@SuppressWarnings("unchecked")
public final void execute(FeatureSpecification spec, SpecificationContext specificationContext) {
Assert.notNull(spec, "Specification must not be null");
Assert.notNull(spec, "SpecificationContext must not be null");
Class<?> typeArg = GenericTypeResolver.resolveTypeArgument(this.getClass(), AbstractSpecificationExecutor.class);
Assert.isTrue(typeArg.equals(spec.getClass()), "Specification cannot be executed by this executor");
if (spec.validate(specificationContext.getProblemReporter())) {
doExecute((S)spec, specificationContext);
}
}
/**
* Execute the given specification, usually resulting in registration of bean definitions
* against a bean factory.
* @param specification the {@linkplain FeatureSpecification#validate() validated} specification
*/
protected abstract void doExecute(S specification, SpecificationContext specificationContext);
}

View File

@@ -1,127 +0,0 @@
/*
* Copyright 2002-2011 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.context.config;
import org.springframework.beans.factory.parsing.ProblemReporter;
/**
* Interface to be implemented by objects that specify the configuration of a particular feature
* of the Spring container e.g., component-scanning, JMX MBean exporting, AspectJ auto-proxying,
* annotation-driven transaction management, and so on.
*
* <p>Many features of the Spring container can be configured using either XML or annotations.
* As one example, Spring's <em>component scanning</em> feature may be configured using
* either the {@code <context:component-scan>} XML element or (as of Spring 3.1) the
* {@code @ComponentScan} annotation. These two options are equivalent to one another, and users may
* choose between them as a matter of convention or preference. Fundamentally, both are declarative
* mechanisms for <em>specifying</em> how the Spring container should be configured. A {@code
* FeatureSpecification} object, then, is a way of representing this configuration information independent
* of its original source format be it XML, annotations, or otherwise.
*
* <p>A {@code FeatureSpecification} is responsible for {@linkplain #validate validating itself}.
* For example, a component-scanning specification would check that at least one base package has
* been specified, and otherwise register a {@code Problem} with a {@link ProblemReporter}. Taking
* this approach as opposed to throwing exceptions allows for maximum tooling and error reporting
* flexibility.
*
* <p>A {@link FeatureSpecificationExecutor} is used to carry out the instructions within a populated
* {@code FeatureSpecification}; this is where the "real work" happens. In the case of component scanning
* as above, it is within a {@code FeatureSpecificationExecutor} that a bean definition scanner is created,
* configured and invoked against the base packages specified.
*
* <p>{@code FeatureSpecification} objects may be populated and executed by Spring XML namespace element
* parsers on order to separate the concerns of XML parsing from Spring bean definition creation and
* registration. This type of use is mostly a framework-internal matter. More interesting to end users is
* the use of {@code FeatureSpecification} objects within {@code @FeatureConfiguration} classes and their
* {@code @Feature} methods. This functionality is new in Spring 3.1 and is the logical evolution of Spring's
* Java-based configuration support first released in Spring 3.0 (see {@code @Configuration} classes and
* {@code @Bean} methods). The primary goal of {@code Feature}-related support is to round out this
* Java-based support and allow users to configure all aspects of the Spring-container without requiring
* the use of XML. See "design notes" below for an example of this kind of use.
*
* <h2>Notes on designing {@code FeatureSpecification} implementations</h2>
*
* <p>The public API of a {@code FeatureSpecification} should be designed for maximum ease of use
* within {@code @Feature} methods. Traditional JavaBean-style getters and setters should be dropped
* for a more fluent style that allows for method chaining. Consider the following example of a
* {@code @Feature} method:
*
* <pre>
* &#64;Feature
* public TxAnnotationDriven tx(PlatformTransactionManager txManager) {
* return new TxAnnotationDriven(txManager).proxyTargetClass(true);
* }
* </pre>
*
* Notice how the creation and configuration of the {@code TxAnnotationDriven} specification is
* concise and reads well. This is facilitated by mutator methods that always return the
* specification object's 'this' reference, allowing for method chaining. A secondary design goal
* of this approach is that the resulting code tends to mirror corresponding XML namespace
* declarations, which most Spring users are already familiar with. For example, compare the
* code above with its XML counterpart:
*
* <p>{@code <tx:annotation-driven transaction-manager="txManager" proxy-target-class="true"/>}
*
* <p>Typically, a user will call only the constructor and 'mutator' methods of a specification
* object. The accessor/getter methods, however, are typically called only by the specification's
* {@linkplain FeatureSpecificationExecutor executor} for the purpose of populating and registering
* bean definitions with the Spring container. For this reason, it is recommended that accessor
* methods be given package-private visibility. This creates a better experience for users from
* an IDE content-assist point of view as they will see only the public mutator methods, reducing
* any possible confusion.
*
* <p>Implementations should take care to allow for use of string-based bean names, placeholder
* (<code>"${...}"</code>) and SpEL (<code>"#{...}</code>) expressions wherever they may be useful.
* While it is generally desirable to refer to dependent beans in pure Java, in certain cases a
* user may wish or need to refer to a bean by name. For example, the {@code TxAnnotationDriven} specification
* referenced above allows for specifying its transaction-manager reference by {@code String} or by
* {@code PlatformTransactionManager} reference. Such strings should always be candidates for placeholder
* replacement and SpEL evaluation for maximum configurability as well as parity with the feature set
* available in Spring XML. With regard to SpEL expressions, users should assume that only expressions
* evaluating to a bean name will be supported. While it is technically possible with SpEL to resolve
* a bean instance, specification executors will not support such use unless explicitly indicated.
*
* <p>See the Javadoc for {@code @FeatureConfiguration} classes and {@code @Feature} methods for
* information on their lifecycle and semantics.
*
* @author Chris Beams
* @since 3.1
* @see FeatureSpecificationExecutor
* @see AbstractSpecificationExecutor
* @see org.springframework.context.annotation.Feature
* @see org.springframework.context.annotation.FeatureConfiguration
*/
public interface FeatureSpecification {
/**
* Validate this specification instance to ensure all required properties
* have been set, including checks on mutually exclusive or mutually
* dependent properties. May in some cases modify the state of the
* specification e.g., instantiating types specified as strings.
* @see AbstractSpecificationExecutor#execute(FeatureSpecification, SpecificationContext)
* @return whether any problems occurred during validation
*/
boolean validate(ProblemReporter problemReporter);
/**
* Execute this specification instance, carrying out the instructions
* specified within. Should work by delegating to an underlying
* {@link FeatureSpecificationExecutor} for proper separation of concerns.
*/
void execute(SpecificationContext specificationContext);
}

View File

@@ -1,41 +0,0 @@
/*
* Copyright 2002-2011 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.context.config;
/**
* Interface for executing a populated {@link FeatureSpecification}. Provides
* a generic mechanism for handling container configuration metadata regardless of
* origin in XML, annotations, or other source format.
*
* TODO SPR-7420: document (clean up)
*
* @author Chris Beams
* @since 3.1
* @see AbstractSpecificationExecutor
* @see org.springframework.beans.factory.xml.BeanDefinitionParser
* @see org.springframework.context.annotation.FeatureAnnotationParser
* @see org.springframework.context.annotation.ComponentScanExecutor
*/
public interface FeatureSpecificationExecutor {
/**
* Execute the given specification, usually resulting in registration
* of bean definitions against a bean factory.
*/
void execute(FeatureSpecification spec, SpecificationContext specificationContext);
}

View File

@@ -1,35 +0,0 @@
/*
* Copyright 2002-2011 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.context.config;
/**
* TODO SPR-7420: document
*
* @author Chris Beams
* @since 3.1
*/
public interface SourceAwareSpecification extends FeatureSpecification {
String sourceName();
SourceAwareSpecification sourceName(String sourceName);
Object source();
SourceAwareSpecification source(Object source);
}

View File

@@ -1,81 +0,0 @@
/*
* Copyright 2002-2011 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.context.config;
import org.springframework.beans.factory.parsing.ComponentRegistrar;
import org.springframework.beans.factory.parsing.ProblemReporter;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.core.env.Environment;
import org.springframework.core.io.ResourceLoader;
/**
* TODO: rename to SpecificationContext?
*
* @author Chris Beams
* @since 3.1
*/
public class SpecificationContext {
private BeanDefinitionRegistry registry;
private ComponentRegistrar registrar;
private ResourceLoader resourceLoader;
private Environment environment;
private ProblemReporter problemReporter;
public SpecificationContext() { }
public void setRegistry(BeanDefinitionRegistry registry) {
this.registry = registry;
}
public BeanDefinitionRegistry getRegistry() {
return this.registry;
}
public void setRegistrar(ComponentRegistrar registrar) {
this.registrar = registrar;
}
public ComponentRegistrar getRegistrar() {
return this.registrar;
}
public void setResourceLoader(ResourceLoader resourceLoader) {
this.resourceLoader = resourceLoader;
}
public ResourceLoader getResourceLoader() {
return this.resourceLoader;
}
public void setEnvironment(Environment environment) {
this.environment = environment;
}
public Environment getEnvironment() {
return this.environment;
}
public void setProblemReporter(ProblemReporter problemReporter) {
this.problemReporter = problemReporter;
}
public ProblemReporter getProblemReporter() {
return this.problemReporter;
}
}