提交 111fb71f 编写于 作者: C Chris Beams

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
上级 0a790c14
...@@ -7,6 +7,8 @@ Changes in version 3.1 M2 (2011-??-??) ...@@ -7,6 +7,8 @@ Changes in version 3.1 M2 (2011-??-??)
-------------------------------------- --------------------------------------
* deprecated AbstractJUnit38SpringContextTests and AbstractTransactionalJUnit38SpringContextTests * deprecated AbstractJUnit38SpringContextTests and AbstractTransactionalJUnit38SpringContextTests
* eliminated @Feature support in favor of @Enable* and framework-provided @Configuration classes
* introduced @EnableTransactionManagement, @EnableScheduling, and other @Enable* annotations
Changes in version 3.1 M1 (2011-02-11) Changes in version 3.1 M1 (2011-02-11)
......
...@@ -16,13 +16,12 @@ ...@@ -16,13 +16,12 @@
package org.springframework.aop.config; package org.springframework.aop.config;
import org.w3c.dom.Element;
import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.parsing.BeanComponentDefinition; import org.springframework.beans.factory.parsing.BeanComponentDefinition;
import org.springframework.beans.factory.parsing.ComponentRegistrar;
import org.springframework.beans.factory.parsing.ComponentRegistrarAdapter;
import org.springframework.beans.factory.support.BeanDefinitionRegistry; import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.xml.ParserContext; import org.springframework.beans.factory.xml.ParserContext;
import org.w3c.dom.Element;
/** /**
* Utility class for handling registration of auto-proxy creators used internally * Utility class for handling registration of auto-proxy creators used internally
...@@ -53,41 +52,22 @@ public abstract class AopNamespaceUtils { ...@@ -53,41 +52,22 @@ public abstract class AopNamespaceUtils {
private static final String EXPOSE_PROXY_ATTRIBUTE = "expose-proxy"; private static final String EXPOSE_PROXY_ATTRIBUTE = "expose-proxy";
/**
* @deprecated since Spring 3.1 in favor of
* {@link #registerAutoProxyCreatorIfNecessary(BeanDefinitionRegistry, ComponentRegistrar, Object, Boolean, Boolean)}
*/
@Deprecated
public static void registerAutoProxyCreatorIfNecessary( public static void registerAutoProxyCreatorIfNecessary(
ParserContext parserContext, Element sourceElement) { ParserContext parserContext, Element sourceElement) {
BeanDefinition beanDefinition = AopConfigUtils.registerAutoProxyCreatorIfNecessary( BeanDefinition beanDefinition = AopConfigUtils.registerAutoProxyCreatorIfNecessary(
parserContext.getRegistry(), parserContext.extractSource(sourceElement)); parserContext.getRegistry(), parserContext.extractSource(sourceElement));
useClassProxyingIfNecessary(parserContext.getRegistry(), sourceElement); useClassProxyingIfNecessary(parserContext.getRegistry(), sourceElement);
registerComponentIfNecessary(beanDefinition, new ComponentRegistrarAdapter(parserContext));
}
public static void registerAutoProxyCreatorIfNecessary(
BeanDefinitionRegistry registry, ComponentRegistrar parserContext, Object source, Boolean proxyTargetClass, Boolean exposeProxy) {
BeanDefinition beanDefinition =
AopConfigUtils.registerAutoProxyCreatorIfNecessary(registry, source);
useClassProxyingIfNecessary(registry, proxyTargetClass, exposeProxy);
registerComponentIfNecessary(beanDefinition, parserContext); registerComponentIfNecessary(beanDefinition, parserContext);
} }
public static void registerAutoProxyCreatorIfNecessary(
BeanDefinitionRegistry registry, ComponentRegistrar parserContext, Object source, Boolean proxyTargetClass) {
registerAutoProxyCreatorIfNecessary(registry, parserContext, source, proxyTargetClass, false);
}
public static void registerAspectJAutoProxyCreatorIfNecessary( public static void registerAspectJAutoProxyCreatorIfNecessary(
ParserContext parserContext, Element sourceElement) { ParserContext parserContext, Element sourceElement) {
BeanDefinition beanDefinition = AopConfigUtils.registerAspectJAutoProxyCreatorIfNecessary( BeanDefinition beanDefinition = AopConfigUtils.registerAspectJAutoProxyCreatorIfNecessary(
parserContext.getRegistry(), parserContext.extractSource(sourceElement)); parserContext.getRegistry(), parserContext.extractSource(sourceElement));
useClassProxyingIfNecessary(parserContext.getRegistry(), sourceElement); useClassProxyingIfNecessary(parserContext.getRegistry(), sourceElement);
registerComponentIfNecessary(beanDefinition, new ComponentRegistrarAdapter(parserContext)); registerComponentIfNecessary(beanDefinition, parserContext);
} }
public static void registerAspectJAnnotationAutoProxyCreatorIfNecessary( public static void registerAspectJAnnotationAutoProxyCreatorIfNecessary(
...@@ -96,7 +76,7 @@ public abstract class AopNamespaceUtils { ...@@ -96,7 +76,7 @@ public abstract class AopNamespaceUtils {
BeanDefinition beanDefinition = AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary( BeanDefinition beanDefinition = AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(
parserContext.getRegistry(), parserContext.extractSource(sourceElement)); parserContext.getRegistry(), parserContext.extractSource(sourceElement));
useClassProxyingIfNecessary(parserContext.getRegistry(), sourceElement); useClassProxyingIfNecessary(parserContext.getRegistry(), sourceElement);
registerComponentIfNecessary(beanDefinition, new ComponentRegistrarAdapter(parserContext)); registerComponentIfNecessary(beanDefinition, parserContext);
} }
/** /**
...@@ -108,7 +88,7 @@ public abstract class AopNamespaceUtils { ...@@ -108,7 +88,7 @@ public abstract class AopNamespaceUtils {
public static void registerAutoProxyCreatorIfNecessary(ParserContext parserContext, Object source) { public static void registerAutoProxyCreatorIfNecessary(ParserContext parserContext, Object source) {
BeanDefinition beanDefinition = AopConfigUtils.registerAutoProxyCreatorIfNecessary( BeanDefinition beanDefinition = AopConfigUtils.registerAutoProxyCreatorIfNecessary(
parserContext.getRegistry(), source); parserContext.getRegistry(), source);
registerComponentIfNecessary(beanDefinition, new ComponentRegistrarAdapter(parserContext)); registerComponentIfNecessary(beanDefinition, parserContext);
} }
/** /**
...@@ -121,12 +101,6 @@ public abstract class AopNamespaceUtils { ...@@ -121,12 +101,6 @@ public abstract class AopNamespaceUtils {
} }
/**
* @deprecated since Spring 3.1 in favor of
* {@link #useClassProxyingIfNecessary(BeanDefinitionRegistry, Boolean, Boolean)}
* which does not require a parameter of type org.w3c.dom.Element
*/
@Deprecated
private static void useClassProxyingIfNecessary(BeanDefinitionRegistry registry, Element sourceElement) { private static void useClassProxyingIfNecessary(BeanDefinitionRegistry registry, Element sourceElement) {
if (sourceElement != null) { if (sourceElement != null) {
boolean proxyTargetClass = Boolean.valueOf(sourceElement.getAttribute(PROXY_TARGET_CLASS_ATTRIBUTE)); boolean proxyTargetClass = Boolean.valueOf(sourceElement.getAttribute(PROXY_TARGET_CLASS_ATTRIBUTE));
...@@ -140,20 +114,11 @@ public abstract class AopNamespaceUtils { ...@@ -140,20 +114,11 @@ public abstract class AopNamespaceUtils {
} }
} }
private static void useClassProxyingIfNecessary(BeanDefinitionRegistry registry, Boolean proxyTargetClass, Boolean exposeProxy) { private static void registerComponentIfNecessary(BeanDefinition beanDefinition, ParserContext parserContext) {
if (proxyTargetClass) {
AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry);
}
if (exposeProxy) {
AopConfigUtils.forceAutoProxyCreatorToExposeProxy(registry);
}
}
private static void registerComponentIfNecessary(BeanDefinition beanDefinition, ComponentRegistrar componentRegistrar) {
if (beanDefinition != null) { if (beanDefinition != null) {
BeanComponentDefinition componentDefinition = BeanComponentDefinition componentDefinition =
new BeanComponentDefinition(beanDefinition, AopConfigUtils.AUTO_PROXY_CREATOR_BEAN_NAME); new BeanComponentDefinition(beanDefinition, AopConfigUtils.AUTO_PROXY_CREATOR_BEAN_NAME);
componentRegistrar.registerComponent(componentDefinition); parserContext.registerComponent(componentDefinition);
} }
} }
......
/*
* 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.beans.factory.parsing;
import org.springframework.beans.factory.config.BeanDefinition;
/**
* TODO SPR-7420: document
*
* @author Chris Beams
* @since 3.1
*/
public interface ComponentRegistrar {
String registerWithGeneratedName(BeanDefinition beanDefinition);
void registerBeanComponent(BeanComponentDefinition component);
void registerComponent(ComponentDefinition component);
}
/*
* 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.beans.factory.parsing;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.xml.ParserContext;
/**
* TODO SPR-7420: document
* <p>Adapter is necessary as opposed to having ParserContext
* implement ComponentRegistrar directly due to tooling issues.
* STS may ship with a version of Spring older that 3.1 (when
* this type was introduced), and will run into
* IncompatibleClassChangeErrors when it's (3.0.5) ParserContext
* tries to mix with our (3.1.0) BeanDefinitionParser
* (and related) infrastructure.
*
* @author Chris Beams
* @since 3.1
*/
public class ComponentRegistrarAdapter implements ComponentRegistrar {
private final ParserContext parserContext;
public ComponentRegistrarAdapter(ParserContext parserContext) {
this.parserContext = parserContext;
}
public String registerWithGeneratedName(BeanDefinition beanDefinition) {
return this.parserContext.getReaderContext().registerWithGeneratedName(beanDefinition);
}
public void registerBeanComponent(BeanComponentDefinition component) {
this.parserContext.registerBeanComponent(component);
}
public void registerComponent(ComponentDefinition component) {
this.parserContext.registerComponent(component);
}
}
/*
* 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.beans.factory.parsing;
/**
* TODO SPR-7420: document
*
* @author Chris Beams
* @since 3.1
*/
public interface ProblemCollector {
void error(String message);
void error(String message, Throwable cause);
void reportProblems(ProblemReporter reporter);
boolean hasErrors();
}
/*
* 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.beans.factory.parsing;
import java.util.ArrayList;
import java.util.List;
import org.springframework.core.io.DescriptiveResource;
/**
* TODO SPR-7420: document
*
* @author Chris Beams
* @since 3.1
*/
public class SimpleProblemCollector implements ProblemCollector {
private Location location = null;
private List<Problem> errors = new ArrayList<Problem>();
public SimpleProblemCollector(Object location) {
if (location != null) {
this.location = new Location(new DescriptiveResource(location.toString()));
}
}
public void error(String message) {
this.errors.add(new Problem(message, this.location));
}
public void error(String message, Throwable cause) {
this.errors.add(new Problem(message, this.location, null, cause));
}
public void reportProblems(ProblemReporter reporter) {
for (Problem error : errors) {
reporter.error(error);
}
}
public boolean hasErrors() {
return this.errors.size() > 0;
}
}
...@@ -22,7 +22,7 @@ import org.springframework.beans.factory.config.BeanDefinition; ...@@ -22,7 +22,7 @@ import org.springframework.beans.factory.config.BeanDefinition;
/** /**
* Interface used by the {@link DefaultBeanDefinitionDocumentReader} to handle custom, * Interface used by the {@link DefaultBeanDefinitionDocumentReader} to handle custom,
* top-level (directly under {@code <beans>}) tags. * top-level (directly under {@code <beans/>}) tags.
* *
* <p>Implementations are free to turn the metadata in the custom tag into as many * <p>Implementations are free to turn the metadata in the custom tag into as many
* {@link BeanDefinition BeanDefinitions} as required. * {@link BeanDefinition BeanDefinitions} as required.
...@@ -30,19 +30,10 @@ import org.springframework.beans.factory.config.BeanDefinition; ...@@ -30,19 +30,10 @@ import org.springframework.beans.factory.config.BeanDefinition;
* <p>The parser locates a {@link BeanDefinitionParser} from the associated * <p>The parser locates a {@link BeanDefinitionParser} from the associated
* {@link NamespaceHandler} for the namespace in which the custom tag resides. * {@link NamespaceHandler} for the namespace in which the custom tag resides.
* *
* <p>Implementations are encouraged to decouple XML parsing from bean registration by
* parsing element(s) into a {@link org.springframework.context.FeatureSpecification
* FeatureSpecification} object and subsequently executing that specification.
* Doing so allows for maximum reuse between XML-based and annotation-based
* configuration options.
*
* @author Rob Harrop * @author Rob Harrop
* @since 2.0 * @since 2.0
* @see NamespaceHandler * @see NamespaceHandler
* @see AbstractBeanDefinitionParser * @see AbstractBeanDefinitionParser
* @see org.springframework.beans.factory.xml.BeanDefinitionDecorator
* @see org.springframework.context.FeatureSpecification
* @see org.springframework.context.AbstractSpecificationExecutor
*/ */
public interface BeanDefinitionParser { public interface BeanDefinitionParser {
...@@ -53,12 +44,11 @@ public interface BeanDefinitionParser { ...@@ -53,12 +44,11 @@ public interface BeanDefinitionParser {
* embedded in the supplied {@link ParserContext}. * embedded in the supplied {@link ParserContext}.
* <p>Implementations must return the primary {@link BeanDefinition} that results * <p>Implementations must return the primary {@link BeanDefinition} that results
* from the parse if they will ever be used in a nested fashion (for example as * from the parse if they will ever be used in a nested fashion (for example as
* an inner tag in a <code>&lt;property/&gt;</code> tag). Implementations may return * an inner tag in a {@code <property/>} tag). Implementations may return
* <code>null</code> if they will <strong>not</strong> be used in a nested fashion. * {@code null} if they will <strong>not</strong> be used in a nested fashion.
* @param element the element that is to be parsed into one or more {@link BeanDefinition BeanDefinitions} * @param element the element that is to be parsed into one or more {@link BeanDefinition BeanDefinitions}
* @param parserContext the object encapsulating the current state of the parsing process; * @param parserContext the object encapsulating the current state of the parsing process;
* provides access to a {@link org.springframework.beans.factory.support.BeanDefinitionRegistry * provides access to a {@link org.springframework.beans.factory.support.BeanDefinitionRegistry}
* BeanDefinitionRegistry}.
* @return the primary {@link BeanDefinition} * @return the primary {@link BeanDefinition}
*/ */
BeanDefinition parse(Element element, ParserContext parserContext); BeanDefinition parse(Element element, ParserContext parserContext);
......
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<beansProjectDescription> <beansProjectDescription>
<version>1</version> <version>1</version>
<pluginVersion><![CDATA[2.5.0.201008272200-CI-R3822-B852]]></pluginVersion> <pluginVersion><![CDATA[2.6.0.201103160035-RELEASE]]></pluginVersion>
<configSuffixes> <configSuffixes>
<configSuffix><![CDATA[xml]]></configSuffix> <configSuffix><![CDATA[xml]]></configSuffix>
</configSuffixes> </configSuffixes>
...@@ -11,7 +11,6 @@ ...@@ -11,7 +11,6 @@
<config>src/test/java/org/springframework/context/annotation/configuration/SecondLevelSubConfig-context.xml</config> <config>src/test/java/org/springframework/context/annotation/configuration/SecondLevelSubConfig-context.xml</config>
<config>src/test/java/org/springframework/context/annotation/configuration/ImportXmlWithAopNamespace-context.xml</config> <config>src/test/java/org/springframework/context/annotation/configuration/ImportXmlWithAopNamespace-context.xml</config>
<config>src/test/java/org/springframework/context/annotation/Spr6602Tests-context.xml</config> <config>src/test/java/org/springframework/context/annotation/Spr6602Tests-context.xml</config>
<config>src/test/java/org/springframework/context/annotation/FeatureConfigurationImportResourceTests-context.xml</config>
</configs> </configs>
<configSets> <configSets>
</configSets> </configSets>
......
...@@ -16,13 +16,13 @@ ...@@ -16,13 +16,13 @@
package org.springframework.context.annotation; 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.Problem;
import org.springframework.beans.factory.parsing.ProblemReporter; import org.springframework.beans.factory.parsing.ProblemReporter;
import org.springframework.core.type.MethodMetadata; 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 Chris Beams
* @author Juergen Hoeller * @author Juergen Hoeller
...@@ -31,30 +31,13 @@ import org.springframework.core.type.MethodMetadata; ...@@ -31,30 +31,13 @@ import org.springframework.core.type.MethodMetadata;
* @see ConfigurationClassParser * @see ConfigurationClassParser
* @see ConfigurationClassBeanDefinitionReader * @see ConfigurationClassBeanDefinitionReader
*/ */
final class BeanMethod { final class BeanMethod extends ConfigurationMethod {
private final MethodMetadata metadata;
private final ConfigurationClass configurationClass;
public BeanMethod(MethodMetadata metadata, ConfigurationClass configurationClass) { public BeanMethod(MethodMetadata metadata, ConfigurationClass configurationClass) {
this.metadata = metadata; super(metadata, configurationClass);
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);
} }
@Override
public void validate(ProblemReporter problemReporter) { public void validate(ProblemReporter problemReporter) {
if (this.configurationClass.getMetadata().isAnnotated(Configuration.class.getName())) { if (this.configurationClass.getMetadata().isAnnotated(Configuration.class.getName())) {
if (!getMetadata().isOverridable()) { if (!getMetadata().isOverridable()) {
...@@ -68,13 +51,6 @@ final class BeanMethod { ...@@ -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. * {@link Bean} methods must be overridable in order to accommodate CGLIB.
*/ */
......
/*
* 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;
}
}
/* /*
* 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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
...@@ -16,60 +16,263 @@ ...@@ -16,60 +16,263 @@
package org.springframework.context.annotation; package org.springframework.context.annotation;
import org.springframework.beans.factory.xml.ParserContext; import java.lang.annotation.Annotation;
import org.springframework.context.config.AbstractSpecificationBeanDefinitionParser; import java.util.Set;
import org.springframework.context.config.FeatureSpecification; import java.util.regex.Pattern;
import org.w3c.dom.Element; import org.w3c.dom.Element;
import org.w3c.dom.Node; import org.w3c.dom.Node;
import org.w3c.dom.NodeList; 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 * Parser for the {@code <context:component-scan/>} element.
* used to populate and execute a {@link ComponentScanSpec} instance.
* *
* @author Mark Fisher * @author Mark Fisher
* @author Ramnivas Laddad * @author Ramnivas Laddad
* @author Juergen Hoeller * @author Juergen Hoeller
* @author Chris Beams
* @since 2.5 * @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) { private static final String BASE_PACKAGE_ATTRIBUTE = "base-package";
ClassLoader classLoader = parserContext.getReaderContext().getResourceLoader().getClassLoader();
private static final String RESOURCE_PATTERN_ATTRIBUTE = "resource-pattern";
ComponentScanSpec spec =
ComponentScanSpec.forDelimitedPackages(element.getAttribute("base-package")) private static final String USE_DEFAULT_FILTERS_ATTRIBUTE = "use-default-filters";
.includeAnnotationConfig(element.getAttribute("annotation-config"))
.useDefaultFilters(element.getAttribute("use-default-filters")) private static final String ANNOTATION_CONFIG_ATTRIBUTE = "annotation-config";
.resourcePattern(element.getAttribute("resource-pattern"))
.beanNameGenerator(element.getAttribute("name-generator"), classLoader) private static final String NAME_GENERATOR_ATTRIBUTE = "name-generator";
.scopeMetadataResolver(element.getAttribute("scope-resolver"), classLoader)
.scopedProxyMode(element.getAttribute("scoped-proxy")) private static final String SCOPE_RESOLVER_ATTRIBUTE = "scope-resolver";
.beanDefinitionDefaults(parserContext.getDelegate().getBeanDefinitionDefaults())
.autowireCandidatePatterns(parserContext.getDelegate().getAutowireCandidatePatterns()); 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. // Parse exclude and include filter elements.
ClassLoader classLoader = scanner.getResourceLoader().getClassLoader();
NodeList nodeList = element.getChildNodes(); NodeList nodeList = element.getChildNodes();
for (int i = 0; i < nodeList.getLength(); i++) { for (int i = 0; i < nodeList.getLength(); i++) {
Node node = nodeList.item(i); Node node = nodeList.item(i);
if (node.getNodeType() == Node.ELEMENT_NODE) { if (node.getNodeType() == Node.ELEMENT_NODE) {
String localName = parserContext.getDelegate().getLocalName(node); String localName = parserContext.getDelegate().getLocalName(node);
String filterType = ((Element)node).getAttribute("type"); try {
String expression = ((Element)node).getAttribute("expression"); if (INCLUDE_FILTER_ELEMENT.equals(localName)) {
if ("include-filter".equals(localName)) { TypeFilter typeFilter = createTypeFilter((Element) node, classLoader);
spec.addIncludeFilter(filterType, expression, 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)) { catch (Exception ex) {
spec.addExcludeFilter(filterType, expression, classLoader); 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;
} }
} }
/*
* 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);
}
}
/*
* 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));
}
}
}
}
...@@ -33,7 +33,6 @@ import org.springframework.beans.factory.annotation.Autowire; ...@@ -33,7 +33,6 @@ import org.springframework.beans.factory.annotation.Autowire;
import org.springframework.beans.factory.annotation.RequiredAnnotationBeanPostProcessor; import org.springframework.beans.factory.annotation.RequiredAnnotationBeanPostProcessor;
import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanDefinitionHolder; 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.Location;
import org.springframework.beans.factory.parsing.Problem; import org.springframework.beans.factory.parsing.Problem;
import org.springframework.beans.factory.parsing.ProblemReporter; import org.springframework.beans.factory.parsing.ProblemReporter;
...@@ -45,8 +44,8 @@ import org.springframework.beans.factory.support.BeanDefinitionReaderUtils; ...@@ -45,8 +44,8 @@ import org.springframework.beans.factory.support.BeanDefinitionReaderUtils;
import org.springframework.beans.factory.support.BeanDefinitionRegistry; import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.GenericBeanDefinition; import org.springframework.beans.factory.support.GenericBeanDefinition;
import org.springframework.beans.factory.support.RootBeanDefinition; import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.context.config.FeatureSpecification; import org.springframework.context.EnvironmentAware;
import org.springframework.context.config.SpecificationContext; import org.springframework.context.ResourceLoaderAware;
import org.springframework.core.Conventions; import org.springframework.core.Conventions;
import org.springframework.core.env.Environment; import org.springframework.core.env.Environment;
import org.springframework.core.io.Resource; import org.springframework.core.io.Resource;
...@@ -94,7 +93,7 @@ public class ConfigurationClassBeanDefinitionReader { ...@@ -94,7 +93,7 @@ public class ConfigurationClassBeanDefinitionReader {
private ResourceLoader resourceLoader; private ResourceLoader resourceLoader;
private SpecificationContext specificationContext; private Environment environment;
/** /**
* Create a new {@link ConfigurationClassBeanDefinitionReader} instance that will be used * Create a new {@link ConfigurationClassBeanDefinitionReader} instance that will be used
...@@ -111,13 +110,7 @@ public class ConfigurationClassBeanDefinitionReader { ...@@ -111,13 +110,7 @@ public class ConfigurationClassBeanDefinitionReader {
this.problemReporter = problemReporter; this.problemReporter = problemReporter;
this.metadataReaderFactory = metadataReaderFactory; this.metadataReaderFactory = metadataReaderFactory;
this.resourceLoader = resourceLoader; this.resourceLoader = resourceLoader;
// TODO SPR-7420: see about passing in the SpecificationContext created in ConfigurationClassPostProcessor this.environment = environment;
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);
} }
...@@ -137,7 +130,6 @@ public class ConfigurationClassBeanDefinitionReader { ...@@ -137,7 +130,6 @@ public class ConfigurationClassBeanDefinitionReader {
*/ */
private void loadBeanDefinitionsForConfigurationClass(ConfigurationClass configClass) { private void loadBeanDefinitionsForConfigurationClass(ConfigurationClass configClass) {
AnnotationMetadata metadata = configClass.getMetadata(); AnnotationMetadata metadata = configClass.getMetadata();
processFeatureAnnotations(metadata);
doLoadBeanDefinitionForConfigurationClassIfNecessary(configClass); doLoadBeanDefinitionForConfigurationClassIfNecessary(configClass);
for (BeanMethod beanMethod : configClass.getBeanMethods()) { for (BeanMethod beanMethod : configClass.getBeanMethods()) {
loadBeanDefinitionsForBeanMethod(beanMethod); loadBeanDefinitionsForBeanMethod(beanMethod);
...@@ -145,27 +137,6 @@ public class ConfigurationClassBeanDefinitionReader { ...@@ -145,27 +137,6 @@ public class ConfigurationClassBeanDefinitionReader {
loadBeanDefinitionsFromImportedResources(configClass.getImportedResources()); 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. * Register the {@link Configuration} class itself as a bean definition.
*/ */
...@@ -200,7 +171,7 @@ public class ConfigurationClassBeanDefinitionReader { ...@@ -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. * with the BeanDefinitionRegistry based on its contents.
*/ */
private void loadBeanDefinitionsForBeanMethod(BeanMethod beanMethod) { private void loadBeanDefinitionsForBeanMethod(BeanMethod beanMethod) {
...@@ -423,8 +394,8 @@ public class ConfigurationClassBeanDefinitionReader { ...@@ -423,8 +394,8 @@ public class ConfigurationClassBeanDefinitionReader {
*/ */
private static class InvalidConfigurationImportProblem extends Problem { private static class InvalidConfigurationImportProblem extends Problem {
public InvalidConfigurationImportProblem(String className, Resource resource, AnnotationMetadata metadata) { public InvalidConfigurationImportProblem(String className, Resource resource, AnnotationMetadata metadata) {
super(String.format("%s was @Import'ed but is not annotated with @FeatureConfiguration or " + super(String.format("%s was @Import'ed but is not annotated with @Configuration " +
"@Configuration nor does it declare any @Bean methods. Update the class to " + "nor does it declare any @Bean methods. Update the class to " +
"meet one of these requirements or do not attempt to @Import it.", className), "meet one of these requirements or do not attempt to @Import it.", className),
new Location(resource, metadata)); new Location(resource, metadata));
} }
......
...@@ -197,15 +197,12 @@ class ConfigurationClassEnhancer { ...@@ -197,15 +197,12 @@ class ConfigurationClassEnhancer {
* Enhance a {@link Bean @Bean} method to check the supplied BeanFactory for the * Enhance a {@link Bean @Bean} method to check the supplied BeanFactory for the
* existence of this bean object. * 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 * @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 * invoking the super implementation of the proxied method i.e., the actual
* {@code @Bean} method. * {@code @Bean} method.
*/ */
public Object intercept(Object enhancedConfigInstance, Method beanMethod, Object[] beanMethodArgs, public Object intercept(Object enhancedConfigInstance, Method beanMethod, Object[] beanMethodArgs,
MethodProxy cglibMethodProxy) throws ProxyCreationException, Throwable { MethodProxy cglibMethodProxy) throws Throwable {
String beanName = BeanAnnotationHelper.determineBeanNameFor(beanMethod); String beanName = BeanAnnotationHelper.determineBeanNameFor(beanMethod);
......
...@@ -16,15 +16,9 @@ ...@@ -16,15 +16,9 @@
package org.springframework.context.annotation; package org.springframework.context.annotation;
import static java.lang.String.format;
import java.io.IOException; import java.io.IOException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.LinkedHashSet; import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
...@@ -32,12 +26,10 @@ import org.apache.commons.logging.Log; ...@@ -32,12 +26,10 @@ import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory; import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.BeanClassLoaderAware; import org.springframework.beans.factory.BeanClassLoaderAware;
import org.springframework.beans.factory.BeanDefinitionStoreException; 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.BeanDefinition;
import org.springframework.beans.factory.config.BeanDefinitionHolder; import org.springframework.beans.factory.config.BeanDefinitionHolder;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor; import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; 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.FailFastProblemReporter;
import org.springframework.beans.factory.parsing.PassThroughSourceExtractor; import org.springframework.beans.factory.parsing.PassThroughSourceExtractor;
import org.springframework.beans.factory.parsing.ProblemReporter; import org.springframework.beans.factory.parsing.ProblemReporter;
...@@ -47,23 +39,14 @@ import org.springframework.beans.factory.support.BeanDefinitionRegistry; ...@@ -47,23 +39,14 @@ import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor; import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor;
import org.springframework.context.EnvironmentAware; import org.springframework.context.EnvironmentAware;
import org.springframework.context.ResourceLoaderAware; 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.Ordered;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.core.env.Environment; import org.springframework.core.env.Environment;
import org.springframework.core.io.DefaultResourceLoader; import org.springframework.core.io.DefaultResourceLoader;
import org.springframework.core.io.ResourceLoader; 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.CachingMetadataReaderFactory;
import org.springframework.core.type.classreading.MetadataReader;
import org.springframework.core.type.classreading.MetadataReaderFactory; import org.springframework.core.type.classreading.MetadataReaderFactory;
import org.springframework.core.type.classreading.SimpleMetadataReaderFactory;
import org.springframework.util.Assert; import org.springframework.util.Assert;
import org.springframework.util.ClassUtils; import org.springframework.util.ClassUtils;
import org.springframework.util.ReflectionUtils;
/** /**
* {@link BeanFactoryPostProcessor} used for bootstrapping processing of * {@link BeanFactoryPostProcessor} used for bootstrapping processing of
...@@ -194,175 +177,13 @@ public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPo ...@@ -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) { private void processConfigurationClasses(BeanDefinitionRegistry registry) {
ConfigurationClassBeanDefinitionReader reader = getConfigurationClassBeanDefinitionReader(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); 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) { private ConfigurationClassBeanDefinitionReader getConfigurationClassBeanDefinitionReader(BeanDefinitionRegistry registry) {
...@@ -377,7 +198,7 @@ public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPo ...@@ -377,7 +198,7 @@ public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPo
* Build and validate a configuration model based on the registry of * Build and validate a configuration model based on the registry of
* {@link Configuration} classes. * {@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>(); Set<BeanDefinitionHolder> configCandidates = new LinkedHashSet<BeanDefinitionHolder>();
for (String beanName : registry.getBeanDefinitionNames()) { for (String beanName : registry.getBeanDefinitionNames()) {
BeanDefinition beanDef = registry.getBeanDefinition(beanName); BeanDefinition beanDef = registry.getBeanDefinition(beanName);
...@@ -391,8 +212,7 @@ public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPo ...@@ -391,8 +212,7 @@ public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPo
return; return;
} }
// Populate a new configuration model by parsing each @Configuration classes // Parse each @Configuration class
ConfigurationClassParser parser = new ConfigurationClassParser(this.metadataReaderFactory, this.problemReporter, this.environment);
for (BeanDefinitionHolder holder : configCandidates) { for (BeanDefinitionHolder holder : configCandidates) {
BeanDefinition bd = holder.getBeanDefinition(); BeanDefinition bd = holder.getBeanDefinition();
try { try {
......
...@@ -16,30 +16,45 @@ ...@@ -16,30 +16,45 @@
package org.springframework.context.annotation; package org.springframework.context.annotation;
import java.lang.annotation.ElementType; import org.springframework.beans.factory.parsing.Location;
import java.lang.annotation.Retention; import org.springframework.beans.factory.parsing.ProblemReporter;
import java.lang.annotation.RetentionPolicy; import org.springframework.core.type.MethodMetadata;
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 * @author Chris Beams
* @since 3.1 * @since 3.1
* @see ComponentScan
* @see org.springframework.context.config.FeatureSpecification
*/ */
@Retention(RetentionPolicy.RUNTIME) abstract class ConfigurationMethod {
@Target(ElementType.ANNOTATION_TYPE)
public @interface FeatureAnnotation { protected final MethodMetadata metadata;
/** protected final ConfigurationClass configurationClass;
* Indicate the class that should be used to parse this annotation
* into a {@code FeatureSpecification}.
*/ public ConfigurationMethod(MethodMetadata metadata, ConfigurationClass configurationClass) {
Class<? extends FeatureAnnotationParser> parser(); 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());
}
} }
/*
* 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();
}
/*
* 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);
}
}
}
/*
* 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 {
}
\ No newline at end of file
/*
* 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);
}
/*
* 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 "";
}
/*
* 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);
}
}
/*
* 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);
}
}
/*
* 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
}
}
/*
* 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);
}
}
/*
* 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;
}
}
/*
* 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);
}
/*
* 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);
}
/*
* 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);
}
/*
* 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);
}
/*
* 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;
}
}
/*
* 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 static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertThat;
import org.junit.Test;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.configuration.StubSpecification;
import org.springframework.context.config.FeatureSpecification;
/**
* Tests that @FeatureConfiguration classes may implement Aware interfaces,
* such as BeanFactoryAware. This is not generally recommended but occasionally
* useful, particularly in testing.
*
* @author Chris Beams
* @since 3.1
*/
public class BeanFactoryAwareFeatureConfigurationTests {
@Test
public void test() {
ConfigurableApplicationContext ctx =
new AnnotationConfigApplicationContext(FeatureConfig.class);
FeatureConfig fc = ctx.getBean(FeatureConfig.class);
assertThat(fc.featureMethodWasCalled, is(true));
assertThat(fc.gotBeanFactoryInTime, is(true));
assertThat(fc.beanFactory, is(ctx.getBeanFactory()));
}
@FeatureConfiguration
static class FeatureConfig implements BeanFactoryAware {
ConfigurableListableBeanFactory beanFactory;
boolean featureMethodWasCalled = false;
boolean gotBeanFactoryInTime = false;
public void setBeanFactory(BeanFactory beanFactory) {
this.beanFactory = (ConfigurableListableBeanFactory)beanFactory;
}
@Feature
public FeatureSpecification f() {
this.featureMethodWasCalled = true;
this.gotBeanFactoryInTime = (this.beanFactory != null);
return new StubSpecification();
}
}
}
/*
* 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 static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertThat;
import org.junit.Before;
import org.junit.Test;
import org.springframework.beans.factory.parsing.BeanDefinitionParsingException;
import org.springframework.beans.factory.parsing.FailFastProblemReporter;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.context.config.SpecificationContext;
import org.springframework.core.io.DefaultResourceLoader;
import org.springframework.mock.env.MockEnvironment;
/**
* Unit tests for {@link ComponentScanExecutor}.
*
* @author Chris Beams
* @since 3.1
*/
public class ComponentScanExecutorTests {
private ComponentScanExecutor executor;
private SpecificationContext specificationContext;
private DefaultListableBeanFactory bf;
@Before
public void setUp() {
this.bf = new DefaultListableBeanFactory();
this.executor = new ComponentScanExecutor();
this.specificationContext = new SpecificationContext();
this.specificationContext.setRegistry(bf);
this.specificationContext.setResourceLoader(new DefaultResourceLoader());
this.specificationContext.setEnvironment(new MockEnvironment());
this.specificationContext.setRegistrar(new SimpleComponentRegistrar(bf));
this.specificationContext.setProblemReporter(new FailFastProblemReporter());
}
@Test
public void validSpec() {
this.executor.execute(new ComponentScanSpec("example.scannable"), this.specificationContext);
assertThat(bf.containsBean("fooServiceImpl"), is(true));
}
@Test(expected=BeanDefinitionParsingException.class)
public void invalidSpec() {
// ff problem reporter should throw due to no packages specified
this.executor.execute(new ComponentScanSpec(), this.specificationContext);
}
}
/*
* 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 static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertThat;
import org.junit.Test;
public class ComponentScanFeatureTests {
@Test
public void viaContextRegistration() {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.register(ComponentScanFeatureConfig.class);
ctx.register(ComponentScanFeatureConfig.Features.class);
ctx.refresh();
ctx.getBean(ComponentScanFeatureConfig.class);
ctx.getBean(TestBean.class);
assertThat("config class bean not found", ctx.containsBeanDefinition("componentScanFeatureConfig"), is(true));
assertThat("@ComponentScan annotated @Configuration class registered directly against " +
"AnnotationConfigApplicationContext did not trigger component scanning as expected",
ctx.containsBean("fooServiceImpl"), is(true));
}
}
@Configuration
//@Import(ComponentScanFeatureConfig.Features.class)
class ComponentScanFeatureConfig {
@FeatureConfiguration
static class Features {
@Feature
public ComponentScanSpec componentScan() {
return new ComponentScanSpec(example.scannable._package.class);
}
}
@Bean
public TestBean testBean() {
return new TestBean();
}
}
\ No newline at end of file
/*
* 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 static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.instanceOf;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.CoreMatchers.nullValue;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.springframework.util.StringUtils.arrayToCommaDelimitedString;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import org.junit.Before;
import org.junit.Test;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.parsing.BeanDefinitionParsingException;
import org.springframework.beans.factory.parsing.FailFastProblemReporter;
import org.springframework.beans.factory.parsing.Problem;
import org.springframework.beans.factory.parsing.ProblemReporter;
import org.springframework.beans.factory.support.BeanNameGenerator;
import org.springframework.beans.factory.support.DefaultBeanNameGenerator;
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.AssignableTypeFilter;
import org.springframework.core.type.filter.TypeFilter;
import org.springframework.util.ClassUtils;
import org.springframework.util.ObjectUtils;
/**
* Unit tests for {@link ComponentScanSpec}.
*
* @author Chris Beams
* @since 3.1
*/
public class ComponentScanSpecTests {
private CollatingProblemReporter problemReporter;
private ClassLoader classLoader;
@Before
public void setUp() {
problemReporter = new CollatingProblemReporter();
classLoader = ClassUtils.getDefaultClassLoader();
}
@Test
public void includeAnnotationConfig() {
ComponentScanSpec spec = new ComponentScanSpec("org.p1");
assertThat(spec.includeAnnotationConfig(), nullValue());
spec.includeAnnotationConfig(true);
assertThat(spec.validate(problemReporter), is(true));
assertThat(spec.includeAnnotationConfig(), is(true));
spec.includeAnnotationConfig(false);
assertThat(spec.validate(problemReporter), is(true));
assertThat(spec.includeAnnotationConfig(), is(false));
spec.includeAnnotationConfig("trUE");
assertThat(spec.validate(problemReporter), is(true));
assertThat(spec.includeAnnotationConfig(), is(true));
spec.includeAnnotationConfig("falSE");
assertThat(spec.validate(problemReporter), is(true));
assertThat(spec.includeAnnotationConfig(), is(false));
spec.includeAnnotationConfig("");
assertThat(spec.validate(problemReporter), is(true));
assertThat(spec.includeAnnotationConfig(), is(false));
spec.includeAnnotationConfig((String)null);
assertThat(spec.validate(problemReporter), is(true));
assertThat(spec.includeAnnotationConfig(), is(false));
spec.includeAnnotationConfig((Boolean)null);
assertThat(spec.validate(problemReporter), is(true));
assertThat(spec.includeAnnotationConfig(), nullValue());
}
@Test
public void resourcePattern() {
ComponentScanSpec spec = new ComponentScanSpec("org.p1");
assertThat(spec.resourcePattern(), nullValue());
assertThat(spec.validate(problemReporter), is(true));
spec.resourcePattern("**/Foo*.class");
assertThat(spec.resourcePattern(), is("**/Foo*.class"));
assertThat(spec.validate(problemReporter), is(true));
spec.resourcePattern("");
assertThat(spec.resourcePattern(), is("**/Foo*.class"));
assertThat(spec.validate(problemReporter), is(true));
spec.resourcePattern(null);
assertThat(spec.resourcePattern(), is("**/Foo*.class"));
assertThat(spec.validate(problemReporter), is(true));
}
@Test
public void useDefaultFilters() {
ComponentScanSpec spec = new ComponentScanSpec("org.p1");
assertThat(spec.useDefaultFilters(), nullValue());
assertThat(spec.validate(problemReporter), is(true));
spec.useDefaultFilters((Boolean)null);
assertThat(spec.useDefaultFilters(), nullValue());
assertThat(spec.validate(problemReporter), is(true));
spec.useDefaultFilters(true);
assertThat(spec.useDefaultFilters(), is(true));
assertThat(spec.validate(problemReporter), is(true));
spec.useDefaultFilters(false);
assertThat(spec.useDefaultFilters(), is(false));
assertThat(spec.validate(problemReporter), is(true));
spec.useDefaultFilters("trUE");
assertThat(spec.useDefaultFilters(), is(true));
assertThat(spec.validate(problemReporter), is(true));
spec.useDefaultFilters("falSE");
assertThat(spec.useDefaultFilters(), is(false));
assertThat(spec.validate(problemReporter), is(true));
spec.useDefaultFilters("");
assertThat(spec.useDefaultFilters(), is(false));
assertThat(spec.validate(problemReporter), is(true));
spec.useDefaultFilters((String)null);
assertThat(spec.useDefaultFilters(), is(false));
assertThat(spec.validate(problemReporter), is(true));
}
@Test
public void includeFilters() {
ComponentScanSpec spec = new ComponentScanSpec("org.p1");
spec.includeFilters(
new AnnotationTypeFilter(MyAnnotation.class),
new AssignableTypeFilter(Object.class));
assertThat(spec.validate(problemReporter), is(true));
assertThat(spec.includeFilters().length, is(2));
}
@Test
public void stringIncludeExcludeFilters() {
ComponentScanSpec spec = new ComponentScanSpec("org.p1");
spec.addIncludeFilter("annotation", MyAnnotation.class.getName(), classLoader);
spec.addExcludeFilter("assignable", Object.class.getName(), classLoader);
spec.addExcludeFilter("annotation", Override.class.getName(), classLoader);
assertThat(spec.validate(problemReporter), is(true));
assertThat(spec.includeFilters().length, is(1));
assertThat(spec.excludeFilters().length, is(2));
}
@Test
public void bogusStringIncludeFilter() throws IOException {
ComponentScanSpec spec = new ComponentScanSpec("org.p1");
spec.addIncludeFilter("bogus-type", "bogus-expr", classLoader);
assertThat(spec.validate(problemReporter), is(false));
assertThat(spec.includeFilters().length, is(1));
try {
spec.includeFilters()[0].match(null, null);
fail("expected placholder TypeFilter to throw exception");
} catch (UnsupportedOperationException ex) {
// expected
}
}
@Test
public void exerciseFilterTypes() throws IOException {
ComponentScanSpec spec = new ComponentScanSpec("org.p1");
spec.addIncludeFilter("aspectj", "*..Bogus", classLoader);
assertThat(spec.validate(problemReporter), is(true));
spec.addIncludeFilter("regex", ".*Foo", classLoader);
assertThat(spec.validate(problemReporter), is(true));
spec.addIncludeFilter("custom", StubTypeFilter.class.getName(), classLoader);
assertThat(spec.validate(problemReporter), is(true));
spec.addIncludeFilter("custom", "org.NonExistentTypeFilter", classLoader);
assertThat(spec.validate(problemReporter), is(false));
spec.addIncludeFilter("custom", NonNoArgResolver.class.getName(), classLoader);
assertThat(spec.validate(problemReporter), is(false));
}
@Test
public void missingBasePackages() {
ComponentScanSpec spec = new ComponentScanSpec();
assertThat(spec.validate(problemReporter), is(false));
}
@Test
public void withBasePackageViaAdderMethod() {
ComponentScanSpec spec = new ComponentScanSpec();
spec.addBasePackage("org.p1");
spec.addBasePackage("org.p2");
assertThat(spec.validate(problemReporter), is(true));
assertExactContents(spec.basePackages(), "org.p1", "org.p2");
}
@Test
public void withBasePackagesViaStringConstructor() {
ComponentScanSpec spec = new ComponentScanSpec("org.p1", "org.p2");
assertThat(spec.validate(problemReporter), is(true));
assertExactContents(spec.basePackages(), "org.p1", "org.p2");
}
@Test
public void withBasePackagesViaClassConstructor() {
ComponentScanSpec spec = new ComponentScanSpec(java.lang.Object.class, java.io.Closeable.class);
assertThat(spec.validate(problemReporter), is(true));
assertExactContents(spec.basePackages(), "java.lang", "java.io");
}
@Test
public void forDelimitedPackages() {
ComponentScanSpec spec = ComponentScanSpec.forDelimitedPackages("pkg.one,pkg.two");
assertTrue(ObjectUtils.containsElement(spec.basePackages(), "pkg.one"));
assertTrue(ObjectUtils.containsElement(spec.basePackages(), "pkg.two"));
assertThat(spec.basePackages().length, is(2));
}
@Test
public void withSomeEmptyBasePackages() {
ComponentScanSpec spec = new ComponentScanSpec("org.p1", "", "org.p3");
assertThat(spec.validate(problemReporter), is(true));
assertExactContents(spec.basePackages(), "org.p1", "org.p3");
}
@Test
public void withAllEmptyBasePackages() {
ComponentScanSpec spec = new ComponentScanSpec("", "", "");
assertThat(spec.validate(problemReporter), is(false));
}
@Test
public void withInstanceBeanNameGenerator() {
ComponentScanSpec spec = new ComponentScanSpec("org.p1");
assertThat(spec.beanNameGenerator(), nullValue());
BeanNameGenerator bng = new DefaultBeanNameGenerator();
spec.beanNameGenerator(bng);
assertThat(spec.validate(problemReporter), is(true));
assertThat(spec.beanNameGenerator(), is(bng));
}
@Test
public void withStringBeanNameGenerator() {
ComponentScanSpec spec = new ComponentScanSpec("org.p1");
spec.beanNameGenerator(DefaultBeanNameGenerator.class.getName(), classLoader);
assertThat(spec.validate(problemReporter), is(true));
assertThat(spec.beanNameGenerator(), instanceOf(DefaultBeanNameGenerator.class));
}
@Test
public void withInstanceScopeMetadataResolver() {
ComponentScanSpec spec = new ComponentScanSpec("org.p1");
assertThat(spec.scopeMetadataResolver(), nullValue());
ScopeMetadataResolver smr = new AnnotationScopeMetadataResolver();
spec.scopeMetadataResolver(smr);
assertThat(spec.validate(problemReporter), is(true));
assertThat(spec.scopeMetadataResolver(), is(smr));
}
@Test
public void withStringScopeMetadataResolver() {
ComponentScanSpec spec = new ComponentScanSpec("org.p1");
spec.scopeMetadataResolver(AnnotationScopeMetadataResolver.class.getName(), classLoader);
assertThat(spec.validate(problemReporter), is(true));
assertThat(spec.scopeMetadataResolver(), instanceOf(AnnotationScopeMetadataResolver.class));
}
@Test
public void withNonAssignableStringScopeMetadataResolver() {
ComponentScanSpec spec = new ComponentScanSpec("org.p1");
spec.scopeMetadataResolver(Object.class.getName(), classLoader);
assertThat(spec.validate(problemReporter), is(false));
}
@Test
public void withNonExistentStringScopeMetadataResolver() {
ComponentScanSpec spec = new ComponentScanSpec("org.p1");
spec.scopeMetadataResolver("org.Bogus", classLoader);
assertThat(spec.validate(problemReporter), is(false));
}
@Test
public void withNonNoArgStringScopeMetadataResolver() {
ComponentScanSpec spec = new ComponentScanSpec("org.p1");
spec.scopeMetadataResolver(NonNoArgResolver.class.getName(), classLoader);
assertThat(spec.validate(problemReporter), is(false));
}
@Test
public void withStringScopedProxyMode() {
ComponentScanSpec spec = new ComponentScanSpec("org.p1");
spec.scopedProxyMode("targetCLASS");
assertThat(spec.validate(problemReporter), is(true));
assertThat(spec.scopedProxyMode(), is(ScopedProxyMode.TARGET_CLASS));
spec.scopedProxyMode("interFACES");
assertThat(spec.validate(problemReporter), is(true));
assertThat(spec.scopedProxyMode(), is(ScopedProxyMode.INTERFACES));
spec.scopedProxyMode("nO");
assertThat(spec.validate(problemReporter), is(true));
assertThat(spec.scopedProxyMode(), is(ScopedProxyMode.NO));
spec.scopedProxyMode("bogus");
assertThat(spec.validate(problemReporter), is(false));
assertThat(spec.scopedProxyMode(), nullValue());
}
@Test
public void withScopeMetadataResolverAndScopedProxyMode() {
ComponentScanSpec spec = new ComponentScanSpec("org.p1");
spec.scopeMetadataResolver(new AnnotationScopeMetadataResolver());
assertThat(spec.validate(problemReporter), is(true));
spec.scopedProxyMode(ScopedProxyMode.INTERFACES);
assertThat(spec.validate(problemReporter), is(false));
}
@Test
public void addBasePackage() {
ComponentScanSpec spec = new ComponentScanSpec();
spec.addBasePackage("foo.bar");
assertThat(spec.validate(problemReporter), is(true));
assertThat(spec.basePackages().length, is(1));
}
@Test
public void addBasePackageWithConstructor() {
ComponentScanSpec spec = new ComponentScanSpec("my.pkg");
spec.addBasePackage("foo.bar");
assertThat(spec.validate(problemReporter), is(true));
assertThat(spec.basePackages().length, is(2));
}
@Test
public void addExcludeFilterString() {
ComponentScanSpec spec = new ComponentScanSpec("my.pkg");
spec.addExcludeFilter("annotation", MyAnnotation.class.getName(), ClassUtils.getDefaultClassLoader());
assertThat(spec.validate(problemReporter), is(true));
assertThat(spec.excludeFilters().length, is(1));
assertThat(spec.excludeFilters()[0], instanceOf(AnnotationTypeFilter.class));
}
@Test(expected=BeanDefinitionParsingException.class)
public void withFailFastProblemReporter() {
new ComponentScanSpec().validate(new FailFastProblemReporter());
}
private <T> void assertExactContents(T[] actual, T... expected) {
if (actual.length >= expected.length) {
for (int i = 0; i < expected.length; i++) {
assertThat(
String.format("element number %d in actual is incorrect. actual: [%s], expected: [%s]",
i, arrayToCommaDelimitedString(actual), arrayToCommaDelimitedString(expected)),
actual[i], equalTo(expected[i]));
}
}
assertThat(String.format("actual contains incorrect number of arguments. actual: [%s], expected: [%s]",
arrayToCommaDelimitedString(actual), arrayToCommaDelimitedString(expected)),
actual.length, equalTo(expected.length));
}
private static class CollatingProblemReporter implements ProblemReporter {
private List<Problem> errors = new ArrayList<Problem>();
private List<Problem> warnings = new ArrayList<Problem>();
public void fatal(Problem problem) {
throw new BeanDefinitionParsingException(problem);
}
public void error(Problem problem) {
this.errors.add(problem);
}
@SuppressWarnings("unused")
public Problem[] getErrors() {
return this.errors.toArray(new Problem[this.errors.size()]);
}
public void warning(Problem problem) {
System.out.println(problem);
this.warnings.add(problem);
}
@SuppressWarnings("unused")
public Problem[] getWarnings() {
return this.warnings.toArray(new Problem[this.warnings.size()]);
}
}
private static class NonNoArgResolver implements ScopeMetadataResolver {
public ScopeMetadata resolveScopeMetadata(BeanDefinition definition) {
throw new UnsupportedOperationException();
}
}
private static class StubTypeFilter implements TypeFilter {
public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory)
throws IOException {
throw new UnsupportedOperationException();
}
}
}
\ No newline at end of file
/*
* 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 static java.lang.String.format;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.instanceOf;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.CoreMatchers.not;
import static org.hamcrest.CoreMatchers.sameInstance;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.fail;
import static org.springframework.context.annotation.EarlyBeanReferenceProxyCreator.MISSING_NO_ARG_CONSTRUCTOR_ERROR_MESSAGE;
import static org.springframework.context.annotation.EarlyBeanReferenceProxyCreator.PRIVATE_NO_ARG_CONSTRUCTOR_ERROR_MESSAGE;
import java.lang.reflect.Method;
import org.junit.Before;
import org.junit.Test;
import org.springframework.aop.support.AopUtils;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.beans.factory.config.DependencyDescriptor;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.core.MethodParameter;
import test.beans.ITestBean;
import test.beans.TestBean;
/**
* Unit tests for {@link EarlyBeanReferenceProxyCreator}, ensuring that
* {@link EarlyBeanReferenceProxy} objects behave properly.
*
* @author Chris Beams
* @since 3.1
*/
public class EarlyBeanReferenceProxyCreatorTests {
private DefaultListableBeanFactory bf;
@Before
public void setUp() {
bf = new DefaultListableBeanFactory();
}
@Test
public void proxyToStringAvoidsEagerInstantiation() throws Exception {
EarlyBeanReferenceProxyCreator pc = new EarlyBeanReferenceProxyCreator(bf);
TestBean proxy = (TestBean) pc.createProxyIfPossible(descriptorFor(TestBean.class));
assertThat(proxy.toString(), equalTo("EarlyBeanReferenceProxy for bean of type TestBean"));
}
@Test(expected=NoSuchBeanDefinitionException.class)
public void proxyThrowsNoSuchBeanDefinitionExceptionWhenDelegatingMethodCallToNonExistentBean() throws Exception {
EarlyBeanReferenceProxyCreator pc = new EarlyBeanReferenceProxyCreator(bf);
TestBean proxy = (TestBean) pc.createProxyIfPossible(descriptorFor(TestBean.class));
proxy.getName();
}
@Test
public void proxyHashCodeAvoidsEagerInstantiation() throws Exception {
EarlyBeanReferenceProxyCreator pc = new EarlyBeanReferenceProxyCreator(bf);
TestBean proxy = (TestBean) pc.createProxyIfPossible(descriptorFor(TestBean.class));
assertThat(proxy.hashCode(), equalTo(System.identityHashCode(proxy)));
}
@Test
public void proxyEqualsAvoidsEagerInstantiation() throws Exception {
EarlyBeanReferenceProxyCreator pc = new EarlyBeanReferenceProxyCreator(bf);
TestBean proxy = (TestBean) pc.createProxyIfPossible(descriptorFor(TestBean.class));
assertThat(proxy.equals(new Object()), is(false));
assertThat(proxy.equals(proxy), is(true));
TestBean proxy2 = (TestBean) pc.createProxyIfPossible(descriptorFor(TestBean.class));
assertThat(proxy, not(sameInstance(proxy2)));
assertThat(proxy.equals(proxy2), is(false));
assertThat(proxy2.equals(proxy), is(false));
assertThat(proxy2.equals(proxy2), is(true));
}
@Test
public void proxyFinalizeAvoidsEagerInstantiation() throws Exception {
EarlyBeanReferenceProxyCreator pc = new EarlyBeanReferenceProxyCreator(bf);
BeanWithFinalizer proxy = (BeanWithFinalizer) pc.createProxyIfPossible(descriptorFor(BeanWithFinalizer.class));
assertThat(BeanWithFinalizer.finalizerWasCalled, is(false));
BeanWithFinalizer.class.getDeclaredMethod("finalize").invoke(proxy);
assertThat(BeanWithFinalizer.finalizerWasCalled, is(false));
}
@Test
public void proxyMethodsDelegateToTargetBeanCausingSingletonRegistrationIfNecessary() throws Exception {
bf.registerBeanDefinition("testBean",
BeanDefinitionBuilder.rootBeanDefinition(TestBean.class)
.addPropertyValue("name", "testBeanName").getBeanDefinition());
EarlyBeanReferenceProxyCreator pc = new EarlyBeanReferenceProxyCreator(bf);
TestBean proxy = (TestBean) pc.createProxyIfPossible(descriptorFor(TestBean.class));
assertThat(bf.containsBeanDefinition("testBean"), is(true));
assertThat(bf.containsSingleton("testBean"), is(false));
assertThat(proxy.getName(), equalTo("testBeanName"));
assertThat(bf.containsSingleton("testBean"), is(true));
}
@Test
public void beanAnnotatedMethodsReturnEarlyProxyAsWell() throws Exception {
bf.registerBeanDefinition("componentWithInterfaceBeanMethod", new RootBeanDefinition(ComponentWithInterfaceBeanMethod.class));
EarlyBeanReferenceProxyCreator pc = new EarlyBeanReferenceProxyCreator(bf);
ComponentWithInterfaceBeanMethod proxy = (ComponentWithInterfaceBeanMethod) pc.createProxyIfPossible(descriptorFor(ComponentWithInterfaceBeanMethod.class));
ITestBean bean = proxy.aBeanMethod();
assertThat(bean, instanceOf(EarlyBeanReferenceProxy.class));
assertThat(bf.containsBeanDefinition("componentWithInterfaceBeanMethod"), is(true));
assertThat("calling a @Bean method on an EarlyBeanReferenceProxy object " +
"should not cause its instantation/registration",
bf.containsSingleton("componentWithInterfaceBeanMethod"), is(false));
Object obj = proxy.normalInstanceMethod();
assertThat(bf.containsSingleton("componentWithInterfaceBeanMethod"), is(true));
assertThat(obj, not(instanceOf(EarlyBeanReferenceProxy.class)));
}
@Test
public void proxiesReturnedFromBeanAnnotatedMethodsDereferenceAndDelegateToTheirTargetBean() throws Exception {
bf.registerBeanDefinition("componentWithConcreteBeanMethod", new RootBeanDefinition(ComponentWithConcreteBeanMethod.class));
RootBeanDefinition beanMethodBeanDef = new RootBeanDefinition();
beanMethodBeanDef.setFactoryBeanName("componentWithConcreteBeanMethod");
beanMethodBeanDef.setFactoryMethodName("aBeanMethod");
bf.registerBeanDefinition("aBeanMethod", beanMethodBeanDef);
EarlyBeanReferenceProxyCreator pc = new EarlyBeanReferenceProxyCreator(bf);
ComponentWithConcreteBeanMethod proxy = (ComponentWithConcreteBeanMethod) pc.createProxyIfPossible(descriptorFor(ComponentWithConcreteBeanMethod.class));
TestBean bean = proxy.aBeanMethod();
assertThat(bean.getName(), equalTo("concrete"));
}
@Test
public void interfaceBeansAreProxied() throws Exception {
EarlyBeanReferenceProxyCreator pc = new EarlyBeanReferenceProxyCreator(bf);
ITestBean proxy = (ITestBean) pc.createProxyIfPossible(descriptorFor(ITestBean.class));
assertThat(proxy, instanceOf(EarlyBeanReferenceProxy.class));
assertThat(AopUtils.isCglibProxyClass(proxy.getClass()), is(true));
assertEquals(
"interface-based bean proxies should have Object as superclass",
proxy.getClass().getSuperclass(), Object.class);
}
@Test
public void concreteBeansAreProxied() throws Exception {
EarlyBeanReferenceProxyCreator pc = new EarlyBeanReferenceProxyCreator(bf);
TestBean proxy = (TestBean) pc.createProxyIfPossible(descriptorFor(TestBean.class));
assertThat(proxy, instanceOf(EarlyBeanReferenceProxy.class));
assertThat(AopUtils.isCglibProxyClass(proxy.getClass()), is(true));
assertEquals(
"concrete bean proxies should have the bean class as superclass",
proxy.getClass().getSuperclass(), TestBean.class);
}
@Test
public void beanAnnotatedMethodsWithInterfaceReturnTypeAreProxied() throws Exception {
bf.registerBeanDefinition("componentWithInterfaceBeanMethod", new RootBeanDefinition(ComponentWithInterfaceBeanMethod.class));
EarlyBeanReferenceProxyCreator pc = new EarlyBeanReferenceProxyCreator(bf);
ComponentWithInterfaceBeanMethod proxy = (ComponentWithInterfaceBeanMethod) pc.createProxyIfPossible(descriptorFor(ComponentWithInterfaceBeanMethod.class));
ITestBean bean = proxy.aBeanMethod();
assertThat(bean, instanceOf(EarlyBeanReferenceProxy.class));
assertThat(AopUtils.isCglibProxyClass(bean.getClass()), is(true));
assertEquals(
"interface-based bean proxies should have Object as superclass",
bean.getClass().getSuperclass(), Object.class);
}
@Test
public void beanAnnotatedMethodsWithConcreteReturnTypeAreProxied() throws Exception {
bf.registerBeanDefinition("componentWithConcreteBeanMethod", new RootBeanDefinition(ComponentWithConcreteBeanMethod.class));
EarlyBeanReferenceProxyCreator pc = new EarlyBeanReferenceProxyCreator(bf);
ComponentWithConcreteBeanMethod proxy = (ComponentWithConcreteBeanMethod) pc.createProxyIfPossible(descriptorFor(ComponentWithConcreteBeanMethod.class));
TestBean bean = proxy.aBeanMethod();
assertThat(bean, instanceOf(EarlyBeanReferenceProxy.class));
assertThat(AopUtils.isCglibProxyClass(bean.getClass()), is(true));
assertEquals(
"concrete bean proxies should have the bean class as superclass",
bean.getClass().getSuperclass(), TestBean.class);
}
@Test
public void attemptToProxyClassMissingNoArgConstructorFailsGracefully() throws Exception {
EarlyBeanReferenceProxyCreator pc = new EarlyBeanReferenceProxyCreator(bf);
try {
pc.createProxyIfPossible(descriptorFor(BeanMissingNoArgConstructor.class));
fail("expected ProxyCreationException");
} catch(ProxyCreationException ex) {
assertThat(ex.getMessage(),
equalTo(format(MISSING_NO_ARG_CONSTRUCTOR_ERROR_MESSAGE, BeanMissingNoArgConstructor.class.getName())));
}
}
@Test
public void attemptToProxyClassWithPrivateNoArgConstructorFailsGracefully() throws Exception {
EarlyBeanReferenceProxyCreator pc = new EarlyBeanReferenceProxyCreator(bf);
try {
pc.createProxyIfPossible(descriptorFor(BeanWithPrivateNoArgConstructor.class));
fail("expected ProxyCreationException");
} catch(ProxyCreationException ex) {
assertThat(ex.getMessage(),
equalTo(format(PRIVATE_NO_ARG_CONSTRUCTOR_ERROR_MESSAGE, BeanWithPrivateNoArgConstructor.class.getName())));
}
}
@Test
public void attemptToProxyFinalClassReturnsNonProxiedInstance() throws Exception {
bf.registerBeanDefinition("finalBean", new RootBeanDefinition(FinalBean.class));
EarlyBeanReferenceProxyCreator pc = new EarlyBeanReferenceProxyCreator(bf);
Object bean = pc.createProxyIfPossible(descriptorFor(FinalBean.class));
assertThat(bean, instanceOf(FinalBean.class));
assertThat(bean, not(instanceOf(EarlyBeanReferenceProxy.class)));
}
private DependencyDescriptor descriptorFor(Class<?> paramType) throws Exception {
@SuppressWarnings("unused")
class C {
void m(ITestBean p) { }
void m(TestBean p) { }
void m(BeanMissingNoArgConstructor p) { }
void m(BeanWithPrivateNoArgConstructor p) { }
void m(FinalBean p) { }
void m(BeanWithFinalizer p) { }
void m(ComponentWithConcreteBeanMethod p) { }
void m(ComponentWithInterfaceBeanMethod p) { }
}
Method targetMethod = C.class.getDeclaredMethod("m", new Class<?>[] { paramType });
MethodParameter mp = new MethodParameter(targetMethod, 0);
DependencyDescriptor dd = new DependencyDescriptor(mp, true, false);
return dd;
}
static class BeanMissingNoArgConstructor {
BeanMissingNoArgConstructor(Object o) { }
}
static class BeanWithPrivateNoArgConstructor {
private BeanWithPrivateNoArgConstructor() { }
}
static final class FinalBean {
}
static class BeanWithFinalizer {
static Boolean finalizerWasCalled = false;
@Override
protected void finalize() throws Throwable {
finalizerWasCalled = true;
}
}
static class ComponentWithConcreteBeanMethod {
@Bean
public TestBean aBeanMethod() {
return new TestBean("concrete");
}
public Object normalInstanceMethod() {
return new Object();
}
}
static class ComponentWithInterfaceBeanMethod {
@Bean
public ITestBean aBeanMethod() {
return new TestBean("interface");
}
public Object normalInstanceMethod() {
return new Object();
}
}
}
/*
* 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 static org.hamcrest.CoreMatchers.instanceOf;
import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertThat;
import org.junit.Test;
import org.springframework.context.annotation.configuration.StubSpecification;
import org.springframework.context.config.FeatureSpecification;
import org.springframework.core.env.Environment;
import org.springframework.core.io.ResourceLoader;
import org.springframework.mock.env.MockEnvironment;
public class FeatureConfigurationClassTests {
@Test(expected=FeatureMethodExecutionException.class)
public void featureConfigurationClassesMustNotContainBeanAnnotatedMethods() {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.register(FeatureConfigWithBeanAnnotatedMethod.class);
ctx.refresh();
}
@Test
public void featureMethodsMayAcceptResourceLoaderParameter() {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.setDisplayName("enclosing app ctx");
ctx.setEnvironment(new MockEnvironment().withProperty("foo", "bar"));
ctx.register(FeatureMethodWithResourceLoaderParameter.class);
ctx.refresh();
}
}
@FeatureConfiguration
class FeatureConfigWithBeanAnnotatedMethod {
/**
* This is illegal use. @FeatureConfiguration classes cannot have @Bean methods.
*/
@Bean
public TestBean testBean() {
return new TestBean();
}
/**
* This will never get called. An exception will first be raised regarding the illegal @Bean method above.
*/
@Feature
public FeatureSpecification feature() {
return new StubSpecification();
}
}
@FeatureConfiguration
class FeatureMethodWithResourceLoaderParameter {
@Feature
public FeatureSpecification feature(ResourceLoader rl,
Environment e) {
// prove that the injected Environment is that of the enclosing app context
assertThat(e.getProperty("foo"), is("bar"));
// prove that the injected ResourceLoader is actually the enclosing application context
Object target = ((EarlyBeanReferenceProxy)rl).dereferenceTargetBean();
assertThat(target, instanceOf(AnnotationConfigApplicationContext.class));
assertThat(((AnnotationConfigApplicationContext)target).getDisplayName(), is("enclosing app ctx"));
return new StubSpecification();
}
}
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:c="http://www.springframework.org/schema/c"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.1.xsd">
<bean class="test.beans.TestBean" c:name="beanFromXml"/>
</beans>
/*
* 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 static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.instanceOf;
import static org.junit.Assert.assertNotSame;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertThat;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.configuration.StubSpecification;
import org.springframework.context.config.FeatureSpecification;
import test.beans.TestBean;
/**
* Tests proving that @FeatureConfiguration classes may be use @ImportResource
* and then parameter autowire beans declared in the imported resource(s).
*
* @author Chris Beams
* @since 3.1
*/
public class FeatureConfigurationImportResourceTests {
@Test
public void importResourceFromFeatureConfiguration() {
ApplicationContext ctx =
new AnnotationConfigApplicationContext(ImportingFeatureConfig.class);
TestBean testBean = ctx.getBean(TestBean.class);
assertThat(testBean.getName(), equalTo("beanFromXml"));
// and just quickly prove that the target of the bean proxied for the Feature method
// is indeed the same singleton instance as the one we just pulled from the container
ImportingFeatureConfig ifc = ctx.getBean(ImportingFeatureConfig.class);
TestBean proxyBean = ifc.testBean;
assertThat(proxyBean, instanceOf(EarlyBeanReferenceProxy.class));
assertNotSame(proxyBean, testBean);
assertSame(((EarlyBeanReferenceProxy)proxyBean).dereferenceTargetBean(), testBean);
}
@FeatureConfiguration
@ImportResource("org/springframework/context/annotation/FeatureConfigurationImportResourceTests-context.xml")
static class ImportingFeatureConfig {
TestBean testBean;
@Feature
public FeatureSpecification f(TestBean testBean) {
this.testBean = testBean;
return new StubSpecification();
}
}
}
/*
* 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 static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertThat;
import org.junit.Test;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.configuration.StubSpecification;
import org.springframework.context.config.FeatureSpecification;
/**
* Tests proving that @Configuration classes may @Import @FeatureConfiguration
* classes, and vice versa.
*
* @author Chris Beams
* @since 3.1
*/
public class FeatureConfigurationImportTests {
@Test
public void importFeatureConfigurationFromConfiguration() {
ConfigurableApplicationContext ctx =
new AnnotationConfigApplicationContext(ImportingConfig.class);
ImportedFeatureConfig ifc = ctx.getBean(ImportedFeatureConfig.class);
assertThat(
"@FeatureConfiguration class was imported and registered " +
"as a bean but its @Feature method was never called",
ifc.featureMethodWasCalled, is(true));
}
@Test
public void importConfigurationFromFeatureConfiguration() {
ConfigurableApplicationContext ctx =
new AnnotationConfigApplicationContext(ImportingFeatureConfig.class);
ImportingFeatureConfig ifc = ctx.getBean(ImportingFeatureConfig.class);
ImportedConfig ic = ctx.getBean(ImportedConfig.class);
assertThat(
"@FeatureConfiguration class was registered directly against " +
"the container but its @Feature method was never called",
ifc.featureMethodWasCalled, is(true));
assertThat(
"@Configuration class was @Imported but its @Bean method" +
"was never registered / called",
ic.beanMethodWasCalled, is(true));
}
@Configuration
@Import(ImportedFeatureConfig.class)
static class ImportingConfig {
}
@FeatureConfiguration
static class ImportedFeatureConfig {
boolean featureMethodWasCalled = false;
@Feature
public FeatureSpecification f() {
this.featureMethodWasCalled = true;
return new StubSpecification();
}
}
@Configuration
static class ImportedConfig {
boolean beanMethodWasCalled = true;
@Bean
public TestBean testBean() {
this.beanMethodWasCalled = true;
return new TestBean();
}
}
@FeatureConfiguration
@Import(ImportedConfig.class)
static class ImportingFeatureConfig {
boolean featureMethodWasCalled = false;
@Feature
public FeatureSpecification f() {
this.featureMethodWasCalled = true;
return new StubSpecification();
}
}
}
/*
* 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 static org.hamcrest.CoreMatchers.instanceOf;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.CoreMatchers.not;
import static org.hamcrest.CoreMatchers.notNullValue;
import static org.junit.Assert.assertThat;
import org.junit.Test;
import org.springframework.context.annotation.configuration.StubSpecification;
import org.springframework.context.config.FeatureSpecification;
import test.beans.ITestBean;
import test.beans.TestBean;
/**
* Tests proving that @Feature methods may reference the product of @Bean methods.
*
* @author Chris Beams
* @since 3.1
*/
public class FeatureMethodBeanReferenceTests {
@Test
public void test() {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.register(FeatureConfig.class, Config.class);
ctx.refresh();
TestBean registeredBean = ctx.getBean("testBean", TestBean.class);
TestBean proxiedBean = ctx.getBean(FeatureConfig.class).testBean;
assertThat(registeredBean, not(instanceOf(EarlyBeanReferenceProxy.class)));
assertThat(proxiedBean, notNullValue());
assertThat(proxiedBean, instanceOf(EarlyBeanReferenceProxy.class));
assertThat(proxiedBean.getSpouse(), is(registeredBean.getSpouse()));
}
@FeatureConfiguration
static class FeatureConfig {
TestBean testBean;
@Feature
public FeatureSpecification f(TestBean testBean) {
this.testBean = testBean;
return new StubSpecification();
}
}
@Configuration
static class Config {
@Bean
public ITestBean testBean() {
return new TestBean(new TestBean("mySpouse"));
}
}
}
/*
* 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 static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.instanceOf;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.CoreMatchers.not;
import static org.junit.Assert.assertThat;
import org.junit.Test;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.context.annotation.configuration.StubSpecification;
import org.springframework.context.config.FeatureSpecification;
import test.beans.ITestBean;
import test.beans.TestBean;
/**
* Tests that @Bean methods referenced from within @Feature methods
* get proxied early to avoid premature instantiation of actual
* bean instances.
*
* @author Chris Beams
* @since 3.1
*/
public class FeatureMethodEarlyBeanProxyTests {
@Test
public void earlyProxyCreationAndBeanRegistrationLifecycle() {
AnnotationConfigApplicationContext ctx =
new AnnotationConfigApplicationContext(FeatureConfig.class);
//
// see additional assertions in FeatureConfig#feature()
//
// sanity check that all the bean definitions we expecting are present
assertThat(ctx.getBeanFactory().containsBeanDefinition("lazyHelperBean"), is(true));
assertThat(ctx.getBeanFactory().containsBeanDefinition("eagerHelperBean"), is(true));
assertThat(ctx.getBeanFactory().containsBeanDefinition("lazyPassthroughBean"), is(true));
assertThat(ctx.getBeanFactory().containsBeanDefinition("eagerPassthroughBean"), is(true));
// the lazy helper bean had methods invoked during feature method execution. it should be registered
assertThat(ctx.getBeanFactory().containsSingleton("lazyHelperBean"), is(true));
// the eager helper bean had methods invoked but should be registered in any case is it is non-lazy
assertThat(ctx.getBeanFactory().containsSingleton("eagerHelperBean"), is(true));
// the lazy passthrough bean was referenced in the feature method, but never invoked. it should not be registered
assertThat(ctx.getBeanFactory().containsSingleton("lazyPassthroughBean"), is(false));
// the eager passthrough bean should be registered in any case as it is non-lazy
assertThat(ctx.getBeanFactory().containsSingleton("eagerPassthroughBean"), is(true));
// now actually fetch all the beans. none should be proxies
assertThat(ctx.getBean("lazyHelperBean"), not(instanceOf(EarlyBeanReferenceProxy.class)));
assertThat(ctx.getBean("eagerHelperBean"), not(instanceOf(EarlyBeanReferenceProxy.class)));
assertThat(ctx.getBean("lazyPassthroughBean"), not(instanceOf(EarlyBeanReferenceProxy.class)));
assertThat(ctx.getBean("eagerPassthroughBean"), not(instanceOf(EarlyBeanReferenceProxy.class)));
}
@Test
public void earlyProxyBeansMayBeInterfaceBasedOrConcrete() {
new AnnotationConfigApplicationContext(FeatureConfigReferencingNonInterfaceBeans.class);
}
}
@FeatureConfiguration
@Import(TestBeanConfig.class)
class FeatureConfig implements BeanFactoryAware {
private DefaultListableBeanFactory beanFactory;
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
this.beanFactory = (DefaultListableBeanFactory)beanFactory;
}
@Feature
public StubSpecification feature(TestBeanConfig beans) {
assertThat(
"The @Configuration class instance itself should be an early-ref proxy",
beans, instanceOf(EarlyBeanReferenceProxy.class));
// invocation of @Bean methods within @Feature methods should return proxies
ITestBean lazyHelperBean = beans.lazyHelperBean();
ITestBean eagerHelperBean = beans.eagerHelperBean();
ITestBean lazyPassthroughBean = beans.lazyPassthroughBean();
ITestBean eagerPassthroughBean = beans.eagerPassthroughBean();
assertThat(lazyHelperBean, instanceOf(EarlyBeanReferenceProxy.class));
assertThat(eagerHelperBean, instanceOf(EarlyBeanReferenceProxy.class));
assertThat(lazyPassthroughBean, instanceOf(EarlyBeanReferenceProxy.class));
assertThat(eagerPassthroughBean, instanceOf(EarlyBeanReferenceProxy.class));
// but at this point, the proxy instances should not have
// been registered as singletons with the container.
assertThat(this.beanFactory.containsSingleton("lazyHelperBean"), is(false));
assertThat(this.beanFactory.containsSingleton("eagerHelperBean"), is(false));
assertThat(this.beanFactory.containsSingleton("lazyPassthroughBean"), is(false));
assertThat(this.beanFactory.containsSingleton("eagerPassthroughBean"), is(false));
// invoking a method on the proxy should cause it to pass through
// to the container, instantiate the actual bean in question and
// register that actual underlying instance as a singleton.
assertThat(lazyHelperBean.getName(), equalTo("lazyHelper"));
assertThat(eagerHelperBean.getName(), equalTo("eagerHelper"));
assertThat(this.beanFactory.containsSingleton("lazyHelperBean"), is(true));
assertThat(this.beanFactory.containsSingleton("eagerHelperBean"), is(true));
// since no methods were called on the passthrough beans, they should remain
// uncreated / unregistered.
assertThat(this.beanFactory.containsSingleton("lazyPassthroughBean"), is(false));
assertThat(this.beanFactory.containsSingleton("eagerPassthroughBean"), is(false));
return new StubSpecification();
}
}
@Configuration
class TestBeanConfig {
@Lazy @Bean
public ITestBean lazyHelperBean() {
return new TestBean("lazyHelper");
}
@Bean
public ITestBean eagerHelperBean() {
return new TestBean("eagerHelper");
}
@Lazy @Bean
public ITestBean lazyPassthroughBean() {
return new TestBean("lazyPassthrough");
}
@Bean
public ITestBean eagerPassthroughBean() {
return new TestBean("eagerPassthrough");
}
}
@FeatureConfiguration
@Import(NonInterfaceBeans.class)
class FeatureConfigReferencingNonInterfaceBeans {
@Feature
public FeatureSpecification feature1(NonInterfaceBeans beans) throws Throwable {
beans.testBean();
return new StubSpecification();
}
@Feature
public FeatureSpecification feature2(TestBean testBean) throws Throwable {
return new StubSpecification();
}
}
@Configuration
class NonInterfaceBeans {
@Bean
public TestBean testBean() {
return new TestBean("invalid");
}
}
/*
* 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 static org.hamcrest.CoreMatchers.equalTo;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.fail;
import org.junit.Test;
import org.springframework.context.annotation.configuration.StubSpecification;
import org.springframework.context.config.FeatureSpecification;
import test.beans.TestBean;
/**
* Tests proving that @Feature methods may reference the product of @Bean methods.
*
* @author Chris Beams
* @since 3.1
*/
public class FeatureMethodErrorTests {
@Test
public void incorrectReturnType() {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.register(FeatureConfig.class);
try {
ctx.refresh();
fail("expected exception");
} catch (FeatureMethodExecutionException ex) {
assertThat(ex.getCause().getMessage(),
equalTo("Return type for @Feature method FeatureConfig.f() must be " +
"assignable to FeatureSpecification"));
}
}
@Test
public void voidReturnType() {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.register(VoidFeatureConfig.class);
try {
ctx.refresh();
fail("expected exception");
} catch (FeatureMethodExecutionException ex) {
assertThat(ex.getCause().getMessage(),
equalTo("Return type for @Feature method VoidFeatureConfig.f() must be " +
"assignable to FeatureSpecification"));
}
}
@Test
public void containsBeanMethod() {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.register(FeatureConfigWithBeanMethod.class);
try {
ctx.refresh();
fail("expected exception");
} catch (FeatureMethodExecutionException ex) {
assertThat(ex.getMessage(),
equalTo("@FeatureConfiguration classes must not contain @Bean-annotated methods. " +
"FeatureConfigWithBeanMethod.testBean() 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."));
}
}
@FeatureConfiguration
static class FeatureConfig {
@Feature
public Object f() {
return new StubSpecification();
}
}
@FeatureConfiguration
static class VoidFeatureConfig {
@Feature
public void f() {
}
}
@FeatureConfiguration
static class FeatureConfigWithBeanMethod {
@Feature
public FeatureSpecification f() {
return new StubSpecification();
}
@Bean
public TestBean testBean() {
return new TestBean();
}
}
}
/*
* 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 static org.hamcrest.CoreMatchers.equalTo;
import static org.junit.Assert.assertThat;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.configuration.StubSpecification;
import org.springframework.context.config.FeatureSpecification;
import test.beans.ITestBean;
import test.beans.TestBean;
/**
* Tests proving that @Feature methods may reference beans using @Qualifier
* as a parameter annotation.
*
* @author Chris Beams
* @since 3.1
*/
public class FeatureMethodQualifiedBeanReferenceTests {
@Test
public void test() {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.register(Features.class, TestBeans.class);
ctx.refresh();
}
@FeatureConfiguration
static class Features {
@Feature
public FeatureSpecification f(@Qualifier("testBean1") ITestBean testBean) {
assertThat(testBean.getName(), equalTo("one"));
return new StubSpecification();
}
}
@Configuration
static class TestBeans {
@Bean
public ITestBean testBean1() {
return new TestBean("one");
}
@Bean
public ITestBean testBean2() {
return new TestBean("two");
}
}
}
/*
* 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 static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertThat;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.configuration.StubSpecification;
/**
* Tests ensuring that @Feature methods can accept @Value-annoted
* parameters, particularly String types. SPR-7974 revealed this
* was failing due to attempting to proxy objects of type String,
* which cannot be done.
*
* @author Chris Beams
*/
public class FeatureMethodValueInjectionTests {
@Test
public void control() {
System.setProperty("foo", "bar");
System.setProperty("num", "2");
Config config = new AnnotationConfigApplicationContext(Config.class).getBean(Config.class);
System.clearProperty("foo");
System.clearProperty("num");
assertThat(config.foo, is("bar"));
assertThat(config.num, is(2));
}
@Test
public void spelValueInjection() {
System.setProperty("foo", "bar");
new AnnotationConfigApplicationContext(SpelValueInjectionFeatureConfig.class);
System.clearProperty("foo");
}
@Test
public void spelIntValueInjection() {
System.setProperty("num", "5");
new AnnotationConfigApplicationContext(SpelIntValueInjectionFeatureConfig.class);
System.clearProperty("num");
}
@Test
public void stringBeanInjection() {
new AnnotationConfigApplicationContext(StringBeanConfig.class, StringBeanInjectionByTypeFeatureConfig.class);
}
@Test
public void qualifiedStringBeanInjection() {
new AnnotationConfigApplicationContext(StringBeanSubConfig.class, StringBeanInjectionByQualifierFeatureConfig.class);
}
@FeatureConfiguration
static class SpelValueInjectionFeatureConfig {
@Feature
public StubSpecification feature(@Value("#{environment['foo']}") String foo) {
return new StubSpecification();
}
}
@FeatureConfiguration
static class SpelIntValueInjectionFeatureConfig {
@Feature
public StubSpecification feature(@Value("#{environment['num']}") int num) {
assertThat(num, is(5));
return new StubSpecification();
}
}
@Configuration
static class StringBeanConfig {
@Bean
public String stringBean() {
return "sb";
}
}
@Configuration
static class StringBeanSubConfig extends StringBeanConfig {
@Bean
public String stringBean2() {
return "sb2";
}
}
@FeatureConfiguration
static class StringBeanInjectionByTypeFeatureConfig {
@Feature
public StubSpecification feature(String string) {
assertThat(string, is("sb"));
return new StubSpecification();
}
}
@FeatureConfiguration
static class StringBeanInjectionByQualifierFeatureConfig {
@Feature
public StubSpecification feature(@Qualifier("stringBean2") String string) {
assertThat(string, is("sb2"));
return new StubSpecification();
}
}
@Configuration
static class Config {
@Value("#{environment['foo']}") String foo;
@Value("#{environment['num']}") int num;
}
}
/*
* 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 static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertThat;
import org.junit.Test;
import org.springframework.context.annotation.configuration.StubSpecification;
import org.springframework.context.config.SpecificationContext;
import org.springframework.context.config.FeatureSpecification;
import org.springframework.context.config.FeatureSpecificationExecutor;
import org.springframework.util.Assert;
/**
* Simple tests to ensure that @Feature methods are invoked and that the
* resulting returned {@link FeatureSpecification} object is delegated to
* the correct {@link FeatureSpecificationExecutor}.
*
* @author Chris Beams
* @since 3.1
*/
public class SimpleFeatureMethodProcessingTests {
@Test
public void test() {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.register(FeatureConfig.class);
assertThat(MySpecificationExecutor.executeMethodWasCalled, is(false));
ctx.refresh();
assertThat(MySpecificationExecutor.executeMethodWasCalled, is(true));
}
@FeatureConfiguration
static class FeatureConfig {
@Feature
public FeatureSpecification f() {
return new StubSpecification(MySpecificationExecutor.class);
}
}
static class MySpecificationExecutor implements FeatureSpecificationExecutor {
static boolean executeMethodWasCalled = false;
public void execute(FeatureSpecification spec, SpecificationContext specificationContext) {
Assert.state(executeMethodWasCalled == false);
executeMethodWasCalled = true;
}
}
}
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean class="org.springframework.context.annotation.configuration.ColourHolder"/>
</beans>
/*
* 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.configuration;
import org.junit.runner.RunWith;
import org.junit.runners.Suite;
import org.junit.runners.Suite.SuiteClasses;
import org.springframework.context.annotation.FeatureMethodEarlyBeanProxyTests;
/**
* Test suite that groups all tests related to @Feature method lifecycle issues.
*
* @author Chris Beams
*/
@RunWith(Suite.class)
@SuiteClasses({
FeatureMethodEarlyBeanProxyTests.class,
ConfigurationClassWithPlaceholderConfigurerBeanTests.class
})
public class FeatureMethodLifecycleIssueTestSuite {
}
/*
* 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.configuration;
import org.springframework.beans.factory.parsing.ProblemCollector;
import org.springframework.context.config.AbstractFeatureSpecification;
import org.springframework.context.config.FeatureSpecification;
import org.springframework.context.config.FeatureSpecificationExecutor;
import org.springframework.context.config.SpecificationContext;
public class StubSpecification extends AbstractFeatureSpecification {
public StubSpecification() {
this(StubSpecificationExecutor.class);
}
public StubSpecification(Class<? extends FeatureSpecificationExecutor> excecutorType) {
super(excecutorType);
}
@Override
protected void doValidate(ProblemCollector problems) {
}
}
class StubSpecificationExecutor implements FeatureSpecificationExecutor {
public void execute(FeatureSpecification spec, SpecificationContext specificationContext) {
}
}
/*
* 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;
/**
* Tests directly or indirectly related to {@link FeatureConfiguration} class and
* {@link Feature} method processing.
*
* @author Chris Beams
* @since 3.1
*
* commented due to classpath visibility differences between Eclipse
* and Ant/Ivy at the command line. Eclipse can see classes across
* project test folders, Ant/Ivy are not configured to do so. Uncomment
* as necessary when doing @Feature-related work.
import org.junit.runner.RunWith;
import org.junit.runners.Suite;
import org.junit.runners.Suite.SuiteClasses;
import org.springframework.transaction.TxNamespaceHandlerTests;
import org.springframework.transaction.annotation.AnnotationTransactionNamespaceHandlerTests;
import org.springframework.transaction.annotation.TxAnnotationDrivenFeatureTests;
import org.springframework.transaction.config.AnnotationDrivenTests;
import org.springframework.web.servlet.config.AnnotationDrivenBeanDefinitionParserTests;
import org.springframework.web.servlet.config.MvcAnnotationDrivenFeatureTests;
import org.springframework.web.servlet.config.MvcDefaultServletHandlerTests;
import org.springframework.web.servlet.config.MvcNamespaceTests;
import org.springframework.web.servlet.config.MvcResourcesTests;
import org.springframework.web.servlet.config.MvcViewControllersTests;
@RunWith(Suite.class)
@SuiteClasses({
EarlyBeanReferenceProxyCreatorTests.class,
SimpleFeatureMethodProcessingTests.class,
BeanFactoryAwareFeatureConfigurationTests.class,
FeatureMethodBeanReferenceTests.class,
FeatureMethodQualifiedBeanReferenceTests.class,
FeatureMethodErrorTests.class,
FeatureConfigurationClassTests.class,
FeatureMethodEarlyBeanProxyTests.class,
FeatureConfigurationImportTests.class,
FeatureConfigurationImportResourceTests.class,
// context:component-scan related
ComponentScanFeatureTests.class,
ComponentScanSpecTests.class,
ComponentScanAnnotationTests.class,
ComponentScanAnnotationIntegrationTests.class,
// tx-related
TxAnnotationDrivenFeatureTests.class,
TxNamespaceHandlerTests.class,
AnnotationTransactionNamespaceHandlerTests.class,
AnnotationDrivenTests.class,
// mvc-related
AnnotationDrivenBeanDefinitionParserTests.class,
MvcAnnotationDrivenFeatureTests.class,
MvcViewControllersTests.class,
MvcResourcesTests.class,
MvcDefaultServletHandlerTests.class,
MvcNamespaceTests.class
})
*/
public class FeatureTestSuite {
}
\ No newline at end of file
...@@ -16,7 +16,15 @@ ...@@ -16,7 +16,15 @@
package org.springframework.transaction.config; package org.springframework.transaction.config;
import org.w3c.dom.Element;
import org.springframework.aop.config.AopNamespaceUtils; import org.springframework.aop.config.AopNamespaceUtils;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.RuntimeBeanReference;
import org.springframework.beans.factory.parsing.BeanComponentDefinition;
import org.springframework.beans.factory.parsing.CompositeComponentDefinition;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.beans.factory.xml.BeanDefinitionParser;
import org.springframework.beans.factory.xml.ParserContext; import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.context.config.AbstractSpecificationBeanDefinitionParser; import org.springframework.context.config.AbstractSpecificationBeanDefinitionParser;
import org.springframework.context.config.FeatureSpecification; import org.springframework.context.config.FeatureSpecification;
...@@ -40,7 +48,7 @@ import org.w3c.dom.Element; ...@@ -40,7 +48,7 @@ import org.w3c.dom.Element;
* @since 2.0 * @since 2.0
* @see TxAnnotationDriven * @see TxAnnotationDriven
*/ */
class AnnotationDrivenBeanDefinitionParser extends AbstractSpecificationBeanDefinitionParser { class AnnotationDrivenBeanDefinitionParser implements BeanDefinitionParser {
/** /**
* The bean name of the internally managed transaction advisor (mode="proxy"). * The bean name of the internally managed transaction advisor (mode="proxy").
......
/* /*
* 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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
...@@ -31,7 +31,7 @@ import org.springframework.util.ClassUtils; ...@@ -31,7 +31,7 @@ import org.springframework.util.ClassUtils;
* @author Christian Dupuis * @author Christian Dupuis
* @since 2.5 * @since 2.5
*/ */
class JtaTransactionManagerBeanDefinitionParser extends AbstractSingleBeanDefinitionParser { public class JtaTransactionManagerBeanDefinitionParser extends AbstractSingleBeanDefinitionParser {
private static final String WEBLOGIC_JTA_TRANSACTION_MANAGER_CLASS_NAME = private static final String WEBLOGIC_JTA_TRANSACTION_MANAGER_CLASS_NAME =
"org.springframework.transaction.jta.WebLogicJtaTransactionManager"; "org.springframework.transaction.jta.WebLogicJtaTransactionManager";
...@@ -74,7 +74,7 @@ class JtaTransactionManagerBeanDefinitionParser extends AbstractSingleBeanDefini ...@@ -74,7 +74,7 @@ class JtaTransactionManagerBeanDefinitionParser extends AbstractSingleBeanDefini
@Override @Override
protected String resolveId(Element element, AbstractBeanDefinition definition, ParserContext parserContext) { protected String resolveId(Element element, AbstractBeanDefinition definition, ParserContext parserContext) {
return TxAnnotationDriven.DEFAULT_TRANSACTION_MANAGER_BEAN_NAME; return TxNamespaceHandler.DEFAULT_TRANSACTION_MANAGER_BEAN_NAME;
} }
} }
...@@ -48,8 +48,6 @@ import org.springframework.util.xml.DomUtils; ...@@ -48,8 +48,6 @@ import org.springframework.util.xml.DomUtils;
*/ */
class TxAdviceBeanDefinitionParser extends AbstractSingleBeanDefinitionParser { class TxAdviceBeanDefinitionParser extends AbstractSingleBeanDefinitionParser {
private static final String TRANSACTION_MANAGER_ATTRIBUTE = "transaction-manager";
private static final String METHOD_ELEMENT = "method"; private static final String METHOD_ELEMENT = "method";
private static final String METHOD_NAME_ATTRIBUTE = "name"; private static final String METHOD_NAME_ATTRIBUTE = "name";
...@@ -76,7 +74,7 @@ class TxAdviceBeanDefinitionParser extends AbstractSingleBeanDefinitionParser { ...@@ -76,7 +74,7 @@ class TxAdviceBeanDefinitionParser extends AbstractSingleBeanDefinitionParser {
@Override @Override
protected void doParse(Element element, ParserContext parserContext, BeanDefinitionBuilder builder) { protected void doParse(Element element, ParserContext parserContext, BeanDefinitionBuilder builder) {
builder.addPropertyReference("transactionManager", element.getAttribute(TRANSACTION_MANAGER_ATTRIBUTE)); builder.addPropertyReference("transactionManager", TxNamespaceHandler.getTransactionManagerName(element));
List<Element> txAttributes = DomUtils.getChildElementsByTagName(element, ATTRIBUTES_ELEMENT); List<Element> txAttributes = DomUtils.getChildElementsByTagName(element, ATTRIBUTES_ELEMENT);
if (txAttributes.size() > 1) { if (txAttributes.size() > 1) {
......
...@@ -39,6 +39,17 @@ import org.springframework.beans.factory.xml.NamespaceHandlerSupport; ...@@ -39,6 +39,17 @@ import org.springframework.beans.factory.xml.NamespaceHandlerSupport;
*/ */
public class TxNamespaceHandler extends NamespaceHandlerSupport { public class TxNamespaceHandler extends NamespaceHandlerSupport {
static final String TRANSACTION_MANAGER_ATTRIBUTE = "transaction-manager";
static final String DEFAULT_TRANSACTION_MANAGER_BEAN_NAME = "transactionManager";
static String getTransactionManagerName(Element element) {
return (element.hasAttribute(TRANSACTION_MANAGER_ATTRIBUTE) ?
element.getAttribute(TRANSACTION_MANAGER_ATTRIBUTE) : DEFAULT_TRANSACTION_MANAGER_BEAN_NAME);
}
public void init() { public void init() {
registerBeanDefinitionParser("advice", new TxAdviceBeanDefinitionParser()); registerBeanDefinitionParser("advice", new TxAdviceBeanDefinitionParser());
registerBeanDefinitionParser("annotation-driven", new AnnotationDrivenBeanDefinitionParser()); registerBeanDefinitionParser("annotation-driven", new AnnotationDrivenBeanDefinitionParser());
......
...@@ -16,36 +16,58 @@ ...@@ -16,36 +16,58 @@
package org.springframework.web.servlet.config; package org.springframework.web.servlet.config;
import java.util.Map;
import org.w3c.dom.Element;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.parsing.BeanComponentDefinition;
import org.springframework.beans.factory.support.ManagedMap;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.beans.factory.xml.BeanDefinitionParser; import org.springframework.beans.factory.xml.BeanDefinitionParser;
import org.springframework.beans.factory.xml.ParserContext; import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.context.config.AbstractSpecificationBeanDefinitionParser;
import org.springframework.context.config.FeatureSpecification;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
import org.springframework.web.servlet.handler.SimpleUrlHandlerMapping; import org.springframework.web.servlet.handler.SimpleUrlHandlerMapping;
import org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter; import org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter;
import org.springframework.web.servlet.resource.DefaultServletHttpRequestHandler; import org.springframework.web.servlet.resource.DefaultServletHttpRequestHandler;
import org.w3c.dom.Element;
/** /**
* {@link BeanDefinitionParser} that parses a {@code default-servlet-handler} element to * {@link BeanDefinitionParser} that parses a {@code default-servlet-handler} element to
* register a {@link DefaultServletHttpRequestHandler}. Will also register a * register a {@link DefaultServletHttpRequestHandler}. Will also register a
* {@link SimpleUrlHandlerMapping} for mapping resource requests, and a * {@link SimpleUrlHandlerMapping} for mapping resource requests, and a
* {@link HttpRequestHandlerAdapter} if necessary. * {@link HttpRequestHandlerAdapter} if necessary.
* *
* @author Rossen Stoyanchev * @author Jeremy Grelle
* @author Chris Beams
* @since 3.0.4 * @since 3.0.4
*/ */
class DefaultServletHandlerBeanDefinitionParser extends AbstractSpecificationBeanDefinitionParser { class DefaultServletHandlerBeanDefinitionParser extends AbstractHttpRequestHandlerBeanDefinitionParser {
/** @Override
* Parses the {@code <mvc:default-servlet-handler/>} tag. public void doParse(Element element, ParserContext parserContext) {
*/ Object source = parserContext.extractSource(element);
public FeatureSpecification doParse(Element element, ParserContext parserContext) {
String defaultServletHandler = element.getAttribute("default-servlet-handler"); String defaultServletName = element.getAttribute("default-servlet-name");
return StringUtils.hasText(defaultServletHandler) ? RootBeanDefinition defaultServletHandlerDef = new RootBeanDefinition(DefaultServletHttpRequestHandler.class);
new MvcDefaultServletHandler(defaultServletHandler) : defaultServletHandlerDef.setSource(source);
new MvcDefaultServletHandler(); defaultServletHandlerDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
if (StringUtils.hasText(defaultServletName)) {
defaultServletHandlerDef.getPropertyValues().add("defaultServletName", defaultServletName);
}
String defaultServletHandlerName = parserContext.getReaderContext().generateBeanName(defaultServletHandlerDef);
parserContext.getRegistry().registerBeanDefinition(defaultServletHandlerName, defaultServletHandlerDef);
parserContext.registerComponent(new BeanComponentDefinition(defaultServletHandlerDef, defaultServletHandlerName));
Map<String, String> urlMap = new ManagedMap<String, String>();
urlMap.put("/**", defaultServletHandlerName);
RootBeanDefinition handlerMappingDef = new RootBeanDefinition(SimpleUrlHandlerMapping.class);
handlerMappingDef.setSource(source);
handlerMappingDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
handlerMappingDef.getPropertyValues().add("urlMap", urlMap);
String handlerMappingBeanName = parserContext.getReaderContext().generateBeanName(handlerMappingDef);
parserContext.getRegistry().registerBeanDefinition(handlerMappingBeanName, handlerMappingDef);
parserContext.registerComponent(new BeanComponentDefinition(handlerMappingDef, handlerMappingBeanName));
} }
} }
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册