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

@ -16,12 +16,12 @@
package org.springframework.aop.config;
import org.w3c.dom.Element;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.parsing.BeanComponentDefinition;
import org.springframework.beans.factory.parsing.ComponentRegistrar;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.xml.ParserContext;
import org.w3c.dom.Element;
/**
* Utility class for handling registration of auto-proxy creators used internally
@ -52,6 +52,11 @@ public abstract class AopNamespaceUtils {
private static final String EXPOSE_PROXY_ATTRIBUTE = "expose-proxy";
/**
* @deprecated since Spring 3.1 in favor of
* {@link #registerAutoProxyCreatorIfNecessary(BeanDefinitionRegistry, ComponentRegistrar, Object, Boolean, Boolean)}
*/
@Deprecated
public static void registerAutoProxyCreatorIfNecessary(
ParserContext parserContext, Element sourceElement) {
@ -61,6 +66,20 @@ public abstract class AopNamespaceUtils {
registerComponentIfNecessary(beanDefinition, parserContext);
}
public static void registerAutoProxyCreatorIfNecessary(
BeanDefinitionRegistry registry, ComponentRegistrar parserContext, Object source, Boolean proxyTargetClass, Boolean exposeProxy) {
BeanDefinition beanDefinition =
AopConfigUtils.registerAutoProxyCreatorIfNecessary(registry, source);
useClassProxyingIfNecessary(registry, proxyTargetClass, exposeProxy);
registerComponentIfNecessary(beanDefinition, parserContext);
}
public static void registerAutoProxyCreatorIfNecessary(
BeanDefinitionRegistry registry, ComponentRegistrar parserContext, Object source, Boolean proxyTargetClass) {
registerAutoProxyCreatorIfNecessary(registry, parserContext, source, proxyTargetClass, false);
}
public static void registerAspectJAutoProxyCreatorIfNecessary(
ParserContext parserContext, Element sourceElement) {
@ -101,6 +120,12 @@ public abstract class AopNamespaceUtils {
}
/**
* @deprecated since Spring 3.1 in favor of
* {@link #useClassProxyingIfNecessary(BeanDefinitionRegistry, Boolean, Boolean)}
* which does not require a parameter of type org.w3c.dom.Element
*/
@Deprecated
private static void useClassProxyingIfNecessary(BeanDefinitionRegistry registry, Element sourceElement) {
if (sourceElement != null) {
boolean proxyTargetClass = Boolean.valueOf(sourceElement.getAttribute(PROXY_TARGET_CLASS_ATTRIBUTE));
@ -114,11 +139,20 @@ public abstract class AopNamespaceUtils {
}
}
private static void registerComponentIfNecessary(BeanDefinition beanDefinition, ParserContext parserContext) {
private static void useClassProxyingIfNecessary(BeanDefinitionRegistry registry, Boolean proxyTargetClass, Boolean exposeProxy) {
if (proxyTargetClass) {
AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry);
}
if (exposeProxy) {
AopConfigUtils.forceAutoProxyCreatorToExposeProxy(registry);
}
}
private static void registerComponentIfNecessary(BeanDefinition beanDefinition, ComponentRegistrar componentRegistrar) {
if (beanDefinition != null) {
BeanComponentDefinition componentDefinition =
new BeanComponentDefinition(beanDefinition, AopConfigUtils.AUTO_PROXY_CREATOR_BEAN_NAME);
parserContext.registerComponent(componentDefinition);
componentRegistrar.registerComponent(componentDefinition);
}
}

View File

@ -86,7 +86,7 @@ public abstract class BeanUtils {
}
/**
* Convenience method to instantiate a class using its no-arg constructor.
* Instantiate a class using its no-arg constructor.
* As this method doesn't try to load classes by name, it should avoid
* class-loading issues.
* <p>Note that this method tries to set the constructor accessible
@ -108,6 +108,27 @@ public abstract class BeanUtils {
}
}
/**
* Instantiate a class using its no-arg constructor and return the new instance
* as the the specified assignable type.
* <p>Useful in cases where
* the type of the class to instantiate (clazz) is not available, but the type
* desired (assignableTo) is known.
* <p>As this method doesn't try to load classes by name, it should avoid
* class-loading issues.
* <p>Note that this method tries to set the constructor accessible
* if given a non-accessible (that is, non-public) constructor.
* @param clazz class to instantiate
* @param assignableTo type that clazz must be assignableTo
* @return the new instance
* @throws BeanInstantiationException if the bean cannot be instantiated
*/
@SuppressWarnings("unchecked")
public static <T> T instantiateClass(Class<?> clazz, Class<T> assignableTo) throws BeanInstantiationException {
Assert.isAssignable(assignableTo, clazz);
return (T)instantiateClass(clazz);
}
/**
* Convenience method to instantiate a class using the given constructor.
* As this method doesn't try to load classes by name, it should avoid

View File

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

View File

@ -0,0 +1,24 @@
/*
* Copyright 2002-2011 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.beans.factory.parsing;
public interface ComponentRegistrar extends BeanDefinitionRegistrar {
void registerBeanComponent(BeanComponentDefinition component);
void registerComponent(ComponentDefinition component);
}

View File

@ -30,12 +30,13 @@ public class ReaderContext {
private final Resource resource;
private final ProblemReporter problemReporter;
private final ReaderEventListener eventListener;
private final SourceExtractor sourceExtractor;
// TODO SPR-7420: review exposing problem reporter
protected final ProblemReporter problemReporter;
public ReaderContext(Resource resource, ProblemReporter problemReporter,
ReaderEventListener eventListener, SourceExtractor sourceExtractor) {

View File

@ -0,0 +1,59 @@
/*
* Copyright 2002-2011 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.beans.factory.parsing;
import java.util.ArrayList;
import java.util.List;
import org.springframework.core.io.DescriptiveResource;
/**
* TODO SPR-7420: document
*
* @author Chris Beams
* @since 3.1
*/
public class SimpleProblemCollector {
private Location location = null;
private List<Problem> errors = new ArrayList<Problem>();
public SimpleProblemCollector(Object location) {
if (location != null) {
this.location = new Location(new DescriptiveResource(location.toString()));
}
}
public void error(String message) {
this.errors.add(new Problem(message, this.location));
}
public void error(String message, Throwable cause) {
this.errors.add(new Problem(message, this.location, null, cause));
}
public void reportProblems(ProblemReporter reporter) {
for (Problem error : errors) {
reporter.error(error);
}
}
public boolean hasErrors() {
return this.errors.size() > 0;
}
}

View File

@ -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.

View File

@ -166,7 +166,7 @@ public class BeanDefinitionReaderUtils {
* for the given bean definition or the definition cannot be registered
*/
public static String registerWithGeneratedName(
AbstractBeanDefinition definition, BeanDefinitionRegistry registry)
BeanDefinition definition, BeanDefinitionRegistry registry)
throws BeanDefinitionStoreException {
String generatedName = generateBeanName(definition, registry, false);

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.
@ -21,9 +21,8 @@ import org.w3c.dom.Element;
import org.springframework.beans.factory.config.BeanDefinition;
/**
* Interface used by the
* {@link org.springframework.beans.factory.xml.DefaultBeanDefinitionDocumentReader} to
* handle custom, top-level (directly under <code>&lt;beans&gt;</code>) tags.
* Interface used by the {@link DefaultBeanDefinitionDocumentReader} to handle custom,
* top-level (directly under {@code <beans>}) tags.
*
* <p>Implementations are free to turn the metadata in the custom tag into as many
* {@link BeanDefinition BeanDefinitions} as required.
@ -31,11 +30,19 @@ import org.springframework.beans.factory.config.BeanDefinition;
* <p>The parser locates a {@link BeanDefinitionParser} from the associated
* {@link NamespaceHandler} for the namespace in which the custom tag resides.
*
* <p>Implementations are encouraged to decouple XML parsing from bean registration by
* parsing element(s) into a {@link org.springframework.context.FeatureSpecification
* FeatureSpecification} object and subsequently executing that specification.
* Doing so allows for maximum reuse between XML-based and annotation-based
* configuration options.
*
* @author Rob Harrop
* @since 2.0
* @see NamespaceHandler
* @see org.springframework.beans.factory.xml.BeanDefinitionDecorator
* @see AbstractBeanDefinitionParser
* @see org.springframework.beans.factory.xml.BeanDefinitionDecorator
* @see org.springframework.context.FeatureSpecification
* @see org.springframework.context.AbstractSpecificationExecutor
*/
public interface BeanDefinitionParser {

View File

@ -21,6 +21,7 @@ import java.util.Stack;
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.parsing.CompositeComponentDefinition;
import org.springframework.beans.factory.support.BeanDefinitionReaderUtils;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
@ -36,7 +37,7 @@ import org.springframework.beans.factory.support.BeanDefinitionRegistry;
* @see XmlReaderContext
* @see BeanDefinitionParserDelegate
*/
public final class ParserContext {
public final class ParserContext implements ComponentRegistrar {
private final XmlReaderContext readerContext;
@ -121,4 +122,8 @@ public final class ParserContext {
registerComponent(component);
}
public String registerWithGeneratedName(BeanDefinition beanDefinition) {
return this.readerContext.registerWithGeneratedName(beanDefinition);
}
}

View File

@ -83,4 +83,9 @@ public class XmlReaderContext extends ReaderContext {
return generatedName;
}
// TODO SPR-7420: review exposing problem reporter
public ProblemReporter getProblemReporter() {
return this.problemReporter;
}
}

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,6 +16,15 @@
package org.springframework.beans.factory;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNotSame;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import java.lang.reflect.Field;
import java.net.MalformedURLException;
import java.security.AccessControlContext;
@ -31,20 +40,13 @@ import java.util.Locale;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import javax.security.auth.Subject;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import static org.junit.Assert.*;
import org.junit.Ignore;
import org.junit.Test;
import test.beans.DerivedTestBean;
import test.beans.DummyFactory;
import test.beans.ITestBean;
import test.beans.LifecycleBean;
import test.beans.NestedTestBean;
import test.beans.TestBean;
import org.springframework.beans.BeansException;
import org.springframework.beans.MutablePropertyValues;
import org.springframework.beans.NotWritablePropertyException;
@ -75,12 +77,19 @@ import org.springframework.beans.propertyeditors.CustomNumberEditor;
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
import org.springframework.core.MethodParameter;
import org.springframework.core.convert.converter.Converter;
import org.springframework.core.convert.support.ConversionServiceFactory;
import org.springframework.core.convert.support.DefaultConversionService;
import org.springframework.core.convert.support.GenericConversionService;
import org.springframework.core.io.Resource;
import org.springframework.core.io.UrlResource;
import org.springframework.util.StopWatch;
import test.beans.DerivedTestBean;
import test.beans.DummyFactory;
import test.beans.ITestBean;
import test.beans.LifecycleBean;
import test.beans.NestedTestBean;
import test.beans.TestBean;
/**
* Tests properties population and autowire behavior.
*
@ -849,7 +858,7 @@ public class DefaultListableBeanFactoryTests {
@Test
public void testCustomConverter() {
DefaultListableBeanFactory lbf = new DefaultListableBeanFactory();
GenericConversionService conversionService = (GenericConversionService) ConversionServiceFactory.createDefaultConversionService();
GenericConversionService conversionService = new DefaultConversionService();
conversionService.addConverter(new Converter<String, Float>() {
public Float convert(String source) {
try {

View File

@ -11,6 +11,7 @@
<config>src/test/java/org/springframework/context/annotation/configuration/SecondLevelSubConfig-context.xml</config>
<config>src/test/java/org/springframework/context/annotation/configuration/ImportXmlWithAopNamespace-context.xml</config>
<config>src/test/java/org/springframework/context/annotation/Spr6602Tests-context.xml</config>
<config>src/test/java/org/springframework/context/annotation/FeatureConfigurationImportResourceTests-context.xml</config>
</configs>
<configSets>
</configSets>

View File

@ -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.

View File

@ -191,6 +191,7 @@
<xsd:annotation>
<xsd:documentation><![CDATA[
Controls the class files eligible for component detection. Defaults to "**/*.class", the recommended value.
Consider use of the include-filter and exclude-filter elements for a more fine-grained approach.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>

View File

@ -65,6 +65,7 @@ public class FooServiceImpl implements FooService {
private boolean initCalled = false;
@SuppressWarnings("unused")
@PostConstruct
private void init() {
if (this.initCalled) {

View File

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

View File

@ -0,0 +1,25 @@
/*
* 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 example.scannable_scoped;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.stereotype.Component;
@Component
@MyScope(BeanDefinition.SCOPE_PROTOTYPE)
public class CustomScopeAnnotationBean {
}

View File

@ -0,0 +1,25 @@
/*
* 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 example.scannable_scoped;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.context.annotation.ScopedProxyMode;
public @interface MyScope {
String value() default BeanDefinition.SCOPE_SINGLETON;
ScopedProxyMode proxyMode() default ScopedProxyMode.DEFAULT;
}

View File

@ -0,0 +1,67 @@
/*
* Copyright 2002-2011 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.context.annotation;
import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertThat;
import org.junit.Test;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.configuration.StubSpecification;
import org.springframework.context.config.FeatureSpecification;
/**
* Tests that @FeatureConfiguration classes may implement Aware interfaces,
* such as BeanFactoryAware. This is not generally recommended but occasionally
* useful, particularly in testing.
*
* @author Chris Beams
* @since 3.1
*/
public class BeanFactoryAwareFeatureConfigurationTests {
@Test
public void test() {
ConfigurableApplicationContext ctx =
new AnnotationConfigApplicationContext(FeatureConfig.class);
FeatureConfig fc = ctx.getBean(FeatureConfig.class);
assertThat(fc.featureMethodWasCalled, is(true));
assertThat(fc.gotBeanFactoryInTime, is(true));
assertThat(fc.beanFactory, is(ctx.getBeanFactory()));
}
@FeatureConfiguration
static class FeatureConfig implements BeanFactoryAware {
ConfigurableListableBeanFactory beanFactory;
boolean featureMethodWasCalled = false;
boolean gotBeanFactoryInTime = false;
public void setBeanFactory(BeanFactory beanFactory) {
this.beanFactory = (ConfigurableListableBeanFactory)beanFactory;
}
@Feature
public FeatureSpecification f() {
this.featureMethodWasCalled = true;
this.gotBeanFactoryInTime = (this.beanFactory != null);
return new StubSpecification();
}
}
}

View File

@ -0,0 +1,249 @@
/*
* Copyright 2002-2011 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.context.annotation;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.CoreMatchers.not;
import static org.hamcrest.CoreMatchers.notNullValue;
import static org.hamcrest.CoreMatchers.sameInstance;
import static org.hamcrest.Matchers.containsString;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.fail;
import static org.springframework.beans.factory.support.BeanDefinitionBuilder.genericBeanDefinition;
import java.io.IOException;
import java.util.HashSet;
import org.junit.Test;
import org.springframework.aop.support.AopUtils;
import org.springframework.beans.factory.annotation.CustomAutowireConfigurer;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.SimpleMapScope;
import org.springframework.beans.factory.parsing.BeanDefinitionParsingException;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.context.annotation.ComponentScan.Filter;
import org.springframework.context.annotation.ComponentScanParserTests.CustomAnnotationAutowiredBean;
import org.springframework.context.support.GenericApplicationContext;
import org.springframework.util.SerializationTestUtils;
import example.scannable.FooService;
import example.scannable.ScopedProxyTestBean;
import example.scannable_scoped.CustomScopeAnnotationBean;
import example.scannable_scoped.MyScope;
/**
* Integration tests for processing ComponentScan-annotated Configuration
* classes.
*
* @author Chris Beams
* @since 3.1
*/
public class ComponentScanAnnotationIntegrationTests {
@Test
public void controlScan() {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.scan(example.scannable._package.class.getPackage().getName());
ctx.refresh();
assertThat("control scan for example.scannable package failed to register FooServiceImpl bean",
ctx.containsBean("fooServiceImpl"), is(true));
}
@Test
public void viaContextRegistration() {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.register(ComponentScanAnnotatedConfig.class);
ctx.refresh();
ctx.getBean(ComponentScanAnnotatedConfig.class);
ctx.getBean(TestBean.class);
assertThat("config class bean not found", ctx.containsBeanDefinition("componentScanAnnotatedConfig"), is(true));
assertThat("@ComponentScan annotated @Configuration class registered directly against " +
"AnnotationConfigApplicationContext did not trigger component scanning as expected",
ctx.containsBean("fooServiceImpl"), is(true));
}
@Test
public void viaContextRegistration_WithValueAttribute() {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.register(ComponentScanAnnotatedConfig_WithValueAttribute.class);
ctx.refresh();
ctx.getBean(ComponentScanAnnotatedConfig_WithValueAttribute.class);
ctx.getBean(TestBean.class);
assertThat("config class bean not found", ctx.containsBeanDefinition("componentScanAnnotatedConfig_WithValueAttribute"), is(true));
assertThat("@ComponentScan annotated @Configuration class registered directly against " +
"AnnotationConfigApplicationContext did not trigger component scanning as expected",
ctx.containsBean("fooServiceImpl"), is(true));
}
@Test
public void viaBeanRegistration() {
DefaultListableBeanFactory bf = new DefaultListableBeanFactory();
bf.registerBeanDefinition("componentScanAnnotatedConfig",
genericBeanDefinition(ComponentScanAnnotatedConfig.class).getBeanDefinition());
bf.registerBeanDefinition("configurationClassPostProcessor",
genericBeanDefinition(ConfigurationClassPostProcessor.class).getBeanDefinition());
GenericApplicationContext ctx = new GenericApplicationContext(bf);
ctx.refresh();
ctx.getBean(ComponentScanAnnotatedConfig.class);
ctx.getBean(TestBean.class);
assertThat("config class bean not found", ctx.containsBeanDefinition("componentScanAnnotatedConfig"), is(true));
assertThat("@ComponentScan annotated @Configuration class registered " +
"as bean definition did not trigger component scanning as expected",
ctx.containsBean("fooServiceImpl"), is(true));
}
@Test
public void invalidComponentScanDeclaration_noPackagesSpecified() {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.register(ComponentScanWithNoPackagesConfig.class);
try {
ctx.refresh();
fail("Expected exception when parsing @ComponentScan definition that declares no packages");
} catch (BeanDefinitionParsingException ex) {
assertThat(ex.getMessage(), containsString("At least one base package must be specified"));
}
}
@Test
public void withCustomBeanNameGenerator() {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.register(ComponentScanWithBeanNameGenenerator.class);
ctx.refresh();
assertThat(ctx.containsBean("custom_fooServiceImpl"), is(true));
assertThat(ctx.containsBean("fooServiceImpl"), is(false));
}
@Test
public void withScopeResolver() {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(ComponentScanWithScopeResolver.class);
// custom scope annotation makes the bean prototype scoped. subsequent calls
// to getBean should return distinct instances.
assertThat(ctx.getBean(CustomScopeAnnotationBean.class), not(sameInstance(ctx.getBean(CustomScopeAnnotationBean.class))));
}
@Test
public void withCustomTypeFilter() {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(ComponentScanWithCustomTypeFilter.class);
CustomAnnotationAutowiredBean testBean = ctx.getBean(CustomAnnotationAutowiredBean.class);
assertThat(testBean.getDependency(), notNullValue());
}
@Test
public void withScopedProxy() throws IOException, ClassNotFoundException {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.register(ComponentScanWithScopedProxy.class);
ctx.getBeanFactory().registerScope("myScope", new SimpleMapScope());
ctx.refresh();
// should cast to the interface
FooService bean = (FooService) ctx.getBean("scopedProxyTestBean");
// should be dynamic proxy
assertThat(AopUtils.isJdkDynamicProxy(bean), is(true));
// test serializability
assertThat(bean.foo(1), equalTo("bar"));
FooService deserialized = (FooService) SerializationTestUtils.serializeAndDeserialize(bean);
assertThat(deserialized, notNullValue());
assertThat(deserialized.foo(1), equalTo("bar"));
}
@Test
public void withBasePackagesAndValueAlias() {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.register(ComponentScanWithBasePackagesAndValueAlias.class);
ctx.refresh();
assertThat(ctx.containsBean("fooServiceImpl"), is(true));
}
}
@Configuration
@ComponentScan(basePackageClasses=example.scannable._package.class)
class ComponentScanAnnotatedConfig {
@Bean
public TestBean testBean() {
return new TestBean();
}
}
@Configuration
@ComponentScan("example.scannable")
class ComponentScanAnnotatedConfig_WithValueAttribute {
@Bean
public TestBean testBean() {
return new TestBean();
}
}
@Configuration
@ComponentScan
class ComponentScanWithNoPackagesConfig { }
@Configuration
@ComponentScan(basePackages="example.scannable", nameGenerator=MyBeanNameGenerator.class)
class ComponentScanWithBeanNameGenenerator { }
class MyBeanNameGenerator extends AnnotationBeanNameGenerator {
@Override
public String generateBeanName(BeanDefinition definition, BeanDefinitionRegistry registry) {
return "custom_" + super.generateBeanName(definition, registry);
}
}
@Configuration
@ComponentScan(basePackages="example.scannable_scoped", scopeResolver=MyScopeMetadataResolver.class)
class ComponentScanWithScopeResolver { }
class MyScopeMetadataResolver extends AnnotationScopeMetadataResolver {
MyScopeMetadataResolver() {
this.scopeAnnotationType = MyScope.class;
}
}
@Configuration
@ComponentScan(basePackages="org.springframework.context.annotation",
useDefaultFilters=false,
includeFilters=@Filter(type=FilterType.CUSTOM, value=ComponentScanParserTests.CustomTypeFilter.class),
// exclude this class from scanning since it's in the scanned package
excludeFilters=@Filter(type=FilterType.ASSIGNABLE_TYPE, value=ComponentScanWithCustomTypeFilter.class))
class ComponentScanWithCustomTypeFilter {
@Bean
@SuppressWarnings({ "rawtypes", "serial", "unchecked" })
public CustomAutowireConfigurer customAutowireConfigurer() {
CustomAutowireConfigurer cac = new CustomAutowireConfigurer();
cac.setCustomQualifierTypes(new HashSet() {{ add(ComponentScanParserTests.CustomAnnotation.class); }});
return cac;
}
public ComponentScanParserTests.CustomAnnotationAutowiredBean testBean() {
return new ComponentScanParserTests.CustomAnnotationAutowiredBean();
}
}
@Configuration
@ComponentScan(basePackages="example.scannable",
scopedProxy=ScopedProxyMode.INTERFACES,
useDefaultFilters=false,
includeFilters=@Filter(type=FilterType.ASSIGNABLE_TYPE, value=ScopedProxyTestBean.class))
class ComponentScanWithScopedProxy { }
@Configuration
@ComponentScan(
value="example.scannable",
basePackages="example.scannable",
basePackageClasses=example.scannable._package.class)
class ComponentScanWithBasePackagesAndValueAlias { }

View File

@ -18,8 +18,7 @@ package org.springframework.context.annotation;
import org.junit.Test;
import org.springframework.beans.factory.support.DefaultBeanNameGenerator;
import org.springframework.context.annotation.ComponentScan.ExcludeFilter;
import org.springframework.context.annotation.ComponentScan.IncludeFilter;
import org.springframework.context.annotation.ComponentScan.Filter;
import org.springframework.core.type.filter.TypeFilter;
/**
@ -27,11 +26,13 @@ import org.springframework.core.type.filter.TypeFilter;
*
* @author Chris Beams
* @since 3.1
* @see ComponentScanAnnotationIntegrationTests
*/
public class ComponentScanAnnotationTests {
@Test
public void test() {
public void noop() {
// no-op; the @ComponentScan-annotated MyConfig class below simply excercises
// available attributes of the annotation.
}
}
@ -39,22 +40,22 @@ public class ComponentScanAnnotationTests {
@Configuration
@ComponentScan(
packageOf={TestBean.class},
basePackageClasses={TestBean.class},
nameGenerator = DefaultBeanNameGenerator.class,
scopedProxy = ScopedProxyMode.NO,
scopeResolver = AnnotationScopeMetadataResolver.class,
useDefaultFilters = false,
resourcePattern = "**/*custom.class",
includeFilters = {
@IncludeFilter(type = FilterType.ANNOTATION, value = MyAnnotation.class)
@Filter(type = FilterType.ANNOTATION, value = MyAnnotation.class)
},
excludeFilters = {
@ExcludeFilter(type = FilterType.CUSTOM, value = TypeFilter.class)
@Filter(type = FilterType.CUSTOM, value = TypeFilter.class)
}
)
class MyConfig {
}
@ComponentScan(packageOf=example.scannable.NamedComponent.class)
@ComponentScan(basePackageClasses=example.scannable.NamedComponent.class)
class SimpleConfig { }

View File

@ -0,0 +1,67 @@
/*
* Copyright 2002-2011 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.context.annotation;
import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertThat;
import org.junit.Before;
import org.junit.Test;
import org.springframework.beans.factory.parsing.BeanDefinitionParsingException;
import org.springframework.beans.factory.parsing.FailFastProblemReporter;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.context.config.ExecutorContext;
import org.springframework.core.io.DefaultResourceLoader;
import org.springframework.mock.env.MockEnvironment;
/**
* Unit tests for {@link ComponentScanExecutor}.
*
* @author Chris Beams
* @since 3.1
*/
public class ComponentScanExecutorTests {
private ComponentScanExecutor executor;
private ExecutorContext executorContext;
private DefaultListableBeanFactory bf;
@Before
public void setUp() {
this.bf = new DefaultListableBeanFactory();
this.executor = new ComponentScanExecutor();
this.executorContext = new ExecutorContext();
this.executorContext.setRegistry(bf);
this.executorContext.setResourceLoader(new DefaultResourceLoader());
this.executorContext.setEnvironment(new MockEnvironment());
this.executorContext.setRegistrar(new SimpleComponentRegistrar(bf));
this.executorContext.setProblemReporter(new FailFastProblemReporter());
}
@Test
public void validSpec() {
this.executor.execute(new ComponentScanSpec("example.scannable"), this.executorContext);
assertThat(bf.containsBean("fooServiceImpl"), is(true));
}
@Test(expected=BeanDefinitionParsingException.class)
public void invalidSpec() {
// ff problem reporter should throw due to no packages specified
this.executor.execute(new ComponentScanSpec(), this.executorContext);
}
}

View File

@ -0,0 +1,55 @@
/*
* Copyright 2002-2011 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.context.annotation;
import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertThat;
import org.junit.Test;
public class ComponentScanFeatureTests {
@Test
public void viaContextRegistration() {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.register(ComponentScanFeatureConfig.class);
ctx.register(ComponentScanFeatureConfig.Features.class);
ctx.refresh();
ctx.getBean(ComponentScanFeatureConfig.class);
ctx.getBean(TestBean.class);
assertThat("config class bean not found", ctx.containsBeanDefinition("componentScanFeatureConfig"), is(true));
assertThat("@ComponentScan annotated @Configuration class registered directly against " +
"AnnotationConfigApplicationContext did not trigger component scanning as expected",
ctx.containsBean("fooServiceImpl"), is(true));
}
}
@Configuration
//@Import(ComponentScanFeatureConfig.Features.class)
class ComponentScanFeatureConfig {
@FeatureConfiguration
static class Features {
@Feature
public ComponentScanSpec componentScan() {
return new ComponentScanSpec(example.scannable._package.class);
}
}
@Bean
public TestBean testBean() {
return new TestBean();
}
}

View File

@ -0,0 +1,411 @@
/*
* Copyright 2002-2011 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.context.annotation;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.instanceOf;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.CoreMatchers.nullValue;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.springframework.util.StringUtils.arrayToCommaDelimitedString;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import org.junit.Before;
import org.junit.Test;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.parsing.BeanDefinitionParsingException;
import org.springframework.beans.factory.parsing.FailFastProblemReporter;
import org.springframework.beans.factory.parsing.Problem;
import org.springframework.beans.factory.parsing.ProblemReporter;
import org.springframework.beans.factory.support.BeanNameGenerator;
import org.springframework.beans.factory.support.DefaultBeanNameGenerator;
import org.springframework.core.type.classreading.MetadataReader;
import org.springframework.core.type.classreading.MetadataReaderFactory;
import org.springframework.core.type.filter.AnnotationTypeFilter;
import org.springframework.core.type.filter.AssignableTypeFilter;
import org.springframework.core.type.filter.TypeFilter;
import org.springframework.util.ClassUtils;
import org.springframework.util.ObjectUtils;
/**
* Unit tests for {@link ComponentScanSpec}.
*
* @author Chris Beams
* @since 3.1
*/
public class ComponentScanSpecTests {
private CollatingProblemReporter problemReporter;
private ClassLoader classLoader;
@Before
public void setUp() {
problemReporter = new CollatingProblemReporter();
classLoader = ClassUtils.getDefaultClassLoader();
}
@Test
public void includeAnnotationConfig() {
ComponentScanSpec spec = new ComponentScanSpec("org.p1");
assertThat(spec.includeAnnotationConfig(), nullValue());
spec.includeAnnotationConfig(true);
assertThat(spec.validate(problemReporter), is(true));
assertThat(spec.includeAnnotationConfig(), is(true));
spec.includeAnnotationConfig(false);
assertThat(spec.validate(problemReporter), is(true));
assertThat(spec.includeAnnotationConfig(), is(false));
spec.includeAnnotationConfig("trUE");
assertThat(spec.validate(problemReporter), is(true));
assertThat(spec.includeAnnotationConfig(), is(true));
spec.includeAnnotationConfig("falSE");
assertThat(spec.validate(problemReporter), is(true));
assertThat(spec.includeAnnotationConfig(), is(false));
spec.includeAnnotationConfig("");
assertThat(spec.validate(problemReporter), is(true));
assertThat(spec.includeAnnotationConfig(), is(false));
spec.includeAnnotationConfig((String)null);
assertThat(spec.validate(problemReporter), is(true));
assertThat(spec.includeAnnotationConfig(), is(false));
spec.includeAnnotationConfig((Boolean)null);
assertThat(spec.validate(problemReporter), is(true));
assertThat(spec.includeAnnotationConfig(), nullValue());
}
@Test
public void resourcePattern() {
ComponentScanSpec spec = new ComponentScanSpec("org.p1");
assertThat(spec.resourcePattern(), nullValue());
assertThat(spec.validate(problemReporter), is(true));
spec.resourcePattern("**/Foo*.class");
assertThat(spec.resourcePattern(), is("**/Foo*.class"));
assertThat(spec.validate(problemReporter), is(true));
spec.resourcePattern("");
assertThat(spec.resourcePattern(), is("**/Foo*.class"));
assertThat(spec.validate(problemReporter), is(true));
spec.resourcePattern(null);
assertThat(spec.resourcePattern(), is("**/Foo*.class"));
assertThat(spec.validate(problemReporter), is(true));
}
@Test
public void useDefaultFilters() {
ComponentScanSpec spec = new ComponentScanSpec("org.p1");
assertThat(spec.useDefaultFilters(), nullValue());
assertThat(spec.validate(problemReporter), is(true));
spec.useDefaultFilters((Boolean)null);
assertThat(spec.useDefaultFilters(), nullValue());
assertThat(spec.validate(problemReporter), is(true));
spec.useDefaultFilters(true);
assertThat(spec.useDefaultFilters(), is(true));
assertThat(spec.validate(problemReporter), is(true));
spec.useDefaultFilters(false);
assertThat(spec.useDefaultFilters(), is(false));
assertThat(spec.validate(problemReporter), is(true));
spec.useDefaultFilters("trUE");
assertThat(spec.useDefaultFilters(), is(true));
assertThat(spec.validate(problemReporter), is(true));
spec.useDefaultFilters("falSE");
assertThat(spec.useDefaultFilters(), is(false));
assertThat(spec.validate(problemReporter), is(true));
spec.useDefaultFilters("");
assertThat(spec.useDefaultFilters(), is(false));
assertThat(spec.validate(problemReporter), is(true));
spec.useDefaultFilters((String)null);
assertThat(spec.useDefaultFilters(), is(false));
assertThat(spec.validate(problemReporter), is(true));
}
@Test
public void includeFilters() {
ComponentScanSpec spec = new ComponentScanSpec("org.p1");
spec.includeFilters(
new AnnotationTypeFilter(MyAnnotation.class),
new AssignableTypeFilter(Object.class));
assertThat(spec.validate(problemReporter), is(true));
assertThat(spec.includeFilters().length, is(2));
}
@Test
public void stringIncludeExcludeFilters() {
ComponentScanSpec spec = new ComponentScanSpec("org.p1");
spec.addIncludeFilter("annotation", MyAnnotation.class.getName(), classLoader);
spec.addExcludeFilter("assignable", Object.class.getName(), classLoader);
spec.addExcludeFilter("annotation", Override.class.getName(), classLoader);
assertThat(spec.validate(problemReporter), is(true));
assertThat(spec.includeFilters().length, is(1));
assertThat(spec.excludeFilters().length, is(2));
}
@Test
public void bogusStringIncludeFilter() throws IOException {
ComponentScanSpec spec = new ComponentScanSpec("org.p1");
spec.addIncludeFilter("bogus-type", "bogus-expr", classLoader);
assertThat(spec.validate(problemReporter), is(false));
assertThat(spec.includeFilters().length, is(1));
try {
spec.includeFilters()[0].match(null, null);
fail("expected placholder TypeFilter to throw exception");
} catch (UnsupportedOperationException ex) {
// expected
}
}
@Test
public void exerciseFilterTypes() throws IOException {
ComponentScanSpec spec = new ComponentScanSpec("org.p1");
spec.addIncludeFilter("aspectj", "*..Bogus", classLoader);
assertThat(spec.validate(problemReporter), is(true));
spec.addIncludeFilter("regex", ".*Foo", classLoader);
assertThat(spec.validate(problemReporter), is(true));
spec.addIncludeFilter("custom", StubTypeFilter.class.getName(), classLoader);
assertThat(spec.validate(problemReporter), is(true));
spec.addIncludeFilter("custom", "org.NonExistentTypeFilter", classLoader);
assertThat(spec.validate(problemReporter), is(false));
spec.addIncludeFilter("custom", NonNoArgResolver.class.getName(), classLoader);
assertThat(spec.validate(problemReporter), is(false));
}
@Test
public void missingBasePackages() {
ComponentScanSpec spec = new ComponentScanSpec();
assertThat(spec.validate(problemReporter), is(false));
}
@Test
public void withBasePackageViaAdderMethod() {
ComponentScanSpec spec = new ComponentScanSpec();
spec.addBasePackage("org.p1");
spec.addBasePackage("org.p2");
assertThat(spec.validate(problemReporter), is(true));
assertExactContents(spec.basePackages(), "org.p1", "org.p2");
}
@Test
public void withBasePackagesViaStringConstructor() {
ComponentScanSpec spec = new ComponentScanSpec("org.p1", "org.p2");
assertThat(spec.validate(problemReporter), is(true));
assertExactContents(spec.basePackages(), "org.p1", "org.p2");
}
@Test
public void withBasePackagesViaClassConstructor() {
ComponentScanSpec spec = new ComponentScanSpec(java.lang.Object.class, java.io.Closeable.class);
assertThat(spec.validate(problemReporter), is(true));
assertExactContents(spec.basePackages(), "java.lang", "java.io");
}
@Test
public void forDelimitedPackages() {
ComponentScanSpec spec = ComponentScanSpec.forDelimitedPackages("pkg.one,pkg.two");
assertTrue(ObjectUtils.containsElement(spec.basePackages(), "pkg.one"));
assertTrue(ObjectUtils.containsElement(spec.basePackages(), "pkg.two"));
assertThat(spec.basePackages().length, is(2));
}
@Test
public void withSomeEmptyBasePackages() {
ComponentScanSpec spec = new ComponentScanSpec("org.p1", "", "org.p3");
assertThat(spec.validate(problemReporter), is(true));
assertExactContents(spec.basePackages(), "org.p1", "org.p3");
}
@Test
public void withAllEmptyBasePackages() {
ComponentScanSpec spec = new ComponentScanSpec("", "", "");
assertThat(spec.validate(problemReporter), is(false));
}
@Test
public void withInstanceBeanNameGenerator() {
ComponentScanSpec spec = new ComponentScanSpec("org.p1");
assertThat(spec.beanNameGenerator(), nullValue());
BeanNameGenerator bng = new DefaultBeanNameGenerator();
spec.beanNameGenerator(bng);
assertThat(spec.validate(problemReporter), is(true));
assertThat(spec.beanNameGenerator(), is(bng));
}
@Test
public void withStringBeanNameGenerator() {
ComponentScanSpec spec = new ComponentScanSpec("org.p1");
spec.beanNameGenerator(DefaultBeanNameGenerator.class.getName(), classLoader);
assertThat(spec.validate(problemReporter), is(true));
assertThat(spec.beanNameGenerator(), instanceOf(DefaultBeanNameGenerator.class));
}
@Test
public void withInstanceScopeMetadataResolver() {
ComponentScanSpec spec = new ComponentScanSpec("org.p1");
assertThat(spec.scopeMetadataResolver(), nullValue());
ScopeMetadataResolver smr = new AnnotationScopeMetadataResolver();
spec.scopeMetadataResolver(smr);
assertThat(spec.validate(problemReporter), is(true));
assertThat(spec.scopeMetadataResolver(), is(smr));
}
@Test
public void withStringScopeMetadataResolver() {
ComponentScanSpec spec = new ComponentScanSpec("org.p1");
spec.scopeMetadataResolver(AnnotationScopeMetadataResolver.class.getName(), classLoader);
assertThat(spec.validate(problemReporter), is(true));
assertThat(spec.scopeMetadataResolver(), instanceOf(AnnotationScopeMetadataResolver.class));
}
@Test
public void withNonAssignableStringScopeMetadataResolver() {
ComponentScanSpec spec = new ComponentScanSpec("org.p1");
spec.scopeMetadataResolver(Object.class.getName(), classLoader);
assertThat(spec.validate(problemReporter), is(false));
}
@Test
public void withNonExistentStringScopeMetadataResolver() {
ComponentScanSpec spec = new ComponentScanSpec("org.p1");
spec.scopeMetadataResolver("org.Bogus", classLoader);
assertThat(spec.validate(problemReporter), is(false));
}
@Test
public void withNonNoArgStringScopeMetadataResolver() {
ComponentScanSpec spec = new ComponentScanSpec("org.p1");
spec.scopeMetadataResolver(NonNoArgResolver.class.getName(), classLoader);
assertThat(spec.validate(problemReporter), is(false));
}
@Test
public void withStringScopedProxyMode() {
ComponentScanSpec spec = new ComponentScanSpec("org.p1");
spec.scopedProxyMode("targetCLASS");
assertThat(spec.validate(problemReporter), is(true));
assertThat(spec.scopedProxyMode(), is(ScopedProxyMode.TARGET_CLASS));
spec.scopedProxyMode("interFACES");
assertThat(spec.validate(problemReporter), is(true));
assertThat(spec.scopedProxyMode(), is(ScopedProxyMode.INTERFACES));
spec.scopedProxyMode("nO");
assertThat(spec.validate(problemReporter), is(true));
assertThat(spec.scopedProxyMode(), is(ScopedProxyMode.NO));
spec.scopedProxyMode("bogus");
assertThat(spec.validate(problemReporter), is(false));
assertThat(spec.scopedProxyMode(), nullValue());
}
@Test
public void withScopeMetadataResolverAndScopedProxyMode() {
ComponentScanSpec spec = new ComponentScanSpec("org.p1");
spec.scopeMetadataResolver(new AnnotationScopeMetadataResolver());
assertThat(spec.validate(problemReporter), is(true));
spec.scopedProxyMode(ScopedProxyMode.INTERFACES);
assertThat(spec.validate(problemReporter), is(false));
}
@Test
public void addBasePackage() {
ComponentScanSpec spec = new ComponentScanSpec();
spec.addBasePackage("foo.bar");
assertThat(spec.validate(problemReporter), is(true));
assertThat(spec.basePackages().length, is(1));
}
@Test
public void addBasePackageWithConstructor() {
ComponentScanSpec spec = new ComponentScanSpec("my.pkg");
spec.addBasePackage("foo.bar");
assertThat(spec.validate(problemReporter), is(true));
assertThat(spec.basePackages().length, is(2));
}
@Test
public void addExcludeFilterString() {
ComponentScanSpec spec = new ComponentScanSpec("my.pkg");
spec.addExcludeFilter("annotation", MyAnnotation.class.getName(), ClassUtils.getDefaultClassLoader());
assertThat(spec.validate(problemReporter), is(true));
assertThat(spec.excludeFilters().length, is(1));
assertThat(spec.excludeFilters()[0], instanceOf(AnnotationTypeFilter.class));
}
@Test(expected=BeanDefinitionParsingException.class)
public void withFailFastProblemReporter() {
new ComponentScanSpec().validate(new FailFastProblemReporter());
}
private <T> void assertExactContents(T[] actual, T... expected) {
if (actual.length >= expected.length) {
for (int i = 0; i < expected.length; i++) {
assertThat(
String.format("element number %d in actual is incorrect. actual: [%s], expected: [%s]",
i, arrayToCommaDelimitedString(actual), arrayToCommaDelimitedString(expected)),
actual[i], equalTo(expected[i]));
}
}
assertThat(String.format("actual contains incorrect number of arguments. actual: [%s], expected: [%s]",
arrayToCommaDelimitedString(actual), arrayToCommaDelimitedString(expected)),
actual.length, equalTo(expected.length));
}
private static class CollatingProblemReporter implements ProblemReporter {
private List<Problem> errors = new ArrayList<Problem>();
private List<Problem> warnings = new ArrayList<Problem>();
public void fatal(Problem problem) {
throw new BeanDefinitionParsingException(problem);
}
public void error(Problem problem) {
this.errors.add(problem);
}
@SuppressWarnings("unused")
public Problem[] getErrors() {
return this.errors.toArray(new Problem[this.errors.size()]);
}
public void warning(Problem problem) {
System.out.println(problem);
this.warnings.add(problem);
}
@SuppressWarnings("unused")
public Problem[] getWarnings() {
return this.warnings.toArray(new Problem[this.warnings.size()]);
}
}
private static class NonNoArgResolver implements ScopeMetadataResolver {
public ScopeMetadata resolveScopeMetadata(BeanDefinition definition) {
throw new UnsupportedOperationException();
}
}
private static class StubTypeFilter implements TypeFilter {
public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory)
throws IOException {
throw new UnsupportedOperationException();
}
}
}

View File

@ -0,0 +1,316 @@
/*
* Copyright 2002-2011 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.context.annotation;
import static java.lang.String.format;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.instanceOf;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.CoreMatchers.not;
import static org.hamcrest.CoreMatchers.sameInstance;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.fail;
import static org.springframework.context.annotation.EarlyBeanReferenceProxyCreator.FINAL_CLASS_ERROR_MESSAGE;
import static org.springframework.context.annotation.EarlyBeanReferenceProxyCreator.MISSING_NO_ARG_CONSTRUCTOR_ERROR_MESSAGE;
import static org.springframework.context.annotation.EarlyBeanReferenceProxyCreator.PRIVATE_NO_ARG_CONSTRUCTOR_ERROR_MESSAGE;
import java.lang.reflect.Method;
import org.junit.Before;
import org.junit.Test;
import org.springframework.aop.support.AopUtils;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.beans.factory.config.DependencyDescriptor;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.core.MethodParameter;
import test.beans.ITestBean;
import test.beans.TestBean;
/**
* Unit tests for {@link EarlyBeanReferenceProxyCreator}, ensuring that
* {@link EarlyBeanReferenceProxy} objects behave properly.
*
* @author Chris Beams
* @since 3.1
*/
public class EarlyBeanReferenceProxyCreatorTests {
private DefaultListableBeanFactory bf;
@Before
public void setUp() {
bf = new DefaultListableBeanFactory();
}
@Test
public void proxyToStringAvoidsEagerInstantiation() throws Exception {
EarlyBeanReferenceProxyCreator pc = new EarlyBeanReferenceProxyCreator(bf);
TestBean proxy = (TestBean) pc.createProxy(descriptorFor(TestBean.class));
assertThat(proxy.toString(), equalTo("EarlyBeanReferenceProxy for bean of type TestBean"));
}
@Test(expected=NoSuchBeanDefinitionException.class)
public void proxyThrowsNoSuchBeanDefinitionExceptionWhenDelegatingMethodCallToNonExistentBean() throws Exception {
EarlyBeanReferenceProxyCreator pc = new EarlyBeanReferenceProxyCreator(bf);
TestBean proxy = (TestBean) pc.createProxy(descriptorFor(TestBean.class));
proxy.getName();
}
@Test
public void proxyHashCodeAvoidsEagerInstantiation() throws Exception {
EarlyBeanReferenceProxyCreator pc = new EarlyBeanReferenceProxyCreator(bf);
TestBean proxy = (TestBean) pc.createProxy(descriptorFor(TestBean.class));
assertThat(proxy.hashCode(), equalTo(System.identityHashCode(proxy)));
}
@Test
public void proxyEqualsAvoidsEagerInstantiation() throws Exception {
EarlyBeanReferenceProxyCreator pc = new EarlyBeanReferenceProxyCreator(bf);
TestBean proxy = (TestBean) pc.createProxy(descriptorFor(TestBean.class));
assertThat(proxy.equals(new Object()), is(false));
assertThat(proxy.equals(proxy), is(true));
TestBean proxy2 = (TestBean) pc.createProxy(descriptorFor(TestBean.class));
assertThat(proxy, not(sameInstance(proxy2)));
assertThat(proxy.equals(proxy2), is(false));
assertThat(proxy2.equals(proxy), is(false));
assertThat(proxy2.equals(proxy2), is(true));
}
@Test
public void proxyFinalizeAvoidsEagerInstantiation() throws Exception {
EarlyBeanReferenceProxyCreator pc = new EarlyBeanReferenceProxyCreator(bf);
BeanWithFinalizer proxy = (BeanWithFinalizer) pc.createProxy(descriptorFor(BeanWithFinalizer.class));
assertThat(BeanWithFinalizer.finalizerWasCalled, is(false));
BeanWithFinalizer.class.getDeclaredMethod("finalize").invoke(proxy);
assertThat(BeanWithFinalizer.finalizerWasCalled, is(false));
}
@Test
public void proxyMethodsDelegateToTargetBeanCausingSingletonRegistrationIfNecessary() throws Exception {
bf.registerBeanDefinition("testBean",
BeanDefinitionBuilder.rootBeanDefinition(TestBean.class)
.addPropertyValue("name", "testBeanName").getBeanDefinition());
EarlyBeanReferenceProxyCreator pc = new EarlyBeanReferenceProxyCreator(bf);
TestBean proxy = (TestBean) pc.createProxy(descriptorFor(TestBean.class));
assertThat(bf.containsBeanDefinition("testBean"), is(true));
assertThat(bf.containsSingleton("testBean"), is(false));
assertThat(proxy.getName(), equalTo("testBeanName"));
assertThat(bf.containsSingleton("testBean"), is(true));
}
@Test
public void beanAnnotatedMethodsReturnEarlyProxyAsWell() throws Exception {
bf.registerBeanDefinition("componentWithInterfaceBeanMethod", new RootBeanDefinition(ComponentWithInterfaceBeanMethod.class));
EarlyBeanReferenceProxyCreator pc = new EarlyBeanReferenceProxyCreator(bf);
ComponentWithInterfaceBeanMethod proxy = (ComponentWithInterfaceBeanMethod) pc.createProxy(descriptorFor(ComponentWithInterfaceBeanMethod.class));
ITestBean bean = proxy.aBeanMethod();
assertThat(bean, instanceOf(EarlyBeanReferenceProxy.class));
assertThat(bf.containsBeanDefinition("componentWithInterfaceBeanMethod"), is(true));
assertThat("calling a @Bean method on an EarlyBeanReferenceProxy object " +
"should not cause its instantation/registration",
bf.containsSingleton("componentWithInterfaceBeanMethod"), is(false));
Object obj = proxy.normalInstanceMethod();
assertThat(bf.containsSingleton("componentWithInterfaceBeanMethod"), is(true));
assertThat(obj, not(instanceOf(EarlyBeanReferenceProxy.class)));
}
@Test
public void proxiesReturnedFromBeanAnnotatedMethodsDereferenceAndDelegateToTheirTargetBean() throws Exception {
bf.registerBeanDefinition("componentWithConcreteBeanMethod", new RootBeanDefinition(ComponentWithConcreteBeanMethod.class));
RootBeanDefinition beanMethodBeanDef = new RootBeanDefinition();
beanMethodBeanDef.setFactoryBeanName("componentWithConcreteBeanMethod");
beanMethodBeanDef.setFactoryMethodName("aBeanMethod");
bf.registerBeanDefinition("aBeanMethod", beanMethodBeanDef);
EarlyBeanReferenceProxyCreator pc = new EarlyBeanReferenceProxyCreator(bf);
ComponentWithConcreteBeanMethod proxy = (ComponentWithConcreteBeanMethod) pc.createProxy(descriptorFor(ComponentWithConcreteBeanMethod.class));
TestBean bean = proxy.aBeanMethod();
assertThat(bean.getName(), equalTo("concrete"));
}
@Test
public void interfaceBeansAreProxied() throws Exception {
EarlyBeanReferenceProxyCreator pc = new EarlyBeanReferenceProxyCreator(bf);
ITestBean proxy = (ITestBean) pc.createProxy(descriptorFor(ITestBean.class));
assertThat(proxy, instanceOf(EarlyBeanReferenceProxy.class));
assertThat(AopUtils.isCglibProxyClass(proxy.getClass()), is(true));
assertEquals(
"interface-based bean proxies should have Object as superclass",
proxy.getClass().getSuperclass(), Object.class);
}
@Test
public void concreteBeansAreProxied() throws Exception {
EarlyBeanReferenceProxyCreator pc = new EarlyBeanReferenceProxyCreator(bf);
TestBean proxy = (TestBean) pc.createProxy(descriptorFor(TestBean.class));
assertThat(proxy, instanceOf(EarlyBeanReferenceProxy.class));
assertThat(AopUtils.isCglibProxyClass(proxy.getClass()), is(true));
assertEquals(
"concrete bean proxies should have the bean class as superclass",
proxy.getClass().getSuperclass(), TestBean.class);
}
@Test
public void beanAnnotatedMethodsWithInterfaceReturnTypeAreProxied() throws Exception {
bf.registerBeanDefinition("componentWithInterfaceBeanMethod", new RootBeanDefinition(ComponentWithInterfaceBeanMethod.class));
EarlyBeanReferenceProxyCreator pc = new EarlyBeanReferenceProxyCreator(bf);
ComponentWithInterfaceBeanMethod proxy = (ComponentWithInterfaceBeanMethod) pc.createProxy(descriptorFor(ComponentWithInterfaceBeanMethod.class));
ITestBean bean = proxy.aBeanMethod();
assertThat(bean, instanceOf(EarlyBeanReferenceProxy.class));
assertThat(AopUtils.isCglibProxyClass(bean.getClass()), is(true));
assertEquals(
"interface-based bean proxies should have Object as superclass",
bean.getClass().getSuperclass(), Object.class);
}
@Test
public void beanAnnotatedMethodsWithConcreteReturnTypeAreProxied() throws Exception {
bf.registerBeanDefinition("componentWithConcreteBeanMethod", new RootBeanDefinition(ComponentWithConcreteBeanMethod.class));
EarlyBeanReferenceProxyCreator pc = new EarlyBeanReferenceProxyCreator(bf);
ComponentWithConcreteBeanMethod proxy = (ComponentWithConcreteBeanMethod) pc.createProxy(descriptorFor(ComponentWithConcreteBeanMethod.class));
TestBean bean = proxy.aBeanMethod();
assertThat(bean, instanceOf(EarlyBeanReferenceProxy.class));
assertThat(AopUtils.isCglibProxyClass(bean.getClass()), is(true));
assertEquals(
"concrete bean proxies should have the bean class as superclass",
bean.getClass().getSuperclass(), TestBean.class);
}
@Test
public void attemptToProxyClassMissingNoArgConstructorFailsGracefully() throws Exception {
EarlyBeanReferenceProxyCreator pc = new EarlyBeanReferenceProxyCreator(bf);
try {
pc.createProxy(descriptorFor(BeanMissingNoArgConstructor.class));
fail("expected ProxyCreationException");
} catch(ProxyCreationException ex) {
assertThat(ex.getMessage(),
equalTo(format(MISSING_NO_ARG_CONSTRUCTOR_ERROR_MESSAGE, BeanMissingNoArgConstructor.class.getName())));
}
}
@Test
public void attemptToProxyClassWithPrivateNoArgConstructorFailsGracefully() throws Exception {
EarlyBeanReferenceProxyCreator pc = new EarlyBeanReferenceProxyCreator(bf);
try {
pc.createProxy(descriptorFor(BeanWithPrivateNoArgConstructor.class));
fail("expected ProxyCreationException");
} catch(ProxyCreationException ex) {
assertThat(ex.getMessage(),
equalTo(format(PRIVATE_NO_ARG_CONSTRUCTOR_ERROR_MESSAGE, BeanWithPrivateNoArgConstructor.class.getName())));
}
}
@Test
public void attemptToProxyFinalClassFailsGracefully() throws Exception {
EarlyBeanReferenceProxyCreator pc = new EarlyBeanReferenceProxyCreator(bf);
try {
pc.createProxy(descriptorFor(FinalBean.class));
fail("expected ProxyCreationException");
} catch(ProxyCreationException ex) {
assertThat(ex.getMessage(),
equalTo(format(FINAL_CLASS_ERROR_MESSAGE, FinalBean.class.getName())));
}
}
private DependencyDescriptor descriptorFor(Class<?> paramType) throws Exception {
@SuppressWarnings("unused")
class C {
void m(ITestBean p) { }
void m(TestBean p) { }
void m(BeanMissingNoArgConstructor p) { }
void m(BeanWithPrivateNoArgConstructor p) { }
void m(FinalBean p) { }
void m(BeanWithFinalizer p) { }
void m(ComponentWithConcreteBeanMethod p) { }
void m(ComponentWithInterfaceBeanMethod p) { }
}
Method targetMethod = C.class.getDeclaredMethod("m", new Class<?>[] { paramType });
MethodParameter mp = new MethodParameter(targetMethod, 0);
DependencyDescriptor dd = new DependencyDescriptor(mp, true, false);
return dd;
}
static class BeanMissingNoArgConstructor {
BeanMissingNoArgConstructor(Object o) { }
}
static class BeanWithPrivateNoArgConstructor {
private BeanWithPrivateNoArgConstructor() { }
}
static final class FinalBean {
}
static class BeanWithFinalizer {
static Boolean finalizerWasCalled = false;
@Override
protected void finalize() throws Throwable {
finalizerWasCalled = true;
}
}
static class ComponentWithConcreteBeanMethod {
@Bean
public TestBean aBeanMethod() {
return new TestBean("concrete");
}
public Object normalInstanceMethod() {
return new Object();
}
}
static class ComponentWithInterfaceBeanMethod {
@Bean
public ITestBean aBeanMethod() {
return new TestBean("interface");
}
public Object normalInstanceMethod() {
return new Object();
}
}
}

View File

@ -0,0 +1,52 @@
/*
* 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.junit.Test;
import org.springframework.context.annotation.configuration.StubSpecification;
import org.springframework.context.config.FeatureSpecification;
public class FeatureConfigurationClassTests {
@Test(expected=FeatureMethodExecutionException.class)
public void featureConfigurationClassesMustNotContainBeanAnnotatedMethods() {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.register(FeatureConfigWithBeanAnnotatedMethod.class);
ctx.refresh();
}
}
@FeatureConfiguration
class FeatureConfigWithBeanAnnotatedMethod {
/**
* This is illegal use. @FeatureConfiguration classes cannot have @Bean methods.
*/
@Bean
public TestBean testBean() {
return new TestBean();
}
/**
* This will never get called. An exception will first be raised regarding the illegal @Bean method above.
*/
@Feature
public FeatureSpecification feature() {
return new StubSpecification();
}
}

View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:c="http://www.springframework.org/schema/c"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.1.xsd">
<bean class="test.beans.TestBean" c:name="beanFromXml"/>
</beans>

View File

@ -0,0 +1,69 @@
/*
* Copyright 2002-2011 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.context.annotation;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.instanceOf;
import static org.junit.Assert.assertNotSame;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertThat;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.configuration.StubSpecification;
import org.springframework.context.config.FeatureSpecification;
import test.beans.TestBean;
/**
* Tests proving that @FeatureConfiguration classes may be use @ImportResource
* and then parameter autowire beans declared in the imported resource(s).
*
* @author Chris Beams
* @since 3.1
*/
public class FeatureConfigurationImportResourceTests {
@Test
public void importResourceFromFeatureConfiguration() {
ApplicationContext ctx =
new AnnotationConfigApplicationContext(ImportingFeatureConfig.class);
TestBean testBean = ctx.getBean(TestBean.class);
assertThat(testBean.getName(), equalTo("beanFromXml"));
// and just quickly prove that the target of the bean proxied for the Feature method
// is indeed the same singleton instance as the one we just pulled from the container
ImportingFeatureConfig ifc = ctx.getBean(ImportingFeatureConfig.class);
TestBean proxyBean = ifc.testBean;
assertThat(proxyBean, instanceOf(EarlyBeanReferenceProxy.class));
assertNotSame(proxyBean, testBean);
assertSame(((EarlyBeanReferenceProxy)proxyBean).dereferenceTargetBean(), testBean);
}
@FeatureConfiguration
@ImportResource("org/springframework/context/annotation/FeatureConfigurationImportResourceTests-context.xml")
static class ImportingFeatureConfig {
TestBean testBean;
@Feature
public FeatureSpecification f(TestBean testBean) {
this.testBean = testBean;
return new StubSpecification();
}
}
}

View File

@ -0,0 +1,105 @@
/*
* Copyright 2002-2011 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.context.annotation;
import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertThat;
import org.junit.Test;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.configuration.StubSpecification;
import org.springframework.context.config.FeatureSpecification;
/**
* Tests proving that @Configuration classes may @Import @FeatureConfiguration
* classes, and vice versa.
*
* @author Chris Beams
* @since 3.1
*/
public class FeatureConfigurationImportTests {
@Test
public void importFeatureConfigurationFromConfiguration() {
ConfigurableApplicationContext ctx =
new AnnotationConfigApplicationContext(ImportingConfig.class);
ImportedFeatureConfig ifc = ctx.getBean(ImportedFeatureConfig.class);
assertThat(
"@FeatureConfiguration class was imported and registered " +
"as a bean but its @Feature method was never called",
ifc.featureMethodWasCalled, is(true));
}
@Test
public void importConfigurationFromFeatureConfiguration() {
ConfigurableApplicationContext ctx =
new AnnotationConfigApplicationContext(ImportingFeatureConfig.class);
ImportingFeatureConfig ifc = ctx.getBean(ImportingFeatureConfig.class);
ImportedConfig ic = ctx.getBean(ImportedConfig.class);
assertThat(
"@FeatureConfiguration class was registered directly against " +
"the container but its @Feature method was never called",
ifc.featureMethodWasCalled, is(true));
assertThat(
"@Configuration class was @Imported but its @Bean method" +
"was never registered / called",
ic.beanMethodWasCalled, is(true));
}
@Configuration
@Import(ImportedFeatureConfig.class)
static class ImportingConfig {
}
@FeatureConfiguration
static class ImportedFeatureConfig {
boolean featureMethodWasCalled = false;
@Feature
public FeatureSpecification f() {
this.featureMethodWasCalled = true;
return new StubSpecification();
}
}
@Configuration
static class ImportedConfig {
boolean beanMethodWasCalled = true;
@Bean
public TestBean testBean() {
this.beanMethodWasCalled = true;
return new TestBean();
}
}
@FeatureConfiguration
@Import(ImportedConfig.class)
static class ImportingFeatureConfig {
boolean featureMethodWasCalled = false;
@Feature
public FeatureSpecification f() {
this.featureMethodWasCalled = true;
return new StubSpecification();
}
}
}

View File

@ -0,0 +1,76 @@
/*
* Copyright 2002-2011 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.context.annotation;
import static org.hamcrest.CoreMatchers.instanceOf;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.CoreMatchers.not;
import static org.hamcrest.CoreMatchers.notNullValue;
import static org.junit.Assert.assertThat;
import org.junit.Test;
import org.springframework.context.annotation.configuration.StubSpecification;
import org.springframework.context.config.FeatureSpecification;
import test.beans.ITestBean;
import test.beans.TestBean;
/**
* Tests proving that @Feature methods may reference the product of @Bean methods.
*
* @author Chris Beams
* @since 3.1
*/
public class FeatureMethodBeanReferenceTests {
@Test
public void test() {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.register(FeatureConfig.class, Config.class);
ctx.refresh();
TestBean registeredBean = ctx.getBean("testBean", TestBean.class);
TestBean proxiedBean = ctx.getBean(FeatureConfig.class).testBean;
assertThat(registeredBean, not(instanceOf(EarlyBeanReferenceProxy.class)));
assertThat(proxiedBean, notNullValue());
assertThat(proxiedBean, instanceOf(EarlyBeanReferenceProxy.class));
assertThat(proxiedBean.getSpouse(), is(registeredBean.getSpouse()));
}
@FeatureConfiguration
static class FeatureConfig {
TestBean testBean;
@Feature
public FeatureSpecification f(TestBean testBean) {
this.testBean = testBean;
return new StubSpecification();
}
}
@Configuration
static class Config {
@Bean
public ITestBean testBean() {
return new TestBean(new TestBean("mySpouse"));
}
}
}

View File

@ -0,0 +1,192 @@
/*
* Copyright 2002-2011 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.context.annotation;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.instanceOf;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.CoreMatchers.not;
import static org.junit.Assert.assertThat;
import org.junit.Test;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.context.annotation.configuration.StubSpecification;
import org.springframework.context.config.FeatureSpecification;
import test.beans.ITestBean;
import test.beans.TestBean;
/**
* Tests that @Bean methods referenced from within @Feature methods
* get proxied early to avoid premature instantiation of actual
* bean instances.
*
* @author Chris Beams
* @since 3.1
*/
public class FeatureMethodEarlyBeanProxyTests {
@Test
public void earlyProxyCreationAndBeanRegistrationLifecycle() {
AnnotationConfigApplicationContext ctx =
new AnnotationConfigApplicationContext(FeatureConfig.class);
//
// see additional assertions in FeatureConfig#feature()
//
// sanity check that all the bean definitions we expecting are present
assertThat(ctx.getBeanFactory().containsBeanDefinition("lazyHelperBean"), is(true));
assertThat(ctx.getBeanFactory().containsBeanDefinition("eagerHelperBean"), is(true));
assertThat(ctx.getBeanFactory().containsBeanDefinition("lazyPassthroughBean"), is(true));
assertThat(ctx.getBeanFactory().containsBeanDefinition("eagerPassthroughBean"), is(true));
// the lazy helper bean had methods invoked during feature method execution. it should be registered
assertThat(ctx.getBeanFactory().containsSingleton("lazyHelperBean"), is(true));
// the eager helper bean had methods invoked but should be registered in any case is it is non-lazy
assertThat(ctx.getBeanFactory().containsSingleton("eagerHelperBean"), is(true));
// the lazy passthrough bean was referenced in the feature method, but never invoked. it should not be registered
assertThat(ctx.getBeanFactory().containsSingleton("lazyPassthroughBean"), is(false));
// the eager passthrough bean should be registered in any case as it is non-lazy
assertThat(ctx.getBeanFactory().containsSingleton("eagerPassthroughBean"), is(true));
// now actually fetch all the beans. none should be proxies
assertThat(ctx.getBean("lazyHelperBean"), not(instanceOf(EarlyBeanReferenceProxy.class)));
assertThat(ctx.getBean("eagerHelperBean"), not(instanceOf(EarlyBeanReferenceProxy.class)));
assertThat(ctx.getBean("lazyPassthroughBean"), not(instanceOf(EarlyBeanReferenceProxy.class)));
assertThat(ctx.getBean("eagerPassthroughBean"), not(instanceOf(EarlyBeanReferenceProxy.class)));
}
@Test
public void earlyProxyBeansMayBeInterfaceBasedOrConcrete() {
new AnnotationConfigApplicationContext(FeatureConfigReferencingNonInterfaceBeans.class);
}
}
@FeatureConfiguration
@Import(TestBeanConfig.class)
class FeatureConfig implements BeanFactoryAware {
private DefaultListableBeanFactory beanFactory;
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
this.beanFactory = (DefaultListableBeanFactory)beanFactory;
}
@Feature
public StubSpecification feature(TestBeanConfig beans) {
assertThat(
"The @Configuration class instance itself should be an early-ref proxy",
beans, instanceOf(EarlyBeanReferenceProxy.class));
// invocation of @Bean methods within @Feature methods should return proxies
ITestBean lazyHelperBean = beans.lazyHelperBean();
ITestBean eagerHelperBean = beans.eagerHelperBean();
ITestBean lazyPassthroughBean = beans.lazyPassthroughBean();
ITestBean eagerPassthroughBean = beans.eagerPassthroughBean();
assertThat(lazyHelperBean, instanceOf(EarlyBeanReferenceProxy.class));
assertThat(eagerHelperBean, instanceOf(EarlyBeanReferenceProxy.class));
assertThat(lazyPassthroughBean, instanceOf(EarlyBeanReferenceProxy.class));
assertThat(eagerPassthroughBean, instanceOf(EarlyBeanReferenceProxy.class));
// but at this point, the proxy instances should not have
// been registered as singletons with the container.
assertThat(this.beanFactory.containsSingleton("lazyHelperBean"), is(false));
assertThat(this.beanFactory.containsSingleton("eagerHelperBean"), is(false));
assertThat(this.beanFactory.containsSingleton("lazyPassthroughBean"), is(false));
assertThat(this.beanFactory.containsSingleton("eagerPassthroughBean"), is(false));
// invoking a method on the proxy should cause it to pass through
// to the container, instantiate the actual bean in question and
// register that actual underlying instance as a singleton.
assertThat(lazyHelperBean.getName(), equalTo("lazyHelper"));
assertThat(eagerHelperBean.getName(), equalTo("eagerHelper"));
assertThat(this.beanFactory.containsSingleton("lazyHelperBean"), is(true));
assertThat(this.beanFactory.containsSingleton("eagerHelperBean"), is(true));
// since no methods were called on the passthrough beans, they should remain
// uncreated / unregistered.
assertThat(this.beanFactory.containsSingleton("lazyPassthroughBean"), is(false));
assertThat(this.beanFactory.containsSingleton("eagerPassthroughBean"), is(false));
return new StubSpecification();
}
}
@Configuration
class TestBeanConfig {
@Lazy @Bean
public ITestBean lazyHelperBean() {
return new TestBean("lazyHelper");
}
@Bean
public ITestBean eagerHelperBean() {
return new TestBean("eagerHelper");
}
@Lazy @Bean
public ITestBean lazyPassthroughBean() {
return new TestBean("lazyPassthrough");
}
@Bean
public ITestBean eagerPassthroughBean() {
return new TestBean("eagerPassthrough");
}
}
@FeatureConfiguration
@Import(NonInterfaceBeans.class)
class FeatureConfigReferencingNonInterfaceBeans {
@Feature
public FeatureSpecification feature1(NonInterfaceBeans beans) throws Throwable {
beans.testBean();
return new StubSpecification();
}
@Feature
public FeatureSpecification feature2(TestBean testBean) throws Throwable {
return new StubSpecification();
}
}
@Configuration
class NonInterfaceBeans {
@Bean
public TestBean testBean() {
return new TestBean("invalid");
}
}

View File

@ -0,0 +1,70 @@
/*
* Copyright 2002-2011 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.context.annotation;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.junit.Assert.assertThat;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.configuration.StubSpecification;
import org.springframework.context.config.FeatureSpecification;
import test.beans.ITestBean;
import test.beans.TestBean;
/**
* Tests proving that @Feature methods may reference beans using @Qualifier
* as a parameter annotation.
*
* @author Chris Beams
* @since 3.1
*/
public class FeatureMethodQualifiedBeanReferenceTests {
@Test
public void test() {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.register(Features.class, TestBeans.class);
ctx.refresh();
}
@FeatureConfiguration
static class Features {
@Feature
public FeatureSpecification f(@Qualifier("testBean1") ITestBean testBean) {
assertThat(testBean.getName(), equalTo("one"));
return new StubSpecification();
}
}
@Configuration
static class TestBeans {
@Bean
public ITestBean testBean1() {
return new TestBean("one");
}
@Bean
public ITestBean testBean2() {
return new TestBean("two");
}
}
}

View File

@ -0,0 +1,64 @@
/*
* Copyright 2002-2011 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.context.annotation;
import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertThat;
import org.junit.Test;
import org.springframework.context.annotation.configuration.StubSpecification;
import org.springframework.context.config.ExecutorContext;
import org.springframework.context.config.FeatureSpecification;
import org.springframework.context.config.FeatureSpecificationExecutor;
import org.springframework.util.Assert;
/**
* Simple tests to ensure that @Feature methods are invoked and that the
* resulting returned {@link FeatureSpecification} object is delegated to
* the correct {@link FeatureSpecificationExecutor}.
*
* @author Chris Beams
* @since 3.1
*/
public class SimpleFeatureMethodProcessingTests {
@Test
public void test() {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.register(FeatureConfig.class);
assertThat(MySpecificationExecutor.executeMethodWasCalled, is(false));
ctx.refresh();
assertThat(MySpecificationExecutor.executeMethodWasCalled, is(true));
}
@FeatureConfiguration
static class FeatureConfig {
@Feature
public FeatureSpecification f() {
return new StubSpecification(MySpecificationExecutor.class);
}
}
static class MySpecificationExecutor implements FeatureSpecificationExecutor {
static boolean executeMethodWasCalled = false;
public void execute(FeatureSpecification spec, ExecutorContext executorContext) {
Assert.state(executeMethodWasCalled == false);
executeMethodWasCalled = true;
}
}
}

View File

@ -16,11 +16,13 @@
package org.springframework.context.annotation.configuration;
import static org.junit.Assert.*;
import org.junit.Test;
import test.beans.ITestBean;
import test.beans.TestBean;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotSame;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import org.junit.Test;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.beans.factory.annotation.Required;
@ -43,7 +45,9 @@ import org.springframework.context.annotation.ConfigurationClassPostProcessor;
import org.springframework.context.annotation.Scope;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.context.support.GenericApplicationContext;
import org.springframework.core.PriorityOrdered;
import test.beans.ITestBean;
import test.beans.TestBean;
/**
* Miscellaneous system tests covering {@link Bean} naming, aliases, scoping and error

View File

@ -0,0 +1,119 @@
/*
* Copyright 2002-2011 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.context.annotation.configuration;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.nullValue;
import static org.junit.Assert.assertThat;
import org.junit.Ignore;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.support.PropertySourcesPlaceholderConfigurer;
import test.beans.ITestBean;
import test.beans.TestBean;
/**
* A configuration class that registers a placeholder configurer @Bean method
* cannot also have @Value fields. Logically, the config class must be instantiated
* in order to invoke the placeholder configurer bean method, and it is a
* chicken-and-egg problem to process the @Value field.
*
* Therefore, placeholder configurers should be put in separate configuration classes
* as has been done in the test below. Simply said, placeholder configurer @Bean methods
* and @Value fields in the same configuration class are mutually exclusive.
*
* @author Chris Beams
*/
public class ConfigurationClassWithPlaceholderConfigurerBeanTests {
/**
* Intentionally ignored test proving that a property placeholder bean
* cannot be declared in the same configuration class that has a @Value
* field in need of placeholder replacement. It's an obvious chicken-and-egg issue.
* The solution is to do as {@link #valueFieldsAreProcessedWhenPlaceholderConfigurerIsSegregated()}
* does and segragate the two bean definitions across configuration classes.
*/
@Ignore @Test
public void valueFieldsAreNotProcessedWhenPlaceholderConfigurerIsIntegrated() {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.register(ConfigWithValueFieldAndPlaceholderConfigurer.class);
System.setProperty("test.name", "foo");
ctx.refresh();
System.clearProperty("test.name");
TestBean testBean = ctx.getBean(TestBean.class);
assertThat(testBean.getName(), nullValue());
}
@Test
public void valueFieldsAreProcessedWhenPlaceholderConfigurerIsSegregated() {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.register(ConfigWithValueField.class);
ctx.register(ConfigWithPlaceholderConfigurer.class);
System.setProperty("test.name", "foo");
ctx.refresh();
System.clearProperty("test.name");
TestBean testBean = ctx.getBean(TestBean.class);
assertThat(testBean.getName(), equalTo("foo"));
}
}
@Configuration
class ConfigWithValueField {
@Value("${test.name}")
private String name;
@Bean
public ITestBean testBean() {
return new TestBean(this.name);
}
}
@Configuration
class ConfigWithPlaceholderConfigurer {
@Bean
public PropertySourcesPlaceholderConfigurer ppc() {
return new PropertySourcesPlaceholderConfigurer();
}
}
@Configuration
class ConfigWithValueFieldAndPlaceholderConfigurer {
@Value("${test.name}")
private String name;
@Bean
public ITestBean testBean() {
return new TestBean(this.name);
}
@Bean
public PropertySourcesPlaceholderConfigurer ppc() {
return new PropertySourcesPlaceholderConfigurer();
}
}

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean class="org.springframework.context.annotation.configuration.ColourHolder"/>
</beans>

View File

@ -0,0 +1,36 @@
/*
* Copyright 2002-2011 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.context.annotation.configuration;
import org.junit.runner.RunWith;
import org.junit.runners.Suite;
import org.junit.runners.Suite.SuiteClasses;
import org.springframework.context.annotation.FeatureMethodEarlyBeanProxyTests;
/**
* Test suite that groups all tests related to @Feature method lifecycle issues.
*
* @author Chris Beams
*/
@RunWith(Suite.class)
@SuiteClasses({
FeatureMethodEarlyBeanProxyTests.class,
ConfigurationClassWithPlaceholderConfigurerBeanTests.class,
})
public class FeatureMethodLifecycleIssueTestSuite {
}

View File

@ -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.configuration;
import org.springframework.beans.factory.parsing.SimpleProblemCollector;
import org.springframework.context.config.AbstractFeatureSpecification;
import org.springframework.context.config.ExecutorContext;
import org.springframework.context.config.FeatureSpecification;
import org.springframework.context.config.FeatureSpecificationExecutor;
public class StubSpecification extends AbstractFeatureSpecification {
public StubSpecification() {
this(StubSpecificationExecutor.class);
}
public StubSpecification(Class<? extends FeatureSpecificationExecutor> excecutorType) {
super(excecutorType);
}
@Override
protected void doValidate(SimpleProblemCollector reporter) {
}
}
class StubSpecificationExecutor implements FeatureSpecificationExecutor {
public void execute(FeatureSpecification spec, ExecutorContext executorContext) {
}
}

View File

@ -36,7 +36,7 @@ import org.junit.Before;
import org.junit.Test;
import org.springframework.beans.MutablePropertyValues;
import org.springframework.context.i18n.LocaleContextHolder;
import org.springframework.core.convert.support.ConversionServiceFactory;
import org.springframework.core.convert.support.DefaultConversionService;
import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.format.annotation.DateTimeFormat.ISO;
import org.springframework.format.support.FormattingConversionService;
@ -54,7 +54,7 @@ public class JodaTimeFormattingTests {
@Before
public void setUp() {
ConversionServiceFactory.addDefaultConverters(conversionService);
DefaultConversionService.addDefaultConverters(conversionService);
JodaTimeFormatterRegistrar registrar = new JodaTimeFormatterRegistrar();
registrar.registerFormatters(conversionService);

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,7 +27,7 @@ import org.junit.Before;
import org.junit.Test;
import org.springframework.beans.MutablePropertyValues;
import org.springframework.context.i18n.LocaleContextHolder;
import org.springframework.core.convert.support.ConversionServiceFactory;
import org.springframework.core.convert.support.DefaultConversionService;
import org.springframework.format.annotation.NumberFormat;
import org.springframework.format.annotation.NumberFormat.Style;
import org.springframework.format.support.FormattingConversionService;
@ -46,7 +46,7 @@ public class NumberFormattingTests {
@Before
public void setUp() {
ConversionServiceFactory.addDefaultConverters(conversionService);
DefaultConversionService.addDefaultConverters(conversionService);
conversionService.setEmbeddedValueResolver(new StringValueResolver() {
public String resolveStringValue(String strVal) {
if ("${pattern}".equals(strVal)) {

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,6 +16,9 @@
package org.springframework.format.support;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Date;
@ -27,10 +30,8 @@ import org.joda.time.DateTime;
import org.joda.time.LocalDate;
import org.joda.time.format.DateTimeFormat;
import org.junit.After;
import static org.junit.Assert.*;
import org.junit.Before;
import org.junit.Test;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.BeanWrapper;
import org.springframework.beans.PropertyAccessorFactory;
@ -40,7 +41,7 @@ import org.springframework.context.i18n.LocaleContextHolder;
import org.springframework.context.support.GenericApplicationContext;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.core.convert.converter.Converter;
import org.springframework.core.convert.support.ConversionServiceFactory;
import org.springframework.core.convert.support.DefaultConversionService;
import org.springframework.format.datetime.joda.DateTimeParser;
import org.springframework.format.datetime.joda.JodaDateTimeFormatAnnotationFormatterFactory;
import org.springframework.format.datetime.joda.ReadablePartialPrinter;
@ -57,7 +58,7 @@ public class FormattingConversionServiceTests {
@Before
public void setUp() {
formattingService = new FormattingConversionService();
ConversionServiceFactory.addDefaultConverters(formattingService);
DefaultConversionService.addDefaultConverters(formattingService);
LocaleContextHolder.setLocale(Locale.US);
}

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.
@ -30,8 +30,6 @@ import java.util.TreeSet;
import junit.framework.TestCase;
import org.junit.Ignore;
import org.junit.Test;
import org.springframework.beans.BeanWithObjectProperty;
import org.springframework.beans.DerivedTestBean;
import org.springframework.beans.ITestBean;
@ -46,7 +44,7 @@ import org.springframework.beans.propertyeditors.CustomNumberEditor;
import org.springframework.context.i18n.LocaleContextHolder;
import org.springframework.context.support.ResourceBundleMessageSource;
import org.springframework.context.support.StaticMessageSource;
import org.springframework.core.convert.support.ConversionServiceFactory;
import org.springframework.core.convert.support.DefaultConversionService;
import org.springframework.format.number.NumberFormatter;
import org.springframework.format.support.FormattingConversionService;
import org.springframework.util.StringUtils;
@ -319,7 +317,7 @@ public class DataBinderTests extends TestCase {
TestBean tb = new TestBean();
DataBinder binder = new DataBinder(tb);
FormattingConversionService conversionService = new FormattingConversionService();
ConversionServiceFactory.addDefaultConverters(conversionService);
DefaultConversionService.addDefaultConverters(conversionService);
conversionService.addFormatterForFieldType(Float.class, new NumberFormatter());
binder.setConversionService(conversionService);
MutablePropertyValues pvs = new MutablePropertyValues();
@ -350,7 +348,7 @@ public class DataBinderTests extends TestCase {
TestBean tb = new TestBean();
DataBinder binder = new DataBinder(tb);
FormattingConversionService conversionService = new FormattingConversionService();
ConversionServiceFactory.addDefaultConverters(conversionService);
DefaultConversionService.addDefaultConverters(conversionService);
conversionService.addFormatterForFieldType(Float.class, new NumberFormatter());
binder.setConversionService(conversionService);
MutablePropertyValues pvs = new MutablePropertyValues();
@ -372,7 +370,7 @@ public class DataBinderTests extends TestCase {
BeanWithIntegerList tb = new BeanWithIntegerList();
DataBinder binder = new DataBinder(tb);
FormattingConversionService conversionService = new FormattingConversionService();
ConversionServiceFactory.addDefaultConverters(conversionService);
DefaultConversionService.addDefaultConverters(conversionService);
conversionService.addFormatterForFieldType(Float.class, new NumberFormatter());
binder.setConversionService(conversionService);
MutablePropertyValues pvs = new MutablePropertyValues();
@ -393,7 +391,7 @@ public class DataBinderTests extends TestCase {
BeanWithIntegerList tb = new BeanWithIntegerList();
DataBinder binder = new DataBinder(tb);
FormattingConversionService conversionService = new FormattingConversionService();
ConversionServiceFactory.addDefaultConverters(conversionService);
DefaultConversionService.addDefaultConverters(conversionService);
conversionService.addFormatterForFieldType(Float.class, new NumberFormatter());
binder.setConversionService(conversionService);
MutablePropertyValues pvs = new MutablePropertyValues();
@ -415,7 +413,7 @@ public class DataBinderTests extends TestCase {
TestBean tb = new TestBean();
DataBinder binder = new DataBinder(tb);
FormattingConversionService conversionService = new FormattingConversionService();
ConversionServiceFactory.addDefaultConverters(conversionService);
DefaultConversionService.addDefaultConverters(conversionService);
conversionService.addFormatterForFieldType(Float.class, new NumberFormatter());
binder.setConversionService(conversionService);
binder.initDirectFieldAccess();
@ -448,7 +446,7 @@ public class DataBinderTests extends TestCase {
DataBinder binder = new DataBinder(tb);
binder.initDirectFieldAccess();
FormattingConversionService conversionService = new FormattingConversionService();
ConversionServiceFactory.addDefaultConverters(conversionService);
DefaultConversionService.addDefaultConverters(conversionService);
conversionService.addFormatterForFieldType(Float.class, new NumberFormatter());
binder.setConversionService(conversionService);
MutablePropertyValues pvs = new MutablePropertyValues();
@ -553,7 +551,7 @@ public class DataBinderTests extends TestCase {
assertEquals(1, disallowedFields.length);
assertEquals("age", disallowedFields[0]);
Map m = binder.getBindingResult().getModel();
Map<?,?> m = binder.getBindingResult().getModel();
assertTrue("There is one element in map", m.size() == 2);
TestBean tb = (TestBean) m.get("person");
assertTrue("Same object", tb.equals(rod));
@ -1415,6 +1413,7 @@ public class DataBinderTests extends TestCase {
assertEquals("badName", nameError.getCode());
}
@SuppressWarnings("unchecked")
public void testBindingWithResortedList() {
IndexedTestBean tb = new IndexedTestBean();
DataBinder binder = new DataBinder(tb, "tb");

View File

@ -29,66 +29,16 @@ import org.springframework.core.convert.converter.GenericConverter;
*
* @author Keith Donald
* @author Juergen Hoeller
* @author Chris Beams
* @since 3.0
*/
public abstract class ConversionServiceFactory {
/**
* Create a new default ConversionService instance that can be safely modified.
*/
public static GenericConversionService createDefaultConversionService() {
GenericConversionService conversionService = new GenericConversionService();
addDefaultConverters(conversionService);
return conversionService;
}
/**
* Populate the given ConversionService instance with all applicable default converters.
*/
public static void addDefaultConverters(GenericConversionService conversionService) {
conversionService.addConverter(new ArrayToCollectionConverter(conversionService));
conversionService.addConverter(new CollectionToArrayConverter(conversionService));
conversionService.addConverter(new ArrayToStringConverter(conversionService));
conversionService.addConverter(new StringToArrayConverter(conversionService));
conversionService.addConverter(new ArrayToObjectConverter(conversionService));
conversionService.addConverter(new ObjectToArrayConverter(conversionService));
conversionService.addConverter(new CollectionToStringConverter(conversionService));
conversionService.addConverter(new StringToCollectionConverter(conversionService));
conversionService.addConverter(new CollectionToObjectConverter(conversionService));
conversionService.addConverter(new ObjectToCollectionConverter(conversionService));
conversionService.addConverter(new ArrayToArrayConverter(conversionService));
conversionService.addConverter(new CollectionToCollectionConverter(conversionService));
conversionService.addConverter(new MapToMapConverter(conversionService));
conversionService.addConverter(new PropertiesToStringConverter());
conversionService.addConverter(new StringToPropertiesConverter());
conversionService.addConverter(new StringToBooleanConverter());
conversionService.addConverter(new StringToCharacterConverter());
conversionService.addConverter(new StringToLocaleConverter());
conversionService.addConverterFactory(new StringToNumberConverterFactory());
conversionService.addConverterFactory(new StringToEnumConverterFactory());
conversionService.addConverter(new NumberToCharacterConverter());
conversionService.addConverterFactory(new CharacterToNumberFactory());
conversionService.addConverterFactory(new NumberToNumberConverterFactory());
conversionService.addConverter(new ObjectToStringConverter());
conversionService.addConverter(new ObjectToObjectConverter());
conversionService.addConverter(new IdToEntityConverter(conversionService));
}
/**
* Register the given converter objects with the given target registry.
* Register the given Converter objects with the given target ConverterRegistry.
* @param converters the converter objects: implementing {@link Converter},
* {@link ConverterFactory}, or {@link GenericConverter}
* @param registry the target registry to register with
* @param registry the target registry
*/
public static void registerConverters(Set<?> converters, ConverterRegistry registry) {
if (converters != null) {
@ -110,4 +60,22 @@ public abstract class ConversionServiceFactory {
}
}
/**
* Create a new default ConversionService instance that can be safely modified.
*
* @deprecated in Spring 3.1 in favor of {@link DefaultConversionService#DefaultConversionService()}
*/
public static GenericConversionService createDefaultConversionService() {
return new DefaultConversionService();
}
/**
* Populate the given ConversionService instance with all applicable default converters.
*
* @deprecated in Spring 3.1 in favor of {@link DefaultConversionService#addDefaultConverters}
*/
public static void addDefaultConverters(GenericConversionService conversionService) {
DefaultConversionService.addDefaultConverters(conversionService);
}
}

View File

@ -0,0 +1,86 @@
/*
* 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.core.convert.support;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.converter.ConverterRegistry;
/**
* A specialization of {@link GenericConversionService} configured by default with
* converters appropriate for most applications.
*
* <p>Designed for direct instantiation but also exposes the static
* {@link #addDefaultConverters} utility method for ad hoc use against any
* {@code GenericConversionService} instance.
*
* @author Chris Beams
* @since 3.1
*/
public class DefaultConversionService extends GenericConversionService {
/**
* Create a new {@code DefaultConversionService} with the set of
* {@linkplain DefaultConversionService#addDefaultConverters default converters}.
*/
public DefaultConversionService() {
addDefaultConverters(this);
}
/**
* Add converters appropriate for most environments.
* @param conversionService the service to register default formatters against
*/
public static void addDefaultConverters(GenericConversionService conversionService) {
conversionService.addConverter(new ArrayToCollectionConverter(conversionService));
conversionService.addConverter(new CollectionToArrayConverter(conversionService));
conversionService.addConverter(new ArrayToStringConverter(conversionService));
conversionService.addConverter(new StringToArrayConverter(conversionService));
conversionService.addConverter(new ArrayToObjectConverter(conversionService));
conversionService.addConverter(new ObjectToArrayConverter(conversionService));
conversionService.addConverter(new CollectionToStringConverter(conversionService));
conversionService.addConverter(new StringToCollectionConverter(conversionService));
conversionService.addConverter(new CollectionToObjectConverter(conversionService));
conversionService.addConverter(new ObjectToCollectionConverter(conversionService));
conversionService.addConverter(new ArrayToArrayConverter(conversionService));
conversionService.addConverter(new CollectionToCollectionConverter(conversionService));
conversionService.addConverter(new MapToMapConverter(conversionService));
conversionService.addConverter(new PropertiesToStringConverter());
conversionService.addConverter(new StringToPropertiesConverter());
conversionService.addConverter(new StringToBooleanConverter());
conversionService.addConverter(new StringToCharacterConverter());
conversionService.addConverter(new StringToLocaleConverter());
conversionService.addConverterFactory(new StringToNumberConverterFactory());
conversionService.addConverterFactory(new StringToEnumConverterFactory());
conversionService.addConverter(new NumberToCharacterConverter());
conversionService.addConverterFactory(new CharacterToNumberFactory());
conversionService.addConverterFactory(new NumberToNumberConverterFactory());
conversionService.addConverter(new ObjectToStringConverter());
conversionService.addConverter(new ObjectToObjectConverter());
conversionService.addConverter(new IdToEntityConverter(conversionService));
}
}

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.
@ -24,11 +24,10 @@ import static org.springframework.util.SystemPropertyUtils.VALUE_SEPARATOR;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.support.ConversionServiceFactory;
import org.springframework.core.convert.support.DefaultConversionService;
import org.springframework.util.PropertyPlaceholderHelper;
import org.springframework.util.PropertyPlaceholderHelper.PlaceholderResolver;
/**
* Abstract base class for resolving properties against any underlying source.
*
@ -39,7 +38,7 @@ public abstract class AbstractPropertyResolver implements ConfigurablePropertyRe
protected final Log logger = LogFactory.getLog(getClass());
protected ConversionService conversionService = ConversionServiceFactory.createDefaultConversionService();
protected ConversionService conversionService = new DefaultConversionService();
private PropertyPlaceholderHelper nonStrictHelper;
private PropertyPlaceholderHelper strictHelper;

View File

@ -69,7 +69,7 @@ public interface Environment extends PropertyResolver {
* Return the set of profiles explicitly made active for this environment. Profiles are used for
* creating logical groupings of bean definitions to be registered conditionally, often based on
* deployment environment. Profiles can be activated by setting {@linkplain
* AbstractEnvironment#ACTIVE_PROFILES_PROPERTY_NAME "spring.profiles.active"} as a system property
* AbstractEnvironment#ACTIVE_PROFILES_PROPERTY_NAME "spring.profile.active"} as a system property
* or by calling {@link ConfigurableEnvironment#setActiveProfiles(String...)}.
*
* <p>If no profiles have explicitly been specified as active, then any 'default' profiles will implicitly

View File

@ -36,6 +36,11 @@ public interface MethodMetadata {
*/
String getMethodName();
/**
* Return the fully-qualified name of the return type of the method.
*/
String getMethodReturnType();
/**
* Return the fully-qualified name of the class that declares this method.
*/

View File

@ -58,7 +58,11 @@ public class StandardMethodMetadata implements MethodMetadata {
public String getMethodName() {
return this.introspectedMethod.getName();
}
public String getMethodReturnType() {
return this.introspectedMethod.getReturnType().getName();
}
public String getDeclaringClassName() {
return this.introspectedMethod.getDeclaringClass().getName();
}

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.
@ -62,7 +62,7 @@ final class AnnotationMetadataReadingVisitor extends ClassMetadataReadingVisitor
@Override
public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
return new MethodMetadataReadingVisitor(name, access, this.getClassName(), this.classLoader, this.methodMetadataMap);
return new MethodMetadataReadingVisitor(name, getReturnTypeFromAsmMethodDescriptor(desc), access, this.getClassName(), this.classLoader, this.methodMetadataMap);
}
@Override
@ -125,6 +125,19 @@ final class AnnotationMetadataReadingVisitor extends ClassMetadataReadingVisitor
}
value = convArray;
}
else if (classValuesAsString) {
if (value instanceof Class) {
value = ((Class) value).getName();
}
else if (value instanceof Class[]) {
Class[] clazzArray = (Class[]) value;
String[] newValue = new String[clazzArray.length];
for (int i = 0; i < clazzArray.length; i++) {
newValue[i] = clazzArray[i].getName();
}
value = newValue;
}
}
result.put(entry.getKey(), value);
}
catch (Exception ex) {
@ -148,4 +161,46 @@ final class AnnotationMetadataReadingVisitor extends ClassMetadataReadingVisitor
return annotatedMethods;
}
/**
* Convert a type descriptor to a classname suitable for classloading with
* Class.forName().
*
* @param typeDescriptor see ASM guide section 2.1.3
*/
private static String convertAsmTypeDescriptorToClassName(String typeDescriptor) {
final String internalName; // See ASM guide section 2.1.2
if ("V".equals(typeDescriptor))
return Void.class.getName();
if ("I".equals(typeDescriptor))
return Integer.class.getName();
if ("Z".equals(typeDescriptor))
return Boolean.class.getName();
// strip the leading array/object/primitive identifier
if (typeDescriptor.startsWith("[["))
internalName = typeDescriptor.substring(3);
else if (typeDescriptor.startsWith("["))
internalName = typeDescriptor.substring(2);
else
internalName = typeDescriptor.substring(1);
// convert slashes to dots
String className = internalName.replace('/', '.');
// and strip trailing semicolon (if present)
if (className.endsWith(";"))
className = className.substring(0, internalName.length() - 1);
return className;
}
/**
* @param methodDescriptor see ASM guide section 2.1.4
*/
private static String getReturnTypeFromAsmMethodDescriptor(String methodDescriptor) {
String returnTypeDescriptor = methodDescriptor.substring(methodDescriptor.indexOf(')') + 1);
return convertAsmTypeDescriptorToClassName(returnTypeDescriptor);
}
}

View File

@ -43,6 +43,8 @@ final class MethodMetadataReadingVisitor extends MethodAdapter implements Method
private final int access;
private String returnType;
private String declaringClassName;
private final ClassLoader classLoader;
@ -51,10 +53,11 @@ final class MethodMetadataReadingVisitor extends MethodAdapter implements Method
private final Map<String, Map<String, Object>> attributeMap = new LinkedHashMap<String, Map<String, Object>>(2);
public MethodMetadataReadingVisitor(String name, int access, String declaringClassName, ClassLoader classLoader,
public MethodMetadataReadingVisitor(String name, String returnType, int access, String declaringClassName, ClassLoader classLoader,
MultiValueMap<String, MethodMetadata> methodMetadataMap) {
super(new EmptyVisitor());
this.name = name;
this.returnType = returnType;
this.access = access;
this.declaringClassName = declaringClassName;
this.classLoader = classLoader;
@ -72,6 +75,10 @@ final class MethodMetadataReadingVisitor extends MethodAdapter implements Method
return this.name;
}
public String getMethodReturnType() {
return this.returnType;
}
public boolean isStatic() {
return ((this.access & Opcodes.ACC_STATIC) != 0);
}

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.
@ -32,6 +32,7 @@ import java.util.Arrays;
* @author Keith Donald
* @author Rod Johnson
* @author Rob Harrop
* @author Chris Beams
* @since 19.03.2004
* @see org.apache.commons.lang.ObjectUtils
*/
@ -121,6 +122,54 @@ public abstract class ObjectUtils {
return false;
}
/**
* Check whether the given array of enum constants contains a constant with the given name,
* ignoring case when determining a match.
* @param enumValues the enum values to check, typically the product of a call to MyEnum.values()
* @param constant the constant name to find (must not be null or empty string)
* @return whether the constant has been found in the given array
*/
public static boolean containsConstant(Enum<?>[] enumValues, String constant) {
return containsConstant(enumValues, constant, false);
}
/**
* Check whether the given array of enum constants contains a constant with the given name.
* @param enumValues the enum values to check, typically the product of a call to MyEnum.values()
* @param constant the constant name to find (must not be null or empty string)
* @param caseSensitive whether case is significant in determining a match
* @return whether the constant has been found in the given array
*/
public static boolean containsConstant(Enum<?>[] enumValues, String constant, boolean caseSensitive) {
for (Enum<?> candidate : enumValues) {
if (caseSensitive ?
candidate.toString().equals(constant) :
candidate.toString().equalsIgnoreCase(constant)) {
return true;
}
}
return false;
}
/**
* Case insensitive alternative to {@link Enum#valueOf(Class, String)}.
* @param <E> the concrete Enum type
* @param enumValues the array of all Enum constants in question, usually per Enum.values()
* @param constant the constant to get the enum value of
* @throws IllegalArgumentException if the given constant is not found in the given array
* of enum values. Use {@link #containsConstant(Enum[], String)} as a guard to avoid this exception.
*/
public static <E extends Enum<?>> E caseInsensitiveValueOf(E[] enumValues, String constant) {
for (E candidate : enumValues) {
if(candidate.toString().equalsIgnoreCase(constant)) {
return candidate;
}
}
throw new IllegalArgumentException(
String.format("constant [%s] does not exist in enum type %s",
constant, enumValues.getClass().getComponentType().getName()));
}
/**
* Append the given object to the given array, returning a new array
* consisting of the input array contents plus the given object.

View File

@ -372,6 +372,20 @@ public abstract class ReflectionUtils {
return (method != null && method.getName().equals("toString") && method.getParameterTypes().length == 0);
}
/**
* Determine whether the given method is originally declared by {@link java.lang.Object}.
*/
public static boolean isObjectMethod(Method method) {
try {
Object.class.getDeclaredMethod(method.getName(), method.getParameterTypes());
return true;
} catch (SecurityException ex) {
return false;
} catch (NoSuchMethodException ex) {
return false;
}
}
/**
* Make the given field accessible, explicitly setting it accessible if
* necessary. The <code>setAccessible(true)</code> method is only called

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.
@ -55,7 +55,7 @@ import org.springframework.core.convert.converter.ConverterRegistry;
*/
public class DefaultConversionTests {
private ConversionService conversionService = ConversionServiceFactory.createDefaultConversionService();
private ConversionService conversionService = new DefaultConversionService();
@Test
public void testStringToCharacter() {

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.
@ -78,6 +78,7 @@ public class GenericConversionServiceTests {
}
@Test
@SuppressWarnings("rawtypes")
public void addConverterNoSourceTargetClassInfoAvailable() {
try {
conversionService.addConverter(new Converter() {
@ -109,7 +110,7 @@ public class GenericConversionServiceTests {
}
public void convertNullTargetClass() {
assertNull(conversionService.convert("3", (Class) null));
assertNull(conversionService.convert("3", (Class<?>) null));
assertNull(conversionService.convert("3", TypeDescriptor.NULL));
}
@ -226,7 +227,7 @@ public class GenericConversionServiceTests {
@Test
public void testStringArrayToResourceArray() {
GenericConversionService conversionService = ConversionServiceFactory.createDefaultConversionService();
GenericConversionService conversionService = new DefaultConversionService();
conversionService.addConverter(new MyStringArrayToResourceArrayConverter());
Resource[] converted = conversionService.convert(new String[] {"x1", "z3"}, Resource[].class);
assertEquals(2, converted.length);
@ -236,7 +237,7 @@ public class GenericConversionServiceTests {
@Test
public void testStringArrayToIntegerArray() {
GenericConversionService conversionService = ConversionServiceFactory.createDefaultConversionService();
GenericConversionService conversionService = new DefaultConversionService();
conversionService.addConverter(new MyStringArrayToIntegerArrayConverter());
Integer[] converted = conversionService.convert(new String[] {"x1", "z3"}, Integer[].class);
assertEquals(2, converted.length);
@ -246,7 +247,7 @@ public class GenericConversionServiceTests {
@Test
public void testStringToIntegerArray() {
GenericConversionService conversionService = ConversionServiceFactory.createDefaultConversionService();
GenericConversionService conversionService = new DefaultConversionService();
conversionService.addConverter(new MyStringToIntegerArrayConverter());
Integer[] converted = conversionService.convert("x1,z3", Integer[].class);
assertEquals(2, converted.length);
@ -256,7 +257,7 @@ public class GenericConversionServiceTests {
@Test
public void testWildcardMap() throws Exception {
GenericConversionService conversionService = ConversionServiceFactory.createDefaultConversionService();
GenericConversionService conversionService = new DefaultConversionService();
Map<String, String> input = new LinkedHashMap<String, String>();
input.put("key", "value");
Object converted = conversionService.convert(input, new TypeDescriptor(getClass().getField("wildcardMap")));
@ -265,14 +266,14 @@ public class GenericConversionServiceTests {
@Test
public void testListOfList() {
GenericConversionService service = ConversionServiceFactory.createDefaultConversionService();
GenericConversionService service = new DefaultConversionService();
List<List<String>> list = Collections.singletonList(Collections.singletonList("Foo"));
assertNotNull(service.convert(list, String.class));
}
@Test
public void testStringToString() {
GenericConversionService service = ConversionServiceFactory.createDefaultConversionService();
GenericConversionService service = new DefaultConversionService();
String value = "myValue";
String result = service.convert(value, String.class);
assertSame(value, result);
@ -280,7 +281,7 @@ public class GenericConversionServiceTests {
@Test
public void testStringToObject() {
GenericConversionService service = ConversionServiceFactory.createDefaultConversionService();
GenericConversionService service = new DefaultConversionService();
String value = "myValue";
Object result = service.convert(value, Object.class);
assertSame(value, result);
@ -288,7 +289,7 @@ public class GenericConversionServiceTests {
@Test
public void testIgnoreCopyConstructor() {
GenericConversionService service = ConversionServiceFactory.createDefaultConversionService();
GenericConversionService service = new DefaultConversionService();
WithCopyConstructor value = new WithCopyConstructor();
Object result = service.convert(value, WithCopyConstructor.class);
assertSame(value, result);
@ -296,7 +297,7 @@ public class GenericConversionServiceTests {
@Test
public void testPerformance1() {
GenericConversionService conversionService = ConversionServiceFactory.createDefaultConversionService();
GenericConversionService conversionService = new DefaultConversionService();
StopWatch watch = new StopWatch("integer->string conversionPerformance");
watch.start("convert 4,000,000 with conversion service");
for (int i = 0; i < 4000000; i++) {
@ -313,7 +314,7 @@ public class GenericConversionServiceTests {
@Test
public void testPerformance2() throws Exception {
GenericConversionService conversionService = ConversionServiceFactory.createDefaultConversionService();
GenericConversionService conversionService = new DefaultConversionService();
StopWatch watch = new StopWatch("list<string> -> list<integer> conversionPerformance");
watch.start("convert 4,000,000 with conversion service");
List<String> source = new LinkedList<String>();
@ -340,7 +341,7 @@ public class GenericConversionServiceTests {
@Test
public void testPerformance3() throws Exception {
GenericConversionService conversionService = ConversionServiceFactory.createDefaultConversionService();
GenericConversionService conversionService = new DefaultConversionService();
StopWatch watch = new StopWatch("map<string, string> -> map<string, integer> conversionPerformance");
watch.start("convert 4,000,000 with conversion service");
Map<String, String> source = new HashMap<String, String>();
@ -387,6 +388,7 @@ public class GenericConversionServiceTests {
TypeDescriptor sourceType = TypeDescriptor.forObject(list);
TypeDescriptor targetType = new TypeDescriptor(getClass().getField("emptyListDifferentTarget"));
assertTrue(conversionService.canConvert(sourceType, targetType));
@SuppressWarnings("unchecked")
LinkedList<Integer> result = (LinkedList<Integer>) conversionService.convert(list, sourceType, targetType);
assertEquals(LinkedList.class, result.getClass());
assertTrue(result.isEmpty());
@ -437,6 +439,7 @@ public class GenericConversionServiceTests {
TypeDescriptor sourceType = TypeDescriptor.forObject(map);
TypeDescriptor targetType = new TypeDescriptor(getClass().getField("emptyMapDifferentTarget"));
assertTrue(conversionService.canConvert(sourceType, targetType));
@SuppressWarnings("unchecked")
LinkedHashMap<String, String> result = (LinkedHashMap<String, String>) conversionService.convert(map, sourceType, targetType);
assertEquals(map, result);
assertEquals(LinkedHashMap.class, result.getClass());

View File

@ -16,6 +16,9 @@
package org.springframework.util;
import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertThat;
import java.io.IOException;
import java.sql.SQLException;
@ -43,10 +46,10 @@ public final class ObjectUtilsTests extends TestCase {
}
public void testIsCompatibleWithThrowsClause() {
Class[] empty = new Class[0];
Class[] exception = new Class[] {Exception.class};
Class[] sqlAndIO = new Class[] {SQLException.class, IOException.class};
Class[] throwable = new Class[] {Throwable.class};
Class<?>[] empty = new Class[0];
Class<?>[] exception = new Class[] {Exception.class};
Class<?>[] sqlAndIO = new Class[] {SQLException.class, IOException.class};
Class<?>[] throwable = new Class[] {Throwable.class};
assertTrue(ObjectUtils.isCompatibleWithThrowsClause(new RuntimeException(), null));
assertTrue(ObjectUtils.isCompatibleWithThrowsClause(new RuntimeException(), empty));
@ -617,6 +620,35 @@ public final class ObjectUtilsTests extends TestCase {
assertEquals("null", ObjectUtils.nullSafeToString((String[]) null));
}
enum Tropes { FOO, BAR, baz };
public void testContainsConstant() {
assertThat(ObjectUtils.containsConstant(Tropes.values(), "FOO"), is(true));
assertThat(ObjectUtils.containsConstant(Tropes.values(), "foo"), is(true));
assertThat(ObjectUtils.containsConstant(Tropes.values(), "BaR"), is(true));
assertThat(ObjectUtils.containsConstant(Tropes.values(), "bar"), is(true));
assertThat(ObjectUtils.containsConstant(Tropes.values(), "BAZ"), is(true));
assertThat(ObjectUtils.containsConstant(Tropes.values(), "baz"), is(true));
assertThat(ObjectUtils.containsConstant(Tropes.values(), "BOGUS"), is(false));
assertThat(ObjectUtils.containsConstant(Tropes.values(), "FOO", true), is(true));
assertThat(ObjectUtils.containsConstant(Tropes.values(), "foo", true), is(false));
}
public void testCaseInsensitiveValueOf() {
assertThat(ObjectUtils.caseInsensitiveValueOf(Tropes.values(), "foo"), is(Tropes.FOO));
assertThat(ObjectUtils.caseInsensitiveValueOf(Tropes.values(), "BAR"), is(Tropes.BAR));
try {
ObjectUtils.caseInsensitiveValueOf(Tropes.values(), "bogus");
fail("expected IllegalArgumentException");
} catch (IllegalArgumentException ex) {
assertThat(ex.getMessage(),
is("constant [bogus] does not exist in enum type " +
"org.springframework.util.ObjectUtilsTests$Tropes"));
}
}
private void assertEqualHashCodes(int expected, Object array) {
int actual = ObjectUtils.nullSafeHashCode(array);
assertEquals(expected, actual);

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.
@ -20,7 +20,7 @@ import org.springframework.core.convert.ConversionException;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.ConverterNotFoundException;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.core.convert.support.ConversionServiceFactory;
import org.springframework.core.convert.support.DefaultConversionService;
import org.springframework.expression.TypeConverter;
import org.springframework.expression.spel.SpelEvaluationException;
import org.springframework.expression.spel.SpelMessage;
@ -45,7 +45,7 @@ public class StandardTypeConverter implements TypeConverter {
public StandardTypeConverter() {
synchronized (this) {
if (defaultConversionService == null) {
defaultConversionService = ConversionServiceFactory.createDefaultConversionService();
defaultConversionService = new DefaultConversionService();
}
}
this.conversionService = defaultConversionService;

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.
@ -29,7 +29,7 @@ import org.junit.Test;
import org.springframework.core.MethodParameter;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.core.convert.support.ConversionServiceFactory;
import org.springframework.core.convert.support.DefaultConversionService;
import org.springframework.expression.EvaluationException;
import org.springframework.expression.Expression;
import org.springframework.expression.TypeConverter;
@ -139,7 +139,7 @@ public class ExpressionTestsUsingCoreConversionService extends ExpressionTestCas
*/
private static class TypeConvertorUsingConversionService implements TypeConverter {
private final ConversionService service = ConversionServiceFactory.createDefaultConversionService();
private final ConversionService service = new DefaultConversionService();
public Object convertValue(Object value, TypeDescriptor typeDescriptor) throws EvaluationException {
return this.service.convert(value, typeDescriptor);

View File

@ -0,0 +1,68 @@
/*
* Copyright 2002-2011 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.context.annotation;
/**
* Tests directly or indirectly related to {@link FeatureConfiguration} class and
* {@link Feature} method processing.
*
* @author Chris Beams
* @since 3.1
*/
/*
* commented due to classpath visibility differences between Eclipse
* and Ant/Ivy at the command line. Eclipse can see classes across
* project test folders, Ant/Ivy are not configured to do so. Uncomment
* as necessary when doing @Feature-related work.
*
@RunWith(Suite.class)
@SuiteClasses({
EarlyBeanReferenceProxyCreatorTests.class,
SimpleFeatureMethodProcessingTests.class,
BeanFactoryAwareFeatureConfigurationTests.class,
FeatureMethodBeanReferenceTests.class,
FeatureMethodQualifiedBeanReferenceTests.class,
FeatureConfigurationClassTests.class,
FeatureMethodEarlyBeanProxyTests.class,
FeatureConfigurationImportTests.class,
FeatureConfigurationImportResourceTests.class,
// context:component-scan related
ComponentScanFeatureTests.class,
ComponentScanSpecTests.class,
ComponentScanAnnotationTests.class,
ComponentScanAnnotationIntegrationTests.class,
// tx-related
TxAnnotationDrivenFeatureTests.class,
TxNamespaceHandlerTests.class,
AnnotationTransactionNamespaceHandlerTests.class,
AnnotationDrivenTests.class,
// mvc-related
AnnotationDrivenBeanDefinitionParserTests.class,
MvcAnnotationDrivenFeatureTests.class,
MvcViewControllersTests.class,
MvcResourcesTests.class,
MvcDefaultServletHandlerTests.class,
MvcNamespaceTests.class,
})
*/
public class FeatureTestSuite {
}

View File

@ -1,4 +1,21 @@
/*
* 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.expression.spel.support;
import java.beans.PropertyEditor;
import org.springframework.beans.BeansException;
@ -8,7 +25,7 @@ import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.core.convert.support.ConversionServiceFactory;
import org.springframework.core.convert.support.DefaultConversionService;
import org.springframework.expression.TypeConverter;
/**
@ -26,7 +43,7 @@ class BeanFactoryTypeConverter implements TypeConverter, BeanFactoryAware {
public BeanFactoryTypeConverter() {
synchronized (this) {
if (defaultConversionService == null) {
defaultConversionService = ConversionServiceFactory.createDefaultConversionService();
defaultConversionService = new DefaultConversionService();
}
}
this.conversionService = defaultConversionService;

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,24 +16,18 @@
package org.springframework.transaction.config;
import org.w3c.dom.Element;
import org.springframework.aop.config.AopNamespaceUtils;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.RuntimeBeanReference;
import org.springframework.beans.factory.parsing.BeanComponentDefinition;
import org.springframework.beans.factory.parsing.CompositeComponentDefinition;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.beans.factory.xml.BeanDefinitionParser;
import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.transaction.annotation.AnnotationTransactionAttributeSource;
import org.springframework.transaction.interceptor.BeanFactoryTransactionAttributeSourceAdvisor;
import org.springframework.transaction.interceptor.TransactionInterceptor;
import org.springframework.context.config.ExecutorContext;
import org.w3c.dom.Element;
/**
* {@link org.springframework.beans.factory.xml.BeanDefinitionParser}
* implementation that allows users to easily configure all the infrastructure
* beans required to enable annotation-driven transaction demarcation.
* {@link org.springframework.beans.factory.xml.BeanDefinitionParser
* BeanDefinitionParser} implementation that allows users to easily configure
* all the infrastructure beans required to enable annotation-driven transaction
* demarcation.
*
* <p>By default, all proxies are created as JDK proxies. This may cause some
* problems if you are injecting objects as concrete classes rather than
@ -43,7 +37,9 @@ import org.springframework.transaction.interceptor.TransactionInterceptor;
*
* @author Juergen Hoeller
* @author Rob Harrop
* @author Chris Beams
* @since 2.0
* @see TxAnnotationDriven
*/
class AnnotationDrivenBeanDefinitionParser implements BeanDefinitionParser {
@ -51,95 +47,43 @@ class AnnotationDrivenBeanDefinitionParser implements BeanDefinitionParser {
* The bean name of the internally managed transaction advisor (mode="proxy").
*/
public static final String TRANSACTION_ADVISOR_BEAN_NAME =
"org.springframework.transaction.config.internalTransactionAdvisor";
TxAnnotationDrivenExecutor.TRANSACTION_ADVISOR_BEAN_NAME;
/**
* The bean name of the internally managed transaction aspect (mode="aspectj").
*/
public static final String TRANSACTION_ASPECT_BEAN_NAME =
"org.springframework.transaction.config.internalTransactionAspect";
private static final String TRANSACTION_ASPECT_CLASS_NAME =
"org.springframework.transaction.aspectj.AnnotationTransactionAspect";
TxAnnotationDrivenExecutor.TRANSACTION_ASPECT_BEAN_NAME;
/**
* Parses the '<code>&lt;tx:annotation-driven/&gt;</code>' tag. Will
* Parses the {@code <tx:annotation-driven/>} tag. Will
* {@link AopNamespaceUtils#registerAutoProxyCreatorIfNecessary register an AutoProxyCreator}
* with the container as necessary.
*/
public BeanDefinition parse(Element element, ParserContext parserContext) {
String mode = element.getAttribute("mode");
if ("aspectj".equals(mode)) {
// mode="aspectj"
registerTransactionAspect(element, parserContext);
}
else {
// mode="proxy"
AopAutoProxyConfigurer.configureAutoProxyCreator(element, parserContext);
}
new TxAnnotationDriven(element.getAttribute("transaction-manager"))
.order(element.getAttribute("order"))
.mode(element.getAttribute("mode"))
.proxyTargetClass(Boolean.valueOf(element.getAttribute("proxy-target-class")))
.source(parserContext.extractSource(element))
.sourceName(element.getTagName())
.execute(createExecutorContext(parserContext));
return null;
}
private void registerTransactionAspect(Element element, ParserContext parserContext) {
if (!parserContext.getRegistry().containsBeanDefinition(TRANSACTION_ASPECT_BEAN_NAME)) {
RootBeanDefinition def = new RootBeanDefinition();
def.setBeanClassName(TRANSACTION_ASPECT_CLASS_NAME);
def.setFactoryMethodName("aspectOf");
registerTransactionManager(element, def);
parserContext.registerBeanComponent(new BeanComponentDefinition(def, TRANSACTION_ASPECT_BEAN_NAME));
}
}
private static void registerTransactionManager(Element element, BeanDefinition def) {
def.getPropertyValues().add("transactionManagerBeanName",
TxNamespaceHandler.getTransactionManagerName(element));
}
/**
* Inner class to just introduce an AOP framework dependency when actually in proxy mode.
* Adapt the given ParserContext instance into an ExecutorContext.
*
* TODO SPR-7420: consider unifying the two through a superinterface.
* TODO SPR-7420: create a common ParserContext-to-ExecutorContext adapter util
*/
private static class AopAutoProxyConfigurer {
public static void configureAutoProxyCreator(Element element, ParserContext parserContext) {
AopNamespaceUtils.registerAutoProxyCreatorIfNecessary(parserContext, element);
if (!parserContext.getRegistry().containsBeanDefinition(TRANSACTION_ADVISOR_BEAN_NAME)) {
Object eleSource = parserContext.extractSource(element);
// Create the TransactionAttributeSource definition.
RootBeanDefinition sourceDef = new RootBeanDefinition(AnnotationTransactionAttributeSource.class);
sourceDef.setSource(eleSource);
sourceDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
String sourceName = parserContext.getReaderContext().registerWithGeneratedName(sourceDef);
// Create the TransactionInterceptor definition.
RootBeanDefinition interceptorDef = new RootBeanDefinition(TransactionInterceptor.class);
interceptorDef.setSource(eleSource);
interceptorDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
registerTransactionManager(element, interceptorDef);
interceptorDef.getPropertyValues().add("transactionAttributeSource", new RuntimeBeanReference(sourceName));
String interceptorName = parserContext.getReaderContext().registerWithGeneratedName(interceptorDef);
// Create the TransactionAttributeSourceAdvisor definition.
RootBeanDefinition advisorDef = new RootBeanDefinition(BeanFactoryTransactionAttributeSourceAdvisor.class);
advisorDef.setSource(eleSource);
advisorDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
advisorDef.getPropertyValues().add("transactionAttributeSource", new RuntimeBeanReference(sourceName));
advisorDef.getPropertyValues().add("adviceBeanName", interceptorName);
if (element.hasAttribute("order")) {
advisorDef.getPropertyValues().add("order", element.getAttribute("order"));
}
parserContext.getRegistry().registerBeanDefinition(TRANSACTION_ADVISOR_BEAN_NAME, advisorDef);
CompositeComponentDefinition compositeDef = new CompositeComponentDefinition(element.getTagName(), eleSource);
compositeDef.addNestedComponent(new BeanComponentDefinition(sourceDef, sourceName));
compositeDef.addNestedComponent(new BeanComponentDefinition(interceptorDef, interceptorName));
compositeDef.addNestedComponent(new BeanComponentDefinition(advisorDef, TRANSACTION_ADVISOR_BEAN_NAME));
parserContext.registerComponent(compositeDef);
}
}
private ExecutorContext createExecutorContext(ParserContext parserContext) {
ExecutorContext executorContext = new ExecutorContext();
executorContext.setRegistry(parserContext.getRegistry());
executorContext.setRegistrar(parserContext);
executorContext.setProblemReporter(parserContext.getReaderContext().getProblemReporter());
return executorContext;
}
}

View File

@ -74,7 +74,7 @@ public class JtaTransactionManagerBeanDefinitionParser extends AbstractSingleBea
@Override
protected String resolveId(Element element, AbstractBeanDefinition definition, ParserContext parserContext) {
return TxNamespaceHandler.DEFAULT_TRANSACTION_MANAGER_BEAN_NAME;
return TxAnnotationDriven.DEFAULT_TRANSACTION_MANAGER_BEAN_NAME;
}
}

View File

@ -37,50 +37,55 @@ import org.springframework.util.StringUtils;
import org.springframework.util.xml.DomUtils;
/**
* {@link org.springframework.beans.factory.xml.BeanDefinitionParser}
* for the <code>&lt;tx:advice&gt;</code> tag.
* {@link org.springframework.beans.factory.xml.BeanDefinitionParser
* BeanDefinitionParser} for the {@code <tx:advice/>} tag.
*
* @author Rob Harrop
* @author Juergen Hoeller
* @author Adrian Colyer
* @author Chris Beams
* @since 2.0
*/
class TxAdviceBeanDefinitionParser extends AbstractSingleBeanDefinitionParser {
private static final String ATTRIBUTES = "attributes";
private static final String TRANSACTION_MANAGER_ATTRIBUTE = "transaction-manager";
private static final String TIMEOUT = "timeout";
private static final String METHOD_ELEMENT = "method";
private static final String READ_ONLY = "read-only";
private static final String METHOD_NAME_ATTRIBUTE = "name";
private static final String NAME_MAP = "nameMap";
private static final String ATTRIBUTES_ELEMENT = "attributes";
private static final String PROPAGATION = "propagation";
private static final String TIMEOUT_ATTRIBUTE = "timeout";
private static final String ISOLATION = "isolation";
private static final String READ_ONLY_ATTRIBUTE = "read-only";
private static final String ROLLBACK_FOR = "rollback-for";
private static final String PROPAGATION_ATTRIBUTE = "propagation";
private static final String NO_ROLLBACK_FOR = "no-rollback-for";
private static final String ISOLATION_ATTRIBUTE = "isolation";
private static final String ROLLBACK_FOR_ATTRIBUTE = "rollback-for";
private static final String NO_ROLLBACK_FOR_ATTRIBUTE = "no-rollback-for";
@Override
protected Class getBeanClass(Element element) {
protected Class<?> getBeanClass(Element element) {
return TransactionInterceptor.class;
}
@Override
protected void doParse(Element element, ParserContext parserContext, BeanDefinitionBuilder builder) {
builder.addPropertyReference("transactionManager", TxNamespaceHandler.getTransactionManagerName(element));
builder.addPropertyReference("transactionManager", element.getAttribute(TRANSACTION_MANAGER_ATTRIBUTE));
List txAttributes = DomUtils.getChildElementsByTagName(element, ATTRIBUTES);
List<Element> txAttributes = DomUtils.getChildElementsByTagName(element, ATTRIBUTES_ELEMENT);
if (txAttributes.size() > 1) {
parserContext.getReaderContext().error(
"Element <attributes> is allowed at most once inside element <advice>", element);
}
else if (txAttributes.size() == 1) {
// Using attributes source.
Element attributeSourceElement = (Element) txAttributes.get(0);
Element attributeSourceElement = txAttributes.get(0);
RootBeanDefinition attributeSourceDefinition = parseAttributeSource(attributeSourceElement, parserContext);
builder.addPropertyValue("transactionAttributeSource", attributeSourceDefinition);
}
@ -92,20 +97,21 @@ class TxAdviceBeanDefinitionParser extends AbstractSingleBeanDefinitionParser {
}
private RootBeanDefinition parseAttributeSource(Element attrEle, ParserContext parserContext) {
List<Element> methods = DomUtils.getChildElementsByTagName(attrEle, "method");
ManagedMap transactionAttributeMap = new ManagedMap(methods.size());
List<Element> methods = DomUtils.getChildElementsByTagName(attrEle, METHOD_ELEMENT);
ManagedMap<TypedStringValue, RuleBasedTransactionAttribute> transactionAttributeMap =
new ManagedMap<TypedStringValue, RuleBasedTransactionAttribute>(methods.size());
transactionAttributeMap.setSource(parserContext.extractSource(attrEle));
for (Element methodEle : methods) {
String name = methodEle.getAttribute("name");
String name = methodEle.getAttribute(METHOD_NAME_ATTRIBUTE);
TypedStringValue nameHolder = new TypedStringValue(name);
nameHolder.setSource(parserContext.extractSource(methodEle));
RuleBasedTransactionAttribute attribute = new RuleBasedTransactionAttribute();
String propagation = methodEle.getAttribute(PROPAGATION);
String isolation = methodEle.getAttribute(ISOLATION);
String timeout = methodEle.getAttribute(TIMEOUT);
String readOnly = methodEle.getAttribute(READ_ONLY);
String propagation = methodEle.getAttribute(PROPAGATION_ATTRIBUTE);
String isolation = methodEle.getAttribute(ISOLATION_ATTRIBUTE);
String timeout = methodEle.getAttribute(TIMEOUT_ATTRIBUTE);
String readOnly = methodEle.getAttribute(READ_ONLY_ATTRIBUTE);
if (StringUtils.hasText(propagation)) {
attribute.setPropagationBehaviorName(RuleBasedTransactionAttribute.PREFIX_PROPAGATION + propagation);
}
@ -121,16 +127,16 @@ class TxAdviceBeanDefinitionParser extends AbstractSingleBeanDefinitionParser {
}
}
if (StringUtils.hasText(readOnly)) {
attribute.setReadOnly(Boolean.valueOf(methodEle.getAttribute(READ_ONLY)));
attribute.setReadOnly(Boolean.valueOf(methodEle.getAttribute(READ_ONLY_ATTRIBUTE)));
}
List<RollbackRuleAttribute> rollbackRules = new LinkedList<RollbackRuleAttribute>();
if (methodEle.hasAttribute(ROLLBACK_FOR)) {
String rollbackForValue = methodEle.getAttribute(ROLLBACK_FOR);
if (methodEle.hasAttribute(ROLLBACK_FOR_ATTRIBUTE)) {
String rollbackForValue = methodEle.getAttribute(ROLLBACK_FOR_ATTRIBUTE);
addRollbackRuleAttributesTo(rollbackRules,rollbackForValue);
}
if (methodEle.hasAttribute(NO_ROLLBACK_FOR)) {
String noRollbackForValue = methodEle.getAttribute(NO_ROLLBACK_FOR);
if (methodEle.hasAttribute(NO_ROLLBACK_FOR_ATTRIBUTE)) {
String noRollbackForValue = methodEle.getAttribute(NO_ROLLBACK_FOR_ATTRIBUTE);
addNoRollbackRuleAttributesTo(rollbackRules,noRollbackForValue);
}
attribute.setRollbackRules(rollbackRules);
@ -140,7 +146,7 @@ class TxAdviceBeanDefinitionParser extends AbstractSingleBeanDefinitionParser {
RootBeanDefinition attributeSourceDefinition = new RootBeanDefinition(NameMatchTransactionAttributeSource.class);
attributeSourceDefinition.setSource(parserContext.extractSource(attrEle));
attributeSourceDefinition.getPropertyValues().add(NAME_MAP, transactionAttributeMap);
attributeSourceDefinition.getPropertyValues().add("nameMap", transactionAttributeMap);
return attributeSourceDefinition;
}

View File

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

View File

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

Some files were not shown because too many files have changed in this diff Show More