Introduce FeatureSpecification support

Introduce FeatureSpecification interface and implementations

    FeatureSpecification objects decouple the configuration of
    spring container features from the concern of parsing XML
    namespaces, allowing for reuse in code-based configuration
    (see @Feature* annotations below).

    * ComponentScanSpec
    * TxAnnotationDriven
    * MvcAnnotationDriven
    * MvcDefaultServletHandler
    * MvcResources
    * MvcViewControllers

Refactor associated BeanDefinitionParsers to delegate to new impls above

    The following BeanDefinitionParser implementations now deal only
    with the concern of XML parsing.  Validation is handled by their
    corresponding FeatureSpecification object.  Bean definition creation
    and registration is handled by their corresponding
    FeatureSpecificationExecutor type.

    * ComponentScanBeanDefinitionParser
    * AnnotationDrivenBeanDefinitionParser (tx)
    * AnnotationDrivenBeanDefinitionParser (mvc)
    * DefaultServletHandlerBeanDefinitionParser
    * ResourcesBeanDefinitionParser
    * ViewControllerBeanDefinitionParser

Update AopNamespaceUtils to decouple from XML (DOM API)

    Methods necessary for executing TxAnnotationDriven specification
    (and eventually, the AspectJAutoProxy specification) have been
    added that accept boolean arguments for whether to proxy
    target classes and whether to expose the proxy via threadlocal.

    Methods that accepted and introspected DOM Element objects still
    exist but have been deprecated.

Introduce @FeatureConfiguration classes and @Feature methods

    Allow for creation and configuration of FeatureSpecification objects
    at the user level.  A companion for @Configuration classes allowing
    for completely code-driven configuration of the Spring container.

    See changes in ConfigurationClassPostProcessor for implementation
    details.

    See Feature*Tests for usage examples.

    FeatureTestSuite in .integration-tests is a JUnit test suite designed
    to aggregate all BDP and Feature* related tests for a convenient way
    to confirm that Feature-related changes don't break anything.
    Uncomment this test and execute from Eclipse / IDEA. Due to classpath
    issues, this cannot be compiled by Ant/Ivy at the command line.

Introduce @FeatureAnnotation meta-annotation and @ComponentScan impl

    @FeatureAnnotation provides an alternate mechanism for creating
    and executing FeatureSpecification objects.  See @ComponentScan
    and its corresponding ComponentScanAnnotationParser implementation
    for details.  See ComponentScanAnnotationIntegrationTests for usage
    examples

Introduce Default[Formatting]ConversionService implementations

    Allows for convenient instantiation of ConversionService objects
    containing defaults appropriate for most environments.  Replaces
    similar support originally in ConversionServiceFactory (which is now
    deprecated). This change was justified by the need to avoid use
    of FactoryBeans in @Configuration classes (such as
    FormattingConversionServiceFactoryBean). It is strongly preferred
    that users simply instantiate and configure the objects that underlie
    our FactoryBeans. In the case of the ConversionService types, the
    easiest way to do this is to create Default* subtypes. This also
    follows convention with the rest of the framework.

Minor updates to util classes

    All in service of changes above. See diffs for self-explanatory
    details.

    * BeanUtils
    * ObjectUtils
    * ReflectionUtils
This commit is contained in:
Chris Beams
2011-02-08 14:42:33 +00:00
parent b04987ccc3
commit b4fea47d5c
127 changed files with 7397 additions and 1132 deletions

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2010 the original author or authors.
* 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.
@@ -141,5 +141,4 @@ public class AnnotatedBeanDefinitionReader {
definitionHolder = AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
BeanDefinitionReaderUtils.registerBeanDefinition(definitionHolder, this.registry);
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2010 the original author or authors.
* 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.
@@ -37,10 +37,9 @@ import org.springframework.util.Assert;
*/
public class AnnotationScopeMetadataResolver implements ScopeMetadataResolver {
private Class<? extends Annotation> scopeAnnotationType = Scope.class;
private final ScopedProxyMode defaultProxyMode;
protected Class<? extends Annotation> scopeAnnotationType = Scope.class;
private final ScopedProxyMode defaultProxyMode;
/**
* Create a new instance of the <code>AnnotationScopeMetadataResolver</code> class.

View File

@@ -0,0 +1,51 @@
/*
* 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.Method;
import org.springframework.core.annotation.AnnotationUtils;
/**
* Utilities for processing {@link Bean}-annotated methods.
*
* @author Chris Beams
* @since 3.1
*/
class BeanAnnotationHelper {
/**
* Return whether the given method is annotated directly or indirectly with @Bean.
*/
public static boolean isBeanAnnotated(Method method) {
return AnnotationUtils.findAnnotation(method, Bean.class) != null;
}
public static String determineBeanNameFor(Method beanMethod) {
// by default the bean name is the name of the @Bean-annotated method
String beanName = beanMethod.getName();
// check to see if the user has explicitly set the bean name
Bean bean = AnnotationUtils.findAnnotation(beanMethod, Bean.class);
if (bean != null && bean.name().length > 0) {
beanName = bean.name()[0];
}
return beanName;
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2009 the original author or authors.
* 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.
@@ -208,9 +208,10 @@ public class ClassPathBeanDefinitionScanner extends ClassPathScanningCandidateCo
* <p>This method does <i>not</i> register an annotation config processor
* but rather leaves this up to the caller.
* @param basePackages the packages to check for annotated classes
* @return number of beans registered
* @return set of beans registered if any for tooling registration purposes (never {@code null})
*/
protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
Assert.notEmpty(basePackages, "At least one base package must be specified");
Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<BeanDefinitionHolder>();
for (String basePackage : basePackages) {
Set<BeanDefinition> candidates = findCandidateComponents(basePackage);

View File

@@ -69,8 +69,7 @@ import org.springframework.util.ClassUtils;
*/
public class ClassPathScanningCandidateComponentProvider implements EnvironmentCapable, ResourceLoaderAware {
private static final String DEFAULT_RESOURCE_PATTERN = "**/*.class";
static final String DEFAULT_RESOURCE_PATTERN = "**/*.class";
protected final Log logger = LogFactory.getLog(getClass());

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2010 the original author or authors.
* 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.
@@ -16,59 +16,137 @@
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.beans.factory.support.BeanNameGenerator;
import org.springframework.core.type.filter.TypeFilter;
/**
* Configures component scanning directives for use with {@link Configuration}
* classes. Provides support parallel with Spring XML's
* {@code <context:component-scan>} element.
* Configures component scanning directives for use with {@link Configuration @Configuration}
* classes. Provides support parallel with Spring XML's {@code <context:component-scan>}
* element.
*
* TODO SPR-7508: complete documentation.
* <p>One of {@link #basePackageClasses()}, {@link #basePackages()} or its alias {@link #value()}
* must be specified.
*
* <p>Note that the {@code <context:component-scan>} element has an {@code annotation-config}
* attribute, however this annotation does not. This is because in almost all cases when
* using {@code @ComponentScan}, default annotation config processing (e.g.
* processing {@code @Autowired} and friends) is assumed. Furthermore, when using
* {@link AnnotationConfigApplicationContext}, annotation config processors are always
* registered, meaning that any attempt to disable them at the {@code @ComponentScan} level
* would be ignored.
*
* @author Chris Beams
* @since 3.1
* @see FilterType
* @see Configuration
*/
@Documented
@FeatureAnnotation(parser=ComponentScanAnnotationParser.class)
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface ComponentScan {
/** base packages to scan */
/**
* Alias for the {@link #basePackages()} attribute.
* Allows for more concise annotation declarations e.g.:
* {@code @ComponentScan("org.my.pkg")} instead of
* {@code @ComponentScan(basePackages="org.my.pkg")}.
*/
String[] value() default {};
Class<?>[] packageOf() default Void.class;
/**
* Base packages to scan for annotated components.
* <p>{@link #value()} is an alias for (and mutually exclusive with) this attribute.
* <p>Use {@link #basePackageClasses()} for a type-safe alternative to String-based package names.
*/
String[] basePackages() default {};
/**
* Type-safe alternative to {@link #basePackages()} for specifying the packages
* to scan for annotated components. The package of each class specified will be scanned.
* <p>Consider creating a special no-op marker class or interface in each package
* that serves no purpose other than being referenced by this attribute.
*/
Class<?>[] basePackageClasses() default {};
/**
* The {@link BeanNameGenerator} class to be used for naming detected components
* within the Spring container.
*/
Class<? extends BeanNameGenerator> nameGenerator() default AnnotationBeanNameGenerator.class;
/**
* The {@link ScopeMetadataResolver} to be used for resolving the scope of detected components.
*/
Class<? extends ScopeMetadataResolver> scopeResolver() default AnnotationScopeMetadataResolver.class;
String resourcePattern() default "**/*.class";
/**
* Indicates whether proxies should be generated for detected components, which may be
* necessary when using scopes in a proxy-style fashion.
* <p>The default is defer to the default behavior of the component scanner used to
* execute the actual scan.
* @see ClassPathBeanDefinitionScanner#setScopedProxyMode(ScopedProxyMode)
*/
ScopedProxyMode scopedProxy() default ScopedProxyMode.DEFAULT;
/**
* Controls the class files eligible for component detection.
* <p>Consider use of {@link #includeFilters()} and {@link #excludeFilters()}
* for a more flexible approach.
*/
String resourcePattern() default ClassPathScanningCandidateComponentProvider.DEFAULT_RESOURCE_PATTERN;
/**
* Indicates whether automatic detection of classes annotated with {@code @Component}
* {@code @Repository}, {@code @Service}, or {@code @Controller} should be enabled.
*/
boolean useDefaultFilters() default true;
IncludeFilter[] includeFilters() default {};
/**
* Specifies which types are eligible for component scanning.
* <p>Further narrows the set of candidate components from everything in
* {@link #basePackages()} to everything in the base packages that matches
* the given filter or filters.
* @see #resourcePattern()
*/
Filter[] includeFilters() default {};
ExcludeFilter[] excludeFilters() default {};
/**
* Specifies which types are not eligible for component scanning.
* @see #resourcePattern()
*/
Filter[] excludeFilters() default {};
/**
* Declares the type filter to be used as an {@linkplain ComponentScan#includeFilters()
* include filter} or {@linkplain ComponentScan#includeFilters() exclude filter}.
*/
@Retention(RetentionPolicy.SOURCE)
@interface IncludeFilter {
@interface Filter {
/**
* The type of filter to use.
* <p>Note that the filter types available are limited to those that may
* be expressed as a {@code Class} in the {@link #value()} attribute. This is
* in contrast to {@code <context:component-scan/>}, which allows for
* expression-based (i.e., string-based) filters such as AspectJ pointcuts.
* These filter types are intentionally not supported here, and not available
* in the {@link FilterType} enum.
* @see FilterType
*/
FilterType type() default FilterType.ANNOTATION;
Class<?> value();
}
@Retention(RetentionPolicy.SOURCE)
@interface ExcludeFilter {
FilterType type() default FilterType.ANNOTATION;
/**
* The class to use as the filter. In the case of {@link FilterType#ANNOTATION},
* the class will be the annotation itself. In the case of
* {@link FilterType#ASSIGNABLE_TYPE}, the class will be the type that detected
* components should be assignable to. And in the case of {@link FilterType#CUSTOM},
* the class will be an implementation of {@link TypeFilter}.
*/
Class<?> value();
}

View File

@@ -0,0 +1,97 @@
/*
* 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-2009 the original author or authors.
* 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.
@@ -16,264 +16,80 @@
package org.springframework.context.annotation;
import java.lang.annotation.Annotation;
import java.util.Set;
import java.util.regex.Pattern;
import org.springframework.beans.factory.config.BeanDefinition;
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.config.ExecutorContext;
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 &lt;context:component-scan/&gt; element.
*
* Parser for the {@code <context:component-scan/>} element. Parsed metadata is
* used to populate and execute a {@link ComponentScanSpec} instance.
*
* @author Mark Fisher
* @author Ramnivas Laddad
* @author Juergen Hoeller
* @author Chris Beams
* @since 2.5
* @see ComponentScan
* @see ComponentScanSpec
* @see ComponentScanExecutor
*/
public class ComponentScanBeanDefinitionParser implements BeanDefinitionParser {
private static final String BASE_PACKAGE_ATTRIBUTE = "base-package";
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();
ClassLoader classLoader = readerContext.getResourceLoader().getClassLoader();
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.setEnvironment(parserContext.getDelegate().getEnvironment());
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) {
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"));
// 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);
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);
}
String filterType = ((Element)node).getAttribute("type");
String expression = ((Element)node).getAttribute("expression");
if ("include-filter".equals(localName)) {
spec.addIncludeFilter(filterType, expression, classLoader);
}
catch (Exception ex) {
readerContext.error(ex.getMessage(), readerContext.extractSource(element), ex.getCause());
else if ("exclude-filter".equals(localName)) {
spec.addExcludeFilter(filterType, expression, classLoader);
}
}
}
spec.beanDefinitionDefaults(parserContext.getDelegate().getBeanDefinitionDefaults())
.autowireCandidatePatterns(parserContext.getDelegate().getAutowireCandidatePatterns())
.source(readerContext.extractSource(element))
.sourceName(element.getTagName())
.execute(createExecutorContext(parserContext));
return null;
}
@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;
// Adapt the given ParserContext instance into an ExecutorContext.
// TODO SPR-7420: create a common ParserContext-to-ExecutorContext adapter utility
// or otherwise unify these two types
private ExecutorContext createExecutorContext(ParserContext parserContext) {
ExecutorContext executorContext = new ExecutorContext();
executorContext.setRegistry(parserContext.getRegistry());
executorContext.setRegistrar(parserContext);
executorContext.setResourceLoader(parserContext.getReaderContext().getResourceLoader());
executorContext.setEnvironment(parserContext.getDelegate().getEnvironment());
executorContext.setProblemReporter(parserContext.getReaderContext().getProblemReporter());
return executorContext;
}
}

View File

@@ -0,0 +1,110 @@
/*
* 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.ExecutorContext;
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, ExecutorContext executorContext) {
BeanDefinitionRegistry registry = executorContext.getRegistry();
ResourceLoader resourceLoader = executorContext.getResourceLoader();
Environment environment = executorContext.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));
}
}
executorContext.getRegistrar().registerComponent(compositeDef);
}
}

View File

@@ -0,0 +1,460 @@
/*
* 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.SimpleProblemCollector;
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(SimpleProblemCollector 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, SimpleProblemCollector 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(SimpleProblemCollector 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

@@ -59,7 +59,7 @@ import org.springframework.stereotype.Component;
* @see AnnotationConfigApplicationContext
* @see org.springframework.context.annotation.Profile
*/
@Target({ElementType.TYPE})
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component

View File

@@ -175,5 +175,6 @@ final class ConfigurationClass {
getSimpleName(), count, methodName), new Location(getResource(), getMetadata()));
}
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2010 the original author or authors.
* 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.
@@ -27,12 +27,13 @@ import java.util.Set;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition;
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;
@@ -43,23 +44,28 @@ 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.ExecutorContext;
import org.springframework.context.config.FeatureSpecification;
import org.springframework.core.Conventions;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.core.env.Environment;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.core.type.MethodMetadata;
import org.springframework.core.type.StandardAnnotationMetadata;
import org.springframework.core.type.classreading.MetadataReader;
import org.springframework.core.type.classreading.MetadataReaderFactory;
import org.springframework.core.type.classreading.SimpleMetadataReaderFactory;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
/**
* Reads a given fully-populated configuration model, registering bean definitions
* with the given {@link BeanDefinitionRegistry} based on its contents.
* Reads a given fully-populated set of ConfigurationClass instances, registering bean
* definitions with the given {@link BeanDefinitionRegistry} based on its contents.
*
* <p>This class was modeled after the {@link BeanDefinitionReader} hierarchy, but does
* not implement/extend any of its artifacts as a configuration model is not a {@link Resource}.
* not implement/extend any of its artifacts as a set of configuration classes is not a
* {@link Resource}.
*
* @author Chris Beams
* @author Juergen Hoeller
@@ -85,6 +91,7 @@ class ConfigurationClassBeanDefinitionReader {
private final MetadataReaderFactory metadataReaderFactory;
private ExecutorContext executorContext;
/**
* Create a new {@link ConfigurationClassBeanDefinitionReader} instance that will be used
@@ -92,13 +99,20 @@ class ConfigurationClassBeanDefinitionReader {
* @param problemReporter
* @param metadataReaderFactory
*/
public ConfigurationClassBeanDefinitionReader(BeanDefinitionRegistry registry, SourceExtractor sourceExtractor,
ProblemReporter problemReporter, MetadataReaderFactory metadataReaderFactory) {
public ConfigurationClassBeanDefinitionReader(final BeanDefinitionRegistry registry, SourceExtractor sourceExtractor,
ProblemReporter problemReporter, MetadataReaderFactory metadataReaderFactory,
ResourceLoader resourceLoader, Environment environment) {
this.registry = registry;
this.sourceExtractor = sourceExtractor;
this.problemReporter = problemReporter;
this.metadataReaderFactory = metadataReaderFactory;
this.executorContext = new ExecutorContext();
this.executorContext.setRegistry(this.registry);
this.executorContext.setRegistrar(new SimpleComponentRegistrar(this.registry));
this.executorContext.setResourceLoader(resourceLoader);
this.executorContext.setEnvironment(environment);
this.executorContext.setProblemReporter(problemReporter);
}
@@ -117,15 +131,38 @@ class ConfigurationClassBeanDefinitionReader {
* class itself, all its {@link Bean} methods
*/
private void loadBeanDefinitionsForConfigurationClass(ConfigurationClass configClass) {
AnnotationMetadata metadata = configClass.getMetadata();
processFeatureAnnotations(metadata);
doLoadBeanDefinitionForConfigurationClassIfNecessary(configClass);
for (ConfigurationClassMethod method : configClass.getMethods()) {
loadBeanDefinitionsForModelMethod(method);
for (ConfigurationClassMethod beanMethod : configClass.getMethods()) {
loadBeanDefinitionsForBeanMethod(beanMethod);
}
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.executorContext);
}
}
} catch (BeanDefinitionParsingException ex) {
throw ex;
}
catch (Exception ex) {
// TODO SPR-7420: what exception to throw?
throw new RuntimeException(ex);
}
}
/**
* Registers the {@link Configuration} class itself as a bean definition.
* Register the {@link Configuration} class itself as a bean definition.
*/
private void doLoadBeanDefinitionForConfigurationClassIfNecessary(ConfigurationClass configClass) {
if (configClass.getBeanName() != null) {
@@ -161,9 +198,9 @@ class ConfigurationClassBeanDefinitionReader {
* Read a particular {@link ConfigurationClassMethod}, registering bean definitions
* with the BeanDefinitionRegistry based on its contents.
*/
private void loadBeanDefinitionsForModelMethod(ConfigurationClassMethod method) {
ConfigurationClass configClass = method.getConfigurationClass();
MethodMetadata metadata = method.getMetadata();
private void loadBeanDefinitionsForBeanMethod(ConfigurationClassMethod beanMethod) {
ConfigurationClass configClass = beanMethod.getConfigurationClass();
MethodMetadata metadata = beanMethod.getMetadata();
RootBeanDefinition beanDef = new ConfigurationClassBeanDefinition(configClass);
beanDef.setResource(configClass.getResource());
@@ -176,7 +213,7 @@ class ConfigurationClassBeanDefinitionReader {
// consider name and any aliases
Map<String, Object> beanAttributes = metadata.getAnnotationAttributes(Bean.class.getName());
List<String> names = new ArrayList<String>(Arrays.asList((String[]) beanAttributes.get("name")));
String beanName = (names.size() > 0 ? names.remove(0) : method.getMetadata().getMethodName());
String beanName = (names.size() > 0 ? names.remove(0) : beanMethod.getMetadata().getMethodName());
for (String alias : names) {
this.registry.registerAlias(beanName, alias);
}
@@ -190,7 +227,7 @@ class ConfigurationClassBeanDefinitionReader {
// overriding is legal, return immediately
if (logger.isDebugEnabled()) {
logger.debug(String.format("Skipping loading bean definition for %s: a definition for bean " +
"'%s' already exists. This is likely due to an override in XML.", method, beanName));
"'%s' already exists. This is likely due to an override in XML.", beanMethod, beanName));
}
return;
}
@@ -255,7 +292,8 @@ class ConfigurationClassBeanDefinitionReader {
registry.registerBeanDefinition(beanName, beanDefToRegister);
}
private void loadBeanDefinitionsFromImportedResources(Map<String, Class<?>> importedResources) {
Map<Class<?>, BeanDefinitionReader> readerInstanceCache = new HashMap<Class<?>, BeanDefinitionReader>();
for (Map.Entry<String, Class<?>> entry : importedResources.entrySet()) {
@@ -357,7 +395,7 @@ class ConfigurationClassBeanDefinitionReader {
@Override
public boolean isFactoryMethod(Method candidate) {
return (super.isFactoryMethod(candidate) && AnnotationUtils.findAnnotation(candidate, Bean.class) != null);
return (super.isFactoryMethod(candidate) && BeanAnnotationHelper.isBeanAnnotated(candidate));
}
@Override
@@ -372,14 +410,12 @@ class ConfigurationClassBeanDefinitionReader {
* declare at least one {@link Bean @Bean} method.
*/
private static class InvalidConfigurationImportProblem extends Problem {
public InvalidConfigurationImportProblem(String className, Resource resource, AnnotationMetadata metadata) {
super(String.format("%s was imported as a Configuration class but is not annotated " +
"with @Configuration nor does it declare any @Bean methods. Update the class to " +
"meet either of these requirements or do not attempt to import it.", className),
super(String.format("%s was @Import'ed as a but is not annotated with @FeatureConfiguration or " +
"@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

@@ -74,7 +74,7 @@ class ConfigurationClassEnhancer {
// handling a @Bean-annotated method; otherwise, return index of the NoOp callback.
callbackFilter = new CallbackFilter() {
public int accept(Method candidateMethod) {
return (AnnotationUtils.findAnnotation(candidateMethod, Bean.class) != null ? 0 : 1);
return (BeanAnnotationHelper.isBeanAnnotated(candidateMethod) ? 0 : 1);
}
};
}
@@ -162,19 +162,21 @@ 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 obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
// by default the bean name is the name of the @Bean-annotated method
String beanName = method.getName();
public Object intercept(Object enhancedConfigInstance, Method beanMethod, Object[] beanMethodArgs,
MethodProxy cglibMethodProxy) throws ProxyCreationException, Throwable {
// check to see if the user has explicitly set the bean name
Bean bean = AnnotationUtils.findAnnotation(method, Bean.class);
if (bean != null && bean.name().length > 0) {
beanName = bean.name()[0];
}
String beanName = BeanAnnotationHelper.determineBeanNameFor(beanMethod);
// determine whether this bean is a scoped-proxy
Scope scope = AnnotationUtils.findAnnotation(method, Scope.class);
Scope scope = AnnotationUtils.findAnnotation(beanMethod, Scope.class);
if (scope != null && scope.proxyMode() != ScopedProxyMode.NO) {
String scopedBeanName = ScopedProxyCreator.getTargetBeanName(beanName);
if (this.beanFactory.isCurrentlyInCreation(scopedBeanName)) {
@@ -206,8 +208,7 @@ class ConfigurationClassEnhancer {
return this.beanFactory.getBean(beanName);
}
// no cached instance of the bean exists - actually create and return the bean
return proxy.invokeSuper(obj, args);
return cglibMethodProxy.invokeSuper(enhancedConfigInstance, beanMethodArgs);
}
/**

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2010 the original author or authors.
* 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.
@@ -67,7 +67,7 @@ final class ConfigurationClassMethod {
}
}
}
@Override
public String toString() {
return String.format("[%s:name=%s,declaringClass=%s]",

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2010 the original author or authors.
* 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.
@@ -16,9 +16,15 @@
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;
@@ -26,10 +32,12 @@ 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;
@@ -38,12 +46,24 @@ import org.springframework.beans.factory.support.AbstractBeanDefinition;
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.ExecutorContext;
import org.springframework.context.config.FeatureSpecification;
import org.springframework.context.config.SourceAwareSpecification;
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
@@ -63,7 +83,7 @@ import org.springframework.util.ClassUtils;
* @since 3.0
*/
public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPostProcessor,
BeanClassLoaderAware, EnvironmentAware {
ResourceLoaderAware, BeanClassLoaderAware, EnvironmentAware {
/** Whether the CGLIB2 library is present on the classpath */
private static final boolean cglibAvailable = ClassUtils.isPresent(
@@ -76,6 +96,8 @@ public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPo
private ProblemReporter problemReporter = new FailFastProblemReporter();
private ResourceLoader resourceLoader = new DefaultResourceLoader();
private ClassLoader beanClassLoader = ClassUtils.getDefaultClassLoader();
private MetadataReaderFactory metadataReaderFactory = new CachingMetadataReaderFactory();
@@ -88,6 +110,8 @@ public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPo
private Environment environment;
private ConfigurationClassBeanDefinitionReader reader;
/**
* Set the {@link SourceExtractor} to use for generated bean definitions
@@ -118,6 +142,11 @@ public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPo
this.setMetadataReaderFactoryCalled = true;
}
public void setResourceLoader(ResourceLoader resourceLoader) {
Assert.notNull(resourceLoader, "ResourceLoader must not be null");
this.resourceLoader = resourceLoader;
}
public void setBeanClassLoader(ClassLoader beanClassLoader) {
this.beanClassLoader = beanClassLoader;
if (!this.setMetadataReaderFactoryCalled) {
@@ -126,6 +155,7 @@ public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPo
}
public void setEnvironment(Environment environment) {
Assert.notNull(environment, "Environment must not be null");
this.environment = environment;
}
@@ -143,7 +173,7 @@ public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPo
"postProcessBeanFactory already called for this post-processor");
}
this.postProcessBeanDefinitionRegistryCalled = true;
processConfigBeanDefinitions(registry);
processConfigurationClasses(registry);
}
/**
@@ -158,18 +188,196 @@ public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPo
this.postProcessBeanFactoryCalled = true;
if (!this.postProcessBeanDefinitionRegistryCalled) {
// BeanDefinitionRegistryPostProcessor hook apparently not supported...
// Simply call processConfigBeanDefinitions lazily at this point then.
processConfigBeanDefinitions((BeanDefinitionRegistry) beanFactory);
// Simply call processConfigurationClasses lazily at this point then.
processConfigurationClasses((BeanDefinitionRegistry)beanFactory);
}
enhanceConfigurationClasses(beanFactory);
}
/**
* Find and process all @Configuration classes with @Feature methods in the given registry.
*/
private void processConfigurationClasses(BeanDefinitionRegistry registry) {
ConfigurationClassBeanDefinitionReader reader = getConfigurationClassBeanDefinitionReader(registry);
processConfigBeanDefinitions(registry, reader);
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 ExecutorContext executorContext = createExecutorContext(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, executorContext, 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,
ExecutorContext executorContext, EarlyBeanReferenceProxyCreator proxyCreator) {
try {
// get the return type
if (!(FeatureSpecification.class.isAssignableFrom(featureMethod.getReturnType()))) {
// TODO SPR-7420: raise a Problem instead?
throw new IllegalArgumentException(
"return type from @Feature methods must be assignable to FeatureSpecification");
}
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.createProxy(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(executorContext);
} catch (Exception ex) {
throw new FeatureMethodExecutionException(ex);
}
}
private ExecutorContext createExecutorContext(ConfigurableListableBeanFactory beanFactory) {
final BeanDefinitionRegistry registry = (BeanDefinitionRegistry) beanFactory;
ExecutorContext executorContext = new ExecutorContext();
executorContext.setEnvironment(this.environment);
executorContext.setResourceLoader(this.resourceLoader);
executorContext.setRegistry(registry);
executorContext.setRegistrar(new SimpleComponentRegistrar(registry));
// TODO SPR-7420: how to get hold of the current problem reporter here?
executorContext.setProblemReporter(new FailFastProblemReporter());
return executorContext;
}
private ConfigurationClassBeanDefinitionReader getConfigurationClassBeanDefinitionReader(BeanDefinitionRegistry registry) {
if (this.reader == null) {
this.reader = new ConfigurationClassBeanDefinitionReader(
registry, this.sourceExtractor, this.problemReporter, this.metadataReaderFactory, this.resourceLoader, this.environment);
}
return this.reader;
}
/**
* Build and validate a configuration model based on the registry of
* {@link Configuration} classes.
*/
public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
public void processConfigBeanDefinitions(BeanDefinitionRegistry registry, ConfigurationClassBeanDefinitionReader reader) {
Set<BeanDefinitionHolder> configCandidates = new LinkedHashSet<BeanDefinitionHolder>();
for (String beanName : registry.getBeanDefinitionNames()) {
BeanDefinition beanDef = registry.getBeanDefinition(beanName);
@@ -202,8 +410,6 @@ public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPo
parser.validate();
// Read the model and create bean definitions based on its content
ConfigurationClassBeanDefinitionReader reader = new ConfigurationClassBeanDefinitionReader(
registry, this.sourceExtractor, this.problemReporter, this.metadataReaderFactory);
reader.loadBeanDefinitions(parser.getConfigurationClasses());
}

View File

@@ -0,0 +1,30 @@
/*
* 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

@@ -0,0 +1,256 @@
/*
* 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 FINAL_CLASS_ERROR_MESSAGE =
"Cannot create subclass proxy for bean type %s because it is a final class. " +
"Make the class non-final or inject the bean by interface rather than by concrete class.";
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.
*/
public Object createProxy(DependencyDescriptor 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");
if ((clazz.getModifiers() & Modifier.FINAL) != 0) {
throw new ProxyCreationException(String.format(FINAL_CLASS_ERROR_MESSAGE, clazz.getName()));
}
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

@@ -0,0 +1,34 @@
/*
* 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

@@ -0,0 +1,45 @@
/*
* 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

@@ -0,0 +1,61 @@
/*
* 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

@@ -0,0 +1,54 @@
/*
* 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

@@ -0,0 +1,28 @@
/*
* 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

@@ -0,0 +1,26 @@
/*
* 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

@@ -0,0 +1,46 @@
/*
* 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;
public 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

@@ -0,0 +1,77 @@
/*
* 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.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) {
SimpleProblemCollector collector = new SimpleProblemCollector(this.source());
this.doValidate(collector);
collector.reportProblems(problemReporter);
return collector.hasErrors() ? false : true;
}
protected abstract void doValidate(SimpleProblemCollector reporter);
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(ExecutorContext executorContext) {
FeatureSpecificationExecutor executor =
BeanUtils.instantiateClass(this.executorType, FeatureSpecificationExecutor.class);
executor.execute(this, executorContext);
}
}

View File

@@ -0,0 +1,54 @@
/*
* 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, ExecutorContext executorContext) {
Assert.notNull(spec, "Specification must not be null");
Assert.notNull(spec, "ExecutorContext 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(executorContext.getProblemReporter())) {
doExecute((S)spec, executorContext);
}
}
/**
* 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, ExecutorContext executorContext);
}

View File

@@ -0,0 +1,28 @@
/*
* 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 enum AdviceMode {
PROXY,
ASPECTJ
}

View File

@@ -0,0 +1,81 @@
/*
* 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 ExecutorContext {
private BeanDefinitionRegistry registry;
private ComponentRegistrar registrar;
private ResourceLoader resourceLoader;
private Environment environment;
private ProblemReporter problemReporter;
public ExecutorContext() { }
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

@@ -0,0 +1,127 @@
/*
* 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 by bean 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 featureset
* 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(Specification)
* @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(ExecutorContext executorContext);
}

View File

@@ -0,0 +1,41 @@
/*
* 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, ExecutorContext executorContext);
}

View File

@@ -0,0 +1,35 @@
/*
* 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,5 +1,5 @@
/*
* Copyright 2002-2010 the original author or authors.
* 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.
@@ -22,16 +22,30 @@ import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.support.ConversionServiceFactory;
import org.springframework.core.convert.support.DefaultConversionService;
import org.springframework.core.convert.support.GenericConversionService;
/**
* A factory for a ConversionService that installs default converters appropriate for most environments.
* Set the {@link #setConverters "converters"} property to supplement or override the default converters.
* A factory providing convenient access to a ConversionService configured with
* converters appropriate for most environments. Set the {@link #setConverters
* "converters"} property to supplement the default converters.
*
* <p>This implementation creates a {@link DefaultConversionService}. Subclasses
* may override {@link #createConversionService()} in order to return a
* {@link GenericConversionService} instance of their choosing.
*
* <p>Like all {@code FactoryBean} implementations, this class is suitable for
* use when configuring a Spring application context using Spring {@code <beans>}
* XML. When configuring the container with
* {@link org.springframework.context.annotation.Configuration @Configuration}
* classes, simply instantiate, configure and return the appropriate
* {@code ConversionService} object from a {@link
* org.springframework.context.annotation.Bean @Bean} method.
*
* @author Keith Donald
* @author Juergen Hoeller
* @author Chris Beams
* @since 3.0
* @see ConversionServiceFactory#createDefaultConversionService()
*/
public class ConversionServiceFactoryBean implements FactoryBean<ConversionService>, InitializingBean {
@@ -52,17 +66,17 @@ public class ConversionServiceFactoryBean implements FactoryBean<ConversionServi
public void afterPropertiesSet() {
this.conversionService = createConversionService();
ConversionServiceFactory.addDefaultConverters(this.conversionService);
ConversionServiceFactory.registerConverters(this.converters, this.conversionService);
}
/**
* Create the ConversionService instance returned by this factory bean.
* <p>Creates a simple {@link GenericConversionService} instance by default.
* Subclasses may override to customize the ConversionService instance that gets created.
* Subclasses may override to customize the ConversionService instance that
* gets created.
*/
protected GenericConversionService createConversionService() {
return new GenericConversionService();
return new DefaultConversionService();
}

View File

@@ -0,0 +1,135 @@
/*
* 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.format.support;
import java.util.Calendar;
import java.util.Collections;
import java.util.Date;
import java.util.HashSet;
import java.util.Set;
import org.springframework.core.convert.support.DefaultConversionService;
import org.springframework.format.AnnotationFormatterFactory;
import org.springframework.format.FormatterRegistry;
import org.springframework.format.Parser;
import org.springframework.format.Printer;
import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.format.datetime.joda.JodaTimeFormatterRegistrar;
import org.springframework.format.number.NumberFormatAnnotationFormatterFactory;
import org.springframework.util.ClassUtils;
import org.springframework.util.StringValueResolver;
/**
* A specialization of {@link FormattingConversionService} configured by default with
* converters and formatters appropriate for most applications.
*
* <p>Designed for direct instantiation but also exposes the static {@link #addDefaultFormatters}
* utility method for ad hoc use against any {@code FormatterRegistry} instance, just
* as {@code DefaultConversionService} exposes its own
* {@link DefaultConversionService#addDefaultConverters addDefaultConverters} method.
*
* @author Chris Beams
* @since 3.1
*/
public class DefaultFormattingConversionService extends FormattingConversionService {
private static final boolean jodaTimePresent = ClassUtils.isPresent(
"org.joda.time.LocalDate", DefaultFormattingConversionService.class.getClassLoader());
/**
* Create a new {@code DefaultFormattingConversionService} with the set of
* {@linkplain DefaultConversionService#addDefaultConverters default converters} and
* {@linkplain #addDefaultFormatters default formatters}.
*/
public DefaultFormattingConversionService() {
this(null, true);
}
/**
* Create a new {@code DefaultFormattingConversionService} with the set of
* {@linkplain DefaultConversionService#addDefaultConverters default converters} and,
* based on the value of {@code registerDefaultFormatters}, the set of
* {@linkplain #addDefaultFormatters default formatters}.
* @param registerDefaultFormatters whether to register default formatters
*/
public DefaultFormattingConversionService(boolean registerDefaultFormatters) {
this(null, registerDefaultFormatters);
}
/**
* Create a new {@code DefaultFormattingConversionService} with the set of
* {@linkplain DefaultConversionService#addDefaultConverters default converters} and,
* based on the value of {@code registerDefaultFormatters}, the set of
* {@linkplain #addDefaultFormatters default formatters}
* @param embeddedValueResolver delegated to {@link #setEmbeddedValueResolver(StringValueResolver)}
* prior to calling {@link #addDefaultFormatters}.
* @param registerDefaultFormatters whether to register default formatters
*/
public DefaultFormattingConversionService(StringValueResolver embeddedValueResolver, boolean registerDefaultFormatters) {
this.setEmbeddedValueResolver(embeddedValueResolver);
DefaultConversionService.addDefaultConverters(this);
if (registerDefaultFormatters) {
addDefaultFormatters(this);
}
}
/**
* Add formatters appropriate for most environments, including number formatters and a Joda-Time
* date formatter if Joda-Time is present on the classpath.
* @param formatterRegistry the service to register default formatters against
*/
public static void addDefaultFormatters(FormatterRegistry formatterRegistry) {
formatterRegistry.addFormatterForFieldAnnotation(new NumberFormatAnnotationFormatterFactory());
if (jodaTimePresent) {
new JodaTimeFormatterRegistrar().registerFormatters(formatterRegistry);
} else {
formatterRegistry.addFormatterForFieldAnnotation(new NoJodaDateTimeFormatAnnotationFormatterFactory());
}
}
/**
* Dummy AnnotationFormatterFactory that simply fails if @DateTimeFormat is being used
* without the JodaTime library being present.
*/
private static final class NoJodaDateTimeFormatAnnotationFormatterFactory
implements AnnotationFormatterFactory<DateTimeFormat> {
private final Set<Class<?>> fieldTypes;
public NoJodaDateTimeFormatAnnotationFormatterFactory() {
Set<Class<?>> rawFieldTypes = new HashSet<Class<?>>(4);
rawFieldTypes.add(Date.class);
rawFieldTypes.add(Calendar.class);
rawFieldTypes.add(Long.class);
this.fieldTypes = Collections.unmodifiableSet(rawFieldTypes);
}
public Set<Class<?>> getFieldTypes() {
return this.fieldTypes;
}
public Printer<?> getPrinter(DateTimeFormat annotation, Class<?> fieldType) {
throw new IllegalStateException("JodaTime library not available - @DateTimeFormat not supported");
}
public Parser<?> getParser(DateTimeFormat annotation, Class<?> fieldType) {
throw new IllegalStateException("JodaTime library not available - @DateTimeFormat not supported");
}
}
}

View File

@@ -16,10 +16,6 @@
package org.springframework.format.support;
import java.util.Calendar;
import java.util.Collections;
import java.util.Date;
import java.util.HashSet;
import java.util.Set;
import org.springframework.beans.factory.FactoryBean;
@@ -32,48 +28,51 @@ import org.springframework.format.FormatterRegistrar;
import org.springframework.format.FormatterRegistry;
import org.springframework.format.Parser;
import org.springframework.format.Printer;
import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.format.datetime.joda.JodaTimeFormatterRegistrar;
import org.springframework.format.number.NumberFormatAnnotationFormatterFactory;
import org.springframework.util.ClassUtils;
import org.springframework.util.StringValueResolver;
/**
* <p>A factory for a {@link FormattingConversionService} that installs default
* converters and formatters for common types such as numbers and datetimes.
*
* <p>Converters and formatters can be registered declaratively through
* A factory providing convenient access to a {@code FormattingConversionService}
* configured with converters and formatters for common types such as numbers and
* datetimes.
*
* <p>Additional converters and formatters can be registered declaratively through
* {@link #setConverters(Set)} and {@link #setFormatters(Set)}. Another option
* is to register converters and formatters in code by implementing the
* {@link FormatterRegistrar} interface. You can then configure provide the set
* is to register converters and formatters in code by implementing the
* {@link FormatterRegistrar} interface. You can then configure provide the set
* of registrars to use through {@link #setFormatterRegistrars(Set)}.
*
* <p>A good example for registering converters and formatters in code is
* <code>JodaTimeFormatterRegistrar</code>, which registers a number of
*
* <p>A good example for registering converters and formatters in code is
* <code>JodaTimeFormatterRegistrar</code>, which registers a number of
* date-related formatters and converters. For a more detailed list of cases
* see {@link #setFormatterRegistrars(Set)}
*
* see {@link #setFormatterRegistrars(Set)}
*
* <p>Like all {@code FactoryBean} implementations, this class is suitable for
* use when configuring a Spring application context using Spring {@code <beans>}
* XML. When configuring the container with
* {@link org.springframework.context.annotation.Configuration @Configuration}
* classes, simply instantiate, configure and return the appropriate
* {@code FormattingConversionService} object from a
* {@link org.springframework.context.annotation.Bean @Bean} method.
*
* @author Keith Donald
* @author Juergen Hoeller
* @author Rossen Stoyanchev
* @author Chris Beams
* @since 3.0
*/
public class FormattingConversionServiceFactoryBean
implements FactoryBean<FormattingConversionService>, EmbeddedValueResolverAware, InitializingBean {
private static final boolean jodaTimePresent = ClassUtils.isPresent(
"org.joda.time.LocalDate", FormattingConversionService.class.getClassLoader());
private Set<?> converters;
private Set<?> formatters;
private Set<FormatterRegistrar> formatterRegistrars;
private StringValueResolver embeddedValueResolver;
private FormattingConversionService conversionService;
private StringValueResolver embeddedValueResolver;
private boolean registerDefaultFormatters = true;
/**
@@ -129,12 +128,12 @@ public class FormattingConversionServiceFactoryBean
this.registerDefaultFormatters = registerDefaultFormatters;
}
// implementing InitializingBean
public void afterPropertiesSet() {
this.conversionService = new FormattingConversionService();
this.conversionService.setEmbeddedValueResolver(this.embeddedValueResolver);
ConversionServiceFactory.addDefaultConverters(this.conversionService);
this.conversionService = new DefaultFormattingConversionService(this.embeddedValueResolver, this.registerDefaultFormatters);
ConversionServiceFactory.registerConverters(this.converters, this.conversionService);
addDefaultFormatters();
registerFormatters();
}
@@ -162,24 +161,14 @@ public class FormattingConversionServiceFactoryBean
* through FormatterRegistrars.
* @see #setFormatters(Set)
* @see #setFormatterRegistrars(Set)
* @deprecated since Spring 3.1 in favor of {@link #setFormatterRegistrars(Set)}
*/
@Deprecated
protected void installFormatters(FormatterRegistry registry) {
}
// private helper methods
private void addDefaultFormatters() {
if (registerDefaultFormatters) {
this.conversionService.addFormatterForFieldAnnotation(new NumberFormatAnnotationFormatterFactory());
if (jodaTimePresent) {
new JodaTimeFormatterRegistrar().registerFormatters(this.conversionService);
} else {
this.conversionService
.addFormatterForFieldAnnotation(new NoJodaDateTimeFormatAnnotationFormatterFactory());
}
}
}
private void registerFormatters() {
if (this.formatters != null) {
for (Object formatter : this.formatters) {
@@ -201,34 +190,4 @@ public class FormattingConversionServiceFactoryBean
installFormatters(this.conversionService);
}
/**
* Dummy AnnotationFormatterFactory that simply fails if @DateTimeFormat is being used
* without the JodaTime library being present.
*/
private static final class NoJodaDateTimeFormatAnnotationFormatterFactory
implements AnnotationFormatterFactory<DateTimeFormat> {
private final Set<Class<?>> fieldTypes;
public NoJodaDateTimeFormatAnnotationFormatterFactory() {
Set<Class<?>> rawFieldTypes = new HashSet<Class<?>>(4);
rawFieldTypes.add(Date.class);
rawFieldTypes.add(Calendar.class);
rawFieldTypes.add(Long.class);
this.fieldTypes = Collections.unmodifiableSet(rawFieldTypes);
}
public Set<Class<?>> getFieldTypes() {
return this.fieldTypes;
}
public Printer<?> getPrinter(DateTimeFormat annotation, Class<?> fieldType) {
throw new IllegalStateException("JodaTime library not available - @DateTimeFormat not supported");
}
public Parser<?> getParser(DateTimeFormat annotation, Class<?> fieldType) {
throw new IllegalStateException("JodaTime library not available - @DateTimeFormat not supported");
}
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2008 the original author or authors.
* 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.