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

git-svn-id: https://src.springframework.org/svn/spring-framework/trunk@4255 50f2f4bb-b051-0410-bef5-90022cba6387
This commit is contained in:
Chris Beams 2011-05-06 19:03:52 +00:00
parent e6f3fd2de0
commit c892028705
80 changed files with 857 additions and 6737 deletions

View File

@ -7,6 +7,8 @@ Changes in version 3.1 M2 (2011-??-??)
--------------------------------------
* 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)

View File

@ -16,13 +16,12 @@
package org.springframework.aop.config;
import org.w3c.dom.Element;
import org.springframework.beans.factory.config.BeanDefinition;
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.xml.ParserContext;
import org.w3c.dom.Element;
/**
* Utility class for handling registration of auto-proxy creators used internally
@ -53,41 +52,22 @@ public abstract class AopNamespaceUtils {
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(
ParserContext parserContext, Element sourceElement) {
BeanDefinition beanDefinition = AopConfigUtils.registerAutoProxyCreatorIfNecessary(
parserContext.getRegistry(), parserContext.extractSource(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);
}
public static void registerAutoProxyCreatorIfNecessary(
BeanDefinitionRegistry registry, ComponentRegistrar parserContext, Object source, Boolean proxyTargetClass) {
registerAutoProxyCreatorIfNecessary(registry, parserContext, source, proxyTargetClass, false);
}
public static void registerAspectJAutoProxyCreatorIfNecessary(
ParserContext parserContext, Element sourceElement) {
BeanDefinition beanDefinition = AopConfigUtils.registerAspectJAutoProxyCreatorIfNecessary(
parserContext.getRegistry(), parserContext.extractSource(sourceElement));
useClassProxyingIfNecessary(parserContext.getRegistry(), sourceElement);
registerComponentIfNecessary(beanDefinition, new ComponentRegistrarAdapter(parserContext));
registerComponentIfNecessary(beanDefinition, parserContext);
}
public static void registerAspectJAnnotationAutoProxyCreatorIfNecessary(
@ -96,7 +76,7 @@ public abstract class AopNamespaceUtils {
BeanDefinition beanDefinition = AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(
parserContext.getRegistry(), parserContext.extractSource(sourceElement));
useClassProxyingIfNecessary(parserContext.getRegistry(), sourceElement);
registerComponentIfNecessary(beanDefinition, new ComponentRegistrarAdapter(parserContext));
registerComponentIfNecessary(beanDefinition, parserContext);
}
/**
@ -108,7 +88,7 @@ public abstract class AopNamespaceUtils {
public static void registerAutoProxyCreatorIfNecessary(ParserContext parserContext, Object source) {
BeanDefinition beanDefinition = AopConfigUtils.registerAutoProxyCreatorIfNecessary(
parserContext.getRegistry(), source);
registerComponentIfNecessary(beanDefinition, new ComponentRegistrarAdapter(parserContext));
registerComponentIfNecessary(beanDefinition, parserContext);
}
/**
@ -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) {
if (sourceElement != null) {
boolean proxyTargetClass = Boolean.valueOf(sourceElement.getAttribute(PROXY_TARGET_CLASS_ATTRIBUTE));
@ -140,20 +114,11 @@ public abstract class AopNamespaceUtils {
}
}
private static void useClassProxyingIfNecessary(BeanDefinitionRegistry registry, Boolean proxyTargetClass, Boolean exposeProxy) {
if (proxyTargetClass) {
AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry);
}
if (exposeProxy) {
AopConfigUtils.forceAutoProxyCreatorToExposeProxy(registry);
}
}
private static void registerComponentIfNecessary(BeanDefinition beanDefinition, ComponentRegistrar componentRegistrar) {
private static void registerComponentIfNecessary(BeanDefinition beanDefinition, ParserContext parserContext) {
if (beanDefinition != null) {
BeanComponentDefinition componentDefinition =
new BeanComponentDefinition(beanDefinition, AopConfigUtils.AUTO_PROXY_CREATOR_BEAN_NAME);
componentRegistrar.registerComponent(componentDefinition);
parserContext.registerComponent(componentDefinition);
}
}

View File

@ -1,35 +0,0 @@
/*
* Copyright 2002-2011 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.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);
}

View File

@ -1,55 +0,0 @@
/*
* Copyright 2002-2011 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.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);
}
}

View File

@ -1,35 +0,0 @@
/*
* Copyright 2002-2011 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.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();
}

View File

@ -1,59 +0,0 @@
/*
* Copyright 2002-2011 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.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;
}
}

View File

@ -22,7 +22,7 @@ import org.springframework.beans.factory.config.BeanDefinition;
/**
* 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
* {@link BeanDefinition BeanDefinitions} as required.
@ -30,19 +30,10 @@ import org.springframework.beans.factory.config.BeanDefinition;
* <p>The parser locates a {@link BeanDefinitionParser} from the associated
* {@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
* @since 2.0
* @see NamespaceHandler
* @see AbstractBeanDefinitionParser
* @see org.springframework.beans.factory.xml.BeanDefinitionDecorator
* @see org.springframework.context.FeatureSpecification
* @see org.springframework.context.AbstractSpecificationExecutor
*/
public interface BeanDefinitionParser {
@ -53,12 +44,11 @@ public interface BeanDefinitionParser {
* embedded in the supplied {@link ParserContext}.
* <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
* an inner tag in a <code>&lt;property/&gt;</code> tag). Implementations may return
* <code>null</code> if they will <strong>not</strong> be used in a nested fashion.
* an inner tag in a {@code <property/>} tag). Implementations may return
* {@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 parserContext the object encapsulating the current state of the parsing process;
* provides access to a {@link org.springframework.beans.factory.support.BeanDefinitionRegistry
* BeanDefinitionRegistry}.
* provides access to a {@link org.springframework.beans.factory.support.BeanDefinitionRegistry}
* @return the primary {@link BeanDefinition}
*/
BeanDefinition parse(Element element, ParserContext parserContext);

View File

@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<beansProjectDescription>
<version>1</version>
<pluginVersion><![CDATA[2.5.0.201008272200-CI-R3822-B852]]></pluginVersion>
<pluginVersion><![CDATA[2.6.0.201103160035-RELEASE]]></pluginVersion>
<configSuffixes>
<configSuffix><![CDATA[xml]]></configSuffix>
</configSuffixes>
@ -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/ImportXmlWithAopNamespace-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>
<configSets>
</configSets>

View File

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

View File

@ -1,97 +0,0 @@
/*
* Copyright 2002-2011 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.context.annotation;
import java.util.Map;
import org.springframework.context.annotation.ComponentScan.Filter;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
/**
* {@link FeatureAnnotationParser} implementation that reads attributes from a
* {@link ComponentScan @ComponentScan} annotation into a {@link ComponentScanSpec}
* which can in turn be executed by {@link ComponentScanExecutor}.
* {@link ComponentScanBeanDefinitionParser} serves the same role for
* the {@code <context:component-scan>} XML element.
*
* <p>Note that {@link ComponentScanSpec} objects may be directly
* instantiated and returned from {@link Feature @Feature} methods as an
* alternative to using the {@link ComponentScan @ComponentScan} annotation.
*
* @author Chris Beams
* @since 3.1
* @see ComponentScan
* @see ComponentScanSpec
* @see ComponentScanExecutor
* @see ComponentScanBeanDefinitionParser
* @see ConfigurationClassBeanDefinitionReader
*/
final class ComponentScanAnnotationParser implements FeatureAnnotationParser {
/**
* Create and return a new {@link ComponentScanSpec} from the given
* {@link ComponentScan} annotation metadata.
* @throws IllegalArgumentException if ComponentScan attributes are not present in metadata
*/
public ComponentScanSpec process(AnnotationMetadata metadata) {
Map<String, Object> attribs = metadata.getAnnotationAttributes(ComponentScan.class.getName(), true);
Assert.notNull(attribs, String.format("@ComponentScan annotation not found " +
"while parsing metadata for class [%s].", metadata.getClassName()));
ClassLoader classLoader = ClassUtils.getDefaultClassLoader();
ComponentScanSpec spec = new ComponentScanSpec();
for (String pkg : (String[])attribs.get("value")) {
spec.addBasePackage(pkg);
}
for (String pkg : (String[])attribs.get("basePackages")) {
spec.addBasePackage(pkg);
}
for (String className : (String[])attribs.get("basePackageClasses")) {
spec.addBasePackage(className.substring(0, className.lastIndexOf('.')));
}
String resolverAttribute = "scopeResolver";
if (!((String)attribs.get(resolverAttribute)).equals(((Class<?>)AnnotationUtils.getDefaultValue(ComponentScan.class, resolverAttribute)).getName())) {
spec.scopeMetadataResolver((String)attribs.get(resolverAttribute), classLoader);
}
String scopedProxyAttribute = "scopedProxy";
ScopedProxyMode scopedProxyMode = (ScopedProxyMode) attribs.get(scopedProxyAttribute);
if (scopedProxyMode != ((ScopedProxyMode)AnnotationUtils.getDefaultValue(ComponentScan.class, scopedProxyAttribute))) {
spec.scopedProxyMode(scopedProxyMode);
}
for (Filter filter : (Filter[]) attribs.get("includeFilters")) {
spec.addIncludeFilter(filter.type().toString(), filter.value().getName(), classLoader);
}
for (Filter filter : (Filter[]) attribs.get("excludeFilters")) {
spec.addExcludeFilter(filter.type().toString(), filter.value().getName(), classLoader);
}
spec.resourcePattern((String)attribs.get("resourcePattern"))
.useDefaultFilters((Boolean)attribs.get("useDefaultFilters"))
.beanNameGenerator((String)attribs.get("nameGenerator"), classLoader)
.source(metadata.getClassName())
.sourceName(metadata.getClassName());
return spec;
}
}

View File

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

View File

@ -1,110 +0,0 @@
/*
* Copyright 2002-2011 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.context.annotation;
import java.util.Set;
import org.springframework.beans.factory.config.BeanDefinitionHolder;
import org.springframework.beans.factory.parsing.BeanComponentDefinition;
import org.springframework.beans.factory.parsing.CompositeComponentDefinition;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.context.config.AbstractSpecificationExecutor;
import org.springframework.context.config.SpecificationContext;
import org.springframework.core.env.Environment;
import org.springframework.core.io.ResourceLoader;
import org.springframework.core.type.filter.TypeFilter;
/**
* Executes the {@link ComponentScanSpec} feature specification.
*
* @author Chris Beams
* @since 3.1
* @see ComponentScanSpec
* @see ComponentScanBeanDefinitionParser
* @see ComponentScan
*/
final class ComponentScanExecutor extends AbstractSpecificationExecutor<ComponentScanSpec> {
/**
* Configure a {@link ClassPathBeanDefinitionScanner} based on the content of
* the given specification and perform actual scanning and bean definition
* registration.
*/
protected void doExecute(ComponentScanSpec spec, SpecificationContext specificationContext) {
BeanDefinitionRegistry registry = specificationContext.getRegistry();
ResourceLoader resourceLoader = specificationContext.getResourceLoader();
Environment environment = specificationContext.getEnvironment();
ClassPathBeanDefinitionScanner scanner = spec.useDefaultFilters() == null ?
new ClassPathBeanDefinitionScanner(registry) :
new ClassPathBeanDefinitionScanner(registry, spec.useDefaultFilters());
scanner.setResourceLoader(resourceLoader);
scanner.setEnvironment(environment);
if (spec.beanDefinitionDefaults() != null) {
scanner.setBeanDefinitionDefaults(spec.beanDefinitionDefaults());
}
if (spec.autowireCandidatePatterns() != null) {
scanner.setAutowireCandidatePatterns(spec.autowireCandidatePatterns());
}
if (spec.resourcePattern() != null) {
scanner.setResourcePattern(spec.resourcePattern());
}
if (spec.beanNameGenerator() != null) {
scanner.setBeanNameGenerator(spec.beanNameGenerator());
}
if (spec.includeAnnotationConfig() != null) {
scanner.setIncludeAnnotationConfig(spec.includeAnnotationConfig());
}
if (spec.scopeMetadataResolver() != null) {
scanner.setScopeMetadataResolver(spec.scopeMetadataResolver());
}
if (spec.scopedProxyMode() != null) {
scanner.setScopedProxyMode(spec.scopedProxyMode());
}
for (TypeFilter filter : spec.includeFilters()) {
scanner.addIncludeFilter(filter);
}
for (TypeFilter filter : spec.excludeFilters()) {
scanner.addExcludeFilter(filter);
}
Set<BeanDefinitionHolder> scannedBeans = scanner.doScan(spec.basePackages());
Object source = spec.source();
String sourceName = spec.sourceName();
CompositeComponentDefinition compositeDef = new CompositeComponentDefinition(sourceName, source);
for (BeanDefinitionHolder beanDefHolder : scannedBeans) {
compositeDef.addNestedComponent(new BeanComponentDefinition(beanDefHolder));
}
// Register annotation config processors, if necessary.
if ((spec.includeAnnotationConfig() != null) && spec.includeAnnotationConfig()) {
Set<BeanDefinitionHolder> processorDefinitions =
AnnotationConfigUtils.registerAnnotationConfigProcessors(registry, source);
for (BeanDefinitionHolder processorDefinition : processorDefinitions) {
compositeDef.addNestedComponent(new BeanComponentDefinition(processorDefinition));
}
}
specificationContext.getRegistrar().registerComponent(compositeDef);
}
}

View File

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

View File

@ -33,7 +33,6 @@ import org.springframework.beans.factory.annotation.Autowire;
import org.springframework.beans.factory.annotation.RequiredAnnotationBeanPostProcessor;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanDefinitionHolder;
import org.springframework.beans.factory.parsing.BeanDefinitionParsingException;
import org.springframework.beans.factory.parsing.Location;
import org.springframework.beans.factory.parsing.Problem;
import org.springframework.beans.factory.parsing.ProblemReporter;
@ -45,8 +44,8 @@ import org.springframework.beans.factory.support.BeanDefinitionReaderUtils;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.GenericBeanDefinition;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.context.config.FeatureSpecification;
import org.springframework.context.config.SpecificationContext;
import org.springframework.context.EnvironmentAware;
import org.springframework.context.ResourceLoaderAware;
import org.springframework.core.Conventions;
import org.springframework.core.env.Environment;
import org.springframework.core.io.Resource;
@ -94,7 +93,7 @@ public class ConfigurationClassBeanDefinitionReader {
private ResourceLoader resourceLoader;
private SpecificationContext specificationContext;
private Environment environment;
/**
* Create a new {@link ConfigurationClassBeanDefinitionReader} instance that will be used
@ -111,13 +110,7 @@ public class ConfigurationClassBeanDefinitionReader {
this.problemReporter = problemReporter;
this.metadataReaderFactory = metadataReaderFactory;
this.resourceLoader = resourceLoader;
// TODO SPR-7420: see about passing in the SpecificationContext created in ConfigurationClassPostProcessor
this.specificationContext = new SpecificationContext();
this.specificationContext.setRegistry(this.registry);
this.specificationContext.setRegistrar(new SimpleComponentRegistrar(this.registry));
this.specificationContext.setResourceLoader(this.resourceLoader);
this.specificationContext.setEnvironment(environment);
this.specificationContext.setProblemReporter(problemReporter);
this.environment = environment;
}
@ -137,7 +130,6 @@ public class ConfigurationClassBeanDefinitionReader {
*/
private void loadBeanDefinitionsForConfigurationClass(ConfigurationClass configClass) {
AnnotationMetadata metadata = configClass.getMetadata();
processFeatureAnnotations(metadata);
doLoadBeanDefinitionForConfigurationClassIfNecessary(configClass);
for (BeanMethod beanMethod : configClass.getBeanMethods()) {
loadBeanDefinitionsForBeanMethod(beanMethod);
@ -145,27 +137,6 @@ public class ConfigurationClassBeanDefinitionReader {
loadBeanDefinitionsFromImportedResources(configClass.getImportedResources());
}
private void processFeatureAnnotations(AnnotationMetadata metadata) {
try {
for (String annotationType : metadata.getAnnotationTypes()) {
MetadataReader metadataReader = new SimpleMetadataReaderFactory().getMetadataReader(annotationType);
if (metadataReader.getAnnotationMetadata().isAnnotated(FeatureAnnotation.class.getName())) {
Map<String, Object> annotationAttributes = metadataReader.getAnnotationMetadata().getAnnotationAttributes(FeatureAnnotation.class.getName(), true);
// TODO SPR-7420: this is where we can catch user-defined types and avoid instantiating them for STS purposes
FeatureAnnotationParser processor = (FeatureAnnotationParser) BeanUtils.instantiateClass(Class.forName((String)annotationAttributes.get("parser")));
FeatureSpecification spec = processor.process(metadata);
spec.execute(this.specificationContext);
}
}
} catch (BeanDefinitionParsingException ex) {
throw ex;
}
catch (Exception ex) {
// TODO SPR-7420: what exception to throw?
throw new RuntimeException(ex);
}
}
/**
* Register the {@link Configuration} class itself as a bean definition.
*/
@ -200,7 +171,7 @@ public class ConfigurationClassBeanDefinitionReader {
}
/**
* Read a particular {@link BeanMethod}, registering bean definitions
* Read the given {@link BeanMethod}, registering bean definitions
* with the BeanDefinitionRegistry based on its contents.
*/
private void loadBeanDefinitionsForBeanMethod(BeanMethod beanMethod) {
@ -423,8 +394,8 @@ public class ConfigurationClassBeanDefinitionReader {
*/
private static class InvalidConfigurationImportProblem extends Problem {
public InvalidConfigurationImportProblem(String className, Resource resource, AnnotationMetadata metadata) {
super(String.format("%s was @Import'ed but is not annotated with @FeatureConfiguration or " +
"@Configuration nor does it declare any @Bean methods. Update the class to " +
super(String.format("%s was @Import'ed but is not annotated with @Configuration " +
"nor does it declare any @Bean methods. Update the class to " +
"meet one of these requirements or do not attempt to @Import it.", className),
new Location(resource, metadata));
}

View File

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

View File

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

View File

@ -0,0 +1,60 @@
/*
* Copyright 2002-2011 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.context.annotation;
import org.springframework.beans.factory.parsing.Location;
import org.springframework.beans.factory.parsing.ProblemReporter;
import org.springframework.core.type.MethodMetadata;
/**
* @author Chris Beams
* @since 3.1
*/
abstract class ConfigurationMethod {
protected final MethodMetadata metadata;
protected final ConfigurationClass configurationClass;
public ConfigurationMethod(MethodMetadata metadata, ConfigurationClass configurationClass) {
this.metadata = metadata;
this.configurationClass = configurationClass;
}
public MethodMetadata getMetadata() {
return this.metadata;
}
public ConfigurationClass getConfigurationClass() {
return this.configurationClass;
}
public Location getResourceLocation() {
return new Location(this.configurationClass.getResource(), this.metadata);
}
public void validate(ProblemReporter problemReporter) {
}
@Override
public String toString() {
return String.format("[%s:name=%s,declaringClass=%s]",
this.getClass().getSimpleName(), this.getMetadata().getMethodName(), this.getMetadata().getDeclaringClassName());
}
}

View File

@ -1,30 +0,0 @@
/*
* Copyright 2002-2011 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.context.annotation;
/**
* Marker interface indicating that an object is a proxy for a bean referenced
* from within a {@link Feature @Feature} method.
*
* @author Chris Beams
* @since 3.1
*/
public interface EarlyBeanReferenceProxy {
Object dereferenceTargetBean();
}

View File

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

View File

@ -1,34 +0,0 @@
/*
* Copyright 2002-2011 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.context.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* TODO SPR-7420: document
*
* @author Chris Beams
* @since 3.1
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Feature {
}

View File

@ -1,45 +0,0 @@
/*
* Copyright 2002-2011 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.context.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Meta-annotation indicating that an annotation should be processed
* to produce a {@code FeatureSpecification}.
*
* <p>See {@link ComponentScan @ComponentScan} for an implementation example.
*
* @author Chris Beams
* @since 3.1
* @see ComponentScan
* @see org.springframework.context.config.FeatureSpecification
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface FeatureAnnotation {
/**
* Indicate the class that should be used to parse this annotation
* into a {@code FeatureSpecification}.
*/
Class<? extends FeatureAnnotationParser> parser();
}

View File

@ -1,61 +0,0 @@
/*
* Copyright 2002-2011 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.context.annotation;
import org.springframework.context.config.FeatureSpecification;
import org.springframework.context.config.FeatureSpecificationExecutor;
import org.springframework.core.type.AnnotationMetadata;
/**
* Interface for parsing {@link AnnotationMetadata} from a {@link FeatureAnnotation}
* into a {@link FeatureSpecification} object. Used in conjunction with a
* {@link FeatureSpecificationExecutor} to provide a source-agnostic approach to
* handling configuration metadata.
*
* <p>For example, Spring's component-scanning can be configured via XML using
* the {@code context:component-scan} element or via the {@link ComponentScan}
* annotation. In either case, the metadata is the same -- only the source
* format differs. {@link ComponentScanBeanDefinitionParser} is used to create
* a specification from the {@code <context:component-scan>} XML element, while
* {@link ComponentScanAnnotationParser} creates a specification from the
* the annotation style. They both produce a {@link ComponentScanSpec}
* object that is ultimately delegated to a {@link ComponentScanExecutor}
* which understands how to configure a {@link ClassPathBeanDefinitionScanner},
* perform actual scanning, and register actual bean definitions against the
* container.
*
* <p>Implementations must be instantiable via a no-arg constructor.
*
* TODO SPR-7420: documentation (clean up)
* TODO SPR-7420: rework so annotations declare their creator.
*
*
* @author Chris Beams
* @since 3.1
* @see FeatureAnnotation#parser()
* @see FeatureSpecification
* @see FeatureSpecificationExecutor
*/
public interface FeatureAnnotationParser {
/**
* Parse the given annotation metadata and populate a {@link FeatureSpecification}
* object suitable for execution by a {@link FeatureSpecificationExecutor}.
*/
FeatureSpecification process(AnnotationMetadata metadata);
}

View File

@ -1,54 +0,0 @@
/*
* Copyright 2002-2011 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.context.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.stereotype.Component;
/**
* TODO SPR-7420: document
*
* @author Chris Beams
* @since 3.1
* @see Configuration
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface FeatureConfiguration {
/**
* Explicitly specify the name of the Spring bean definition associated
* with this FeatureConfiguration class. If left unspecified (the common case),
* a bean name will be automatically generated.
*
* <p>The custom name applies only if the FeatureConfiguration class is picked up via
* component scanning or supplied directly to a {@link AnnotationConfigApplicationContext}.
* If the FeatureConfiguration class is registered as a traditional XML bean definition,
* the name/id of the bean element will take precedence.
*
* @return the specified bean name, if any
* @see org.springframework.beans.factory.support.DefaultBeanNameGenerator
*/
String value() default "";
}

View File

@ -1,28 +0,0 @@
/*
* Copyright 2002-2011 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.context.annotation;
@SuppressWarnings("serial")
class FeatureMethodExecutionException extends RuntimeException {
public FeatureMethodExecutionException(Throwable cause) {
super(cause);
}
public FeatureMethodExecutionException(String message) {
super(message);
}
}

View File

@ -1,26 +0,0 @@
/*
* Copyright 2002-2011 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.context.annotation;
@SuppressWarnings("serial")
class ProxyCreationException extends RuntimeException {
public ProxyCreationException(String message) {
super(message);
}
}

View File

@ -1,52 +0,0 @@
/*
* Copyright 2002-2011 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.context.annotation;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.parsing.BeanComponentDefinition;
import org.springframework.beans.factory.parsing.ComponentDefinition;
import org.springframework.beans.factory.parsing.ComponentRegistrar;
import org.springframework.beans.factory.support.BeanDefinitionReaderUtils;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
/**
* TODO SPR-7420: document
*
* @author Chris Beams
* @since 3.1
*/
class SimpleComponentRegistrar implements ComponentRegistrar {
private final BeanDefinitionRegistry registry;
public SimpleComponentRegistrar(BeanDefinitionRegistry registry) {
this.registry = registry;
}
public String registerWithGeneratedName(BeanDefinition beanDefinition) {
return BeanDefinitionReaderUtils.registerWithGeneratedName(beanDefinition, this.registry);
}
public void registerBeanComponent(BeanComponentDefinition component) {
BeanDefinitionReaderUtils.registerBeanDefinition(component, this.registry);
registerComponent(component);
}
public void registerComponent(ComponentDefinition component) {
// no-op
}
}

View File

@ -1,78 +0,0 @@
/*
* Copyright 2002-2011 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.context.config;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.parsing.ProblemCollector;
import org.springframework.beans.factory.parsing.ProblemReporter;
import org.springframework.beans.factory.parsing.SimpleProblemCollector;
/**
* TODO SPR-7420: document
*
* @author Chris Beams
* @since 3.1
*/
public abstract class AbstractFeatureSpecification implements SourceAwareSpecification {
private static final Object DUMMY_SOURCE = new Object();
private static final String DUMMY_SOURCE_NAME = "dummySource";
protected Class<? extends FeatureSpecificationExecutor> executorType;
private Object source = DUMMY_SOURCE;
private String sourceName = DUMMY_SOURCE_NAME;
protected AbstractFeatureSpecification(Class<? extends FeatureSpecificationExecutor> executorType) {
this.executorType = executorType;
}
public final boolean validate(ProblemReporter problemReporter) {
ProblemCollector collector = new SimpleProblemCollector(this.source());
this.doValidate(collector);
collector.reportProblems(problemReporter);
return collector.hasErrors() ? false : true;
}
protected abstract void doValidate(ProblemCollector problems);
public AbstractFeatureSpecification source(Object source) {
this.source = source;
return this;
}
public Object source() {
return this.source;
}
public AbstractFeatureSpecification sourceName(String sourceName) {
this.sourceName = sourceName;
return this;
}
public String sourceName() {
return this.sourceName;
}
public void execute(SpecificationContext specificationContext) {
FeatureSpecificationExecutor executor =
BeanUtils.instantiateClass(this.executorType);
executor.execute(this, specificationContext);
}
}

View File

@ -1,83 +0,0 @@
/*
* Copyright 2002-2011 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.context.config;
import java.lang.reflect.Field;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.parsing.ComponentRegistrarAdapter;
import org.springframework.beans.factory.parsing.ProblemReporter;
import org.springframework.beans.factory.parsing.ReaderContext;
import org.springframework.beans.factory.xml.BeanDefinitionParser;
import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.core.env.DefaultEnvironment;
import org.w3c.dom.Element;
/**
* TODO SPR-7420: document
*
* @author Chris Beams
* @since 3.1
*/
public abstract class AbstractSpecificationBeanDefinitionParser implements BeanDefinitionParser {
public final BeanDefinition parse(Element element, ParserContext parserContext) {
FeatureSpecification spec = doParse(element, parserContext);
if (spec instanceof SourceAwareSpecification) {
((SourceAwareSpecification)spec).source(parserContext.getReaderContext().extractSource(element));
((SourceAwareSpecification)spec).sourceName(element.getTagName());
}
spec.execute(specificationContextFrom(parserContext));
return null;
}
abstract protected FeatureSpecification doParse(Element element, ParserContext parserContext);
/**
* Adapt the given ParserContext into a SpecificationContext.
*/
private SpecificationContext specificationContextFrom(ParserContext parserContext) {
SpecificationContext specContext = new SpecificationContext();
specContext.setRegistry(parserContext.getRegistry());
specContext.setRegistrar(new ComponentRegistrarAdapter(parserContext));
specContext.setResourceLoader(parserContext.getReaderContext().getResourceLoader());
try {
// again, STS constraints around the addition of the new getEnvironment()
// method in 3.1.0 (it's not present in STS current spring version, 3.0.5)
// TODO 3.1 GA: remove this block prior to 3.1 GA
specContext.setEnvironment(parserContext.getDelegate().getEnvironment());
} catch (NoSuchMethodError ex) {
specContext.setEnvironment(new DefaultEnvironment());
}
try {
// access the reader context's problem reporter reflectively in order to
// compensate for tooling (STS) constraints around introduction of changes
// to parser context / reader context classes.
// TODO 3.1 GA: remove this block prior to 3.1 GA
Field field = ReaderContext.class.getDeclaredField("problemReporter");
field.setAccessible(true);
ProblemReporter problemReporter = (ProblemReporter)field.get(parserContext.getReaderContext());
specContext.setProblemReporter(problemReporter);
} catch (Exception ex) {
throw new IllegalStateException(
"Could not access field 'ReaderContext#problemReporter' on object " +
parserContext.getReaderContext(), ex);
}
return specContext;
}
}

View File

@ -1,54 +0,0 @@
/*
* Copyright 2002-2011 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.context.config;
import org.springframework.core.GenericTypeResolver;
import org.springframework.util.Assert;
/**
* TODO SPR-7420: document
*
* @author Chris Beams
* @since 3.1
*/
public abstract class AbstractSpecificationExecutor<S extends FeatureSpecification> implements FeatureSpecificationExecutor {
/**
* {@inheritDoc}
* <p>This implementation {@linkplain FeatureSpecification#validate() validates} the
* given specification and delegates it to {@link #doExecute(FeatureSpecification)}
* only if valid.
*/
@SuppressWarnings("unchecked")
public final void execute(FeatureSpecification spec, SpecificationContext specificationContext) {
Assert.notNull(spec, "Specification must not be null");
Assert.notNull(spec, "SpecificationContext must not be null");
Class<?> typeArg = GenericTypeResolver.resolveTypeArgument(this.getClass(), AbstractSpecificationExecutor.class);
Assert.isTrue(typeArg.equals(spec.getClass()), "Specification cannot be executed by this executor");
if (spec.validate(specificationContext.getProblemReporter())) {
doExecute((S)spec, specificationContext);
}
}
/**
* Execute the given specification, usually resulting in registration of bean definitions
* against a bean factory.
* @param specification the {@linkplain FeatureSpecification#validate() validated} specification
*/
protected abstract void doExecute(S specification, SpecificationContext specificationContext);
}

View File

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

View File

@ -1,41 +0,0 @@
/*
* Copyright 2002-2011 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.context.config;
/**
* Interface for executing a populated {@link FeatureSpecification}. Provides
* a generic mechanism for handling container configuration metadata regardless of
* origin in XML, annotations, or other source format.
*
* TODO SPR-7420: document (clean up)
*
* @author Chris Beams
* @since 3.1
* @see AbstractSpecificationExecutor
* @see org.springframework.beans.factory.xml.BeanDefinitionParser
* @see org.springframework.context.annotation.FeatureAnnotationParser
* @see org.springframework.context.annotation.ComponentScanExecutor
*/
public interface FeatureSpecificationExecutor {
/**
* Execute the given specification, usually resulting in registration
* of bean definitions against a bean factory.
*/
void execute(FeatureSpecification spec, SpecificationContext specificationContext);
}

View File

@ -1,35 +0,0 @@
/*
* Copyright 2002-2011 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.context.config;
/**
* TODO SPR-7420: document
*
* @author Chris Beams
* @since 3.1
*/
public interface SourceAwareSpecification extends FeatureSpecification {
String sourceName();
SourceAwareSpecification sourceName(String sourceName);
Object source();
SourceAwareSpecification source(Object source);
}

View File

@ -1,81 +0,0 @@
/*
* Copyright 2002-2011 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.context.config;
import org.springframework.beans.factory.parsing.ComponentRegistrar;
import org.springframework.beans.factory.parsing.ProblemReporter;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.core.env.Environment;
import org.springframework.core.io.ResourceLoader;
/**
* TODO: rename to SpecificationContext?
*
* @author Chris Beams
* @since 3.1
*/
public class SpecificationContext {
private BeanDefinitionRegistry registry;
private ComponentRegistrar registrar;
private ResourceLoader resourceLoader;
private Environment environment;
private ProblemReporter problemReporter;
public SpecificationContext() { }
public void setRegistry(BeanDefinitionRegistry registry) {
this.registry = registry;
}
public BeanDefinitionRegistry getRegistry() {
return this.registry;
}
public void setRegistrar(ComponentRegistrar registrar) {
this.registrar = registrar;
}
public ComponentRegistrar getRegistrar() {
return this.registrar;
}
public void setResourceLoader(ResourceLoader resourceLoader) {
this.resourceLoader = resourceLoader;
}
public ResourceLoader getResourceLoader() {
return this.resourceLoader;
}
public void setEnvironment(Environment environment) {
this.environment = environment;
}
public Environment getEnvironment() {
return this.environment;
}
public void setProblemReporter(ProblemReporter problemReporter) {
this.problemReporter = problemReporter;
}
public ProblemReporter getProblemReporter() {
return this.problemReporter;
}
}

View File

@ -1,67 +0,0 @@
/*
* Copyright 2002-2011 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.context.annotation;
import 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();
}
}
}

View File

@ -1,67 +0,0 @@
/*
* Copyright 2002-2011 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.context.annotation;
import 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);
}
}

View File

@ -1,55 +0,0 @@
/*
* Copyright 2002-2011 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.context.annotation;
import 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();
}
}

View File

@ -1,411 +0,0 @@
/*
* Copyright 2002-2011 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.context.annotation;
import 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();
}
}
}

View File

@ -1,312 +0,0 @@
/*
* Copyright 2002-2011 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.context.annotation;
import 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();
}
}
}

View File

@ -1,83 +0,0 @@
/*
* Copyright 2002-2011 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.context.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();
}
}

View File

@ -1,10 +0,0 @@
<?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>

View File

@ -1,69 +0,0 @@
/*
* Copyright 2002-2011 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.context.annotation;
import 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();
}
}
}

View File

@ -1,105 +0,0 @@
/*
* Copyright 2002-2011 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.context.annotation;
import 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();
}
}
}

View File

@ -1,76 +0,0 @@
/*
* Copyright 2002-2011 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.context.annotation;
import 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"));
}
}
}

View File

@ -1,192 +0,0 @@
/*
* Copyright 2002-2011 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.context.annotation;
import 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");
}
}

View File

@ -1,113 +0,0 @@
/*
* Copyright 2002-2011 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.context.annotation;
import 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();
}
}
}

View File

@ -1,70 +0,0 @@
/*
* Copyright 2002-2011 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.context.annotation;
import 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");
}
}
}

View File

@ -1,135 +0,0 @@
/*
* Copyright 2002-2011 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.context.annotation;
import 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;
}
}

View File

@ -1,64 +0,0 @@
/*
* Copyright 2002-2011 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.context.annotation;
import 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;
}
}
}

View File

@ -1,8 +0,0 @@
<?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>

View File

@ -1,37 +0,0 @@
/*
* Copyright 2002-2011 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.context.annotation.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 {
}

View File

@ -1,46 +0,0 @@
/*
* Copyright 2002-2011 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.context.annotation.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) {
}
}

View File

@ -1,80 +0,0 @@
/*
* Copyright 2002-2011 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.context.annotation;
/**
* 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 {
}

View File

@ -16,7 +16,15 @@
package org.springframework.transaction.config;
import org.w3c.dom.Element;
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.context.config.AbstractSpecificationBeanDefinitionParser;
import org.springframework.context.config.FeatureSpecification;
@ -40,7 +48,7 @@ import org.w3c.dom.Element;
* @since 2.0
* @see TxAnnotationDriven
*/
class AnnotationDrivenBeanDefinitionParser extends AbstractSpecificationBeanDefinitionParser {
class AnnotationDrivenBeanDefinitionParser implements BeanDefinitionParser {
/**
* The bean name of the internally managed transaction advisor (mode="proxy").

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2011 the original author or authors.
* Copyright 2002-2009 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -31,7 +31,7 @@ import org.springframework.util.ClassUtils;
* @author Christian Dupuis
* @since 2.5
*/
class JtaTransactionManagerBeanDefinitionParser extends AbstractSingleBeanDefinitionParser {
public class JtaTransactionManagerBeanDefinitionParser extends AbstractSingleBeanDefinitionParser {
private static final String WEBLOGIC_JTA_TRANSACTION_MANAGER_CLASS_NAME =
"org.springframework.transaction.jta.WebLogicJtaTransactionManager";
@ -74,7 +74,7 @@ class JtaTransactionManagerBeanDefinitionParser extends AbstractSingleBeanDefini
@Override
protected String resolveId(Element element, AbstractBeanDefinition definition, ParserContext parserContext) {
return TxAnnotationDriven.DEFAULT_TRANSACTION_MANAGER_BEAN_NAME;
return TxNamespaceHandler.DEFAULT_TRANSACTION_MANAGER_BEAN_NAME;
}
}

View File

@ -48,8 +48,6 @@ import org.springframework.util.xml.DomUtils;
*/
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_NAME_ATTRIBUTE = "name";
@ -76,7 +74,7 @@ class TxAdviceBeanDefinitionParser extends AbstractSingleBeanDefinitionParser {
@Override
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);
if (txAttributes.size() > 1) {

View File

@ -1,198 +0,0 @@
/*
* Copyright 2002-2011 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.transaction.config;
import org.springframework.beans.factory.parsing.ProblemCollector;
import org.springframework.context.config.AbstractFeatureSpecification;
import org.springframework.context.config.AdviceMode;
import org.springframework.context.config.FeatureSpecificationExecutor;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
/**
* TODO SPR-7420: document
*
* @author Chris Beams
* @since 3.1
*/
public final class TxAnnotationDriven extends AbstractFeatureSpecification {
static final String DEFAULT_TRANSACTION_MANAGER_BEAN_NAME = "transactionManager";
private static final Class<? extends FeatureSpecificationExecutor> EXECUTOR_TYPE = TxAnnotationDrivenExecutor.class;
private Object txManager = null;
private Object order = null;
private Boolean proxyTargetClass = false;
private Object mode = AdviceMode.PROXY;
/**
* Create a {@code TxAnnotationDriven} specification assumes the presence of a
* {@link PlatformTransactionManager} bean named {@value #DEFAULT_TRANSACTION_MANAGER_BEAN_NAME}.
*
* <p>See the alternate constructors defined here if your transaction manager does
* not follow this default naming or you wish to refer to it by bean instance rather
* than by bean name.
* @see #TxAnnotationDriven(String)
* @see #TxAnnotationDriven(PlatformTransactionManager)
*/
public TxAnnotationDriven() {
this(DEFAULT_TRANSACTION_MANAGER_BEAN_NAME);
}
/**
* Create a new {@code TxAnnotationDriven} specification that will use the specified
* transaction manager bean name.
*
* @param txManagerBeanName name of {@link PlatformTransactionManager} bean or a
* ${placeholder} or SpEL #{expression} resolving to bean name. If {@code null},
* falls back to default value of {@value #DEFAULT_TRANSACTION_MANAGER_BEAN_NAME}.
*/
public TxAnnotationDriven(String txManagerBeanName) {
super(EXECUTOR_TYPE);
this.txManager = txManagerBeanName != null ?
txManagerBeanName :
DEFAULT_TRANSACTION_MANAGER_BEAN_NAME;
}
/**
* Create a new TxAnnotationDriven specification that will use the specified transaction
* manager.
*
* @param txManager the {@link PlatformTransactionManager} bean to use. Must not be {@code null}.
*/
public TxAnnotationDriven(PlatformTransactionManager txManager) {
super(EXECUTOR_TYPE);
Assert.notNull(txManager, "transaction manager must not be null");
this.txManager = txManager;
}
/**
* Return the transaction manager to use. May be a {@link PlatformTransactionManager}
* instance or a String representing the bean name or a placeholder or SpEL expression
* that resolves to the bean name.
*/
Object transactionManager() {
return this.txManager;
}
/**
* Indicate how transactional advice should be applied.
* @see AdviceMode
*/
public TxAnnotationDriven mode(AdviceMode mode) {
this.mode = mode;
return this;
}
/**
* Indicate how transactional advice should be applied.
* @param name matching one of the labels in the AdviceMode enum;
* placeholder and SpEL expressions are not allowed.
* @see AdviceMode
*/
TxAnnotationDriven mode(String mode) {
if (StringUtils.hasText(mode)) {
this.mode = mode;
}
return this;
}
/**
* Return how transactional advice should be applied.
*/
AdviceMode mode() {
if (this.mode instanceof AdviceMode) {
return (AdviceMode)this.mode;
}
if (this.mode instanceof String) {
return ObjectUtils.caseInsensitiveValueOf(AdviceMode.values(), (String)this.mode);
}
// TODO SPR-7420: deal with in validate & raise problem
throw new IllegalStateException(
"invalid type for field 'mode' (must be of type AdviceMode or String): "
+ this.mode.getClass().getName());
}
/**
* Indicate whether class-based (CGLIB) proxies are to be created as opposed
* to standard Java interface-based proxies.
*
* <p>Note: Class-based proxies require the {@link Transactional @Transactional}
* annotation to be defined on the concrete class. Annotations in interfaces will
* not work in that case (they will rather only work with interface-based proxies)!
*/
public TxAnnotationDriven proxyTargetClass(Boolean proxyTargetClass) {
this.proxyTargetClass = proxyTargetClass;
return this;
}
/**
* Return whether class-based (CGLIB) proxies are to be created as opposed
* to standard Java interface-based proxies.
*/
Boolean proxyTargetClass() {
return this.proxyTargetClass;
}
/**
* Indicate the ordering of the execution of the transaction advisor
* when multiple advice executes at a specific joinpoint. The default is
* {@code null}, indicating that default ordering should be used.
*/
public TxAnnotationDriven order(int order) {
this.order = order;
return this;
}
/**
* Indicate the ordering of the execution of the transaction advisor
* when multiple advice executes at a specific joinpoint. The default is
* {@code null}, indicating that default ordering should be used.
*/
public TxAnnotationDriven order(String order) {
if (StringUtils.hasText(order)) {
this.order = order;
}
return this;
}
/**
* Return the ordering of the execution of the transaction advisor
* when multiple advice executes at a specific joinpoint. May return
* {@code null}, indicating that default ordering should be used.
*/
Object order() {
return this.order;
}
@Override
protected void doValidate(ProblemCollector problems) {
if (this.mode instanceof String) {
if (!ObjectUtils.containsConstant(AdviceMode.values(), (String)this.mode)) {
problems.error("no such mode name: " + this.mode);
}
}
}
}

View File

@ -1,139 +0,0 @@
/*
* Copyright 2002-2011 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.transaction.config;
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.ComponentRegistrar;
import org.springframework.beans.factory.parsing.CompositeComponentDefinition;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.context.config.AbstractSpecificationExecutor;
import org.springframework.context.config.SpecificationContext;
import org.springframework.transaction.annotation.AnnotationTransactionAttributeSource;
import org.springframework.transaction.interceptor.BeanFactoryTransactionAttributeSourceAdvisor;
import org.springframework.transaction.interceptor.TransactionInterceptor;
import org.springframework.util.Assert;
/**
* TODO SPR-7420: document
*
* @author Chris Beams
* @since 3.1
*/
final class TxAnnotationDrivenExecutor extends AbstractSpecificationExecutor<TxAnnotationDriven> {
/**
* The bean name of the internally managed transaction advisor (used when mode == PROXY).
*/
public static final String TRANSACTION_ADVISOR_BEAN_NAME =
"org.springframework.transaction.config.internalTransactionAdvisor";
/**
* The bean name of the internally managed transaction aspect (used when mode == ASPECTJ).
*/
public static final String TRANSACTION_ASPECT_BEAN_NAME =
"org.springframework.transaction.config.internalTransactionAspect";
private static final String TRANSACTION_ASPECT_CLASS_NAME =
"org.springframework.transaction.aspectj.AnnotationTransactionAspect";
@Override
protected void doExecute(TxAnnotationDriven txSpec, SpecificationContext specificationContext) {
BeanDefinitionRegistry registry = specificationContext.getRegistry();
ComponentRegistrar registrar = specificationContext.getRegistrar();
switch (txSpec.mode()) {
case ASPECTJ:
registerTransactionAspect(txSpec, registry, registrar);
break;
case PROXY:
AopAutoProxyConfigurer.configureAutoProxyCreator(txSpec, registry, registrar);
break;
default:
throw new IllegalArgumentException(
String.format("AdviceMode %s is not supported", txSpec.mode()));
}
}
private void registerTransactionAspect(TxAnnotationDriven spec, BeanDefinitionRegistry registry, ComponentRegistrar registrar) {
if (!registry.containsBeanDefinition(TRANSACTION_ASPECT_BEAN_NAME)) {
RootBeanDefinition def = new RootBeanDefinition();
def.setBeanClassName(TRANSACTION_ASPECT_CLASS_NAME);
def.setFactoryMethodName("aspectOf");
registerTransactionManager(spec, def);
registrar.registerBeanComponent(new BeanComponentDefinition(def, TRANSACTION_ASPECT_BEAN_NAME));
}
}
private static void registerTransactionManager(TxAnnotationDriven spec, BeanDefinition def) {
Object txManager = spec.transactionManager();
Assert.notNull(txManager, "transactionManager must be specified");
if (txManager instanceof String) {
def.getPropertyValues().add("transactionManagerBeanName", txManager);
} else {
def.getPropertyValues().add("transactionManager", txManager);
}
}
/**
* Inner class to just introduce an AOP framework dependency when actually in proxy mode.
*/
private static class AopAutoProxyConfigurer {
public static void configureAutoProxyCreator(TxAnnotationDriven txSpec, BeanDefinitionRegistry registry, ComponentRegistrar registrar) {
Object source = txSpec.source();
AopNamespaceUtils.registerAutoProxyCreatorIfNecessary(registry, registrar, source, txSpec.proxyTargetClass());
if (!registry.containsBeanDefinition(TRANSACTION_ADVISOR_BEAN_NAME)) {
// Create the TransactionAttributeSource definition.
RootBeanDefinition sourceDef = new RootBeanDefinition(AnnotationTransactionAttributeSource.class);
sourceDef.setSource(source);
sourceDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
String sourceName = registrar.registerWithGeneratedName(sourceDef);
// Create the TransactionInterceptor definition.
RootBeanDefinition interceptorDef = new RootBeanDefinition(TransactionInterceptor.class);
interceptorDef.setSource(source);
interceptorDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
registerTransactionManager(txSpec, interceptorDef);
interceptorDef.getPropertyValues().add("transactionAttributeSource", new RuntimeBeanReference(sourceName));
String interceptorName = registrar.registerWithGeneratedName(interceptorDef);
// Create the TransactionAttributeSourceAdvisor definition.
RootBeanDefinition advisorDef = new RootBeanDefinition(BeanFactoryTransactionAttributeSourceAdvisor.class);
advisorDef.setSource(source);
advisorDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
advisorDef.getPropertyValues().add("transactionAttributeSource", new RuntimeBeanReference(sourceName));
advisorDef.getPropertyValues().add("adviceBeanName", interceptorName);
if (txSpec.order() != null) {
advisorDef.getPropertyValues().add("order", txSpec.order());
}
registry.registerBeanDefinition(TRANSACTION_ADVISOR_BEAN_NAME, advisorDef);
CompositeComponentDefinition compositeDef = new CompositeComponentDefinition(txSpec.sourceName(), source);
compositeDef.addNestedComponent(new BeanComponentDefinition(sourceDef, sourceName));
compositeDef.addNestedComponent(new BeanComponentDefinition(interceptorDef, interceptorName));
compositeDef.addNestedComponent(new BeanComponentDefinition(advisorDef, TRANSACTION_ADVISOR_BEAN_NAME));
registrar.registerComponent(compositeDef);
}
}
}
}

View File

@ -39,6 +39,17 @@ import org.springframework.beans.factory.xml.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() {
registerBeanDefinitionParser("advice", new TxAdviceBeanDefinitionParser());
registerBeanDefinitionParser("annotation-driven", new AnnotationDrivenBeanDefinitionParser());

View File

@ -1,147 +0,0 @@
/*
* Copyright 2002-2011 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.transaction.annotation;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.fail;
import java.util.Map;
import org.junit.Test;
import org.springframework.aop.support.AopUtils;
import org.springframework.beans.factory.CannotLoadBeanClassException;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Feature;
import org.springframework.context.annotation.FeatureConfiguration;
import org.springframework.context.config.AdviceMode;
import org.springframework.stereotype.Service;
import org.springframework.transaction.CallCountingTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.AnnotationTransactionNamespaceHandlerTests.TransactionalTestBean;
import org.springframework.transaction.config.TxAnnotationDriven;
/**
* Integration tests for {@link TxAnnotationDriven} support within @Configuration
* classes. Adapted from original tx: namespace tests at
* {@link AnnotationTransactionNamespaceHandlerTests}.
*
* @author Chris Beams
* @since 3.1
*/
public class TxAnnotationDrivenFeatureTests {
@Test
public void transactionProxyIsCreated() {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.register(TxFeature.class, TxManagerConfig.class);
ctx.refresh();
TransactionalTestBean bean = ctx.getBean(TransactionalTestBean.class);
assertThat("testBean is not a proxy", AopUtils.isAopProxy(bean), is(true));
Map<?,?> services = ctx.getBeansWithAnnotation(Service.class);
assertThat("Stereotype annotation not visible", services.containsKey("testBean"), is(true));
}
@Test
public void txManagerIsResolvedOnInvocationOfTransactionalMethod() {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.register(TxFeature.class, TxManagerConfig.class);
ctx.refresh();
TransactionalTestBean bean = ctx.getBean(TransactionalTestBean.class);
// invoke a transactional method, causing the PlatformTransactionManager bean to be resolved.
bean.findAllFoos();
}
@Test
public void txManagerIsResolvedCorrectlyWhenMultipleManagersArePresent() {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.register(TxFeature.class, MultiTxManagerConfig.class);
ctx.refresh();
TransactionalTestBean bean = ctx.getBean(TransactionalTestBean.class);
// invoke a transactional method, causing the PlatformTransactionManager bean to be resolved.
bean.findAllFoos();
}
/**
* A cheap test just to prove that in ASPECTJ mode, the AnnotationTransactionAspect does indeed
* get loaded -- or in this case, attempted to be loaded at which point the test fails.
*/
@Test
public void proxyTypeAspectJCausesRegistrationOfAnnotationTransactionAspect() {
try {
new AnnotationConfigApplicationContext(TxWithAspectJFeature.class, TxManagerConfig.class);
fail("should have thrown CNFE when trying to load AnnotationTransactionAspect. " +
"Do you actually have org.springframework.aspects on the classpath?");
} catch (CannotLoadBeanClassException ex) {
ClassNotFoundException cause = (ClassNotFoundException) ex.getCause();
assertThat(cause.getMessage(), equalTo("org.springframework.transaction.aspectj.AnnotationTransactionAspect"));
}
}
}
@FeatureConfiguration
class TxFeature {
@Feature
public TxAnnotationDriven tx(TxManagerConfig txManagerConfig) {
return new TxAnnotationDriven(txManagerConfig.txManager());
}
}
@FeatureConfiguration
class TxWithAspectJFeature {
@Feature
public TxAnnotationDriven tx(PlatformTransactionManager txManager) {
return new TxAnnotationDriven(txManager).mode(AdviceMode.ASPECTJ);
}
}
@Configuration
class TxManagerConfig {
@Bean
public TransactionalTestBean testBean() {
return new TransactionalTestBean();
}
@Bean
public PlatformTransactionManager txManager() {
return new CallCountingTransactionManager();
}
}
@Configuration
class MultiTxManagerConfig extends TxManagerConfig {
@Bean
public PlatformTransactionManager txManager2() {
return new CallCountingTransactionManager();
}
}

View File

@ -0,0 +1,57 @@
/*
* Copyright 2002-2010 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.web.servlet.config;
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.RootBeanDefinition;
import org.springframework.beans.factory.xml.BeanDefinitionParser;
import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter;
/**
* Abstract base class for {@link BeanDefinitonParser}s that register an HttpRequestHandler.
*
* @author Jeremy Grelle
* @since 3.0.4
*/
abstract class AbstractHttpRequestHandlerBeanDefinitionParser implements BeanDefinitionParser{
private static final String HANDLER_ADAPTER_BEAN_NAME = "org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter";
public BeanDefinition parse(Element element, ParserContext parserContext) {
Object source = parserContext.extractSource(element);
registerHandlerAdapterIfNecessary(parserContext, source);
doParse(element, parserContext);
return null;
}
public abstract void doParse(Element element, ParserContext parserContext);
private void registerHandlerAdapterIfNecessary(ParserContext parserContext, Object source) {
if (!parserContext.getRegistry().containsBeanDefinition(HANDLER_ADAPTER_BEAN_NAME)) {
RootBeanDefinition handlerAdapterDef = new RootBeanDefinition(HttpRequestHandlerAdapter.class);
handlerAdapterDef.setSource(source);
handlerAdapterDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
parserContext.getRegistry().registerBeanDefinition(HANDLER_ADAPTER_BEAN_NAME, handlerAdapterDef);
parserContext.registerComponent(new BeanComponentDefinition(handlerAdapterDef, HANDLER_ADAPTER_BEAN_NAME));
}
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2011 the original author or authors.
* Copyright 2002-2010 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -16,65 +16,265 @@
package org.springframework.web.servlet.config;
import java.util.ArrayList;
import java.util.List;
import org.w3c.dom.Element;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanDefinitionHolder;
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.ManagedList;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.beans.factory.xml.BeanDefinitionParser;
import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.context.config.AbstractSpecificationBeanDefinitionParser;
import org.springframework.context.config.FeatureSpecification;
import org.springframework.core.convert.ConversionService;
import org.springframework.format.support.FormattingConversionServiceFactoryBean;
import org.springframework.http.converter.ByteArrayHttpMessageConverter;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.ResourceHttpMessageConverter;
import org.springframework.http.converter.StringHttpMessageConverter;
import org.springframework.http.converter.feed.AtomFeedHttpMessageConverter;
import org.springframework.http.converter.feed.RssChannelHttpMessageConverter;
import org.springframework.http.converter.json.MappingJacksonHttpMessageConverter;
import org.springframework.http.converter.xml.Jaxb2RootElementHttpMessageConverter;
import org.springframework.http.converter.xml.SourceHttpMessageConverter;
import org.springframework.http.converter.xml.XmlAwareFormHttpMessageConverter;
import org.springframework.util.ClassUtils;
import org.springframework.util.xml.DomUtils;
import org.springframework.validation.Validator;
import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;
import org.springframework.web.bind.support.ConfigurableWebBindingInitializer;
import org.springframework.web.bind.support.WebArgumentResolver;
import org.springframework.web.servlet.handler.ConversionServiceExposingInterceptor;
import org.springframework.web.servlet.handler.MappedInterceptor;
import org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMethodAdapter;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMethodExceptionResolver;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMethodMapping;
import org.springframework.web.servlet.mvc.method.annotation.support.ServletWebArgumentResolverAdapter;
import org.w3c.dom.Element;
import org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver;
/**
* {@link BeanDefinitionParser} that parses the {@code annotation-driven} element
* to configure a Spring MVC web application.
* {@link BeanDefinitionParser} that parses the {@code annotation-driven} element to configure a Spring MVC web
* application.
*
* <p>Responsible for:
* <ol>
* <li>Registering a DefaultAnnotationHandlerMapping bean for mapping HTTP Servlet Requests to @Controller methods
* using @RequestMapping annotations.
* <li>Registering a AnnotationMethodHandlerAdapter bean for invoking annotated @Controller methods.
* Will configure the HandlerAdapter's <code>webBindingInitializer</code> property for centrally configuring
* {@code @Controller} {@code DataBinder} instances:
* <ul>
* <li>Configures the conversionService if specified, otherwise defaults to a fresh {@link ConversionService} instance
* created by the default {@link FormattingConversionServiceFactoryBean}.
* <li>Configures the validator if specified, otherwise defaults to a fresh {@link Validator} instance created by the
* default {@link LocalValidatorFactoryBean} <em>if the JSR-303 API is present on the classpath</em>.
* <li>Configures standard {@link org.springframework.http.converter.HttpMessageConverter HttpMessageConverters},
* including the {@link Jaxb2RootElementHttpMessageConverter} <em>if JAXB2 is present on the classpath</em>, and
* the {@link MappingJacksonHttpMessageConverter} <em>if Jackson is present on the classpath</em>.
* </ul>
* </ol>
*
* @author Keith Donald
* @author Juergen Hoeller
* @author Arjen Poutsma
* @author Rossen Stoyanchev
* @author Chris Beams
* @since 3.0
* @see MvcAnnotationDriven
* @see MvcAnnotationDrivenExecutor
*/
class AnnotationDrivenBeanDefinitionParser extends AbstractSpecificationBeanDefinitionParser {
class AnnotationDrivenBeanDefinitionParser implements BeanDefinitionParser {
/**
* Parses the {@code <mvc:annotation-driven/>} tag.
*/
@Override
protected FeatureSpecification doParse(Element element, ParserContext parserContext) {
MvcAnnotationDriven spec = new MvcAnnotationDriven();
private static final boolean jsr303Present = ClassUtils.isPresent(
"javax.validation.Validator", AnnotationDrivenBeanDefinitionParser.class.getClassLoader());
private static final boolean jaxb2Present =
ClassUtils.isPresent("javax.xml.bind.Binder", AnnotationDrivenBeanDefinitionParser.class.getClassLoader());
private static final boolean jacksonPresent =
ClassUtils.isPresent("org.codehaus.jackson.map.ObjectMapper", AnnotationDrivenBeanDefinitionParser.class.getClassLoader()) &&
ClassUtils.isPresent("org.codehaus.jackson.JsonGenerator", AnnotationDrivenBeanDefinitionParser.class.getClassLoader());
private static boolean romePresent =
ClassUtils.isPresent("com.sun.syndication.feed.WireFeed", AnnotationDrivenBeanDefinitionParser.class.getClassLoader());
public BeanDefinition parse(Element element, ParserContext parserContext) {
Object source = parserContext.extractSource(element);
CompositeComponentDefinition compDefinition = new CompositeComponentDefinition(element.getTagName(), source);
parserContext.pushContainingComponent(compDefinition);
RootBeanDefinition methodMappingDef = new RootBeanDefinition(RequestMappingHandlerMethodMapping.class);
methodMappingDef.setSource(source);
methodMappingDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
methodMappingDef.getPropertyValues().add("order", 0);
String methodMappingName = parserContext.getReaderContext().registerWithGeneratedName(methodMappingDef);
RuntimeBeanReference conversionService = getConversionService(element, source, parserContext);
RuntimeBeanReference validator = getValidator(element, source, parserContext);
RuntimeBeanReference messageCodesResolver = getMessageCodesResolver(element, source, parserContext);
RootBeanDefinition bindingDef = new RootBeanDefinition(ConfigurableWebBindingInitializer.class);
bindingDef.setSource(source);
bindingDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
bindingDef.getPropertyValues().add("conversionService", conversionService);
bindingDef.getPropertyValues().add("validator", validator);
bindingDef.getPropertyValues().add("messageCodesResolver", messageCodesResolver);
ManagedList<?> messageConverters = getMessageConverters(element, source, parserContext);
ManagedList<?> argumentResolvers = getArgumentResolvers(element, source, parserContext);
RootBeanDefinition methodAdapterDef = new RootBeanDefinition(RequestMappingHandlerMethodAdapter.class);
methodAdapterDef.setSource(source);
methodAdapterDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
methodAdapterDef.getPropertyValues().add("webBindingInitializer", bindingDef);
methodAdapterDef.getPropertyValues().add("messageConverters", messageConverters);
if (argumentResolvers != null) {
methodAdapterDef.getPropertyValues().add("customArgumentResolvers", argumentResolvers);
}
String methodAdapterName = parserContext.getReaderContext().registerWithGeneratedName(methodAdapterDef);
RootBeanDefinition csInterceptorDef = new RootBeanDefinition(ConversionServiceExposingInterceptor.class);
csInterceptorDef.setSource(source);
csInterceptorDef.getConstructorArgumentValues().addIndexedArgumentValue(0, conversionService);
RootBeanDefinition mappedCsInterceptorDef = new RootBeanDefinition(MappedInterceptor.class);
mappedCsInterceptorDef.setSource(source);
mappedCsInterceptorDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
mappedCsInterceptorDef.getConstructorArgumentValues().addIndexedArgumentValue(0, (Object) null);
mappedCsInterceptorDef.getConstructorArgumentValues().addIndexedArgumentValue(1, csInterceptorDef);
String mappedInterceptorName = parserContext.getReaderContext().registerWithGeneratedName(mappedCsInterceptorDef);
RootBeanDefinition methodExceptionResolver = new RootBeanDefinition(RequestMappingHandlerMethodExceptionResolver.class);
methodExceptionResolver.setSource(source);
methodExceptionResolver.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
methodExceptionResolver.getPropertyValues().add("messageConverters", messageConverters);
methodExceptionResolver.getPropertyValues().add("order", 0);
String methodExceptionResolverName =
parserContext.getReaderContext().registerWithGeneratedName(methodExceptionResolver);
RootBeanDefinition responseStatusExceptionResolver = new RootBeanDefinition(ResponseStatusExceptionResolver.class);
responseStatusExceptionResolver.setSource(source);
responseStatusExceptionResolver.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
responseStatusExceptionResolver.getPropertyValues().add("order", 1);
String responseStatusExceptionResolverName =
parserContext.getReaderContext().registerWithGeneratedName(responseStatusExceptionResolver);
RootBeanDefinition defaultExceptionResolver = new RootBeanDefinition(DefaultHandlerExceptionResolver.class);
defaultExceptionResolver.setSource(source);
defaultExceptionResolver.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
defaultExceptionResolver.getPropertyValues().add("order", 2);
String defaultExceptionResolverName =
parserContext.getReaderContext().registerWithGeneratedName(defaultExceptionResolver);
parserContext.registerComponent(new BeanComponentDefinition(methodMappingDef, methodMappingName));
parserContext.registerComponent(new BeanComponentDefinition(methodAdapterDef, methodAdapterName));
parserContext.registerComponent(new BeanComponentDefinition(methodExceptionResolver, methodExceptionResolverName));
parserContext.registerComponent(new BeanComponentDefinition(responseStatusExceptionResolver, responseStatusExceptionResolverName));
parserContext.registerComponent(new BeanComponentDefinition(defaultExceptionResolver, defaultExceptionResolverName));
parserContext.registerComponent(new BeanComponentDefinition(mappedCsInterceptorDef, mappedInterceptorName));
parserContext.popAndRegisterContainingComponent();
return null;
}
private RuntimeBeanReference getConversionService(Element element, Object source, ParserContext parserContext) {
RuntimeBeanReference conversionServiceRef;
if (element.hasAttribute("conversion-service")) {
String conversionService = element.getAttribute("conversion-service");
spec.conversionService(conversionService);
conversionServiceRef = new RuntimeBeanReference(element.getAttribute("conversion-service"));
}
else {
RootBeanDefinition conversionDef = new RootBeanDefinition(FormattingConversionServiceFactoryBean.class);
conversionDef.setSource(source);
conversionDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
String conversionName = parserContext.getReaderContext().registerWithGeneratedName(conversionDef);
parserContext.registerComponent(new BeanComponentDefinition(conversionDef, conversionName));
conversionServiceRef = new RuntimeBeanReference(conversionName);
}
return conversionServiceRef;
}
private RuntimeBeanReference getValidator(Element element, Object source, ParserContext parserContext) {
if (element.hasAttribute("validator")) {
spec.validator(element.getAttribute("validator"));
return new RuntimeBeanReference(element.getAttribute("validator"));
}
else if (jsr303Present) {
RootBeanDefinition validatorDef = new RootBeanDefinition(LocalValidatorFactoryBean.class);
validatorDef.setSource(source);
validatorDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
String validatorName = parserContext.getReaderContext().registerWithGeneratedName(validatorDef);
parserContext.registerComponent(new BeanComponentDefinition(validatorDef, validatorName));
return new RuntimeBeanReference(validatorName);
}
else {
return null;
}
}
private RuntimeBeanReference getMessageCodesResolver(Element element, Object source, ParserContext parserContext) {
if (element.hasAttribute("message-codes-resolver")) {
spec.messageCodesResolver(element.getAttribute("message-codes-resolver"));
}
Element convertersElement = DomUtils.getChildElementByTagName(element, "message-converters");
if (convertersElement != null) {
if (convertersElement.hasAttribute("register-defaults")) {
spec.shouldRegisterDefaultMessageConverters(Boolean.valueOf(convertersElement
.getAttribute("register-defaults")));
}
spec.messageConverters(extractBeanSubElements(convertersElement, parserContext));
return new RuntimeBeanReference(element.getAttribute("message-codes-resolver"));
} else {
return null;
}
}
private ManagedList<?> getArgumentResolvers(Element element, Object source, ParserContext parserContext) {
Element resolversElement = DomUtils.getChildElementByTagName(element, "argument-resolvers");
if (resolversElement != null) {
ManagedList<BeanDefinitionHolder> beanDefs = extractBeanSubElements(resolversElement, parserContext);
spec.argumentResolvers(wrapWebArgumentResolverBeanDefs(beanDefs));
ManagedList<BeanDefinitionHolder> argumentResolvers = extractBeanSubElements(resolversElement, parserContext);
return wrapWebArgumentResolverBeanDefs(argumentResolvers);
}
return null;
}
private ManagedList<?> getMessageConverters(Element element, Object source, ParserContext parserContext) {
Element convertersElement = DomUtils.getChildElementByTagName(element, "message-converters");
ManagedList<? super Object> messageConverters = new ManagedList<Object>();
if (convertersElement != null) {
messageConverters.setSource(source);
for (Element converter : DomUtils.getChildElementsByTagName(convertersElement, "bean")) {
BeanDefinitionHolder beanDef = parserContext.getDelegate().parseBeanDefinitionElement(converter);
beanDef = parserContext.getDelegate().decorateBeanDefinitionIfRequired(converter, beanDef);
messageConverters.add(beanDef);
}
}
return spec;
if (convertersElement == null || Boolean.valueOf(convertersElement.getAttribute("register-defaults"))) {
messageConverters.setSource(source);
messageConverters.add(createConverterBeanDefinition(ByteArrayHttpMessageConverter.class, source));
RootBeanDefinition stringConverterDef = createConverterBeanDefinition(StringHttpMessageConverter.class,
source);
stringConverterDef.getPropertyValues().add("writeAcceptCharset", false);
messageConverters.add(stringConverterDef);
messageConverters.add(createConverterBeanDefinition(ResourceHttpMessageConverter.class, source));
messageConverters.add(createConverterBeanDefinition(SourceHttpMessageConverter.class, source));
messageConverters.add(createConverterBeanDefinition(XmlAwareFormHttpMessageConverter.class, source));
if (jaxb2Present) {
messageConverters
.add(createConverterBeanDefinition(Jaxb2RootElementHttpMessageConverter.class, source));
}
if (jacksonPresent) {
messageConverters.add(createConverterBeanDefinition(MappingJacksonHttpMessageConverter.class, source));
}
if (romePresent) {
messageConverters.add(createConverterBeanDefinition(AtomFeedHttpMessageConverter.class, source));
messageConverters.add(createConverterBeanDefinition(RssChannelHttpMessageConverter.class, source));
}
}
return messageConverters;
}
private RootBeanDefinition createConverterBeanDefinition(Class<? extends HttpMessageConverter> converterClass,
Object source) {
RootBeanDefinition beanDefinition = new RootBeanDefinition(converterClass);
beanDefinition.setSource(source);
beanDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
return beanDefinition;
}
private ManagedList<BeanDefinitionHolder> extractBeanSubElements(Element parentElement, ParserContext parserContext) {
@ -99,8 +299,7 @@ class AnnotationDrivenBeanDefinitionParser extends AbstractSpecificationBeanDefi
RootBeanDefinition adapter = new RootBeanDefinition(ServletWebArgumentResolverAdapter.class);
adapter.getConstructorArgumentValues().addIndexedArgumentValue(0, beanDef);
result.add(new BeanDefinitionHolder(adapter, beanDef.getBeanName() + "Adapter"));
}
else {
} else {
result.add(beanDef);
}
}

View File

@ -16,15 +16,20 @@
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.ParserContext;
import org.springframework.context.config.AbstractSpecificationBeanDefinitionParser;
import org.springframework.context.config.FeatureSpecification;
import org.springframework.util.StringUtils;
import org.springframework.web.servlet.handler.SimpleUrlHandlerMapping;
import org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter;
import org.springframework.web.servlet.resource.DefaultServletHttpRequestHandler;
import org.w3c.dom.Element;
/**
* {@link BeanDefinitionParser} that parses a {@code default-servlet-handler} element to
@ -32,20 +37,37 @@ import org.w3c.dom.Element;
* {@link SimpleUrlHandlerMapping} for mapping resource requests, and a
* {@link HttpRequestHandlerAdapter} if necessary.
*
* @author Rossen Stoyanchev
* @author Chris Beams
* @author Jeremy Grelle
* @since 3.0.4
*/
class DefaultServletHandlerBeanDefinitionParser extends AbstractSpecificationBeanDefinitionParser {
class DefaultServletHandlerBeanDefinitionParser extends AbstractHttpRequestHandlerBeanDefinitionParser {
/**
* Parses the {@code <mvc:default-servlet-handler/>} tag.
*/
public FeatureSpecification doParse(Element element, ParserContext parserContext) {
String defaultServletHandler = element.getAttribute("default-servlet-handler");
return StringUtils.hasText(defaultServletHandler) ?
new MvcDefaultServletHandler(defaultServletHandler) :
new MvcDefaultServletHandler();
@Override
public void doParse(Element element, ParserContext parserContext) {
Object source = parserContext.extractSource(element);
String defaultServletName = element.getAttribute("default-servlet-name");
RootBeanDefinition defaultServletHandlerDef = new RootBeanDefinition(DefaultServletHttpRequestHandler.class);
defaultServletHandlerDef.setSource(source);
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));
}
}

View File

@ -18,54 +18,59 @@ package org.springframework.web.servlet.config;
import java.util.List;
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.RootBeanDefinition;
import org.springframework.beans.factory.xml.BeanDefinitionParser;
import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.context.config.AbstractSpecificationBeanDefinitionParser;
import org.springframework.context.config.FeatureSpecification;
import org.springframework.util.xml.DomUtils;
import org.springframework.web.servlet.handler.MappedInterceptor;
import org.w3c.dom.Element;
/**
* {@link org.springframework.beans.factory.xml.BeanDefinitionParser} that parses
* a {@code interceptors} element to register set of {@link MappedInterceptor}
* definitions.
* {@link org.springframework.beans.factory.xml.BeanDefinitionParser} that parses a {@code interceptors} element to register
* a set of {@link MappedInterceptor} definitions.
*
* @author Keith Donald
* @author Rossen Stoyanchev
*
* @since 3.0
*/
class InterceptorsBeanDefinitionParser extends AbstractSpecificationBeanDefinitionParser {
class InterceptorsBeanDefinitionParser implements BeanDefinitionParser {
/**
* Parses the {@code <mvc:interceptors/>} tag.
*/
public FeatureSpecification doParse(Element element, ParserContext parserContext) {
MvcInterceptors mvcInterceptors = new MvcInterceptors();
public BeanDefinition parse(Element element, ParserContext parserContext) {
CompositeComponentDefinition compDefinition = new CompositeComponentDefinition(element.getTagName(), parserContext.extractSource(element));
parserContext.pushContainingComponent(compDefinition);
List<Element> interceptors = DomUtils.getChildElementsByTagName(element, new String[] { "bean", "interceptor" });
for (Element interceptor : interceptors) {
RootBeanDefinition mappedInterceptorDef = new RootBeanDefinition(MappedInterceptor.class);
mappedInterceptorDef.setSource(parserContext.extractSource(interceptor));
mappedInterceptorDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
String[] pathPatterns;
BeanDefinitionHolder interceptorDef;
if ("interceptor".equals(interceptor.getLocalName())) {
List<Element> paths = DomUtils.getChildElementsByTagName(interceptor, "mapping");
String[] pathPatterns = new String[paths.size()];
pathPatterns = new String[paths.size()];
for (int i = 0; i < paths.size(); i++) {
pathPatterns[i] = paths.get(i).getAttribute("path");
}
Element beanElement = DomUtils.getChildElementByTagName(interceptor, "bean");
mvcInterceptors.interceptor(pathPatterns, parseBeanElement(parserContext, beanElement));
Element interceptorBean = DomUtils.getChildElementByTagName(interceptor, "bean");
interceptorDef = parserContext.getDelegate().parseBeanDefinitionElement(interceptorBean);
interceptorDef = parserContext.getDelegate().decorateBeanDefinitionIfRequired(interceptorBean, interceptorDef);
} else {
mvcInterceptors.interceptor(null, parseBeanElement(parserContext, interceptor));
pathPatterns = null;
interceptorDef = parserContext.getDelegate().parseBeanDefinitionElement(interceptor);
interceptorDef = parserContext.getDelegate().decorateBeanDefinitionIfRequired(interceptor, interceptorDef);
}
mappedInterceptorDef.getConstructorArgumentValues().addIndexedArgumentValue(0, pathPatterns);
mappedInterceptorDef.getConstructorArgumentValues().addIndexedArgumentValue(1, interceptorDef);
String mappedInterceptorName = parserContext.getReaderContext().registerWithGeneratedName(mappedInterceptorDef);
parserContext.registerComponent(new BeanComponentDefinition(mappedInterceptorDef, mappedInterceptorName));
}
return mvcInterceptors;
}
private BeanDefinitionHolder parseBeanElement(ParserContext parserContext, Element interceptor) {
BeanDefinitionHolder beanDef = parserContext.getDelegate().parseBeanDefinitionElement(interceptor);
beanDef = parserContext.getDelegate().decorateBeanDefinitionIfRequired(interceptor, beanDef);
return beanDef;
parserContext.popAndRegisterContainingComponent();
return null;
}
}

View File

@ -1,259 +0,0 @@
/*
* Copyright 2002-2011 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.servlet.config;
import org.springframework.beans.factory.config.BeanDefinitionHolder;
import org.springframework.beans.factory.parsing.ProblemCollector;
import org.springframework.beans.factory.support.ManagedList;
import org.springframework.context.config.AbstractFeatureSpecification;
import org.springframework.context.config.FeatureSpecificationExecutor;
import org.springframework.core.convert.ConversionService;
import org.springframework.format.support.FormattingConversionServiceFactoryBean;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.feed.AtomFeedHttpMessageConverter;
import org.springframework.http.converter.feed.RssChannelHttpMessageConverter;
import org.springframework.http.converter.json.MappingJacksonHttpMessageConverter;
import org.springframework.http.converter.xml.Jaxb2RootElementHttpMessageConverter;
import org.springframework.validation.MessageCodesResolver;
import org.springframework.validation.Validator;
import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;
import org.springframework.web.bind.support.WebArgumentResolver;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.servlet.mvc.method.annotation.support.ServletWebArgumentResolverAdapter;
/**
* Specifies the Spring MVC "annotation-driven" container feature. The
* feature provides the following fine-grained configuration:
*
* <ul>
* <li>{@code DefaultAnnotationHandlerMapping} bean for mapping HTTP Servlet Requests
* to {@code @Controller} methods using {@code @RequestMapping} annotations.
* <li>{@code AnnotationMethodHandlerAdapter} bean for invoking annotated
* {@code @Controller} methods.
* <li>{@code HandlerExceptionResolver} beans for invoking {@code @ExceptionHandler}
* controller methods and for mapping Spring exception to HTTP status codes.
* </ul>
*
* <p>The {@code HandlerAdapter} is further configured with the following, which apply
* globally (across controllers invoked though the {@code AnnotationMethodHandlerAdapter}):
*
* <ul>
* <li>{@link ConversionService} - a custom instance can be provided via
* {@link #conversionService(ConversionService)}. Otherwise it defaults to a fresh
* {@link ConversionService} instance created by the default
* {@link FormattingConversionServiceFactoryBean}.
* <li>{@link Validator} - a custom instance can be provided via
* {@link #validator(Validator)}. Otherwise it defaults to a fresh {@code Validator}
* instance created by the default {@link LocalValidatorFactoryBean} <em>assuming
* JSR-303 API is present on the classpath</em>.
* <li>{@code HttpMessageConverter} beans including the {@link
* Jaxb2RootElementHttpMessageConverter} <em>assuming JAXB2 is present on the
* classpath</em>, the {@link MappingJacksonHttpMessageConverter} <em>assuming Jackson
* is present on the classpath</em>, and the {@link AtomFeedHttpMessageConverter} and the
* {@link RssChannelHttpMessageConverter} converters <em>assuming Rome is present on
* the classpath</em>.
* <li>Optionally, custom {@code WebArgumentResolver} beans to use for resolving
* custom arguments to handler methods. These are typically implemented to detect
* special parameter types, resolving well-known argument values for them.
* </ul>
*
* @author Rossen Stoyanchev
* @since 3.1
*/
public final class MvcAnnotationDriven extends AbstractFeatureSpecification {
private static final Class<? extends FeatureSpecificationExecutor> EXECUTOR_TYPE = MvcAnnotationDrivenExecutor.class;
private Object conversionService;
private Object validator;
private Object messageCodesResolver;
private boolean shouldRegisterDefaultMessageConverters = true;
private ManagedList<? super Object> messageConverters = new ManagedList<Object>();
private ManagedList<? super Object> argumentResolvers = new ManagedList<Object>();
/**
* Creates an MvcAnnotationDriven specification.
*/
public MvcAnnotationDriven() {
super(EXECUTOR_TYPE);
}
/**
* <p> The ConversionService bean instance to use for type conversion during
* field binding. This is not required input. It only needs to be provided
* explicitly if custom converters or formatters need to be configured.
*
* <p> If not provided, a default FormattingConversionService is registered
* that contains converters to/from standard JDK types. In addition, full
* support for date/time formatting will be installed if the Joda Time
* library is present on the classpath.
*
* @param conversionService the ConversionService instance to use
*/
public MvcAnnotationDriven conversionService(ConversionService conversionService) {
this.conversionService = conversionService;
return this;
}
/**
* <p> The ConversionService to use for type conversion during field binding.
* This is an alternative to {@link #conversionService(ConversionService)}
* allowing you to provide a bean name rather than a bean instance.
*
* @param conversionService the ConversionService bean name
*/
public MvcAnnotationDriven conversionService(String conversionService) {
this.conversionService = conversionService;
return this;
}
Object conversionService() {
return this.conversionService;
}
/**
* The HttpMessageConverter types to use for converting @RequestBody method
* parameters and @ResponseBody method return values. HttpMessageConverter
* registrations provided here will take precedence over HttpMessageConverter
* types registered by default.
* Also see {@link #shouldRegisterDefaultMessageConverters(boolean)} if
* default registrations are to be turned off altogether.
*
* @param converters the message converters
*/
public MvcAnnotationDriven messageConverters(HttpMessageConverter<?>... converters) {
for (HttpMessageConverter<?> converter : converters) {
this.messageConverters.add(converter);
}
return this;
}
void messageConverters(ManagedList<BeanDefinitionHolder> converterBeanDefinitions) {
this.messageConverters.addAll(converterBeanDefinitions);
}
ManagedList<?> messageConverters() {
return this.messageConverters;
}
/**
* Indicates whether or not default HttpMessageConverter registrations should
* be added in addition to the ones provided via
* {@link #messageConverters(HttpMessageConverter...)}
*
* @param shouldRegister true will result in registration of defaults.
*/
public MvcAnnotationDriven shouldRegisterDefaultMessageConverters(boolean shouldRegister) {
this.shouldRegisterDefaultMessageConverters = shouldRegister;
return this;
}
boolean shouldRegisterDefaultMessageConverters() {
return this.shouldRegisterDefaultMessageConverters;
}
public MvcAnnotationDriven argumentResolvers(HandlerMethodArgumentResolver... resolvers) {
for (HandlerMethodArgumentResolver resolver : resolvers) {
this.argumentResolvers.add(resolver);
}
return this;
}
public MvcAnnotationDriven argumentResolvers(WebArgumentResolver... resolvers) {
for (WebArgumentResolver resolver : resolvers) {
this.argumentResolvers.add(new ServletWebArgumentResolverAdapter(resolver));
}
return this;
}
void argumentResolvers(ManagedList<BeanDefinitionHolder> resolverBeanDefinitions) {
this.argumentResolvers.addAll(resolverBeanDefinitions);
}
ManagedList<?> argumentResolvers() {
return this.argumentResolvers;
}
/**
* The Validator bean instance to use to validate Controller model objects.
* This is not required input. It only needs to be specified explicitly if
* a custom Validator needs to be configured.
*
* <p> If not specified, JSR-303 validation will be installed if a JSR-303
* provider is present on the classpath.
*
* @param validator the Validator bean instance
*/
public MvcAnnotationDriven validator(Validator validator) {
this.validator = validator;
return this;
}
/**
* The Validator bean instance to use to validate Controller model objects.
* This is an alternative to {@link #validator(Validator)} allowing you to
* provide a bean name rather than a bean instance.
*
* @param validator the Validator bean name
*/
public MvcAnnotationDriven validator(String validator) {
this.validator = validator;
return this;
}
Object validator() {
return this.validator;
}
/**
* The MessageCodesResolver to use to build message codes from data binding
* and validation error codes. This is not required input. If not specified
* the DefaultMessageCodesResolver is used.
*
* @param messageCodesResolver the MessageCodesResolver bean instance
*/
public MvcAnnotationDriven messageCodesResolver(MessageCodesResolver messageCodesResolver) {
this.messageCodesResolver = messageCodesResolver;
return this;
}
/**
* The MessageCodesResolver to use to build message codes from data binding
* and validation error codes. This is an alternative to
* {@link #messageCodesResolver(MessageCodesResolver)} allowing you to provide
* a bean name rather than a bean instance.
*
* @param messageCodesResolver the MessageCodesResolver bean name
*/
public MvcAnnotationDriven messageCodesResolver(String messageCodesResolver) {
this.messageCodesResolver = messageCodesResolver;
return this;
}
Object messageCodesResolver() {
return this.messageCodesResolver;
}
@Override
protected void doValidate(ProblemCollector problems) {
}
}

View File

@ -1,236 +0,0 @@
/*
* Copyright 2002-2011 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.servlet.config;
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.ComponentRegistrar;
import org.springframework.beans.factory.parsing.CompositeComponentDefinition;
import org.springframework.beans.factory.support.ManagedList;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.context.config.AbstractSpecificationExecutor;
import org.springframework.context.config.SpecificationContext;
import org.springframework.format.support.FormattingConversionServiceFactoryBean;
import org.springframework.http.converter.ByteArrayHttpMessageConverter;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.ResourceHttpMessageConverter;
import org.springframework.http.converter.StringHttpMessageConverter;
import org.springframework.http.converter.feed.AtomFeedHttpMessageConverter;
import org.springframework.http.converter.feed.RssChannelHttpMessageConverter;
import org.springframework.http.converter.json.MappingJacksonHttpMessageConverter;
import org.springframework.http.converter.xml.Jaxb2RootElementHttpMessageConverter;
import org.springframework.http.converter.xml.SourceHttpMessageConverter;
import org.springframework.http.converter.xml.XmlAwareFormHttpMessageConverter;
import org.springframework.util.ClassUtils;
import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;
import org.springframework.web.bind.support.ConfigurableWebBindingInitializer;
import org.springframework.web.servlet.handler.ConversionServiceExposingInterceptor;
import org.springframework.web.servlet.handler.MappedInterceptor;
import org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMethodAdapter;
import org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMethodMapping;
import org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver;
/**
* Executes {@link MvcAnnotationDriven} specifications, creating and registering
* bean definitions as appropriate based on the configuration within.
*
* @author Keith Donald
* @author Juergen Hoeller
* @author Arjen Poutsma
* @author Rossen Stoyanchev
* @since 3.1
* @see MvcAnnotationDriven
*/
final class MvcAnnotationDrivenExecutor extends AbstractSpecificationExecutor<MvcAnnotationDriven> {
private static final boolean jsr303Present = ClassUtils.isPresent("javax.validation.Validator",
AnnotationDrivenBeanDefinitionParser.class.getClassLoader());
private static final boolean jaxb2Present = ClassUtils.isPresent("javax.xml.bind.Binder",
AnnotationDrivenBeanDefinitionParser.class.getClassLoader());
private static final boolean jacksonPresent = ClassUtils.isPresent("org.codehaus.jackson.map.ObjectMapper",
AnnotationDrivenBeanDefinitionParser.class.getClassLoader())
&& ClassUtils.isPresent("org.codehaus.jackson.JsonGenerator",
AnnotationDrivenBeanDefinitionParser.class.getClassLoader());
private static boolean romePresent = ClassUtils.isPresent("com.sun.syndication.feed.WireFeed",
AnnotationDrivenBeanDefinitionParser.class.getClassLoader());
@Override
public void doExecute(MvcAnnotationDriven spec, SpecificationContext specContext) {
ComponentRegistrar registrar = specContext.getRegistrar();
Object source = spec.source();
RootBeanDefinition methodMappingDef = new RootBeanDefinition(RequestMappingHandlerMethodMapping.class);
methodMappingDef.setSource(source);
methodMappingDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
methodMappingDef.getPropertyValues().add("order", 0);
String methodMappingName = registrar.registerWithGeneratedName(methodMappingDef);
Object conversionService = getConversionService(spec, registrar);
Object validator = getValidator(spec, registrar);
Object messageCodesResolver = getMessageCodesResolver(spec, registrar);
RootBeanDefinition bindingDef = new RootBeanDefinition(ConfigurableWebBindingInitializer.class);
bindingDef.setSource(source);
bindingDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
bindingDef.getPropertyValues().add("conversionService", conversionService);
bindingDef.getPropertyValues().add("validator", validator);
bindingDef.getPropertyValues().add("messageCodesResolver", messageCodesResolver);
ManagedList<? super Object> messageConverters = getMessageConverters(spec, registrar);
RootBeanDefinition methodAdapterDef = new RootBeanDefinition(RequestMappingHandlerMethodAdapter.class);
methodAdapterDef.setSource(source);
methodAdapterDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
methodAdapterDef.getPropertyValues().add("webBindingInitializer", bindingDef);
methodAdapterDef.getPropertyValues().add("messageConverters", messageConverters);
if (!spec.argumentResolvers().isEmpty()) {
methodAdapterDef.getPropertyValues().add("customArgumentResolvers", spec.argumentResolvers());
}
String methodAdapterName = registrar.registerWithGeneratedName(methodAdapterDef);
RootBeanDefinition csInterceptorDef = new RootBeanDefinition(ConversionServiceExposingInterceptor.class);
csInterceptorDef.setSource(source);
csInterceptorDef.getConstructorArgumentValues().addIndexedArgumentValue(0, conversionService);
RootBeanDefinition mappedCsInterceptorDef = new RootBeanDefinition(MappedInterceptor.class);
mappedCsInterceptorDef.setSource(source);
mappedCsInterceptorDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
mappedCsInterceptorDef.getConstructorArgumentValues().addIndexedArgumentValue(0, (Object) null);
mappedCsInterceptorDef.getConstructorArgumentValues().addIndexedArgumentValue(1, csInterceptorDef);
String mappedInterceptorName = registrar.registerWithGeneratedName(mappedCsInterceptorDef);
RootBeanDefinition methodExceptionResolver = new RootBeanDefinition(
ExceptionHandlerExceptionResolver.class);
methodExceptionResolver.setSource(source);
methodExceptionResolver.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
methodExceptionResolver.getPropertyValues().add("messageConverters", messageConverters);
methodExceptionResolver.getPropertyValues().add("order", 0);
String methodExceptionResolverName = registrar.registerWithGeneratedName(methodExceptionResolver);
RootBeanDefinition responseStatusExceptionResolver = new RootBeanDefinition(
ResponseStatusExceptionResolver.class);
responseStatusExceptionResolver.setSource(source);
responseStatusExceptionResolver.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
responseStatusExceptionResolver.getPropertyValues().add("order", 1);
String responseStatusExceptionResolverName = registrar
.registerWithGeneratedName(responseStatusExceptionResolver);
RootBeanDefinition defaultExceptionResolver = new RootBeanDefinition(DefaultHandlerExceptionResolver.class);
defaultExceptionResolver.setSource(source);
defaultExceptionResolver.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
defaultExceptionResolver.getPropertyValues().add("order", 2);
String defaultExceptionResolverName = registrar.registerWithGeneratedName(defaultExceptionResolver);
CompositeComponentDefinition compDefinition = new CompositeComponentDefinition(spec.sourceName(), source);
compDefinition.addNestedComponent(new BeanComponentDefinition(methodMappingDef, methodMappingName));
compDefinition.addNestedComponent(new BeanComponentDefinition(methodAdapterDef, methodAdapterName));
compDefinition.addNestedComponent(new BeanComponentDefinition(methodExceptionResolver, methodExceptionResolverName));
compDefinition.addNestedComponent(new BeanComponentDefinition(responseStatusExceptionResolver,
responseStatusExceptionResolverName));
compDefinition.addNestedComponent(new BeanComponentDefinition(defaultExceptionResolver,
defaultExceptionResolverName));
compDefinition.addNestedComponent(new BeanComponentDefinition(mappedCsInterceptorDef, mappedInterceptorName));
registrar.registerComponent(compDefinition);
}
private Object getConversionService(MvcAnnotationDriven spec, ComponentRegistrar registrar) {
if (spec.conversionService() != null) {
return getBeanOrReference(spec.conversionService());
} else {
RootBeanDefinition conversionDef = new RootBeanDefinition(FormattingConversionServiceFactoryBean.class);
conversionDef.setSource(spec.source());
conversionDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
String conversionName = registrar.registerWithGeneratedName(conversionDef);
registrar.registerComponent(new BeanComponentDefinition(conversionDef, conversionName));
return new RuntimeBeanReference(conversionName);
}
}
private Object getValidator(MvcAnnotationDriven spec, ComponentRegistrar registrar) {
if (spec.validator() != null) {
return getBeanOrReference(spec.validator());
} else if (jsr303Present) {
RootBeanDefinition validatorDef = new RootBeanDefinition(LocalValidatorFactoryBean.class);
validatorDef.setSource(spec.source());
validatorDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
String validatorName = registrar.registerWithGeneratedName(validatorDef);
registrar.registerComponent(new BeanComponentDefinition(validatorDef, validatorName));
return new RuntimeBeanReference(validatorName);
} else {
return null;
}
}
private Object getMessageCodesResolver(MvcAnnotationDriven spec, ComponentRegistrar registrar) {
if (spec.messageCodesResolver() != null) {
return getBeanOrReference(spec.messageCodesResolver());
} else {
return null;
}
}
private ManagedList<? super Object> getMessageConverters(MvcAnnotationDriven spec, ComponentRegistrar registrar) {
ManagedList<? super Object> messageConverters = new ManagedList<Object>();
Object source = spec.source();
messageConverters.setSource(source);
messageConverters.addAll(spec.messageConverters());
if (spec.shouldRegisterDefaultMessageConverters()) {
messageConverters.add(createConverterBeanDefinition(ByteArrayHttpMessageConverter.class, source));
RootBeanDefinition stringConverterDef = createConverterBeanDefinition(StringHttpMessageConverter.class,
source);
stringConverterDef.getPropertyValues().add("writeAcceptCharset", false);
messageConverters.add(stringConverterDef);
messageConverters.add(createConverterBeanDefinition(ResourceHttpMessageConverter.class, source));
messageConverters.add(createConverterBeanDefinition(SourceHttpMessageConverter.class, source));
messageConverters.add(createConverterBeanDefinition(XmlAwareFormHttpMessageConverter.class, source));
if (jaxb2Present) {
messageConverters
.add(createConverterBeanDefinition(Jaxb2RootElementHttpMessageConverter.class, source));
}
if (jacksonPresent) {
messageConverters.add(createConverterBeanDefinition(MappingJacksonHttpMessageConverter.class, source));
}
if (romePresent) {
messageConverters.add(createConverterBeanDefinition(AtomFeedHttpMessageConverter.class, source));
messageConverters.add(createConverterBeanDefinition(RssChannelHttpMessageConverter.class, source));
}
}
return messageConverters;
}
@SuppressWarnings("rawtypes")
private RootBeanDefinition createConverterBeanDefinition(Class<? extends HttpMessageConverter> converterClass,
Object source) {
RootBeanDefinition beanDefinition = new RootBeanDefinition(converterClass);
beanDefinition.setSource(source);
beanDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
return beanDefinition;
}
private Object getBeanOrReference(Object bean) {
if (bean != null && bean instanceof String) {
return new RuntimeBeanReference((String) bean);
} else {
return bean;
}
}
}

View File

@ -1,91 +0,0 @@
/*
* Copyright 2002-2011 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.servlet.config;
import org.springframework.beans.factory.parsing.ProblemCollector;
import org.springframework.context.config.AbstractFeatureSpecification;
import org.springframework.context.config.FeatureSpecificationExecutor;
import org.springframework.web.servlet.handler.SimpleUrlHandlerMapping;
import org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter;
import org.springframework.web.servlet.resource.DefaultServletHttpRequestHandler;
/**
* Specifies the Spring MVC "default-servlet-handler" container feature. The
* feature provides the following fine-grained configuration:
*
* <ul>
* <li>{@link DefaultServletHttpRequestHandler} for serving static files by
* forwarding to the Servlet container's "default" Servlet.
* <li>{@link SimpleUrlHandlerMapping} to map the above request handler to "/**"
* <li>{@link HttpRequestHandlerAdapter} to enable the DispatcherServlet to be
* able to invoke the above request handler.
* </ul>
*
* This handler will forward all requests to the default Servlet. Therefore
* it is important that it remains last in the order of all other URL
* HandlerMappings. That will be the case if you use the {@link MvcAnnotationDriven}
* feature or alternatively if you are setting up your customized HandlerMapping
* instance be sure to set its "order" property to a value lower than that of
* the DefaultServletHttpRequestHandler, which is Integer.MAX_VALUE.
*
* @author Rossen Stoyanchev
* @since 3.1
*/
public final class MvcDefaultServletHandler extends AbstractFeatureSpecification {
private static final Class<? extends FeatureSpecificationExecutor> EXECUTOR_TYPE = MvcDefaultServletHandlerExecutor.class;
private String defaultServletName;
/**
* <p>Creates an instance of MvcDefaultServletHandler without.
* If this constructor is used the {@link DefaultServletHttpRequestHandler}
* will try to auto-detect the container's default Servlet at startup time
* using a list of known names.
*
* <p>If the default Servlet cannot be detected because of using an
* unknown container or because it has been manually configured, an
* alternate constructor provided here can be used to specify the
* servlet name explicitly.
*/
public MvcDefaultServletHandler() {
super(EXECUTOR_TYPE);
}
/**
* The name of the default Servlet to forward to for static resource requests.
* The {@link DefaultServletHttpRequestHandler} will try to auto-detect the
* container's default Servlet at startup time using a list of known names.
* However if the default Servlet cannot be detected because of using an unknown
* container or because it has been manually configured, you can use this
* constructor to set the servlet name explicitly.
*
* @param defaultServletName the name of the default servlet
*/
public MvcDefaultServletHandler(String defaultServletName) {
this();
this.defaultServletName = defaultServletName;
}
String defaultServletName() {
return this.defaultServletName;
}
@Override
protected void doValidate(ProblemCollector problems) {
}
}

View File

@ -1,82 +0,0 @@
/*
* Copyright 2002-2011 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.servlet.config;
import java.util.Map;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.parsing.BeanComponentDefinition;
import org.springframework.beans.factory.parsing.ComponentRegistrar;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.ManagedMap;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.context.config.AbstractSpecificationExecutor;
import org.springframework.context.config.SpecificationContext;
import org.springframework.util.StringUtils;
import org.springframework.web.servlet.handler.SimpleUrlHandlerMapping;
import org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter;
import org.springframework.web.servlet.resource.DefaultServletHttpRequestHandler;
/**
* Executes {@link MvcDefaultServletHandler} specifications, creating and
* registering bean definitions as appropriate based on the configuration
* within.
*
* @author Jeremy Grelle
* @author Rossen Stoyanchev
* @since 3.1
*/
final class MvcDefaultServletHandlerExecutor extends AbstractSpecificationExecutor<MvcDefaultServletHandler> {
private static final String HANDLER_ADAPTER_BEAN_NAME = "org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter";
@Override
protected void doExecute(MvcDefaultServletHandler spec, SpecificationContext specContext) {
BeanDefinitionRegistry registry = specContext.getRegistry();
ComponentRegistrar registrar = specContext.getRegistrar();
Object source = spec.source();
if (!registry.containsBeanDefinition(HANDLER_ADAPTER_BEAN_NAME)) {
RootBeanDefinition handlerAdapterDef = new RootBeanDefinition(HttpRequestHandlerAdapter.class);
handlerAdapterDef.setSource(source);
handlerAdapterDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
registry.registerBeanDefinition(HANDLER_ADAPTER_BEAN_NAME, handlerAdapterDef);
registrar.registerComponent(new BeanComponentDefinition(handlerAdapterDef, HANDLER_ADAPTER_BEAN_NAME));
}
RootBeanDefinition defaultServletHandlerDef = new RootBeanDefinition(DefaultServletHttpRequestHandler.class);
defaultServletHandlerDef.setSource(source);
defaultServletHandlerDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
if (StringUtils.hasText(spec.defaultServletName())) {
defaultServletHandlerDef.getPropertyValues().add("defaultServletName", spec.defaultServletName());
}
String defaultServletHandlerName = registrar.registerWithGeneratedName(defaultServletHandlerDef);
registry.registerBeanDefinition(defaultServletHandlerName, defaultServletHandlerDef);
registrar.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 = registrar.registerWithGeneratedName(handlerMappingDef);
registry.registerBeanDefinition(handlerMappingBeanName, handlerMappingDef);
registrar.registerComponent(new BeanComponentDefinition(handlerMappingDef, handlerMappingBeanName));
}
}

View File

@ -1,158 +0,0 @@
/*
* Copyright 2002-2011 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.servlet.config;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;
import org.springframework.beans.factory.config.BeanDefinitionHolder;
import org.springframework.beans.factory.parsing.ProblemCollector;
import org.springframework.context.config.AbstractFeatureSpecification;
import org.springframework.context.config.FeatureSpecificationExecutor;
import org.springframework.util.StringUtils;
import org.springframework.web.context.request.WebRequestInterceptor;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.handler.MappedInterceptor;
/**
* Specifies the Spring MVC "interceptors" container feature. The feature
* registers one or more {@link MappedInterceptor} bean definitions. A
* MappedInterceptor encapsulates an interceptor and one or more (optional)
* path patterns to which the interceptor is mapped. The interceptor can be
* of type {@link HandlerInterceptor} or {@link WebRequestInterceptor}.
* An interceptor can also be provided without path patterns in which case
* it applies globally to all handler invocations.
*
* @author Rossen Stoyanchev
* @since 3.1
*/
public class MvcInterceptors extends AbstractFeatureSpecification {
private static final Class<? extends FeatureSpecificationExecutor> EXECUTOR_TYPE = MvcInterceptorsExecutor.class;
private Map<Object, String[]> interceptorMappings = new LinkedHashMap<Object, String[]>();
/**
* Creates an MvcInterceptors instance.
*/
public MvcInterceptors() {
super(EXECUTOR_TYPE);
}
/**
* Add one or more {@link HandlerInterceptor HandlerInterceptors} that should
* intercept all handler invocations.
*
* @param interceptors one or more interceptors
*/
public MvcInterceptors globalInterceptors(HandlerInterceptor... interceptors) {
addInterceptorMappings(null, interceptors);
return this;
}
/**
* Add one or more {@link WebRequestInterceptor WebRequestInterceptors} that should
* intercept all handler invocations.
*
* @param interceptors one or more interceptors
*/
public MvcInterceptors globalInterceptors(WebRequestInterceptor... interceptors) {
addInterceptorMappings(null, interceptors);
return this;
}
/**
* Add one or more interceptors by bean name that should intercept all handler
* invocations.
*
* @param interceptors interceptor bean names
*/
public MvcInterceptors globalInterceptors(String... interceptors) {
addInterceptorMappings(null, interceptors);
return this;
}
/**
* Add one or more {@link HandlerInterceptor HandlerInterceptors} and map
* them to the specified path patterns.
*
* @param pathPatterns the pathPatterns to map the interceptor to
* @param interceptors the interceptors
*/
public MvcInterceptors mappedInterceptors(String[] pathPatterns, HandlerInterceptor... interceptors) {
addInterceptorMappings(pathPatterns, interceptors);
return this;
}
/**
* Add one or more {@link WebRequestInterceptor WebRequestInterceptors} and
* map them to the specified path patterns.
*
* @param pathPatterns the pathPatterns to map the interceptor to
* @param interceptors the interceptors
*/
public MvcInterceptors mappedInterceptors(String[] pathPatterns, WebRequestInterceptor... interceptors) {
addInterceptorMappings(pathPatterns, interceptors);
return this;
}
/**
* Add one or more interceptors by bean name and map them to the specified
* path patterns.
*
* @param pathPatterns the pathPatterns to map to
* @param interceptors the interceptors
*/
public MvcInterceptors mappedInterceptors(String[] pathPatterns, String... interceptors) {
addInterceptorMappings(pathPatterns, interceptors);
return this;
}
void interceptor(String[] pathPatterns, BeanDefinitionHolder interceptor) {
addInterceptorMappings(pathPatterns, new Object[] { interceptor });
}
Map<Object, String[]> interceptorMappings() {
return Collections.unmodifiableMap(interceptorMappings);
}
private void addInterceptorMappings(String[] pathPatterns, Object[] interceptors) {
for (Object interceptor : interceptors) {
interceptorMappings.put(interceptor, pathPatterns);
}
}
@Override
protected void doValidate(ProblemCollector problems) {
if (interceptorMappings.size() == 0) {
problems.error("No interceptors defined.");
}
for (Object interceptor : interceptorMappings.keySet()) {
if (interceptor == null) {
problems.error("Null interceptor provided.");
}
if (interceptorMappings.get(interceptor) != null) {
for (String pattern : interceptorMappings.get(interceptor)) {
if (!StringUtils.hasText(pattern)) {
problems.error("Empty path pattern specified for " + interceptor);
}
}
}
}
}
}

View File

@ -1,60 +0,0 @@
/*
* Copyright 2002-2011 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.servlet.config;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.parsing.BeanComponentDefinition;
import org.springframework.beans.factory.parsing.ComponentRegistrar;
import org.springframework.beans.factory.parsing.CompositeComponentDefinition;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.context.config.AbstractSpecificationExecutor;
import org.springframework.context.config.SpecificationContext;
import org.springframework.web.servlet.handler.MappedInterceptor;
/**
* Executes {@link MvcInterceptors} specification, creating and registering
* bean definitions as appropriate based on the configuration within.
*
* @author Keith Donald
* @author Rossen Stoyanchev
*
* @since 3.1
*/
final class MvcInterceptorsExecutor extends AbstractSpecificationExecutor<MvcInterceptors> {
@Override
protected void doExecute(MvcInterceptors spec, SpecificationContext specContext) {
ComponentRegistrar registrar = specContext.getRegistrar();
Object source = spec.source();
CompositeComponentDefinition compDefinition = new CompositeComponentDefinition(spec.sourceName(), source);
for (Object interceptor : spec.interceptorMappings().keySet()) {
RootBeanDefinition beanDef = new RootBeanDefinition(MappedInterceptor.class);
beanDef.setSource(source);
beanDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
beanDef.getConstructorArgumentValues().addIndexedArgumentValue(0,
spec.interceptorMappings().get(interceptor));
beanDef.getConstructorArgumentValues().addIndexedArgumentValue(1, interceptor);
String beanName = registrar.registerWithGeneratedName(beanDef);
compDefinition.addNestedComponent(new BeanComponentDefinition(beanDef, beanName));
}
registrar.registerComponent(compDefinition);
}
}

View File

@ -1,179 +0,0 @@
/*
* Copyright 2002-2011 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.servlet.config;
import org.springframework.beans.factory.parsing.ProblemCollector;
import org.springframework.context.config.AbstractFeatureSpecification;
import org.springframework.context.config.FeatureSpecificationExecutor;
import org.springframework.core.Ordered;
import org.springframework.core.io.Resource;
import org.springframework.util.StringUtils;
import org.springframework.web.servlet.handler.SimpleUrlHandlerMapping;
import org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter;
import org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping;
import org.springframework.web.servlet.resource.ResourceHttpRequestHandler;
/**
* Specifies the Spring MVC "resources" container feature. The
* feature provides the following fine-grained configuration:
*
* <ul>
* <li>{@link ResourceHttpRequestHandler} to serve static resources from a
* list of web-root relative, classpath, or other locations.
* <li>{@link SimpleUrlHandlerMapping} to map the above request handler to a
* a specific path pattern (e.g. "/resources/**").
* <li>{@link HttpRequestHandlerAdapter} to enable the DispatcherServlet to be
* able to invoke the above request handler.
* </ul>
*
* @author Rossen Stoynchev
* @since 3.1
*/
public final class MvcResources extends AbstractFeatureSpecification {
private static final Class<? extends FeatureSpecificationExecutor> EXECUTOR_TYPE = MvcResourcesExecutor.class;
private Object[] locations;
private String mapping;
private Object cachePeriod;
private Object order = Ordered.LOWEST_PRECEDENCE - 1;
/**
* Create an MvcResources specification instance. See alternate constructor
* you prefer to use {@link Resource} instances instead of {@code String}-based
* resource locations.
*
* @param mapping - the URL path pattern within the current Servlet context to
* use to identify resource requests (e.g. "/resources/**").
* @param locations - locations of resources containing static content to be
* served. Each location must point to a valid directory. Locations will be
* checked in the order specified. For example if "/" and
* "classpath:/META-INF/public-web-resources/" are configured resources will
* be served from the Web root and from any JAR on the classpath that contains
* a /META-INF/public-web-resources/ directory, with resources under the Web root
* taking precedence.
*/
public MvcResources(String mapping, String... locations) {
super(EXECUTOR_TYPE);
this.locations = locations;
this.mapping = mapping;
}
/**
* Create an MvcResources specification instance. See alternate constructor
* defined here if you prefer to use String-based path patterns.
*
* @param mapping - the URL path pattern within the current Servlet context to
* use to identify resource requests (e.g. "/resources/**").
* @param resources - Spring {@link Resource} objects containing static
* content to be served. Resources will be checked in the order specified.
*/
public MvcResources(String mapping, Resource... resources) {
super(EXECUTOR_TYPE);
this.locations = resources;
this.mapping = mapping;
}
/**
* The period of time resources should be cached for in seconds.
* The default is to not send any cache headers but rather to rely on
* last-modified timestamps only.
* <p>Set this to 0 in order to send cache headers that prevent caching,
* or to a positive number of seconds in order to send cache headers
* with the given max-age value.
*
* @param cachePeriod the cache period in seconds
*/
public MvcResources cachePeriod(Integer cachePeriod) {
this.cachePeriod = cachePeriod;
return this;
}
/**
* Specify a cachePeriod as a String. An alternative to {@link #cachePeriod(Integer)}.
* The String must represent an Integer after placeholder and SpEL expression
* resolution.
*
* @param cachePeriod the cache period in seconds
*/
public MvcResources cachePeriod(String cachePeriod) {
this.cachePeriod = cachePeriod;
return this;
}
/**
* Specify a cachePeriod as a String. An alternative to {@link #cachePeriod(Integer)}.
* The String must represent an Integer after placeholder and SpEL expression
* resolution.
* <p>Sets the order for the SimpleUrlHandlerMapping used to match resource
* requests relative to order value for other HandlerMapping instances
* such as the {@link DefaultAnnotationHandlerMapping} used to match
* controller requests.
*
* @param order the order to use. The default value is
* {@link Ordered#LOWEST_PRECEDENCE} - 1.
*/
public MvcResources order(Integer order) {
this.order = order;
return this;
}
/**
* Specify an order as a String. An alternative to {@link #order(Integer)}.
* The String must represent an Integer after placeholder and SpEL expression
* resolution.
*
* @param order the order to use. The default value is
* {@link Ordered#LOWEST_PRECEDENCE} - 1.
*/
public MvcResources order(String order) {
this.order = order;
return this;
}
// Package private accessors
Object cachePeriod() {
return cachePeriod;
}
Object[] locations() {
return this.locations;
}
String mapping() {
return mapping;
}
Object order() {
return order;
}
@Override
protected void doValidate(ProblemCollector problems) {
if (!StringUtils.hasText(mapping)) {
problems.error("Mapping is required");
}
if (locations == null || locations.length == 0) {
problems.error("At least one location is required");
}
}
}

View File

@ -1,85 +0,0 @@
/*
* Copyright 2002-2011 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.servlet.config;
import java.util.Map;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.parsing.BeanComponentDefinition;
import org.springframework.beans.factory.parsing.ComponentRegistrar;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.ManagedMap;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.context.config.AbstractSpecificationExecutor;
import org.springframework.context.config.SpecificationContext;
import org.springframework.web.servlet.handler.SimpleUrlHandlerMapping;
import org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter;
import org.springframework.web.servlet.resource.ResourceHttpRequestHandler;
/**
* Executes {@link MvcResources} specifications, creating and registering
* bean definitions as appropriate based on the configuration within.
*
* @author Keith Donald
* @author Jeremy Grelle
* @author Rossen Stoyanchev
* @since 3.1
*/
final class MvcResourcesExecutor extends AbstractSpecificationExecutor<MvcResources> {
private static final String HANDLER_ADAPTER_BEAN_NAME = "org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter";
@Override
protected void doExecute(MvcResources spec, SpecificationContext specContext) {
BeanDefinitionRegistry registry = specContext.getRegistry();
ComponentRegistrar registrar = specContext.getRegistrar();
Object source = spec.source();
if (!registry.containsBeanDefinition(HANDLER_ADAPTER_BEAN_NAME)) {
RootBeanDefinition handlerAdapterDef = new RootBeanDefinition(HttpRequestHandlerAdapter.class);
handlerAdapterDef.setSource(source);
handlerAdapterDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
registry.registerBeanDefinition(HANDLER_ADAPTER_BEAN_NAME, handlerAdapterDef);
registrar.registerComponent(new BeanComponentDefinition(handlerAdapterDef, HANDLER_ADAPTER_BEAN_NAME));
}
RootBeanDefinition resourceHandlerDef = new RootBeanDefinition(ResourceHttpRequestHandler.class);
resourceHandlerDef.setSource(source);
resourceHandlerDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
resourceHandlerDef.getPropertyValues().add("locations", spec.locations());
if (spec.cachePeriod() != null) {
resourceHandlerDef.getPropertyValues().add("cacheSeconds", spec.cachePeriod());
}
String resourceHandlerBeanName = registrar.registerWithGeneratedName(resourceHandlerDef);
registry.registerBeanDefinition(resourceHandlerBeanName, resourceHandlerDef);
registrar.registerComponent(new BeanComponentDefinition(resourceHandlerDef, resourceHandlerBeanName));
Map<String, String> urlMap = new ManagedMap<String, String>();
urlMap.put(spec.mapping(), resourceHandlerBeanName);
RootBeanDefinition handlerMappingDef = new RootBeanDefinition(SimpleUrlHandlerMapping.class);
handlerMappingDef.setSource(source);
handlerMappingDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
handlerMappingDef.getPropertyValues().add("urlMap", urlMap);
if (spec.order() != null) {
handlerMappingDef.getPropertyValues().add("order", spec.order());
}
String beanName = registrar.registerWithGeneratedName(handlerMappingDef);
registry.registerBeanDefinition(beanName, handlerMappingDef);
registrar.registerComponent(new BeanComponentDefinition(handlerMappingDef, beanName));
}
}

View File

@ -1,90 +0,0 @@
/*
* Copyright 2002-2011 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.servlet.config;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import org.springframework.beans.factory.parsing.ProblemCollector;
import org.springframework.context.config.AbstractFeatureSpecification;
import org.springframework.context.config.FeatureSpecificationExecutor;
import org.springframework.util.StringUtils;
import org.springframework.web.servlet.handler.SimpleUrlHandlerMapping;
import org.springframework.web.servlet.mvc.ParameterizableViewController;
import org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter;
/**
* Specifies the Spring MVC "View Controllers" container feature. The
* feature allows specifying one or more path to view name mappings.
* It sets up the following fine-grained configuration:
*
* <ul>
* <li>{@link ParameterizableViewController} for each path/view name pair.
* <li>{@link SimpleUrlHandlerMapping} mapping each view controller to its path.
* <li>{@link SimpleControllerHandlerAdapter} to enable the DispatcherServlet
* to invoke the view controllers.
* </ul>
*
* @author Rossen Stoyanchev
* @author Chris Beams
* @since 3.1
*/
public final class MvcViewControllers extends AbstractFeatureSpecification {
private static final Class<? extends FeatureSpecificationExecutor> EXECUTOR_TYPE = MvcViewControllersExecutor.class;
private Map<String, String> mappings = new HashMap<String, String>();
public MvcViewControllers(String path) {
this(path, null);
}
public MvcViewControllers(String path, String viewName) {
super(EXECUTOR_TYPE);
this.mappings.put(path, viewName);
}
public MvcViewControllers viewController(String path) {
return this.viewController(path, null);
}
public MvcViewControllers viewController(String path, String viewName) {
this.mappings.put(path, viewName);
return this;
}
Map<String, String> mappings() {
return Collections.unmodifiableMap(mappings);
}
@Override
protected void doValidate(ProblemCollector problems) {
if (mappings.size() == 0) {
problems.error("At least one ViewController must be defined");
}
for (String path : mappings.keySet()) {
if (!StringUtils.hasText(path)) {
problems.error("The path attribute in a ViewController is required");
}
String viewName = mappings.get(path);
if (viewName != null && viewName.isEmpty()) {
problems.error("The view name in a ViewController may be null but not empty.");
}
}
}
}

View File

@ -1,89 +0,0 @@
/*
* Copyright 2002-2011 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/package org.springframework.web.servlet.config;
import java.util.Map;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.parsing.BeanComponentDefinition;
import org.springframework.beans.factory.parsing.ComponentRegistrar;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.ManagedMap;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.context.config.AbstractSpecificationExecutor;
import org.springframework.context.config.SpecificationContext;
import org.springframework.web.servlet.handler.SimpleUrlHandlerMapping;
import org.springframework.web.servlet.mvc.ParameterizableViewController;
import org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter;
/**
* Executes {@link MvcViewControllers} specification, creating and registering
* bean definitions as appropriate based on the configuration within.
*
* @author Keith Donald
* @author Christian Dupuis
* @author Rossen Stoyanchev
* @since 3.1
*/
final class MvcViewControllersExecutor extends AbstractSpecificationExecutor<MvcViewControllers> {
private static final String HANDLER_ADAPTER_BEAN_NAME = "org.springframework.web.servlet.config.viewControllerHandlerAdapter";
private static final String HANDLER_MAPPING_BEAN_NAME = "org.springframework.web.servlet.config.viewControllerHandlerMapping";
@Override
protected void doExecute(MvcViewControllers spec, SpecificationContext specContext) {
BeanDefinitionRegistry registry = specContext.getRegistry();
ComponentRegistrar registrar = specContext.getRegistrar();
Object source = spec.source();
if (!registry.containsBeanDefinition(HANDLER_ADAPTER_BEAN_NAME)) {
RootBeanDefinition handlerAdapterDef = new RootBeanDefinition(SimpleControllerHandlerAdapter.class);
handlerAdapterDef.setSource(source);
handlerAdapterDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
registry.registerBeanDefinition(HANDLER_ADAPTER_BEAN_NAME, handlerAdapterDef);
registrar.registerComponent(new BeanComponentDefinition(handlerAdapterDef, HANDLER_ADAPTER_BEAN_NAME));
}
BeanDefinition handlerMappingBeanDef = null;
if (!registry.containsBeanDefinition(HANDLER_MAPPING_BEAN_NAME)) {
RootBeanDefinition beanDef = new RootBeanDefinition(SimpleUrlHandlerMapping.class);
beanDef.setSource(source);
beanDef.getPropertyValues().add("order", "1");
beanDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
registry.registerBeanDefinition(HANDLER_MAPPING_BEAN_NAME, beanDef);
registrar.registerComponent(new BeanComponentDefinition(beanDef, HANDLER_MAPPING_BEAN_NAME));
handlerMappingBeanDef = beanDef;
} else {
handlerMappingBeanDef = registry.getBeanDefinition(HANDLER_MAPPING_BEAN_NAME);
}
for (Map.Entry<String, String> entry : spec.mappings().entrySet()) {
RootBeanDefinition viewControllerDef = new RootBeanDefinition(ParameterizableViewController.class);
viewControllerDef.setSource(source);
if (entry.getValue() != null) {
viewControllerDef.getPropertyValues().add("viewName", entry.getValue());
}
if (!handlerMappingBeanDef.getPropertyValues().contains("urlMap")) {
handlerMappingBeanDef.getPropertyValues().add("urlMap", new ManagedMap<String, BeanDefinition>());
}
@SuppressWarnings("unchecked")
Map<String, BeanDefinition> urlMap = (Map<String, BeanDefinition>) handlerMappingBeanDef
.getPropertyValues().getPropertyValue("urlMap").getValue();
urlMap.put(entry.getKey(), viewControllerDef);
}
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2011 the original author or authors.
* Copyright 2002-2010 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -16,40 +16,89 @@
package org.springframework.web.servlet.config;
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 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.ParserContext;
import org.springframework.core.Ordered;
import org.springframework.util.StringUtils;
import org.springframework.web.servlet.handler.SimpleUrlHandlerMapping;
import org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter;
import org.springframework.web.servlet.resource.ResourceHttpRequestHandler;
/**
* {@link org.springframework.beans.factory.xml.BeanDefinitionParser} that parses a
* {@code resources} element.
* {@code resources} element to register a {@link ResourceHttpRequestHandler}.
* Will also register a {@link SimpleUrlHandlerMapping} for mapping resource requests,
* and a {@link HttpRequestHandlerAdapter} if necessary.
*
* @author Rossen Stoyanchev
* @author Keith Donald
* @author Jeremy Grelle
* @since 3.0.4
* @see MvcResources
* @see MvcResourcesExecutor
*/
class ResourcesBeanDefinitionParser extends AbstractSpecificationBeanDefinitionParser {
class ResourcesBeanDefinitionParser extends AbstractHttpRequestHandlerBeanDefinitionParser implements BeanDefinitionParser {
/**
* Parses the {@code <mvc:resources/>} tag
*/
public FeatureSpecification doParse(Element element, ParserContext parserContext) {
String mapping = element.getAttribute("mapping");
String[] locations =
StringUtils.commaDelimitedListToStringArray(element.getAttribute("location"));
@Override
public void doParse(Element element, ParserContext parserContext) {
Object source = parserContext.extractSource(element);
registerResourceMappings(parserContext, element, source);
}
MvcResources spec = new MvcResources(mapping, locations);
if (element.hasAttribute("cache-period")) {
spec.cachePeriod(element.getAttribute("cache-period"));
}
if (element.hasAttribute("order")) {
spec.order(element.getAttribute("order"));
private void registerResourceMappings(ParserContext parserContext, Element element, Object source) {
String resourceHandlerName = registerResourceHandler(parserContext, element, source);
if (resourceHandlerName == null) {
return;
}
return spec;
Map<String, String> urlMap = new ManagedMap<String, String>();
String resourceRequestPath = element.getAttribute("mapping");
if (!StringUtils.hasText(resourceRequestPath)) {
parserContext.getReaderContext().error("The 'mapping' attribute is required.", parserContext.extractSource(element));
return;
}
urlMap.put(resourceRequestPath, resourceHandlerName);
RootBeanDefinition handlerMappingDef = new RootBeanDefinition(SimpleUrlHandlerMapping.class);
handlerMappingDef.setSource(source);
handlerMappingDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
handlerMappingDef.getPropertyValues().add("urlMap", urlMap);
String order = element.getAttribute("order");
// use a default of near-lowest precedence, still allowing for even lower precedence in other mappings
handlerMappingDef.getPropertyValues().add("order", StringUtils.hasText(order) ? order : Ordered.LOWEST_PRECEDENCE - 1);
String beanName = parserContext.getReaderContext().generateBeanName(handlerMappingDef);
parserContext.getRegistry().registerBeanDefinition(beanName, handlerMappingDef);
parserContext.registerComponent(new BeanComponentDefinition(handlerMappingDef, beanName));
}
private String registerResourceHandler(ParserContext parserContext, Element element, Object source) {
String locationAttr = element.getAttribute("location");
if (!StringUtils.hasText(locationAttr)) {
parserContext.getReaderContext().error("The 'location' attribute is required.", parserContext.extractSource(element));
return null;
}
RootBeanDefinition resourceHandlerDef = new RootBeanDefinition(ResourceHttpRequestHandler.class);
resourceHandlerDef.setSource(source);
resourceHandlerDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
resourceHandlerDef.getPropertyValues().add("locations", StringUtils.commaDelimitedListToStringArray(locationAttr));
String cacheSeconds = element.getAttribute("cache-period");
if (StringUtils.hasText(cacheSeconds)) {
resourceHandlerDef.getPropertyValues().add("cacheSeconds", cacheSeconds);
}
String beanName = parserContext.getReaderContext().generateBeanName(resourceHandlerDef);
parserContext.getRegistry().registerBeanDefinition(beanName, resourceHandlerDef);
parserContext.registerComponent(new BeanComponentDefinition(resourceHandlerDef, beanName));
return beanName;
}
}

View File

@ -16,30 +16,88 @@
package org.springframework.web.servlet.config;
import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.context.config.AbstractSpecificationBeanDefinitionParser;
import org.springframework.context.config.FeatureSpecification;
import org.springframework.web.servlet.mvc.ParameterizableViewController;
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.ParserContext;
import org.springframework.web.servlet.handler.SimpleUrlHandlerMapping;
import org.springframework.web.servlet.mvc.ParameterizableViewController;
import org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter;
/**
* {@link org.springframework.beans.factory.xml.BeanDefinitionParser} that parses a
* {@code view-controller} element to register a {@link ParameterizableViewController}.
* Will also register a {@link SimpleUrlHandlerMapping} for view controllers.
*
* @author Rossen Stoyanchev
* @author Keith Donald
* @author Christian Dupuis
* @since 3.0
* @see MvcViewControllers
* @see MvcViewControllersExecutor
*/
class ViewControllerBeanDefinitionParser extends AbstractSpecificationBeanDefinitionParser {
class ViewControllerBeanDefinitionParser implements BeanDefinitionParser {
/**
* Parses the {@code <mvc:view-controller/>} tag.
*/
public FeatureSpecification doParse(Element element, ParserContext parserContext) {
String path = element.getAttribute("path");
String viewName = element.getAttribute("view-name");
return new MvcViewControllers(path, viewName.isEmpty() ? null : viewName);
private static final String HANDLER_ADAPTER_BEAN_NAME =
"org.springframework.web.servlet.config.viewControllerHandlerAdapter";
private static final String HANDLER_MAPPING_BEAN_NAME =
"org.springframework.web.servlet.config.viewControllerHandlerMapping";
public BeanDefinition parse(Element element, ParserContext parserContext) {
Object source = parserContext.extractSource(element);
// Register handler adapter
registerHanderAdapter(parserContext, source);
// Register handler mapping
BeanDefinition handlerMappingDef = registerHandlerMapping(parserContext, source);
// Create view controller bean definition
RootBeanDefinition viewControllerDef = new RootBeanDefinition(ParameterizableViewController.class);
viewControllerDef.setSource(source);
if (element.hasAttribute("view-name")) {
viewControllerDef.getPropertyValues().add("viewName", element.getAttribute("view-name"));
}
Map<String, BeanDefinition> urlMap;
if (handlerMappingDef.getPropertyValues().contains("urlMap")) {
urlMap = (Map<String, BeanDefinition>) handlerMappingDef.getPropertyValues().getPropertyValue("urlMap").getValue();
}
else {
urlMap = new ManagedMap<String, BeanDefinition>();
handlerMappingDef.getPropertyValues().add("urlMap", urlMap);
}
urlMap.put(element.getAttribute("path"), viewControllerDef);
return null;
}
private void registerHanderAdapter(ParserContext parserContext, Object source) {
if (!parserContext.getRegistry().containsBeanDefinition(HANDLER_ADAPTER_BEAN_NAME)) {
RootBeanDefinition handlerAdapterDef = new RootBeanDefinition(SimpleControllerHandlerAdapter.class);
handlerAdapterDef.setSource(source);
handlerAdapterDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
parserContext.getRegistry().registerBeanDefinition(HANDLER_ADAPTER_BEAN_NAME, handlerAdapterDef);
parserContext.registerComponent(new BeanComponentDefinition(handlerAdapterDef, HANDLER_ADAPTER_BEAN_NAME));
}
}
private BeanDefinition registerHandlerMapping(ParserContext parserContext, Object source) {
if (!parserContext.getRegistry().containsBeanDefinition(HANDLER_MAPPING_BEAN_NAME)) {
RootBeanDefinition handlerMappingDef = new RootBeanDefinition(SimpleUrlHandlerMapping.class);
handlerMappingDef.setSource(source);
handlerMappingDef.getPropertyValues().add("order", "1");
handlerMappingDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
parserContext.getRegistry().registerBeanDefinition(HANDLER_MAPPING_BEAN_NAME, handlerMappingDef);
parserContext.registerComponent(new BeanComponentDefinition(handlerMappingDef, HANDLER_MAPPING_BEAN_NAME));
return handlerMappingDef;
}
else {
return parserContext.getRegistry().getBeanDefinition(HANDLER_MAPPING_BEAN_NAME);
}
}
}

View File

@ -1,107 +0,0 @@
/*
* Copyright 2002-2011 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.servlet.config;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import java.util.List;
import org.junit.Test;
import org.springframework.beans.DirectFieldAccessor;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Feature;
import org.springframework.context.annotation.FeatureConfiguration;
import org.springframework.format.support.DefaultFormattingConversionService;
import org.springframework.format.support.FormattingConversionService;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.StringHttpMessageConverter;
import org.springframework.validation.MessageCodesResolver;
import org.springframework.validation.Validator;
import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;
import org.springframework.web.bind.support.ConfigurableWebBindingInitializer;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMethodAdapter;
import org.springframework.web.servlet.mvc.method.annotation.support.ServletWebArgumentResolverAdapter;
/**
* Integration tests for the {@link MvcAnnotationDriven} feature specification.
* @author Rossen Stoyanchev
* @author Chris Beams
* @since 3.1
*/
public class MvcAnnotationDrivenFeatureTests {
@SuppressWarnings("unchecked")
@Test
public void testMessageCodesResolver() {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.register(MvcFeature.class, MvcBeans.class);
ctx.refresh();
RequestMappingHandlerMethodAdapter adapter = ctx.getBean(RequestMappingHandlerMethodAdapter.class);
assertNotNull(adapter);
Object initializer = new DirectFieldAccessor(adapter).getPropertyValue("webBindingInitializer");
assertNotNull(initializer);
MessageCodesResolver resolver = ((ConfigurableWebBindingInitializer) initializer).getMessageCodesResolver();
assertNotNull(resolver);
assertEquals("test.foo.bar", resolver.resolveMessageCodes("foo", "bar")[0]);
Object value = new DirectFieldAccessor(adapter).getPropertyValue("customArgumentResolvers");
assertNotNull(value);
List<HandlerMethodArgumentResolver> resolvers = (List<HandlerMethodArgumentResolver>) value;
assertEquals(2, resolvers.size());
assertTrue(resolvers.get(0) instanceof ServletWebArgumentResolverAdapter);
assertTrue(resolvers.get(1) instanceof TestHandlerMethodArgumentResolver);
Object converters = new DirectFieldAccessor(adapter).getPropertyValue("messageConverters");
assertNotNull(converters);
List<HttpMessageConverter<?>> convertersArray = (List<HttpMessageConverter<?>>) converters;
assertTrue("Default converters are registered in addition to the custom one", convertersArray.size() > 1);
assertTrue(convertersArray.get(0) instanceof StringHttpMessageConverter);
}
}
@FeatureConfiguration
class MvcFeature {
@Feature
public MvcAnnotationDriven annotationDriven(MvcBeans mvcBeans) {
return new MvcAnnotationDriven()
.conversionService(mvcBeans.conversionService())
.messageCodesResolver(mvcBeans.messageCodesResolver())
.validator(mvcBeans.validator())
.messageConverters(new StringHttpMessageConverter())
.argumentResolvers(new TestWebArgumentResolver())
.argumentResolvers(new TestHandlerMethodArgumentResolver());
}
}
@Configuration
class MvcBeans {
@Bean
public FormattingConversionService conversionService() {
return new DefaultFormattingConversionService();
}
@Bean
public Validator validator() {
return new LocalValidatorFactoryBean();
}
@Bean MessageCodesResolver messageCodesResolver() {
return new TestMessageCodesResolver();
}
}

View File

@ -1,61 +0,0 @@
/*
* Copyright 2002-2011 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.servlet.config;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import org.junit.Test;
import org.springframework.beans.DirectFieldAccessor;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Feature;
import org.springframework.context.annotation.FeatureConfiguration;
import org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter;
import org.springframework.web.servlet.resource.DefaultServletHttpRequestHandler;
/**
* Test fixture for {@link MvcDefaultServletHandler} feature specification.
* @author Rossen Stoyanchev
* @since 3.1
*/
public class MvcDefaultServletHandlerTests {
@Test
public void testDefaultServletHandler() {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.register(MvcDefaultServletHandlerFeature.class);
ctx.refresh();
HttpRequestHandlerAdapter adapter = ctx.getBean(HttpRequestHandlerAdapter.class);
assertNotNull(adapter);
DefaultServletHttpRequestHandler handler = ctx.getBean(DefaultServletHttpRequestHandler.class);
assertNotNull(handler);
String defaultServletHandlerName = (String) new DirectFieldAccessor(handler)
.getPropertyValue("defaultServletName");
assertEquals("foo", defaultServletHandlerName);
}
@FeatureConfiguration
private static class MvcDefaultServletHandlerFeature {
@SuppressWarnings("unused")
@Feature
public MvcDefaultServletHandler defaultServletHandler() {
return new MvcDefaultServletHandler("foo");
}
}
}

View File

@ -1,126 +0,0 @@
/*
* Copyright 2002-2011 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.servlet.config;
import static org.easymock.EasyMock.capture;
import static org.easymock.EasyMock.createMock;
import static org.easymock.EasyMock.replay;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import java.util.Iterator;
import org.easymock.Capture;
import org.junit.Test;
import org.springframework.beans.factory.parsing.Problem;
import org.springframework.beans.factory.parsing.ProblemReporter;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Feature;
import org.springframework.context.annotation.FeatureConfiguration;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.handler.MappedInterceptor;
import org.springframework.web.servlet.handler.UserRoleAuthorizationInterceptor;
import org.springframework.web.servlet.i18n.LocaleChangeInterceptor;
import org.springframework.web.servlet.theme.ThemeChangeInterceptor;
/**
* Test fixture for {@link MvcInterceptors}.
* @author Rossen Stoyanchev
*/
public class MvcInterceptorsTests {
@Test
public void testInterceptors() {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.register(MvcInterceptorsFeature.class);
ctx.refresh();
Iterator<MappedInterceptor> itr = ctx.getBeansOfType(MappedInterceptor.class).values().iterator();
MappedInterceptor interceptor = itr.next();
assertTrue(interceptor.getInterceptor() instanceof UserRoleAuthorizationInterceptor);
assertNull(interceptor.getPathPatterns());
interceptor = itr.next();
assertTrue(interceptor.getInterceptor() instanceof LocaleChangeInterceptor);
assertArrayEquals(new String[] { "/locale", "/locale/**" }, interceptor.getPathPatterns());
interceptor = itr.next();
assertTrue(interceptor.getInterceptor() instanceof ThemeChangeInterceptor);
assertArrayEquals(new String[] { "/theme", "/theme/**" }, interceptor.getPathPatterns());
}
@Test
public void validateNoInterceptors() {
ProblemReporter reporter = createMock(ProblemReporter.class);
Capture<Problem> captured = new Capture<Problem>();
reporter.error(capture(captured));
replay(reporter);
boolean result = new MvcInterceptors().validate(reporter);
assertFalse(result);
assertEquals("No interceptors defined.", captured.getValue().getMessage());
}
@Test
public void validateNullHandler() {
ProblemReporter reporter = createMock(ProblemReporter.class);
Capture<Problem> captured = new Capture<Problem>();
reporter.error(capture(captured));
replay(reporter);
HandlerInterceptor[] interceptors = new HandlerInterceptor[] { null };
boolean result = new MvcInterceptors().globalInterceptors(interceptors).validate(reporter);
assertFalse(result);
assertTrue(captured.getValue().getMessage().contains("Null interceptor"));
}
@Test
public void validateEmptyPath() {
ProblemReporter reporter = createMock(ProblemReporter.class);
Capture<Problem> captured = new Capture<Problem>();
reporter.error(capture(captured));
replay(reporter);
HandlerInterceptor[] interceptors = new HandlerInterceptor[] { new LocaleChangeInterceptor() };
String[] patterns = new String[] { "" };
boolean result = new MvcInterceptors().mappedInterceptors(patterns, interceptors).validate(reporter);
assertFalse(result);
assertTrue(captured.getValue().getMessage().startsWith("Empty path pattern specified for "));
}
@FeatureConfiguration
private static class MvcInterceptorsFeature {
@SuppressWarnings("unused")
@Feature
public MvcInterceptors interceptors() {
return new MvcInterceptors()
.globalInterceptors(new UserRoleAuthorizationInterceptor())
.mappedInterceptors(new String[] { "/locale", "/locale/**" }, new LocaleChangeInterceptor())
.mappedInterceptors(new String[] { "/theme", "/theme/**"}, new ThemeChangeInterceptor());
}
}
}

View File

@ -1,98 +0,0 @@
/*
* Copyright 2002-2011 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.servlet.config;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import java.util.List;
import org.junit.Test;
import org.springframework.beans.DirectFieldAccessor;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Feature;
import org.springframework.context.annotation.FeatureConfiguration;
import org.springframework.core.io.Resource;
import org.springframework.web.servlet.handler.SimpleUrlHandlerMapping;
import org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter;
import org.springframework.web.servlet.resource.ResourceHttpRequestHandler;
/**
* Test fixture for {@link MvcResources} feature specification.
* @author Rossen Stoyanchev
* @since 3.1
*/
public class MvcResourcesTests {
@Test
public void testResources() {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.register(MvcResourcesFeature.class);
ctx.refresh();
HttpRequestHandlerAdapter adapter = ctx.getBean(HttpRequestHandlerAdapter.class);
assertNotNull(adapter);
ResourceHttpRequestHandler handler = ctx.getBean(ResourceHttpRequestHandler.class);
assertNotNull(handler);
@SuppressWarnings("unchecked")
List<Resource> locations = (List<Resource>) new DirectFieldAccessor(handler).getPropertyValue("locations");
assertNotNull(locations);
assertEquals(2, locations.size());
assertEquals("foo", locations.get(0).getFilename());
assertEquals("bar", locations.get(1).getFilename());
SimpleUrlHandlerMapping mapping = ctx.getBean(SimpleUrlHandlerMapping.class);
assertEquals(1, mapping.getOrder());
assertSame(handler, mapping.getHandlerMap().get("/resources/**"));
}
@Test
public void testInvalidResources() {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.register(InvalidMvcResourcesFeature.class);
try {
ctx.refresh();
fail("Invalid feature spec should not validate");
} catch (RuntimeException e) {
assertTrue(e.getCause().getMessage().contains("Mapping is required"));
// TODO : should all problems be in the message ?
}
}
@FeatureConfiguration
private static class MvcResourcesFeature {
@SuppressWarnings("unused")
@Feature
public MvcResources resources() {
return new MvcResources("/resources/**", new String[] { "/foo", "/bar" }).cachePeriod(86400).order(1);
}
}
@FeatureConfiguration
private static class InvalidMvcResourcesFeature {
@SuppressWarnings("unused")
@Feature
public MvcResources resources() {
return new MvcResources(" ", new String[] {});
}
}
}

View File

@ -1,131 +0,0 @@
/*
* Copyright 2002-2011 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.servlet.config;
import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import java.util.Map;
import org.junit.Test;
import org.springframework.beans.factory.parsing.FailFastProblemReporter;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Feature;
import org.springframework.context.annotation.FeatureConfiguration;
import org.springframework.web.servlet.handler.SimpleUrlHandlerMapping;
import org.springframework.web.servlet.mvc.ParameterizableViewController;
import org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter;
/**
* Test fixture for {@link MvcViewControllers} feature specification.
* @author Rossen Stoyanchev
* @since 3.1
*/
public class MvcViewControllersTests {
@Test
public void testMvcViewControllers() {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.register(MvcViewControllersFeature.class);
ctx.refresh();
SimpleControllerHandlerAdapter adapter = ctx.getBean(SimpleControllerHandlerAdapter.class);
assertNotNull(adapter);
SimpleUrlHandlerMapping handler = ctx.getBean(SimpleUrlHandlerMapping.class);
assertNotNull(handler);
Map<String, ?> urlMap = handler.getUrlMap();
assertNotNull(urlMap);
assertEquals(2, urlMap.size());
ParameterizableViewController controller = (ParameterizableViewController) urlMap.get("/");
assertNotNull(controller);
assertEquals("home", controller.getViewName());
controller = (ParameterizableViewController) urlMap.get("/account");
assertNotNull(controller);
assertNull(controller.getViewName());
}
@Test
public void testEmptyPath() {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.register(EmptyPathViewControllersFeature.class);
try {
ctx.refresh();
fail("expected exception");
} catch (Exception ex) {
assertTrue(ex.getCause().getMessage().contains("path attribute"));
}
}
@Test
public void testEmptyViewName() {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.register(EmptyViewNameViewControllersFeature.class);
try {
ctx.refresh();
fail("expected exception");
} catch (Exception ex) {
assertTrue(ex.getCause().getMessage().contains("not empty"));
}
}
@Test
public void testNullViewName() {
FailFastProblemReporter problemReporter = new FailFastProblemReporter();
assertThat(new MvcViewControllers("/some/path").validate(problemReporter), is(true));
}
@FeatureConfiguration
private static class MvcViewControllersFeature {
@SuppressWarnings("unused")
@Feature
public MvcViewControllers mvcViewControllers() {
return new MvcViewControllers("/", "home").viewController("/account");
}
}
@FeatureConfiguration
private static class EmptyViewNameViewControllersFeature {
@SuppressWarnings("unused")
@Feature
public MvcViewControllers mvcViewControllers() {
return new MvcViewControllers("/some/path", "");
}
}
@FeatureConfiguration
private static class EmptyPathViewControllersFeature {
@SuppressWarnings("unused")
@Feature
public MvcViewControllers mvcViewControllers() {
return new MvcViewControllers("", "someViewName");
}
}
}