diff --git a/org.springframework.aop/src/main/java/org/springframework/aop/config/AopNamespaceUtils.java b/org.springframework.aop/src/main/java/org/springframework/aop/config/AopNamespaceUtils.java
index ae9888dca4d..c3985b8934e 100644
--- a/org.springframework.aop/src/main/java/org/springframework/aop/config/AopNamespaceUtils.java
+++ b/org.springframework.aop/src/main/java/org/springframework/aop/config/AopNamespaceUtils.java
@@ -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);
}
}
diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/BeanUtils.java b/org.springframework.beans/src/main/java/org/springframework/beans/BeanUtils.java
index 7c5ba8295ab..dcbe5a54e51 100644
--- a/org.springframework.beans/src/main/java/org/springframework/beans/BeanUtils.java
+++ b/org.springframework.beans/src/main/java/org/springframework/beans/BeanUtils.java
@@ -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.
*
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.
+ *
Useful in cases where
+ * the type of the class to instantiate (clazz) is not available, but the type
+ * desired (assignableTo) is known.
+ *
As this method doesn't try to load classes by name, it should avoid
+ * class-loading issues.
+ *
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 instantiateClass(Class> clazz, Class 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
diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/parsing/BeanDefinitionRegistrar.java b/org.springframework.beans/src/main/java/org/springframework/beans/factory/parsing/BeanDefinitionRegistrar.java
new file mode 100644
index 00000000000..9184b9c0a3b
--- /dev/null
+++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/parsing/BeanDefinitionRegistrar.java
@@ -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);
+
+}
diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/parsing/ComponentRegistrar.java b/org.springframework.beans/src/main/java/org/springframework/beans/factory/parsing/ComponentRegistrar.java
new file mode 100644
index 00000000000..94eb6541b62
--- /dev/null
+++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/parsing/ComponentRegistrar.java
@@ -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);
+}
diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/parsing/ReaderContext.java b/org.springframework.beans/src/main/java/org/springframework/beans/factory/parsing/ReaderContext.java
index 64689f6486c..39c9741a971 100644
--- a/org.springframework.beans/src/main/java/org/springframework/beans/factory/parsing/ReaderContext.java
+++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/parsing/ReaderContext.java
@@ -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) {
diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/parsing/SimpleProblemCollector.java b/org.springframework.beans/src/main/java/org/springframework/beans/factory/parsing/SimpleProblemCollector.java
new file mode 100644
index 00000000000..2f90f761fae
--- /dev/null
+++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/parsing/SimpleProblemCollector.java
@@ -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 errors = new ArrayList();
+
+ 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;
+ }
+
+}
diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/BeanDefinitionBuilder.java b/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/BeanDefinitionBuilder.java
index 478697fffcf..3b338d5b27b 100644
--- a/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/BeanDefinitionBuilder.java
+++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/BeanDefinitionBuilder.java
@@ -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.
diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/BeanDefinitionReaderUtils.java b/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/BeanDefinitionReaderUtils.java
index 1d80a290cfc..26603663fd3 100644
--- a/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/BeanDefinitionReaderUtils.java
+++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/BeanDefinitionReaderUtils.java
@@ -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);
diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/xml/BeanDefinitionParser.java b/org.springframework.beans/src/main/java/org/springframework/beans/factory/xml/BeanDefinitionParser.java
index 93ecbb44bb5..2f352d9dddf 100644
--- a/org.springframework.beans/src/main/java/org/springframework/beans/factory/xml/BeanDefinitionParser.java
+++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/xml/BeanDefinitionParser.java
@@ -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 <beans>) tags.
+ * Interface used by the {@link DefaultBeanDefinitionDocumentReader} to handle custom,
+ * top-level (directly under {@code }) tags.
*
* 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;
*
The parser locates a {@link BeanDefinitionParser} from the associated
* {@link NamespaceHandler} for the namespace in which the custom tag resides.
*
+ *
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 {
diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/xml/ParserContext.java b/org.springframework.beans/src/main/java/org/springframework/beans/factory/xml/ParserContext.java
index c36314c0d14..78ad74e2f2c 100644
--- a/org.springframework.beans/src/main/java/org/springframework/beans/factory/xml/ParserContext.java
+++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/xml/ParserContext.java
@@ -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);
+ }
+
}
diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/xml/XmlReaderContext.java b/org.springframework.beans/src/main/java/org/springframework/beans/factory/xml/XmlReaderContext.java
index cbb5a0d5040..8dd361ce399 100644
--- a/org.springframework.beans/src/main/java/org/springframework/beans/factory/xml/XmlReaderContext.java
+++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/xml/XmlReaderContext.java
@@ -83,4 +83,9 @@ public class XmlReaderContext extends ReaderContext {
return generatedName;
}
+ // TODO SPR-7420: review exposing problem reporter
+ public ProblemReporter getProblemReporter() {
+ return this.problemReporter;
+ }
+
}
diff --git a/org.springframework.beans/src/test/java/org/springframework/beans/factory/DefaultListableBeanFactoryTests.java b/org.springframework.beans/src/test/java/org/springframework/beans/factory/DefaultListableBeanFactoryTests.java
index eef452f0ee9..f65f0ae2927 100644
--- a/org.springframework.beans/src/test/java/org/springframework/beans/factory/DefaultListableBeanFactoryTests.java
+++ b/org.springframework.beans/src/test/java/org/springframework/beans/factory/DefaultListableBeanFactoryTests.java
@@ -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() {
public Float convert(String source) {
try {
diff --git a/org.springframework.context/.springBeans b/org.springframework.context/.springBeans
index 84849427f41..c36c1e5de08 100644
--- a/org.springframework.context/.springBeans
+++ b/org.springframework.context/.springBeans
@@ -11,6 +11,7 @@
src/test/java/org/springframework/context/annotation/configuration/SecondLevelSubConfig-context.xml
src/test/java/org/springframework/context/annotation/configuration/ImportXmlWithAopNamespace-context.xml
src/test/java/org/springframework/context/annotation/Spr6602Tests-context.xml
+ src/test/java/org/springframework/context/annotation/FeatureConfigurationImportResourceTests-context.xml
diff --git a/org.springframework.context/src/main/java/org/springframework/context/annotation/AnnotatedBeanDefinitionReader.java b/org.springframework.context/src/main/java/org/springframework/context/annotation/AnnotatedBeanDefinitionReader.java
index fd8b5dc25b8..334cc233f12 100644
--- a/org.springframework.context/src/main/java/org/springframework/context/annotation/AnnotatedBeanDefinitionReader.java
+++ b/org.springframework.context/src/main/java/org/springframework/context/annotation/AnnotatedBeanDefinitionReader.java
@@ -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);
}
-
}
diff --git a/org.springframework.context/src/main/java/org/springframework/context/annotation/AnnotationScopeMetadataResolver.java b/org.springframework.context/src/main/java/org/springframework/context/annotation/AnnotationScopeMetadataResolver.java
index 1c201527b94..62f07731908 100644
--- a/org.springframework.context/src/main/java/org/springframework/context/annotation/AnnotationScopeMetadataResolver.java
+++ b/org.springframework.context/src/main/java/org/springframework/context/annotation/AnnotationScopeMetadataResolver.java
@@ -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 AnnotationScopeMetadataResolver class.
diff --git a/org.springframework.context/src/main/java/org/springframework/context/annotation/BeanAnnotationHelper.java b/org.springframework.context/src/main/java/org/springframework/context/annotation/BeanAnnotationHelper.java
new file mode 100644
index 00000000000..f44d7b50518
--- /dev/null
+++ b/org.springframework.context/src/main/java/org/springframework/context/annotation/BeanAnnotationHelper.java
@@ -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;
+ }
+
+}
diff --git a/org.springframework.context/src/main/java/org/springframework/context/annotation/ClassPathBeanDefinitionScanner.java b/org.springframework.context/src/main/java/org/springframework/context/annotation/ClassPathBeanDefinitionScanner.java
index 4f780ef343b..9488edf117c 100644
--- a/org.springframework.context/src/main/java/org/springframework/context/annotation/ClassPathBeanDefinitionScanner.java
+++ b/org.springframework.context/src/main/java/org/springframework/context/annotation/ClassPathBeanDefinitionScanner.java
@@ -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
* This method does not 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 doScan(String... basePackages) {
+ Assert.notEmpty(basePackages, "At least one base package must be specified");
Set beanDefinitions = new LinkedHashSet();
for (String basePackage : basePackages) {
Set candidates = findCandidateComponents(basePackage);
diff --git a/org.springframework.context/src/main/java/org/springframework/context/annotation/ClassPathScanningCandidateComponentProvider.java b/org.springframework.context/src/main/java/org/springframework/context/annotation/ClassPathScanningCandidateComponentProvider.java
index 9ea48239b14..459d7b271cf 100644
--- a/org.springframework.context/src/main/java/org/springframework/context/annotation/ClassPathScanningCandidateComponentProvider.java
+++ b/org.springframework.context/src/main/java/org/springframework/context/annotation/ClassPathScanningCandidateComponentProvider.java
@@ -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());
diff --git a/org.springframework.context/src/main/java/org/springframework/context/annotation/ComponentScan.java b/org.springframework.context/src/main/java/org/springframework/context/annotation/ComponentScan.java
index 043a485ea3c..c45ca50401f 100644
--- a/org.springframework.context/src/main/java/org/springframework/context/annotation/ComponentScan.java
+++ b/org.springframework.context/src/main/java/org/springframework/context/annotation/ComponentScan.java
@@ -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 } element.
+ * Configures component scanning directives for use with {@link Configuration @Configuration}
+ * classes. Provides support parallel with Spring XML's {@code }
+ * element.
*
- * TODO SPR-7508: complete documentation.
+ * One of {@link #basePackageClasses()}, {@link #basePackages()} or its alias {@link #value()}
+ * must be specified.
+ *
+ *
Note that the {@code } 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.
+ * {@link #value()} is an alias for (and mutually exclusive with) this attribute.
+ *
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.
+ *
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.
+ *
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.
+ *
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.
+ *
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.
+ *
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 }, 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();
}
diff --git a/org.springframework.context/src/main/java/org/springframework/context/annotation/ComponentScanAnnotationParser.java b/org.springframework.context/src/main/java/org/springframework/context/annotation/ComponentScanAnnotationParser.java
new file mode 100644
index 00000000000..47c1abedaa9
--- /dev/null
+++ b/org.springframework.context/src/main/java/org/springframework/context/annotation/ComponentScanAnnotationParser.java
@@ -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 } XML element.
+ *
+ * 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 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;
+ }
+
+}
diff --git a/org.springframework.context/src/main/java/org/springframework/context/annotation/ComponentScanBeanDefinitionParser.java b/org.springframework.context/src/main/java/org/springframework/context/annotation/ComponentScanBeanDefinitionParser.java
index ee1a4920224..bb56e713e73 100644
--- a/org.springframework.context/src/main/java/org/springframework/context/annotation/ComponentScanBeanDefinitionParser.java
+++ b/org.springframework.context/src/main/java/org/springframework/context/annotation/ComponentScanBeanDefinitionParser.java
@@ -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 <context:component-scan/> element.
- *
+ * Parser for the {@code } 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 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 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 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 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) 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;
}
}
diff --git a/org.springframework.context/src/main/java/org/springframework/context/annotation/ComponentScanExecutor.java b/org.springframework.context/src/main/java/org/springframework/context/annotation/ComponentScanExecutor.java
new file mode 100644
index 00000000000..ac146d176f2
--- /dev/null
+++ b/org.springframework.context/src/main/java/org/springframework/context/annotation/ComponentScanExecutor.java
@@ -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 {
+
+ /**
+ * 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 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 processorDefinitions =
+ AnnotationConfigUtils.registerAnnotationConfigProcessors(registry, source);
+ for (BeanDefinitionHolder processorDefinition : processorDefinitions) {
+ compositeDef.addNestedComponent(new BeanComponentDefinition(processorDefinition));
+ }
+ }
+
+ executorContext.getRegistrar().registerComponent(compositeDef);
+ }
+
+}
diff --git a/org.springframework.context/src/main/java/org/springframework/context/annotation/ComponentScanSpec.java b/org.springframework.context/src/main/java/org/springframework/context/annotation/ComponentScanSpec.java
new file mode 100644
index 00000000000..6a85d98b680
--- /dev/null
+++ b/org.springframework.context/src/main/java/org/springframework/context/annotation/ComponentScanSpec.java
@@ -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 component-scanning 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 basePackages = new ArrayList();
+ private Object beanNameGenerator = null;
+ private Object scopeMetadataResolver = null;
+ private Object scopedProxyMode = null;
+ private Boolean useDefaultFilters = null;
+ private List includeFilters = new ArrayList();
+ private List excludeFilters = new ArrayList();
+
+ 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 nullSafeTypedObject(Object object, Class 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 packages = new ArrayList();
+ 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) 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));
+ }
+
+ }
+ }
+
+
+}
diff --git a/org.springframework.context/src/main/java/org/springframework/context/annotation/Configuration.java b/org.springframework.context/src/main/java/org/springframework/context/annotation/Configuration.java
index a277ff9efe2..2c8e6f40a85 100644
--- a/org.springframework.context/src/main/java/org/springframework/context/annotation/Configuration.java
+++ b/org.springframework.context/src/main/java/org/springframework/context/annotation/Configuration.java
@@ -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
diff --git a/org.springframework.context/src/main/java/org/springframework/context/annotation/ConfigurationClass.java b/org.springframework.context/src/main/java/org/springframework/context/annotation/ConfigurationClass.java
index 9aae2ecf32e..22a26ebaf47 100644
--- a/org.springframework.context/src/main/java/org/springframework/context/annotation/ConfigurationClass.java
+++ b/org.springframework.context/src/main/java/org/springframework/context/annotation/ConfigurationClass.java
@@ -175,5 +175,6 @@ final class ConfigurationClass {
getSimpleName(), count, methodName), new Location(getResource(), getMetadata()));
}
}
-
+
+
}
diff --git a/org.springframework.context/src/main/java/org/springframework/context/annotation/ConfigurationClassBeanDefinitionReader.java b/org.springframework.context/src/main/java/org/springframework/context/annotation/ConfigurationClassBeanDefinitionReader.java
index 238d92bb845..a75a9d14457 100644
--- a/org.springframework.context/src/main/java/org/springframework/context/annotation/ConfigurationClassBeanDefinitionReader.java
+++ b/org.springframework.context/src/main/java/org/springframework/context/annotation/ConfigurationClassBeanDefinitionReader.java
@@ -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.
*
* 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 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 beanAttributes = metadata.getAnnotationAttributes(Bean.class.getName());
List names = new ArrayList(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> importedResources) {
Map, BeanDefinitionReader> readerInstanceCache = new HashMap, BeanDefinitionReader>();
for (Map.Entry> 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));
}
-
}
}
diff --git a/org.springframework.context/src/main/java/org/springframework/context/annotation/ConfigurationClassEnhancer.java b/org.springframework.context/src/main/java/org/springframework/context/annotation/ConfigurationClassEnhancer.java
index e3f0ae2938a..174ac4eff08 100644
--- a/org.springframework.context/src/main/java/org/springframework/context/annotation/ConfigurationClassEnhancer.java
+++ b/org.springframework.context/src/main/java/org/springframework/context/annotation/ConfigurationClassEnhancer.java
@@ -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);
}
/**
diff --git a/org.springframework.context/src/main/java/org/springframework/context/annotation/ConfigurationClassMethod.java b/org.springframework.context/src/main/java/org/springframework/context/annotation/ConfigurationClassMethod.java
index a176db7ddad..429ba06bce0 100644
--- a/org.springframework.context/src/main/java/org/springframework/context/annotation/ConfigurationClassMethod.java
+++ b/org.springframework.context/src/main/java/org/springframework/context/annotation/ConfigurationClassMethod.java
@@ -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]",
diff --git a/org.springframework.context/src/main/java/org/springframework/context/annotation/ConfigurationClassPostProcessor.java b/org.springframework.context/src/main/java/org/springframework/context/annotation/ConfigurationClassPostProcessor.java
index aa5681648c8..83dd95beec4 100644
--- a/org.springframework.context/src/main/java/org/springframework/context/annotation/ConfigurationClassPostProcessor.java
+++ b/org.springframework.context/src/main/java/org/springframework/context/annotation/ConfigurationClassPostProcessor.java
@@ -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 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 retrieveFeatureConfigurationBeans(ConfigurableListableBeanFactory beanFactory) {
+ Map fcBeans = new HashMap();
+ 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 beanArgs = new ArrayList();
+ 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 configCandidates = new LinkedHashSet();
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());
}
diff --git a/org.springframework.context/src/main/java/org/springframework/context/annotation/EarlyBeanReferenceProxy.java b/org.springframework.context/src/main/java/org/springframework/context/annotation/EarlyBeanReferenceProxy.java
new file mode 100644
index 00000000000..c0cd0965496
--- /dev/null
+++ b/org.springframework.context/src/main/java/org/springframework/context/annotation/EarlyBeanReferenceProxy.java
@@ -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();
+
+}
diff --git a/org.springframework.context/src/main/java/org/springframework/context/annotation/EarlyBeanReferenceProxyCreator.java b/org.springframework.context/src/main/java/org/springframework/context/annotation/EarlyBeanReferenceProxyCreator.java
new file mode 100644
index 00000000000..121be542bd8
--- /dev/null
+++ b/org.springframework.context/src/main/java/org/springframework/context/annotation/EarlyBeanReferenceProxyCreator.java
@@ -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);
+ }
+
+ }
+
+}
+
diff --git a/org.springframework.context/src/main/java/org/springframework/context/annotation/Feature.java b/org.springframework.context/src/main/java/org/springframework/context/annotation/Feature.java
new file mode 100644
index 00000000000..dc54a0e94d7
--- /dev/null
+++ b/org.springframework.context/src/main/java/org/springframework/context/annotation/Feature.java
@@ -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 {
+
+}
\ No newline at end of file
diff --git a/org.springframework.context/src/main/java/org/springframework/context/annotation/FeatureAnnotation.java b/org.springframework.context/src/main/java/org/springframework/context/annotation/FeatureAnnotation.java
new file mode 100644
index 00000000000..20bb2802ba0
--- /dev/null
+++ b/org.springframework.context/src/main/java/org/springframework/context/annotation/FeatureAnnotation.java
@@ -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}.
+ *
+ * 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();
+
+}
diff --git a/org.springframework.context/src/main/java/org/springframework/context/annotation/FeatureAnnotationParser.java b/org.springframework.context/src/main/java/org/springframework/context/annotation/FeatureAnnotationParser.java
new file mode 100644
index 00000000000..65a402de417
--- /dev/null
+++ b/org.springframework.context/src/main/java/org/springframework/context/annotation/FeatureAnnotationParser.java
@@ -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.
+ *
+ *
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 } 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.
+ *
+ * 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);
+
+}
diff --git a/org.springframework.context/src/main/java/org/springframework/context/annotation/FeatureConfiguration.java b/org.springframework.context/src/main/java/org/springframework/context/annotation/FeatureConfiguration.java
new file mode 100644
index 00000000000..6fbfbf2b98b
--- /dev/null
+++ b/org.springframework.context/src/main/java/org/springframework/context/annotation/FeatureConfiguration.java
@@ -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.
+ *
+ *
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 "";
+}
diff --git a/org.springframework.context/src/main/java/org/springframework/context/annotation/FeatureMethodExecutionException.java b/org.springframework.context/src/main/java/org/springframework/context/annotation/FeatureMethodExecutionException.java
new file mode 100644
index 00000000000..e5aa6f54365
--- /dev/null
+++ b/org.springframework.context/src/main/java/org/springframework/context/annotation/FeatureMethodExecutionException.java
@@ -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);
+ }
+}
diff --git a/org.springframework.context/src/main/java/org/springframework/context/annotation/ProxyCreationException.java b/org.springframework.context/src/main/java/org/springframework/context/annotation/ProxyCreationException.java
new file mode 100644
index 00000000000..f85f311f83e
--- /dev/null
+++ b/org.springframework.context/src/main/java/org/springframework/context/annotation/ProxyCreationException.java
@@ -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);
+ }
+
+}
diff --git a/org.springframework.context/src/main/java/org/springframework/context/annotation/SimpleComponentRegistrar.java b/org.springframework.context/src/main/java/org/springframework/context/annotation/SimpleComponentRegistrar.java
new file mode 100644
index 00000000000..4c5e47e0c6f
--- /dev/null
+++ b/org.springframework.context/src/main/java/org/springframework/context/annotation/SimpleComponentRegistrar.java
@@ -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
+ }
+}
diff --git a/org.springframework.context/src/main/java/org/springframework/context/config/AbstractFeatureSpecification.java b/org.springframework.context/src/main/java/org/springframework/context/config/AbstractFeatureSpecification.java
new file mode 100644
index 00000000000..ac0a8a80f64
--- /dev/null
+++ b/org.springframework.context/src/main/java/org/springframework/context/config/AbstractFeatureSpecification.java
@@ -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);
+ }
+
+}
diff --git a/org.springframework.context/src/main/java/org/springframework/context/config/AbstractSpecificationExecutor.java b/org.springframework.context/src/main/java/org/springframework/context/config/AbstractSpecificationExecutor.java
new file mode 100644
index 00000000000..34d7bc08980
--- /dev/null
+++ b/org.springframework.context/src/main/java/org/springframework/context/config/AbstractSpecificationExecutor.java
@@ -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 implements FeatureSpecificationExecutor {
+
+ /**
+ * {@inheritDoc}
+ *
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);
+
+}
diff --git a/org.springframework.context/src/main/java/org/springframework/context/config/AdviceMode.java b/org.springframework.context/src/main/java/org/springframework/context/config/AdviceMode.java
new file mode 100644
index 00000000000..57d9516a68a
--- /dev/null
+++ b/org.springframework.context/src/main/java/org/springframework/context/config/AdviceMode.java
@@ -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
+}
\ No newline at end of file
diff --git a/org.springframework.context/src/main/java/org/springframework/context/config/ExecutorContext.java b/org.springframework.context/src/main/java/org/springframework/context/config/ExecutorContext.java
new file mode 100644
index 00000000000..7c6a3a26dd7
--- /dev/null
+++ b/org.springframework.context/src/main/java/org/springframework/context/config/ExecutorContext.java
@@ -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;
+ }
+
+}
diff --git a/org.springframework.context/src/main/java/org/springframework/context/config/FeatureSpecification.java b/org.springframework.context/src/main/java/org/springframework/context/config/FeatureSpecification.java
new file mode 100644
index 00000000000..7cca49ee45a
--- /dev/null
+++ b/org.springframework.context/src/main/java/org/springframework/context/config/FeatureSpecification.java
@@ -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.
+ *
+ *
Many features of the Spring container can be configured using either XML or annotations.
+ * As one example, Spring's component scanning feature may be configured using
+ * either the {@code } 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 specifying 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.
+ *
+ * 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.
+ *
+ *
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.
+ *
+ *
{@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.
+ *
+ *
Notes on designing {@code FeatureSpecification} implementations
+ *
+ * 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:
+ *
+ *
+ * @Feature
+ * public TxAnnotationDriven tx(PlatformTransactionManager txManager) {
+ * return new TxAnnotationDriven(txManager).proxyTargetClass(true);
+ * }
+ *
+ *
+ * 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:
+ *
+ * {@code }
+ *
+ *
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.
+ *
+ *
Implementations should take care to allow for use of string-based bean names, placeholder
+ * ("${...}") and SpEL ("#{...}) 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.
+ *
+ *
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);
+
+}
diff --git a/org.springframework.context/src/main/java/org/springframework/context/config/FeatureSpecificationExecutor.java b/org.springframework.context/src/main/java/org/springframework/context/config/FeatureSpecificationExecutor.java
new file mode 100644
index 00000000000..42f1c0bdaa5
--- /dev/null
+++ b/org.springframework.context/src/main/java/org/springframework/context/config/FeatureSpecificationExecutor.java
@@ -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);
+
+}
diff --git a/org.springframework.context/src/main/java/org/springframework/context/config/SourceAwareSpecification.java b/org.springframework.context/src/main/java/org/springframework/context/config/SourceAwareSpecification.java
new file mode 100644
index 00000000000..82b6ea09cdd
--- /dev/null
+++ b/org.springframework.context/src/main/java/org/springframework/context/config/SourceAwareSpecification.java
@@ -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);
+
+}
diff --git a/org.springframework.context/src/main/java/org/springframework/context/support/ConversionServiceFactoryBean.java b/org.springframework.context/src/main/java/org/springframework/context/support/ConversionServiceFactoryBean.java
index 4afbb314e8f..a4fb136b2b1 100644
--- a/org.springframework.context/src/main/java/org/springframework/context/support/ConversionServiceFactoryBean.java
+++ b/org.springframework.context/src/main/java/org/springframework/context/support/ConversionServiceFactoryBean.java
@@ -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.
+ *
+ *
This implementation creates a {@link DefaultConversionService}. Subclasses
+ * may override {@link #createConversionService()} in order to return a
+ * {@link GenericConversionService} instance of their choosing.
+ *
+ *
Like all {@code FactoryBean} implementations, this class is suitable for
+ * use when configuring a Spring application context using Spring {@code }
+ * 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, InitializingBean {
@@ -52,17 +66,17 @@ public class ConversionServiceFactoryBean implements FactoryBeanCreates 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();
}
diff --git a/org.springframework.context/src/main/java/org/springframework/format/support/DefaultFormattingConversionService.java b/org.springframework.context/src/main/java/org/springframework/format/support/DefaultFormattingConversionService.java
new file mode 100644
index 00000000000..47a410b7fbe
--- /dev/null
+++ b/org.springframework.context/src/main/java/org/springframework/format/support/DefaultFormattingConversionService.java
@@ -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.
+ *
+ * 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 {
+
+ private final Set> fieldTypes;
+
+ public NoJodaDateTimeFormatAnnotationFormatterFactory() {
+ Set> rawFieldTypes = new HashSet>(4);
+ rawFieldTypes.add(Date.class);
+ rawFieldTypes.add(Calendar.class);
+ rawFieldTypes.add(Long.class);
+ this.fieldTypes = Collections.unmodifiableSet(rawFieldTypes);
+ }
+
+ public Set> 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");
+ }
+ }
+
+}
diff --git a/org.springframework.context/src/main/java/org/springframework/format/support/FormattingConversionServiceFactoryBean.java b/org.springframework.context/src/main/java/org/springframework/format/support/FormattingConversionServiceFactoryBean.java
index d1469f116a0..278d86830ad 100644
--- a/org.springframework.context/src/main/java/org/springframework/format/support/FormattingConversionServiceFactoryBean.java
+++ b/org.springframework.context/src/main/java/org/springframework/format/support/FormattingConversionServiceFactoryBean.java
@@ -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;
/**
- * A factory for a {@link FormattingConversionService} that installs default
- * converters and formatters for common types such as numbers and datetimes.
- *
- *
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.
+ *
+ *
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)}.
- *
- *
A good example for registering converters and formatters in code is
- * JodaTimeFormatterRegistrar, which registers a number of
+ *
+ *
A good example for registering converters and formatters in code is
+ * JodaTimeFormatterRegistrar, 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)}
+ *
+ *
Like all {@code FactoryBean} implementations, this class is suitable for
+ * use when configuring a Spring application context using Spring {@code }
+ * 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, EmbeddedValueResolverAware, InitializingBean {
- private static final boolean jodaTimePresent = ClassUtils.isPresent(
- "org.joda.time.LocalDate", FormattingConversionService.class.getClassLoader());
-
private Set> converters;
private Set> formatters;
private Set 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 {
-
- private final Set> fieldTypes;
-
- public NoJodaDateTimeFormatAnnotationFormatterFactory() {
- Set> rawFieldTypes = new HashSet>(4);
- rawFieldTypes.add(Date.class);
- rawFieldTypes.add(Calendar.class);
- rawFieldTypes.add(Long.class);
- this.fieldTypes = Collections.unmodifiableSet(rawFieldTypes);
- }
-
- public Set> 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");
- }
- }
-
}
diff --git a/org.springframework.context/src/main/java/org/springframework/scripting/support/ScriptFactoryPostProcessor.java b/org.springframework.context/src/main/java/org/springframework/scripting/support/ScriptFactoryPostProcessor.java
index 2458de6f845..28c6bc6a62e 100644
--- a/org.springframework.context/src/main/java/org/springframework/scripting/support/ScriptFactoryPostProcessor.java
+++ b/org.springframework.context/src/main/java/org/springframework/scripting/support/ScriptFactoryPostProcessor.java
@@ -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.
diff --git a/org.springframework.context/src/main/resources/org/springframework/context/config/spring-context-3.1.xsd b/org.springframework.context/src/main/resources/org/springframework/context/config/spring-context-3.1.xsd
index ad93b101270..59903b4e05a 100644
--- a/org.springframework.context/src/main/resources/org/springframework/context/config/spring-context-3.1.xsd
+++ b/org.springframework.context/src/main/resources/org/springframework/context/config/spring-context-3.1.xsd
@@ -191,6 +191,7 @@
diff --git a/org.springframework.context/src/test/java/example/scannable/FooServiceImpl.java b/org.springframework.context/src/test/java/example/scannable/FooServiceImpl.java
index d31815f1eb2..ef2b6094b31 100644
--- a/org.springframework.context/src/test/java/example/scannable/FooServiceImpl.java
+++ b/org.springframework.context/src/test/java/example/scannable/FooServiceImpl.java
@@ -65,6 +65,7 @@ public class FooServiceImpl implements FooService {
private boolean initCalled = false;
+ @SuppressWarnings("unused")
@PostConstruct
private void init() {
if (this.initCalled) {
diff --git a/org.springframework.context/src/test/java/example/scannable/_package.java b/org.springframework.context/src/test/java/example/scannable/_package.java
new file mode 100644
index 00000000000..4be3b9447bc
--- /dev/null
+++ b/org.springframework.context/src/test/java/example/scannable/_package.java
@@ -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 { }
diff --git a/org.springframework.context/src/test/java/example/scannable_scoped/CustomScopeAnnotationBean.java b/org.springframework.context/src/test/java/example/scannable_scoped/CustomScopeAnnotationBean.java
new file mode 100644
index 00000000000..b34d50318f8
--- /dev/null
+++ b/org.springframework.context/src/test/java/example/scannable_scoped/CustomScopeAnnotationBean.java
@@ -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 {
+}
diff --git a/org.springframework.context/src/test/java/example/scannable_scoped/MyScope.java b/org.springframework.context/src/test/java/example/scannable_scoped/MyScope.java
new file mode 100644
index 00000000000..512740b90b4
--- /dev/null
+++ b/org.springframework.context/src/test/java/example/scannable_scoped/MyScope.java
@@ -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;
+}
\ No newline at end of file
diff --git a/org.springframework.context/src/test/java/org/springframework/context/annotation/BeanFactoryAwareFeatureConfigurationTests.java b/org.springframework.context/src/test/java/org/springframework/context/annotation/BeanFactoryAwareFeatureConfigurationTests.java
new file mode 100644
index 00000000000..42f1c9b2a27
--- /dev/null
+++ b/org.springframework.context/src/test/java/org/springframework/context/annotation/BeanFactoryAwareFeatureConfigurationTests.java
@@ -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();
+ }
+ }
+}
diff --git a/org.springframework.context/src/test/java/org/springframework/context/annotation/ComponentScanAnnotationIntegrationTests.java b/org.springframework.context/src/test/java/org/springframework/context/annotation/ComponentScanAnnotationIntegrationTests.java
new file mode 100644
index 00000000000..a418258fe2d
--- /dev/null
+++ b/org.springframework.context/src/test/java/org/springframework/context/annotation/ComponentScanAnnotationIntegrationTests.java
@@ -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 { }
diff --git a/org.springframework.context/src/test/java/org/springframework/context/annotation/ComponentScanAnnotationTests.java b/org.springframework.context/src/test/java/org/springframework/context/annotation/ComponentScanAnnotationTests.java
index 39f8dffc112..56045f669b6 100644
--- a/org.springframework.context/src/test/java/org/springframework/context/annotation/ComponentScanAnnotationTests.java
+++ b/org.springframework.context/src/test/java/org/springframework/context/annotation/ComponentScanAnnotationTests.java
@@ -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 { }
\ No newline at end of file
diff --git a/org.springframework.context/src/test/java/org/springframework/context/annotation/ComponentScanExecutorTests.java b/org.springframework.context/src/test/java/org/springframework/context/annotation/ComponentScanExecutorTests.java
new file mode 100644
index 00000000000..287c05a024f
--- /dev/null
+++ b/org.springframework.context/src/test/java/org/springframework/context/annotation/ComponentScanExecutorTests.java
@@ -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);
+ }
+
+}
diff --git a/org.springframework.context/src/test/java/org/springframework/context/annotation/ComponentScanFeatureTests.java b/org.springframework.context/src/test/java/org/springframework/context/annotation/ComponentScanFeatureTests.java
new file mode 100644
index 00000000000..8ddbd03240f
--- /dev/null
+++ b/org.springframework.context/src/test/java/org/springframework/context/annotation/ComponentScanFeatureTests.java
@@ -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();
+ }
+}
\ No newline at end of file
diff --git a/org.springframework.context/src/test/java/org/springframework/context/annotation/ComponentScanSpecTests.java b/org.springframework.context/src/test/java/org/springframework/context/annotation/ComponentScanSpecTests.java
new file mode 100644
index 00000000000..cc9f1ecbb81
--- /dev/null
+++ b/org.springframework.context/src/test/java/org/springframework/context/annotation/ComponentScanSpecTests.java
@@ -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 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 errors = new ArrayList();
+
+ private List warnings = new ArrayList();
+
+
+ public void fatal(Problem problem) {
+ throw new BeanDefinitionParsingException(problem);
+ }
+
+ public void error(Problem problem) {
+ this.errors.add(problem);
+ }
+
+ @SuppressWarnings("unused")
+ public Problem[] getErrors() {
+ return this.errors.toArray(new Problem[this.errors.size()]);
+ }
+
+ public void warning(Problem problem) {
+ System.out.println(problem);
+ this.warnings.add(problem);
+ }
+
+ @SuppressWarnings("unused")
+ public Problem[] getWarnings() {
+ return this.warnings.toArray(new Problem[this.warnings.size()]);
+ }
+ }
+
+
+ private static class NonNoArgResolver implements ScopeMetadataResolver {
+ public ScopeMetadata resolveScopeMetadata(BeanDefinition definition) {
+ throw new UnsupportedOperationException();
+ }
+ }
+
+ private static class StubTypeFilter implements TypeFilter {
+ public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory)
+ throws IOException {
+ throw new UnsupportedOperationException();
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/org.springframework.context/src/test/java/org/springframework/context/annotation/EarlyBeanReferenceProxyCreatorTests.java b/org.springframework.context/src/test/java/org/springframework/context/annotation/EarlyBeanReferenceProxyCreatorTests.java
new file mode 100644
index 00000000000..ae481bdae0d
--- /dev/null
+++ b/org.springframework.context/src/test/java/org/springframework/context/annotation/EarlyBeanReferenceProxyCreatorTests.java
@@ -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();
+ }
+ }
+}
diff --git a/org.springframework.context/src/test/java/org/springframework/context/annotation/FeatureConfigurationClassTests.java b/org.springframework.context/src/test/java/org/springframework/context/annotation/FeatureConfigurationClassTests.java
new file mode 100644
index 00000000000..a7831d0eaa3
--- /dev/null
+++ b/org.springframework.context/src/test/java/org/springframework/context/annotation/FeatureConfigurationClassTests.java
@@ -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();
+ }
+}
\ No newline at end of file
diff --git a/org.springframework.context/src/test/java/org/springframework/context/annotation/FeatureConfigurationImportResourceTests-context.xml b/org.springframework.context/src/test/java/org/springframework/context/annotation/FeatureConfigurationImportResourceTests-context.xml
new file mode 100644
index 00000000000..930cadfa379
--- /dev/null
+++ b/org.springframework.context/src/test/java/org/springframework/context/annotation/FeatureConfigurationImportResourceTests-context.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
diff --git a/org.springframework.context/src/test/java/org/springframework/context/annotation/FeatureConfigurationImportResourceTests.java b/org.springframework.context/src/test/java/org/springframework/context/annotation/FeatureConfigurationImportResourceTests.java
new file mode 100644
index 00000000000..466e65b14e3
--- /dev/null
+++ b/org.springframework.context/src/test/java/org/springframework/context/annotation/FeatureConfigurationImportResourceTests.java
@@ -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();
+ }
+ }
+
+}
diff --git a/org.springframework.context/src/test/java/org/springframework/context/annotation/FeatureConfigurationImportTests.java b/org.springframework.context/src/test/java/org/springframework/context/annotation/FeatureConfigurationImportTests.java
new file mode 100644
index 00000000000..53141541225
--- /dev/null
+++ b/org.springframework.context/src/test/java/org/springframework/context/annotation/FeatureConfigurationImportTests.java
@@ -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();
+ }
+ }
+
+}
diff --git a/org.springframework.context/src/test/java/org/springframework/context/annotation/FeatureMethodBeanReferenceTests.java b/org.springframework.context/src/test/java/org/springframework/context/annotation/FeatureMethodBeanReferenceTests.java
new file mode 100644
index 00000000000..bab82187afb
--- /dev/null
+++ b/org.springframework.context/src/test/java/org/springframework/context/annotation/FeatureMethodBeanReferenceTests.java
@@ -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"));
+ }
+ }
+
+}
diff --git a/org.springframework.context/src/test/java/org/springframework/context/annotation/FeatureMethodEarlyBeanProxyTests.java b/org.springframework.context/src/test/java/org/springframework/context/annotation/FeatureMethodEarlyBeanProxyTests.java
new file mode 100644
index 00000000000..62784a84a34
--- /dev/null
+++ b/org.springframework.context/src/test/java/org/springframework/context/annotation/FeatureMethodEarlyBeanProxyTests.java
@@ -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");
+ }
+}
+
diff --git a/org.springframework.context/src/test/java/org/springframework/context/annotation/FeatureMethodQualifiedBeanReferenceTests.java b/org.springframework.context/src/test/java/org/springframework/context/annotation/FeatureMethodQualifiedBeanReferenceTests.java
new file mode 100644
index 00000000000..5ea134eb330
--- /dev/null
+++ b/org.springframework.context/src/test/java/org/springframework/context/annotation/FeatureMethodQualifiedBeanReferenceTests.java
@@ -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");
+ }
+ }
+
+}
diff --git a/org.springframework.context/src/test/java/org/springframework/context/annotation/SimpleFeatureMethodProcessingTests.java b/org.springframework.context/src/test/java/org/springframework/context/annotation/SimpleFeatureMethodProcessingTests.java
new file mode 100644
index 00000000000..4a06cf3e518
--- /dev/null
+++ b/org.springframework.context/src/test/java/org/springframework/context/annotation/SimpleFeatureMethodProcessingTests.java
@@ -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;
+ }
+ }
+
+}
diff --git a/org.springframework.context/src/test/java/org/springframework/context/annotation/configuration/ConfigurationClassProcessingTests.java b/org.springframework.context/src/test/java/org/springframework/context/annotation/configuration/ConfigurationClassProcessingTests.java
index e5ab36f3404..566aa340e35 100644
--- a/org.springframework.context/src/test/java/org/springframework/context/annotation/configuration/ConfigurationClassProcessingTests.java
+++ b/org.springframework.context/src/test/java/org/springframework/context/annotation/configuration/ConfigurationClassProcessingTests.java
@@ -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
diff --git a/org.springframework.context/src/test/java/org/springframework/context/annotation/configuration/ConfigurationClassWithPlaceholderConfigurerBeanTests.java b/org.springframework.context/src/test/java/org/springframework/context/annotation/configuration/ConfigurationClassWithPlaceholderConfigurerBeanTests.java
new file mode 100644
index 00000000000..c81b3d9ab86
--- /dev/null
+++ b/org.springframework.context/src/test/java/org/springframework/context/annotation/configuration/ConfigurationClassWithPlaceholderConfigurerBeanTests.java
@@ -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();
+ }
+
+}
\ No newline at end of file
diff --git a/org.springframework.context/src/test/java/org/springframework/context/annotation/configuration/FeatureMethodAndAutowiredFieldTests.xml b/org.springframework.context/src/test/java/org/springframework/context/annotation/configuration/FeatureMethodAndAutowiredFieldTests.xml
new file mode 100644
index 00000000000..0418dc8251a
--- /dev/null
+++ b/org.springframework.context/src/test/java/org/springframework/context/annotation/configuration/FeatureMethodAndAutowiredFieldTests.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
diff --git a/org.springframework.context/src/test/java/org/springframework/context/annotation/configuration/FeatureMethodLifecycleIssueTestSuite.java b/org.springframework.context/src/test/java/org/springframework/context/annotation/configuration/FeatureMethodLifecycleIssueTestSuite.java
new file mode 100644
index 00000000000..68baf7f7bfc
--- /dev/null
+++ b/org.springframework.context/src/test/java/org/springframework/context/annotation/configuration/FeatureMethodLifecycleIssueTestSuite.java
@@ -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 {
+
+}
diff --git a/org.springframework.context/src/test/java/org/springframework/context/annotation/configuration/StubSpecification.java b/org.springframework.context/src/test/java/org/springframework/context/annotation/configuration/StubSpecification.java
new file mode 100644
index 00000000000..94d7aef2bfd
--- /dev/null
+++ b/org.springframework.context/src/test/java/org/springframework/context/annotation/configuration/StubSpecification.java
@@ -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) {
+ }
+
+}
diff --git a/org.springframework.context/src/test/java/org/springframework/format/datetime/joda/JodaTimeFormattingTests.java b/org.springframework.context/src/test/java/org/springframework/format/datetime/joda/JodaTimeFormattingTests.java
index 1539f69d04e..9c4f6837aae 100644
--- a/org.springframework.context/src/test/java/org/springframework/format/datetime/joda/JodaTimeFormattingTests.java
+++ b/org.springframework.context/src/test/java/org/springframework/format/datetime/joda/JodaTimeFormattingTests.java
@@ -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);
diff --git a/org.springframework.context/src/test/java/org/springframework/format/number/NumberFormattingTests.java b/org.springframework.context/src/test/java/org/springframework/format/number/NumberFormattingTests.java
index 510dbbfea48..7ee161efe62 100644
--- a/org.springframework.context/src/test/java/org/springframework/format/number/NumberFormattingTests.java
+++ b/org.springframework.context/src/test/java/org/springframework/format/number/NumberFormattingTests.java
@@ -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)) {
diff --git a/org.springframework.context/src/test/java/org/springframework/format/support/FormattingConversionServiceTests.java b/org.springframework.context/src/test/java/org/springframework/format/support/FormattingConversionServiceTests.java
index ec27aa11fc6..9fe778dc926 100644
--- a/org.springframework.context/src/test/java/org/springframework/format/support/FormattingConversionServiceTests.java
+++ b/org.springframework.context/src/test/java/org/springframework/format/support/FormattingConversionServiceTests.java
@@ -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);
}
diff --git a/org.springframework.context/src/test/java/org/springframework/validation/DataBinderTests.java b/org.springframework.context/src/test/java/org/springframework/validation/DataBinderTests.java
index 526f5a2ba3a..753748ff7d9 100644
--- a/org.springframework.context/src/test/java/org/springframework/validation/DataBinderTests.java
+++ b/org.springframework.context/src/test/java/org/springframework/validation/DataBinderTests.java
@@ -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");
diff --git a/org.springframework.core/src/main/java/org/springframework/core/convert/support/ConversionServiceFactory.java b/org.springframework.core/src/main/java/org/springframework/core/convert/support/ConversionServiceFactory.java
index 0b3664cf91f..c397ab55ba9 100644
--- a/org.springframework.core/src/main/java/org/springframework/core/convert/support/ConversionServiceFactory.java
+++ b/org.springframework.core/src/main/java/org/springframework/core/convert/support/ConversionServiceFactory.java
@@ -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);
+ }
+
}
diff --git a/org.springframework.core/src/main/java/org/springframework/core/convert/support/DefaultConversionService.java b/org.springframework.core/src/main/java/org/springframework/core/convert/support/DefaultConversionService.java
new file mode 100644
index 00000000000..1eea0c986aa
--- /dev/null
+++ b/org.springframework.core/src/main/java/org/springframework/core/convert/support/DefaultConversionService.java
@@ -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.
+ *
+ * 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));
+ }
+
+}
diff --git a/org.springframework.core/src/main/java/org/springframework/core/env/AbstractPropertyResolver.java b/org.springframework.core/src/main/java/org/springframework/core/env/AbstractPropertyResolver.java
index b489bf6520c..35e20d9dbcd 100644
--- a/org.springframework.core/src/main/java/org/springframework/core/env/AbstractPropertyResolver.java
+++ b/org.springframework.core/src/main/java/org/springframework/core/env/AbstractPropertyResolver.java
@@ -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;
diff --git a/org.springframework.core/src/main/java/org/springframework/core/env/Environment.java b/org.springframework.core/src/main/java/org/springframework/core/env/Environment.java
index 32cacfb2ed3..433aad68992 100644
--- a/org.springframework.core/src/main/java/org/springframework/core/env/Environment.java
+++ b/org.springframework.core/src/main/java/org/springframework/core/env/Environment.java
@@ -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...)}.
*
*
If no profiles have explicitly been specified as active, then any 'default' profiles will implicitly
diff --git a/org.springframework.core/src/main/java/org/springframework/core/type/MethodMetadata.java b/org.springframework.core/src/main/java/org/springframework/core/type/MethodMetadata.java
index b9417e8873d..caea4339e4b 100644
--- a/org.springframework.core/src/main/java/org/springframework/core/type/MethodMetadata.java
+++ b/org.springframework.core/src/main/java/org/springframework/core/type/MethodMetadata.java
@@ -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.
*/
diff --git a/org.springframework.core/src/main/java/org/springframework/core/type/StandardMethodMetadata.java b/org.springframework.core/src/main/java/org/springframework/core/type/StandardMethodMetadata.java
index c6c8f28f80e..328e1f4318c 100644
--- a/org.springframework.core/src/main/java/org/springframework/core/type/StandardMethodMetadata.java
+++ b/org.springframework.core/src/main/java/org/springframework/core/type/StandardMethodMetadata.java
@@ -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();
}
diff --git a/org.springframework.core/src/main/java/org/springframework/core/type/classreading/AnnotationMetadataReadingVisitor.java b/org.springframework.core/src/main/java/org/springframework/core/type/classreading/AnnotationMetadataReadingVisitor.java
index 311955368d1..77bec9fa47c 100644
--- a/org.springframework.core/src/main/java/org/springframework/core/type/classreading/AnnotationMetadataReadingVisitor.java
+++ b/org.springframework.core/src/main/java/org/springframework/core/type/classreading/AnnotationMetadataReadingVisitor.java
@@ -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);
+ }
+
}
diff --git a/org.springframework.core/src/main/java/org/springframework/core/type/classreading/MethodMetadataReadingVisitor.java b/org.springframework.core/src/main/java/org/springframework/core/type/classreading/MethodMetadataReadingVisitor.java
index 1bb40cbbee8..83f4a3dde01 100644
--- a/org.springframework.core/src/main/java/org/springframework/core/type/classreading/MethodMetadataReadingVisitor.java
+++ b/org.springframework.core/src/main/java/org/springframework/core/type/classreading/MethodMetadataReadingVisitor.java
@@ -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> attributeMap = new LinkedHashMap>(2);
- public MethodMetadataReadingVisitor(String name, int access, String declaringClassName, ClassLoader classLoader,
+ public MethodMetadataReadingVisitor(String name, String returnType, int access, String declaringClassName, ClassLoader classLoader,
MultiValueMap 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);
}
diff --git a/org.springframework.core/src/main/java/org/springframework/util/ObjectUtils.java b/org.springframework.core/src/main/java/org/springframework/util/ObjectUtils.java
index c590dfa806d..16a59cbbad7 100644
--- a/org.springframework.core/src/main/java/org/springframework/util/ObjectUtils.java
+++ b/org.springframework.core/src/main/java/org/springframework/util/ObjectUtils.java
@@ -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 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 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.
diff --git a/org.springframework.core/src/main/java/org/springframework/util/ReflectionUtils.java b/org.springframework.core/src/main/java/org/springframework/util/ReflectionUtils.java
index f7ab95038a5..c9588608faa 100644
--- a/org.springframework.core/src/main/java/org/springframework/util/ReflectionUtils.java
+++ b/org.springframework.core/src/main/java/org/springframework/util/ReflectionUtils.java
@@ -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 setAccessible(true) method is only called
diff --git a/org.springframework.core/src/test/java/org/springframework/core/convert/support/DefaultConversionTests.java b/org.springframework.core/src/test/java/org/springframework/core/convert/support/DefaultConversionTests.java
index 2ac63df918d..7b7ff79d149 100644
--- a/org.springframework.core/src/test/java/org/springframework/core/convert/support/DefaultConversionTests.java
+++ b/org.springframework.core/src/test/java/org/springframework/core/convert/support/DefaultConversionTests.java
@@ -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() {
diff --git a/org.springframework.core/src/test/java/org/springframework/core/convert/support/GenericConversionServiceTests.java b/org.springframework.core/src/test/java/org/springframework/core/convert/support/GenericConversionServiceTests.java
index cbe398d945b..1ab672dd93e 100644
--- a/org.springframework.core/src/test/java/org/springframework/core/convert/support/GenericConversionServiceTests.java
+++ b/org.springframework.core/src/test/java/org/springframework/core/convert/support/GenericConversionServiceTests.java
@@ -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 input = new LinkedHashMap();
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 = 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 -> list conversionPerformance");
watch.start("convert 4,000,000 with conversion service");
List source = new LinkedList();
@@ -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 -> map conversionPerformance");
watch.start("convert 4,000,000 with conversion service");
Map source = new HashMap();
@@ -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 result = (LinkedList) 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 result = (LinkedHashMap) conversionService.convert(map, sourceType, targetType);
assertEquals(map, result);
assertEquals(LinkedHashMap.class, result.getClass());
diff --git a/org.springframework.core/src/test/java/org/springframework/util/ObjectUtilsTests.java b/org.springframework.core/src/test/java/org/springframework/util/ObjectUtilsTests.java
index d929faa6922..fcfe1b38d40 100644
--- a/org.springframework.core/src/test/java/org/springframework/util/ObjectUtilsTests.java
+++ b/org.springframework.core/src/test/java/org/springframework/util/ObjectUtilsTests.java
@@ -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);
diff --git a/org.springframework.expression/src/main/java/org/springframework/expression/spel/support/StandardTypeConverter.java b/org.springframework.expression/src/main/java/org/springframework/expression/spel/support/StandardTypeConverter.java
index e6223d86365..8a5d757f410 100644
--- a/org.springframework.expression/src/main/java/org/springframework/expression/spel/support/StandardTypeConverter.java
+++ b/org.springframework.expression/src/main/java/org/springframework/expression/spel/support/StandardTypeConverter.java
@@ -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;
diff --git a/org.springframework.expression/src/test/java/org/springframework/expression/spel/ExpressionTestsUsingCoreConversionService.java b/org.springframework.expression/src/test/java/org/springframework/expression/spel/ExpressionTestsUsingCoreConversionService.java
index 6d148d4322d..c083f29109f 100644
--- a/org.springframework.expression/src/test/java/org/springframework/expression/spel/ExpressionTestsUsingCoreConversionService.java
+++ b/org.springframework.expression/src/test/java/org/springframework/expression/spel/ExpressionTestsUsingCoreConversionService.java
@@ -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);
diff --git a/org.springframework.integration-tests/src/test/java/org/springframework/context/annotation/FeatureTestSuite.java b/org.springframework.integration-tests/src/test/java/org/springframework/context/annotation/FeatureTestSuite.java
new file mode 100644
index 00000000000..51bed0fac12
--- /dev/null
+++ b/org.springframework.integration-tests/src/test/java/org/springframework/context/annotation/FeatureTestSuite.java
@@ -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 {
+
+}
\ No newline at end of file
diff --git a/org.springframework.integration-tests/src/test/java/org/springframework/expression/spel/support/BeanFactoryTypeConverter.java b/org.springframework.integration-tests/src/test/java/org/springframework/expression/spel/support/BeanFactoryTypeConverter.java
index ae91a4055f1..922b39b709d 100644
--- a/org.springframework.integration-tests/src/test/java/org/springframework/expression/spel/support/BeanFactoryTypeConverter.java
+++ b/org.springframework.integration-tests/src/test/java/org/springframework/expression/spel/support/BeanFactoryTypeConverter.java
@@ -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;
diff --git a/org.springframework.transaction/src/main/java/org/springframework/transaction/config/AnnotationDrivenBeanDefinitionParser.java b/org.springframework.transaction/src/main/java/org/springframework/transaction/config/AnnotationDrivenBeanDefinitionParser.java
index dd28db09596..cce36984602 100644
--- a/org.springframework.transaction/src/main/java/org/springframework/transaction/config/AnnotationDrivenBeanDefinitionParser.java
+++ b/org.springframework.transaction/src/main/java/org/springframework/transaction/config/AnnotationDrivenBeanDefinitionParser.java
@@ -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.
*
* 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 '<tx:annotation-driven/>' tag. Will
+ * Parses the {@code } 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;
}
}
diff --git a/org.springframework.transaction/src/main/java/org/springframework/transaction/config/JtaTransactionManagerBeanDefinitionParser.java b/org.springframework.transaction/src/main/java/org/springframework/transaction/config/JtaTransactionManagerBeanDefinitionParser.java
index f90ca8b6b77..ba0a7eab788 100644
--- a/org.springframework.transaction/src/main/java/org/springframework/transaction/config/JtaTransactionManagerBeanDefinitionParser.java
+++ b/org.springframework.transaction/src/main/java/org/springframework/transaction/config/JtaTransactionManagerBeanDefinitionParser.java
@@ -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;
}
}
diff --git a/org.springframework.transaction/src/main/java/org/springframework/transaction/config/TxAdviceBeanDefinitionParser.java b/org.springframework.transaction/src/main/java/org/springframework/transaction/config/TxAdviceBeanDefinitionParser.java
index 9c2bea94a0c..a8a88ca5993 100644
--- a/org.springframework.transaction/src/main/java/org/springframework/transaction/config/TxAdviceBeanDefinitionParser.java
+++ b/org.springframework.transaction/src/main/java/org/springframework/transaction/config/TxAdviceBeanDefinitionParser.java
@@ -37,50 +37,55 @@ import org.springframework.util.StringUtils;
import org.springframework.util.xml.DomUtils;
/**
- * {@link org.springframework.beans.factory.xml.BeanDefinitionParser}
- * for the <tx:advice> tag.
+ * {@link org.springframework.beans.factory.xml.BeanDefinitionParser
+ * BeanDefinitionParser} for the {@code } 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 txAttributes = DomUtils.getChildElementsByTagName(element, ATTRIBUTES_ELEMENT);
if (txAttributes.size() > 1) {
parserContext.getReaderContext().error(
"Element is allowed at most once inside element ", 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 methods = DomUtils.getChildElementsByTagName(attrEle, "method");
- ManagedMap transactionAttributeMap = new ManagedMap(methods.size());
+ List methods = DomUtils.getChildElementsByTagName(attrEle, METHOD_ELEMENT);
+ ManagedMap transactionAttributeMap =
+ new ManagedMap(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 rollbackRules = new LinkedList();
- 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;
}
diff --git a/org.springframework.transaction/src/main/java/org/springframework/transaction/config/TxAnnotationDriven.java b/org.springframework.transaction/src/main/java/org/springframework/transaction/config/TxAnnotationDriven.java
new file mode 100644
index 00000000000..299ed4854b4
--- /dev/null
+++ b/org.springframework.transaction/src/main/java/org/springframework/transaction/config/TxAnnotationDriven.java
@@ -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}.
+ *
+ * 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.
+ *
+ *
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);
+ }
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/org.springframework.transaction/src/main/java/org/springframework/transaction/config/TxAnnotationDrivenExecutor.java b/org.springframework.transaction/src/main/java/org/springframework/transaction/config/TxAnnotationDrivenExecutor.java
new file mode 100644
index 00000000000..cf971c0c672
--- /dev/null
+++ b/org.springframework.transaction/src/main/java/org/springframework/transaction/config/TxAnnotationDrivenExecutor.java
@@ -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 {
+
+ /**
+ * 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);
+ }
+ }
+ }
+}
diff --git a/org.springframework.transaction/src/main/java/org/springframework/transaction/config/TxNamespaceHandler.java b/org.springframework.transaction/src/main/java/org/springframework/transaction/config/TxNamespaceHandler.java
index ac657898304..bdf376f2473 100644
--- a/org.springframework.transaction/src/main/java/org/springframework/transaction/config/TxNamespaceHandler.java
+++ b/org.springframework.transaction/src/main/java/org/springframework/transaction/config/TxNamespaceHandler.java
@@ -39,17 +39,6 @@ import org.springframework.beans.factory.xml.NamespaceHandlerSupport;
*/
public class TxNamespaceHandler extends NamespaceHandlerSupport {
- static final String TRANSACTION_MANAGER_ATTRIBUTE = "transaction-manager";
-
- static final String DEFAULT_TRANSACTION_MANAGER_BEAN_NAME = "transactionManager";
-
-
- static String getTransactionManagerName(Element element) {
- return (element.hasAttribute(TRANSACTION_MANAGER_ATTRIBUTE) ?
- element.getAttribute(TRANSACTION_MANAGER_ATTRIBUTE) : DEFAULT_TRANSACTION_MANAGER_BEAN_NAME);
- }
-
-
public void init() {
registerBeanDefinitionParser("advice", new TxAdviceBeanDefinitionParser());
registerBeanDefinitionParser("annotation-driven", new AnnotationDrivenBeanDefinitionParser());
diff --git a/org.springframework.transaction/src/test/java/org/springframework/transaction/annotation/AnnotationTransactionNamespaceHandlerTests.java b/org.springframework.transaction/src/test/java/org/springframework/transaction/annotation/AnnotationTransactionNamespaceHandlerTests.java
index 86921c7c57e..2b813663196 100644
--- a/org.springframework.transaction/src/test/java/org/springframework/transaction/annotation/AnnotationTransactionNamespaceHandlerTests.java
+++ b/org.springframework.transaction/src/test/java/org/springframework/transaction/annotation/AnnotationTransactionNamespaceHandlerTests.java
@@ -19,6 +19,7 @@ package org.springframework.transaction.annotation;
import java.lang.management.ManagementFactory;
import java.util.Collection;
import java.util.Map;
+
import javax.management.MBeanServer;
import javax.management.ObjectName;
diff --git a/org.springframework.transaction/src/test/java/org/springframework/transaction/annotation/TxAnnotationDrivenFeatureTests.java b/org.springframework.transaction/src/test/java/org/springframework/transaction/annotation/TxAnnotationDrivenFeatureTests.java
new file mode 100644
index 00000000000..7071426d260
--- /dev/null
+++ b/org.springframework.transaction/src/test/java/org/springframework/transaction/annotation/TxAnnotationDrivenFeatureTests.java
@@ -0,0 +1,147 @@
+/*
+ * Copyright 2002-2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.transaction.annotation;
+
+import static org.hamcrest.CoreMatchers.equalTo;
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.fail;
+
+import java.util.Map;
+
+import org.junit.Test;
+import org.springframework.aop.support.AopUtils;
+import org.springframework.beans.factory.CannotLoadBeanClassException;
+import org.springframework.context.annotation.AnnotationConfigApplicationContext;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Feature;
+import org.springframework.context.annotation.FeatureConfiguration;
+import org.springframework.context.config.AdviceMode;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.CallCountingTransactionManager;
+import org.springframework.transaction.PlatformTransactionManager;
+import org.springframework.transaction.annotation.AnnotationTransactionNamespaceHandlerTests.TransactionalTestBean;
+import org.springframework.transaction.config.TxAnnotationDriven;
+
+/**
+ * Integration tests for {@link TxAnnotationDriven} support within @Configuration
+ * classes. Adapted from original tx: namespace tests at
+ * {@link AnnotationTransactionNamespaceHandlerTests}.
+ *
+ * @author Chris Beams
+ * @since 3.1
+ */
+public class TxAnnotationDrivenFeatureTests {
+ @Test
+ public void transactionProxyIsCreated() {
+ AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
+ ctx.register(TxFeature.class, TxManagerConfig.class);
+ ctx.refresh();
+ TransactionalTestBean bean = ctx.getBean(TransactionalTestBean.class);
+ assertThat("testBean is not a proxy", AopUtils.isAopProxy(bean), is(true));
+ Map,?> services = ctx.getBeansWithAnnotation(Service.class);
+ assertThat("Stereotype annotation not visible", services.containsKey("testBean"), is(true));
+ }
+
+ @Test
+ public void txManagerIsResolvedOnInvocationOfTransactionalMethod() {
+ AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
+ ctx.register(TxFeature.class, TxManagerConfig.class);
+ ctx.refresh();
+ TransactionalTestBean bean = ctx.getBean(TransactionalTestBean.class);
+
+ // invoke a transactional method, causing the PlatformTransactionManager bean to be resolved.
+ bean.findAllFoos();
+ }
+
+ @Test
+ public void txManagerIsResolvedCorrectlyWhenMultipleManagersArePresent() {
+ AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
+ ctx.register(TxFeature.class, MultiTxManagerConfig.class);
+ ctx.refresh();
+ TransactionalTestBean bean = ctx.getBean(TransactionalTestBean.class);
+
+ // invoke a transactional method, causing the PlatformTransactionManager bean to be resolved.
+ bean.findAllFoos();
+ }
+
+ /**
+ * A cheap test just to prove that in ASPECTJ mode, the AnnotationTransactionAspect does indeed
+ * get loaded -- or in this case, attempted to be loaded at which point the test fails.
+ */
+ @Test
+ public void proxyTypeAspectJCausesRegistrationOfAnnotationTransactionAspect() {
+ try {
+ new AnnotationConfigApplicationContext(TxWithAspectJFeature.class, TxManagerConfig.class);
+ fail("should have thrown CNFE when trying to load AnnotationTransactionAspect. " +
+ "Do you actually have org.springframework.aspects on the classpath?");
+ } catch (CannotLoadBeanClassException ex) {
+ ClassNotFoundException cause = (ClassNotFoundException) ex.getCause();
+ assertThat(cause.getMessage(), equalTo("org.springframework.transaction.aspectj.AnnotationTransactionAspect"));
+ }
+ }
+
+}
+
+@FeatureConfiguration
+class TxFeature {
+
+ @Feature
+ public TxAnnotationDriven tx(TxManagerConfig txManagerConfig) {
+ return new TxAnnotationDriven(txManagerConfig.txManager());
+ }
+}
+
+
+@FeatureConfiguration
+class TxWithAspectJFeature {
+
+ @Feature
+ public TxAnnotationDriven tx(PlatformTransactionManager txManager) {
+ return new TxAnnotationDriven(txManager).mode(AdviceMode.ASPECTJ);
+ }
+
+}
+
+
+@Configuration
+class TxManagerConfig {
+
+ @Bean
+ public TransactionalTestBean testBean() {
+ return new TransactionalTestBean();
+ }
+
+ @Bean
+ public PlatformTransactionManager txManager() {
+ return new CallCountingTransactionManager();
+ }
+
+}
+
+
+@Configuration
+class MultiTxManagerConfig extends TxManagerConfig {
+
+ @Bean
+ public PlatformTransactionManager txManager2() {
+ return new CallCountingTransactionManager();
+ }
+
+}
+
diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/config/AbstractHttpRequestHandlerBeanDefinitionParser.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/config/AbstractHttpRequestHandlerBeanDefinitionParser.java
deleted file mode 100644
index 13f10cf673b..00000000000
--- a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/config/AbstractHttpRequestHandlerBeanDefinitionParser.java
+++ /dev/null
@@ -1,57 +0,0 @@
-/*
- * Copyright 2002-2010 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.springframework.web.servlet.config;
-
-import org.w3c.dom.Element;
-
-import org.springframework.beans.factory.config.BeanDefinition;
-import org.springframework.beans.factory.parsing.BeanComponentDefinition;
-import org.springframework.beans.factory.support.RootBeanDefinition;
-import org.springframework.beans.factory.xml.BeanDefinitionParser;
-import org.springframework.beans.factory.xml.ParserContext;
-import org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter;
-
-/**
- * Abstract base class for {@link BeanDefinitonParser}s that register an HttpRequestHandler.
- *
- * @author Jeremy Grelle
- * @since 3.0.4
- */
-abstract class AbstractHttpRequestHandlerBeanDefinitionParser implements BeanDefinitionParser{
-
- private static final String HANDLER_ADAPTER_BEAN_NAME = "org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter";
-
- public BeanDefinition parse(Element element, ParserContext parserContext) {
- Object source = parserContext.extractSource(element);
- registerHandlerAdapterIfNecessary(parserContext, source);
- doParse(element, parserContext);
- return null;
- }
-
- public abstract void doParse(Element element, ParserContext parserContext);
-
- private void registerHandlerAdapterIfNecessary(ParserContext parserContext, Object source) {
- if (!parserContext.getRegistry().containsBeanDefinition(HANDLER_ADAPTER_BEAN_NAME)) {
- RootBeanDefinition handlerAdapterDef = new RootBeanDefinition(HttpRequestHandlerAdapter.class);
- handlerAdapterDef.setSource(source);
- handlerAdapterDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
- parserContext.getRegistry().registerBeanDefinition(HANDLER_ADAPTER_BEAN_NAME, handlerAdapterDef);
- parserContext.registerComponent(new BeanComponentDefinition(handlerAdapterDef, HANDLER_ADAPTER_BEAN_NAME));
- }
- }
-
-}
diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/config/AnnotationDrivenBeanDefinitionParser.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/config/AnnotationDrivenBeanDefinitionParser.java
index 9965b8c9e10..6a9ea3af459 100644
--- a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/config/AnnotationDrivenBeanDefinitionParser.java
+++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/config/AnnotationDrivenBeanDefinitionParser.java
@@ -18,264 +18,86 @@ package org.springframework.web.servlet.config;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanDefinitionHolder;
-import org.springframework.beans.factory.config.RuntimeBeanReference;
-import org.springframework.beans.factory.parsing.BeanComponentDefinition;
-import org.springframework.beans.factory.parsing.CompositeComponentDefinition;
import org.springframework.beans.factory.support.ManagedList;
-import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.beans.factory.xml.BeanDefinitionParser;
import org.springframework.beans.factory.xml.ParserContext;
-import org.springframework.core.convert.ConversionService;
-import org.springframework.format.support.FormattingConversionServiceFactoryBean;
-import org.springframework.http.converter.ByteArrayHttpMessageConverter;
-import org.springframework.http.converter.HttpMessageConverter;
-import org.springframework.http.converter.ResourceHttpMessageConverter;
-import org.springframework.http.converter.StringHttpMessageConverter;
-import org.springframework.http.converter.feed.AtomFeedHttpMessageConverter;
-import org.springframework.http.converter.feed.RssChannelHttpMessageConverter;
-import org.springframework.http.converter.json.MappingJacksonHttpMessageConverter;
-import org.springframework.http.converter.xml.Jaxb2RootElementHttpMessageConverter;
-import org.springframework.http.converter.xml.SourceHttpMessageConverter;
-import org.springframework.http.converter.xml.XmlAwareFormHttpMessageConverter;
-import org.springframework.util.ClassUtils;
+import org.springframework.context.config.ExecutorContext;
import org.springframework.util.xml.DomUtils;
-import org.springframework.validation.Validator;
-import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;
-import org.springframework.web.bind.support.ConfigurableWebBindingInitializer;
-import org.springframework.web.servlet.handler.ConversionServiceExposingInterceptor;
-import org.springframework.web.servlet.handler.MappedInterceptor;
-import org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter;
-import org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerExceptionResolver;
-import org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping;
-import org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver;
-import org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver;
import org.w3c.dom.Element;
/**
- * {@link BeanDefinitionParser} that parses the {@code annotation-driven} element to configure a Spring MVC web
- * application.
+ * {@link BeanDefinitionParser} that parses the {@code annotation-driven} element
+ * to configure a Spring MVC web application.
*
- * Responsible for:
- *
- * Registering a DefaultAnnotationHandlerMapping bean for mapping HTTP Servlet Requests to @Controller methods
- * using @RequestMapping annotations.
- * Registering a AnnotationMethodHandlerAdapter bean for invoking annotated @Controller methods.
- * Will configure the HandlerAdapter's webBindingInitializer property for centrally configuring
- * {@code @Controller} {@code DataBinder} instances:
- *
- * Configures the conversionService if specified, otherwise defaults to a fresh {@link ConversionService} instance
- * created by the default {@link FormattingConversionServiceFactoryBean}.
- * Configures the validator if specified, otherwise defaults to a fresh {@link Validator} instance created by the
- * default {@link LocalValidatorFactoryBean} if the JSR-303 API is present on the classpath .
- * Configures standard {@link org.springframework.http.converter.HttpMessageConverter HttpMessageConverters},
- * including the {@link Jaxb2RootElementHttpMessageConverter} if JAXB2 is present on the classpath , and
- * the {@link MappingJacksonHttpMessageConverter} if Jackson is present on the classpath .
- *
- *
- *
- * @author Keith Donald
- * @author Juergen Hoeller
- * @author Arjen Poutsma
* @author Rossen Stoyanchev
* @since 3.0
+ * @see MvcAnnotationDriven
+ * @see MvcAnnotationDrivenExecutor
*/
class AnnotationDrivenBeanDefinitionParser implements BeanDefinitionParser {
- private static final boolean jsr303Present = ClassUtils.isPresent(
- "javax.validation.Validator", AnnotationDrivenBeanDefinitionParser.class.getClassLoader());
-
- private static final boolean jaxb2Present =
- ClassUtils.isPresent("javax.xml.bind.Binder", AnnotationDrivenBeanDefinitionParser.class.getClassLoader());
-
- private static final boolean jacksonPresent =
- ClassUtils.isPresent("org.codehaus.jackson.map.ObjectMapper", AnnotationDrivenBeanDefinitionParser.class.getClassLoader()) &&
- ClassUtils.isPresent("org.codehaus.jackson.JsonGenerator", AnnotationDrivenBeanDefinitionParser.class.getClassLoader());
-
- private static boolean romePresent =
- ClassUtils.isPresent("com.sun.syndication.feed.WireFeed", AnnotationDrivenBeanDefinitionParser.class.getClassLoader());
-
-
+ /**
+ * Parses the {@code } tag.
+ */
public BeanDefinition parse(Element element, ParserContext parserContext) {
- Object source = parserContext.extractSource(element);
-
- CompositeComponentDefinition compDefinition = new CompositeComponentDefinition(element.getTagName(), source);
- parserContext.pushContainingComponent(compDefinition);
-
- RootBeanDefinition annMappingDef = new RootBeanDefinition(DefaultAnnotationHandlerMapping.class);
- annMappingDef.setSource(source);
- annMappingDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
- annMappingDef.getPropertyValues().add("order", 0);
- String annMappingName = parserContext.getReaderContext().registerWithGeneratedName(annMappingDef);
-
- RuntimeBeanReference conversionService = getConversionService(element, source, parserContext);
- RuntimeBeanReference validator = getValidator(element, source, parserContext);
- RuntimeBeanReference messageCodesResolver = getMessageCodesResolver(element, source, parserContext);
-
- RootBeanDefinition bindingDef = new RootBeanDefinition(ConfigurableWebBindingInitializer.class);
- bindingDef.setSource(source);
- bindingDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
- bindingDef.getPropertyValues().add("conversionService", conversionService);
- bindingDef.getPropertyValues().add("validator", validator);
- bindingDef.getPropertyValues().add("messageCodesResolver", messageCodesResolver);
-
- ManagedList> messageConverters = getMessageConverters(element, source, parserContext);
- ManagedList> argumentResolvers = getArgumentResolvers(element, source, parserContext);
-
- RootBeanDefinition annAdapterDef = new RootBeanDefinition(AnnotationMethodHandlerAdapter.class);
- annAdapterDef.setSource(source);
- annAdapterDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
- annAdapterDef.getPropertyValues().add("webBindingInitializer", bindingDef);
- annAdapterDef.getPropertyValues().add("messageConverters", messageConverters);
- if (argumentResolvers != null) {
- annAdapterDef.getPropertyValues().add("customArgumentResolvers", argumentResolvers);
- }
- String annAdapterName = parserContext.getReaderContext().registerWithGeneratedName(annAdapterDef);
-
- RootBeanDefinition csInterceptorDef = new RootBeanDefinition(ConversionServiceExposingInterceptor.class);
- csInterceptorDef.setSource(source);
- csInterceptorDef.getConstructorArgumentValues().addIndexedArgumentValue(0, conversionService);
- RootBeanDefinition mappedCsInterceptorDef = new RootBeanDefinition(MappedInterceptor.class);
- mappedCsInterceptorDef.setSource(source);
- mappedCsInterceptorDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
- mappedCsInterceptorDef.getConstructorArgumentValues().addIndexedArgumentValue(0, (Object) null);
- mappedCsInterceptorDef.getConstructorArgumentValues().addIndexedArgumentValue(1, csInterceptorDef);
- String mappedInterceptorName = parserContext.getReaderContext().registerWithGeneratedName(mappedCsInterceptorDef);
-
- RootBeanDefinition annExceptionResolver = new RootBeanDefinition(AnnotationMethodHandlerExceptionResolver.class);
- annExceptionResolver.setSource(source);
- annExceptionResolver.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
- annExceptionResolver.getPropertyValues().add("messageConverters", messageConverters);
- annExceptionResolver.getPropertyValues().add("order", 0);
- String annExceptionResolverName =
- parserContext.getReaderContext().registerWithGeneratedName(annExceptionResolver);
-
- RootBeanDefinition responseStatusExceptionResolver = new RootBeanDefinition(ResponseStatusExceptionResolver.class);
- responseStatusExceptionResolver.setSource(source);
- responseStatusExceptionResolver.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
- responseStatusExceptionResolver.getPropertyValues().add("order", 1);
- String responseStatusExceptionResolverName =
- parserContext.getReaderContext().registerWithGeneratedName(responseStatusExceptionResolver);
-
- RootBeanDefinition defaultExceptionResolver = new RootBeanDefinition(DefaultHandlerExceptionResolver.class);
- defaultExceptionResolver.setSource(source);
- defaultExceptionResolver.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
- defaultExceptionResolver.getPropertyValues().add("order", 2);
- String defaultExceptionResolverName =
- parserContext.getReaderContext().registerWithGeneratedName(defaultExceptionResolver);
-
- parserContext.registerComponent(new BeanComponentDefinition(annMappingDef, annMappingName));
- parserContext.registerComponent(new BeanComponentDefinition(annAdapterDef, annAdapterName));
- parserContext.registerComponent(new BeanComponentDefinition(annExceptionResolver, annExceptionResolverName));
- parserContext.registerComponent(new BeanComponentDefinition(responseStatusExceptionResolver, responseStatusExceptionResolverName));
- parserContext.registerComponent(new BeanComponentDefinition(defaultExceptionResolver, defaultExceptionResolverName));
- parserContext.registerComponent(new BeanComponentDefinition(mappedCsInterceptorDef, mappedInterceptorName));
- parserContext.popAndRegisterContainingComponent();
-
+ MvcAnnotationDriven spec = createSpecification(element, parserContext);
+ spec.execute(createExecutorContext(parserContext));
return null;
}
-
- private RuntimeBeanReference getConversionService(Element element, Object source, ParserContext parserContext) {
- RuntimeBeanReference conversionServiceRef;
+
+ private MvcAnnotationDriven createSpecification(Element element, ParserContext parserContext) {
+ MvcAnnotationDriven spec = new MvcAnnotationDriven();
if (element.hasAttribute("conversion-service")) {
- conversionServiceRef = new RuntimeBeanReference(element.getAttribute("conversion-service"));
+ String conversionService = element.getAttribute("conversion-service");
+ spec.conversionService(conversionService);
}
- else {
- RootBeanDefinition conversionDef = new RootBeanDefinition(FormattingConversionServiceFactoryBean.class);
- conversionDef.setSource(source);
- conversionDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
- String conversionName = parserContext.getReaderContext().registerWithGeneratedName(conversionDef);
- parserContext.registerComponent(new BeanComponentDefinition(conversionDef, conversionName));
- conversionServiceRef = new RuntimeBeanReference(conversionName);
- }
- return conversionServiceRef;
- }
-
- private RuntimeBeanReference getValidator(Element element, Object source, ParserContext parserContext) {
if (element.hasAttribute("validator")) {
- return new RuntimeBeanReference(element.getAttribute("validator"));
+ spec.validator(element.getAttribute("validator"));
}
- else if (jsr303Present) {
- RootBeanDefinition validatorDef = new RootBeanDefinition(LocalValidatorFactoryBean.class);
- validatorDef.setSource(source);
- validatorDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
- String validatorName = parserContext.getReaderContext().registerWithGeneratedName(validatorDef);
- parserContext.registerComponent(new BeanComponentDefinition(validatorDef, validatorName));
- return new RuntimeBeanReference(validatorName);
- }
- else {
- return null;
- }
- }
-
- private RuntimeBeanReference getMessageCodesResolver(Element element, Object source, ParserContext parserContext) {
if (element.hasAttribute("message-codes-resolver")) {
- return new RuntimeBeanReference(element.getAttribute("message-codes-resolver"));
- } else {
- return null;
+ spec.messageCodesResolver(element.getAttribute("message-codes-resolver"));
}
- }
-
- private ManagedList> getArgumentResolvers(Element element, Object source, ParserContext parserContext) {
- Element resolversElement = DomUtils.getChildElementByTagName(element, "argument-resolvers");
- if (resolversElement != null) {
- ManagedList argumentResolvers = new ManagedList();
- argumentResolvers.setSource(source);
- for (Element resolver : DomUtils.getChildElementsByTagName(resolversElement, "bean")) {
- BeanDefinitionHolder beanDef = parserContext.getDelegate().parseBeanDefinitionElement(resolver);
- beanDef = parserContext.getDelegate().decorateBeanDefinitionIfRequired(resolver, beanDef);
- argumentResolvers.add(beanDef);
- }
- return argumentResolvers;
- }
- return null;
- }
-
- private ManagedList> getMessageConverters(Element element, Object source, ParserContext parserContext) {
Element convertersElement = DomUtils.getChildElementByTagName(element, "message-converters");
if (convertersElement != null) {
- ManagedList messageConverters = new ManagedList();
- messageConverters.setSource(source);
- for (Element converter : DomUtils.getChildElementsByTagName(convertersElement, "bean")) {
- BeanDefinitionHolder beanDef = parserContext.getDelegate().parseBeanDefinitionElement(converter);
- beanDef = parserContext.getDelegate().decorateBeanDefinitionIfRequired(converter, beanDef);
- messageConverters.add(beanDef);
+ if (convertersElement.hasAttribute("register-defaults")) {
+ spec.shouldRegisterDefaultMessageConverters(Boolean.valueOf(convertersElement
+ .getAttribute("register-defaults")));
}
- return messageConverters;
- } else {
- ManagedList messageConverters = new ManagedList();
- messageConverters.setSource(source);
- messageConverters.add(createConverterBeanDefinition(ByteArrayHttpMessageConverter.class, source));
-
- RootBeanDefinition stringConverterDef = createConverterBeanDefinition(StringHttpMessageConverter.class,
- source);
- stringConverterDef.getPropertyValues().add("writeAcceptCharset", false);
- messageConverters.add(stringConverterDef);
-
- messageConverters.add(createConverterBeanDefinition(ResourceHttpMessageConverter.class, source));
- messageConverters.add(createConverterBeanDefinition(SourceHttpMessageConverter.class, source));
- messageConverters.add(createConverterBeanDefinition(XmlAwareFormHttpMessageConverter.class, source));
- if (jaxb2Present) {
- messageConverters
- .add(createConverterBeanDefinition(Jaxb2RootElementHttpMessageConverter.class, source));
- }
- if (jacksonPresent) {
- messageConverters.add(createConverterBeanDefinition(MappingJacksonHttpMessageConverter.class, source));
- }
- if (romePresent) {
- messageConverters.add(createConverterBeanDefinition(AtomFeedHttpMessageConverter.class, source));
- messageConverters.add(createConverterBeanDefinition(RssChannelHttpMessageConverter.class, source));
- }
- return messageConverters;
+ spec.messageConverters(extractBeanSubElements(convertersElement, parserContext));
}
+ Element resolversElement = DomUtils.getChildElementByTagName(element, "argument-resolvers");
+ if (resolversElement != null) {
+ spec.argumentResolvers(extractBeanSubElements(resolversElement, parserContext));
+ }
+
+ spec.source(parserContext.extractSource(element));
+ spec.sourceName(element.getTagName());
+ return spec;
}
- private RootBeanDefinition createConverterBeanDefinition(Class extends HttpMessageConverter> converterClass,
- Object source) {
- RootBeanDefinition beanDefinition = new RootBeanDefinition(converterClass);
- beanDefinition.setSource(source);
- beanDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
-
- return beanDefinition;
+ private ManagedList super Object> extractBeanSubElements(Element parentElement, ParserContext parserContext) {
+ ManagedList super Object> list = new ManagedList();
+ list.setSource(parserContext.extractSource(parentElement));
+ for (Element beanElement : DomUtils.getChildElementsByTagName(parentElement, "bean")) {
+ BeanDefinitionHolder beanDef = parserContext.getDelegate().parseBeanDefinitionElement(beanElement);
+ beanDef = parserContext.getDelegate().decorateBeanDefinitionIfRequired(beanElement, beanDef);
+ list.add(beanDef);
+ }
+ return list;
}
+ /**
+ * 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 ExecutorContext createExecutorContext(ParserContext parserContext) {
+ ExecutorContext executorContext = new ExecutorContext();
+ executorContext.setRegistry(parserContext.getRegistry());
+ executorContext.setRegistrar(parserContext);
+ executorContext.setProblemReporter(parserContext.getReaderContext().getProblemReporter());
+ return executorContext;
+ }
+
}
diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/config/DefaultServletHandlerBeanDefinitionParser.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/config/DefaultServletHandlerBeanDefinitionParser.java
index fe60722234d..a0b05042d32 100644
--- a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/config/DefaultServletHandlerBeanDefinitionParser.java
+++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/config/DefaultServletHandlerBeanDefinitionParser.java
@@ -16,20 +16,15 @@
package org.springframework.web.servlet.config;
-import java.util.Map;
-
-import org.w3c.dom.Element;
-
import org.springframework.beans.factory.config.BeanDefinition;
-import org.springframework.beans.factory.parsing.BeanComponentDefinition;
-import org.springframework.beans.factory.support.ManagedMap;
-import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.beans.factory.xml.BeanDefinitionParser;
import org.springframework.beans.factory.xml.ParserContext;
+import org.springframework.context.config.ExecutorContext;
import org.springframework.util.StringUtils;
import org.springframework.web.servlet.handler.SimpleUrlHandlerMapping;
import org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter;
import org.springframework.web.servlet.resource.DefaultServletHttpRequestHandler;
+import org.w3c.dom.Element;
/**
* {@link BeanDefinitionParser} that parses a {@code default-servlet-handler} element to
@@ -37,37 +32,39 @@ import org.springframework.web.servlet.resource.DefaultServletHttpRequestHandler
* {@link SimpleUrlHandlerMapping} for mapping resource requests, and a
* {@link HttpRequestHandlerAdapter} if necessary.
*
- * @author Jeremy Grelle
+ * @author Rossen Stoyanchev
* @since 3.0.4
*/
-class DefaultServletHandlerBeanDefinitionParser extends AbstractHttpRequestHandlerBeanDefinitionParser {
+class DefaultServletHandlerBeanDefinitionParser implements BeanDefinitionParser {
- @Override
- public void doParse(Element element, ParserContext parserContext) {
- Object source = parserContext.extractSource(element);
-
- String defaultServletName = element.getAttribute("default-servlet-name");
- RootBeanDefinition defaultServletHandlerDef = new RootBeanDefinition(DefaultServletHttpRequestHandler.class);
- defaultServletHandlerDef.setSource(source);
- defaultServletHandlerDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
- if (StringUtils.hasText(defaultServletName)) {
- defaultServletHandlerDef.getPropertyValues().add("defaultServletName", defaultServletName);
- }
- String defaultServletHandlerName = parserContext.getReaderContext().generateBeanName(defaultServletHandlerDef);
- parserContext.getRegistry().registerBeanDefinition(defaultServletHandlerName, defaultServletHandlerDef);
- parserContext.registerComponent(new BeanComponentDefinition(defaultServletHandlerDef, defaultServletHandlerName));
-
- Map urlMap = new ManagedMap();
- urlMap.put("/**", defaultServletHandlerName);
-
- RootBeanDefinition handlerMappingDef = new RootBeanDefinition(SimpleUrlHandlerMapping.class);
- handlerMappingDef.setSource(source);
- handlerMappingDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
- handlerMappingDef.getPropertyValues().add("urlMap", urlMap);
-
- String handlerMappingBeanName = parserContext.getReaderContext().generateBeanName(handlerMappingDef);
- parserContext.getRegistry().registerBeanDefinition(handlerMappingBeanName, handlerMappingDef);
- parserContext.registerComponent(new BeanComponentDefinition(handlerMappingDef, handlerMappingBeanName));
+ /**
+ * Parses the {@code } tag.
+ */
+ public BeanDefinition parse(Element element, ParserContext parserContext) {
+ MvcDefaultServletHandler spec = createSpecification(element, parserContext);
+ spec.execute(createExecutorContext(parserContext));
+ return null;
+ }
+
+ private MvcDefaultServletHandler createSpecification(Element element, ParserContext parserContext) {
+ String defaultServletHandler = element.getAttribute("default-servlet-handler");
+ MvcDefaultServletHandler spec = StringUtils.hasText(defaultServletHandler) ? new MvcDefaultServletHandler(
+ defaultServletHandler) : new MvcDefaultServletHandler();
+ return spec;
+ }
+
+ /**
+ * 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 ExecutorContext createExecutorContext(ParserContext parserContext) {
+ ExecutorContext executorContext = new ExecutorContext();
+ executorContext.setRegistry(parserContext.getRegistry());
+ executorContext.setRegistrar(parserContext);
+ executorContext.setProblemReporter(parserContext.getReaderContext().getProblemReporter());
+ return executorContext;
}
}
diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/config/MvcAnnotationDriven.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/config/MvcAnnotationDriven.java
new file mode 100644
index 00000000000..d975321ea82
--- /dev/null
+++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/config/MvcAnnotationDriven.java
@@ -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.web.servlet.config;
+
+import org.springframework.beans.factory.parsing.SimpleProblemCollector;
+import org.springframework.beans.factory.support.ManagedList;
+import org.springframework.context.config.AbstractFeatureSpecification;
+import org.springframework.context.config.FeatureSpecificationExecutor;
+import org.springframework.core.convert.ConversionService;
+import org.springframework.format.support.FormattingConversionServiceFactoryBean;
+import org.springframework.http.converter.HttpMessageConverter;
+import org.springframework.http.converter.feed.AtomFeedHttpMessageConverter;
+import org.springframework.http.converter.feed.RssChannelHttpMessageConverter;
+import org.springframework.http.converter.json.MappingJacksonHttpMessageConverter;
+import org.springframework.http.converter.xml.Jaxb2RootElementHttpMessageConverter;
+import org.springframework.validation.MessageCodesResolver;
+import org.springframework.validation.Validator;
+import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;
+import org.springframework.web.bind.support.WebArgumentResolver;
+
+/**
+ * Specifies the Spring MVC "annotation-driven" container feature. The
+ * feature provides the following fine-grained configuration:
+ *
+ *
+ * {@code DefaultAnnotationHandlerMapping} bean for mapping HTTP Servlet Requests
+ * to {@code @Controller} methods using {@code @RequestMapping} annotations.
+ * {@code AnnotationMethodHandlerAdapter} bean for invoking annotated
+ * {@code @Controller} methods.
+ * {@code HandlerExceptionResolver} beans for invoking {@code @ExceptionHandler}
+ * controller methods and for mapping Spring exception to HTTP status codes.
+ *
+ *
+ * The {@code HandlerAdapter} is further configured with the following, which apply
+ * globally (across controllers invoked though the {@code AnnotationMethodHandlerAdapter}):
+ *
+ *
+ * {@link ConversionService} - a custom instance can be provided via
+ * {@link #conversionService(ConversionService)}. Otherwise it defaults to a fresh
+ * {@link ConversionService} instance created by the default
+ * {@link FormattingConversionServiceFactoryBean}.
+ * {@link Validator} - a custom instance can be provided via
+ * {@link #validator(Validator)}. Otherwise it defaults to a fresh {@code Validator}
+ * instance created by the default {@link LocalValidatorFactoryBean} assuming
+ * JSR-303 API is present on the classpath .
+ * {@code HttpMessageConverter} beans including the {@link
+ * Jaxb2RootElementHttpMessageConverter} assuming JAXB2 is present on the
+ * classpath , the {@link MappingJacksonHttpMessageConverter} assuming Jackson
+ * is present on the classpath , and the {@link AtomFeedHttpMessageConverter} and the
+ * {@link RssChannelHttpMessageConverter} converters assuming Rome is present on
+ * the classpath .
+ * Optionally, custom {@code WebArgumentResolver} beans to use for resolving
+ * custom arguments to handler methods. These are typically implemented to detect
+ * special parameter types, resolving well-known argument values for them.
+ *
+ *
+ * @author Rossen Stoyanchev
+ * @since 3.1
+ */
+public final class MvcAnnotationDriven extends AbstractFeatureSpecification {
+
+ private static final Class extends FeatureSpecificationExecutor> EXECUTOR_TYPE = MvcAnnotationDrivenExecutor.class;
+
+ private Object conversionService;
+
+ private Object validator;
+
+ private Object messageCodesResolver;
+
+ private boolean shouldRegisterDefaultMessageConverters = true;
+
+ private ManagedList super Object> messageConverters = new ManagedList();
+
+ private ManagedList super Object> argumentResolvers = new ManagedList();
+
+ /**
+ * Creates an MvcAnnotationDriven specification.
+ */
+ public MvcAnnotationDriven() {
+ super(EXECUTOR_TYPE);
+ }
+
+ /**
+ * The ConversionService bean instance to use for type conversion during
+ * field binding. This is not required input. It only needs to be provided
+ * explicitly if custom converters or formatters need to be configured.
+ *
+ *
If not provided, a default FormattingConversionService is registered
+ * that contains converters to/from standard JDK types. In addition, full
+ * support for date/time formatting will be installed if the Joda Time
+ * library is present on the classpath.
+ *
+ * @param conversionService the ConversionService instance to use
+ */
+ public MvcAnnotationDriven conversionService(ConversionService conversionService) {
+ this.conversionService = conversionService;
+ return this;
+ }
+
+ /**
+ *
The ConversionService to use for type conversion during field binding.
+ * This is an alternative to {@link #conversionService(ConversionService)}
+ * allowing you to provide a bean name rather than a bean instance.
+ *
+ * @param conversionService the ConversionService bean name
+ */
+ public MvcAnnotationDriven conversionService(String conversionService) {
+ this.conversionService = conversionService;
+ return this;
+ }
+
+ Object conversionService() {
+ return this.conversionService;
+ }
+
+ /**
+ * The HttpMessageConverter types to use for converting @RequestBody method
+ * parameters and @ResponseBody method return values. HttpMessageConverter
+ * registrations provided here will take precedence over HttpMessageConverter
+ * types registered by default.
+ * Also see {@link #shouldRegisterDefaultMessageConverters(boolean)} if
+ * default registrations are to be turned off altogether.
+ *
+ * @param converters the message converters
+ */
+ public MvcAnnotationDriven messageConverters(HttpMessageConverter>... converters) {
+ for (HttpMessageConverter> converter : converters) {
+ this.messageConverters.add(converter);
+ }
+ return this;
+ }
+
+ void messageConverters(ManagedList super Object> messageConverters) {
+ this.messageConverters = messageConverters;
+ }
+
+ ManagedList> messageConverters() {
+ return this.messageConverters;
+ }
+
+ /**
+ * Indicates whether or not default HttpMessageConverter registrations should
+ * be added in addition to the ones provided via
+ * {@link #messageConverters(HttpMessageConverter...).
+ *
+ * @param shouldRegister true will result in registration of defaults.
+ */
+ public MvcAnnotationDriven shouldRegisterDefaultMessageConverters(boolean shouldRegister) {
+ this.shouldRegisterDefaultMessageConverters = shouldRegister;
+ return this;
+ }
+
+ boolean shouldRegisterDefaultMessageConverters() {
+ return this.shouldRegisterDefaultMessageConverters;
+ }
+
+ public MvcAnnotationDriven argumentResolvers(WebArgumentResolver... resolvers) {
+ for (WebArgumentResolver resolver : resolvers) {
+ this.argumentResolvers.add(resolver);
+ }
+ return this;
+ }
+
+ void argumentResolvers(ManagedList super Object> argumentResolvers) {
+ this.argumentResolvers = argumentResolvers;
+ }
+
+ ManagedList> argumentResolvers() {
+ return this.argumentResolvers;
+ }
+
+ /**
+ * The Validator bean instance to use to validate Controller model objects.
+ * This is not required input. It only needs to be specified explicitly if
+ * a custom Validator needs to be configured.
+ *
+ *
If not specified, JSR-303 validation will be installed if a JSR-303
+ * provider is present on the classpath.
+ *
+ * @param validator the Validator bean instance
+ */
+ public MvcAnnotationDriven validator(Validator validator) {
+ this.validator = validator;
+ return this;
+ }
+
+ /**
+ * The Validator bean instance to use to validate Controller model objects.
+ * This is an alternative to {@link #validator(Validator)} allowing you to
+ * provide a bean name rather than a bean instance.
+ *
+ * @param validator the Validator bean name
+ */
+ public MvcAnnotationDriven validator(String validator) {
+ this.validator = validator;
+ return this;
+ }
+
+ Object validator() {
+ return this.validator;
+ }
+
+ /**
+ * The MessageCodesResolver to use to build message codes from data binding
+ * and validation error codes. This is not required input. If not specified
+ * the DefaultMessageCodesResolver is used.
+ *
+ * @param messageCodesResolver the MessageCodesResolver bean instance
+ */
+ public MvcAnnotationDriven messageCodesResolver(MessageCodesResolver messageCodesResolver) {
+ this.messageCodesResolver = messageCodesResolver;
+ return this;
+ }
+
+ /**
+ * The MessageCodesResolver to use to build message codes from data binding
+ * and validation error codes. This is an alternative to
+ * {@link #messageCodesResolver(MessageCodesResolver)} allowing you to provide
+ * a bean name rather than a bean instance.
+ *
+ * @param messageCodesResolver the MessageCodesResolver bean name
+ */
+ public MvcAnnotationDriven messageCodesResolver(String messageCodesResolver) {
+ this.messageCodesResolver = messageCodesResolver;
+ return this;
+ }
+
+ Object messageCodesResolver() {
+ return this.messageCodesResolver;
+ }
+
+ @Override
+ protected void doValidate(SimpleProblemCollector reporter) {
+ }
+
+}
diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/config/MvcAnnotationDrivenExecutor.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/config/MvcAnnotationDrivenExecutor.java
new file mode 100644
index 00000000000..5e6515a6792
--- /dev/null
+++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/config/MvcAnnotationDrivenExecutor.java
@@ -0,0 +1,235 @@
+/*
+ * Copyright 2002-2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springframework.web.servlet.config;
+
+import org.springframework.beans.factory.config.BeanDefinition;
+import org.springframework.beans.factory.config.RuntimeBeanReference;
+import org.springframework.beans.factory.parsing.BeanComponentDefinition;
+import org.springframework.beans.factory.parsing.ComponentRegistrar;
+import org.springframework.beans.factory.parsing.CompositeComponentDefinition;
+import org.springframework.beans.factory.support.ManagedList;
+import org.springframework.beans.factory.support.RootBeanDefinition;
+import org.springframework.context.config.AbstractSpecificationExecutor;
+import org.springframework.context.config.ExecutorContext;
+import org.springframework.format.support.FormattingConversionServiceFactoryBean;
+import org.springframework.http.converter.ByteArrayHttpMessageConverter;
+import org.springframework.http.converter.HttpMessageConverter;
+import org.springframework.http.converter.ResourceHttpMessageConverter;
+import org.springframework.http.converter.StringHttpMessageConverter;
+import org.springframework.http.converter.feed.AtomFeedHttpMessageConverter;
+import org.springframework.http.converter.feed.RssChannelHttpMessageConverter;
+import org.springframework.http.converter.json.MappingJacksonHttpMessageConverter;
+import org.springframework.http.converter.xml.Jaxb2RootElementHttpMessageConverter;
+import org.springframework.http.converter.xml.SourceHttpMessageConverter;
+import org.springframework.http.converter.xml.XmlAwareFormHttpMessageConverter;
+import org.springframework.util.ClassUtils;
+import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;
+import org.springframework.web.bind.support.ConfigurableWebBindingInitializer;
+import org.springframework.web.servlet.handler.ConversionServiceExposingInterceptor;
+import org.springframework.web.servlet.handler.MappedInterceptor;
+import org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter;
+import org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerExceptionResolver;
+import org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping;
+import org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver;
+import org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver;
+
+/**
+ * Executes {@link MvcAnnotationDriven} specifications, creating and registering
+ * bean definitions as appropriate based on the configuration within.
+ *
+ * @author Keith Donald
+ * @author Juergen Hoeller
+ * @author Arjen Poutsma
+ * @author Rossen Stoyanchev
+ * @since 3.1
+ * @see MvcAnnotationDriven
+ */
+final class MvcAnnotationDrivenExecutor extends AbstractSpecificationExecutor {
+
+ private static final boolean jsr303Present = ClassUtils.isPresent("javax.validation.Validator",
+ AnnotationDrivenBeanDefinitionParser.class.getClassLoader());
+
+ private static final boolean jaxb2Present = ClassUtils.isPresent("javax.xml.bind.Binder",
+ AnnotationDrivenBeanDefinitionParser.class.getClassLoader());
+
+ private static final boolean jacksonPresent = ClassUtils.isPresent("org.codehaus.jackson.map.ObjectMapper",
+ AnnotationDrivenBeanDefinitionParser.class.getClassLoader())
+ && ClassUtils.isPresent("org.codehaus.jackson.JsonGenerator",
+ AnnotationDrivenBeanDefinitionParser.class.getClassLoader());
+
+ private static boolean romePresent = ClassUtils.isPresent("com.sun.syndication.feed.WireFeed",
+ AnnotationDrivenBeanDefinitionParser.class.getClassLoader());
+
+ @Override
+ public void doExecute(MvcAnnotationDriven spec, ExecutorContext executorContext) {
+ ComponentRegistrar registrar = executorContext.getRegistrar();
+ Object source = spec.source();
+
+ RootBeanDefinition annMappingDef = new RootBeanDefinition(DefaultAnnotationHandlerMapping.class);
+ annMappingDef.setSource(source);
+ annMappingDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
+ annMappingDef.getPropertyValues().add("order", 0);
+ String annMappingName = registrar.registerWithGeneratedName(annMappingDef);
+
+ Object conversionService = getConversionService(spec, registrar);
+ Object validator = getValidator(spec, registrar);
+ Object messageCodesResolver = getMessageCodesResolver(spec, registrar);
+
+ RootBeanDefinition bindingDef = new RootBeanDefinition(ConfigurableWebBindingInitializer.class);
+ bindingDef.setSource(source);
+ bindingDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
+ bindingDef.getPropertyValues().add("conversionService", conversionService);
+ bindingDef.getPropertyValues().add("validator", validator);
+ bindingDef.getPropertyValues().add("messageCodesResolver", messageCodesResolver);
+
+ ManagedList super Object> messageConverters = getMessageConverters(spec, registrar);
+
+ RootBeanDefinition annAdapterDef = new RootBeanDefinition(AnnotationMethodHandlerAdapter.class);
+ annAdapterDef.setSource(source);
+ annAdapterDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
+ annAdapterDef.getPropertyValues().add("webBindingInitializer", bindingDef);
+ annAdapterDef.getPropertyValues().add("messageConverters", messageConverters);
+ if (!spec.argumentResolvers().isEmpty()) {
+ annAdapterDef.getPropertyValues().add("customArgumentResolvers", spec.argumentResolvers());
+ }
+ String annAdapterName = registrar.registerWithGeneratedName(annAdapterDef);
+
+ RootBeanDefinition csInterceptorDef = new RootBeanDefinition(ConversionServiceExposingInterceptor.class);
+ csInterceptorDef.setSource(source);
+ csInterceptorDef.getConstructorArgumentValues().addIndexedArgumentValue(0, conversionService);
+ RootBeanDefinition mappedCsInterceptorDef = new RootBeanDefinition(MappedInterceptor.class);
+ mappedCsInterceptorDef.setSource(source);
+ mappedCsInterceptorDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
+ mappedCsInterceptorDef.getConstructorArgumentValues().addIndexedArgumentValue(0, (Object) null);
+ mappedCsInterceptorDef.getConstructorArgumentValues().addIndexedArgumentValue(1, csInterceptorDef);
+ String mappedInterceptorName = registrar.registerWithGeneratedName(mappedCsInterceptorDef);
+
+ RootBeanDefinition annExceptionResolver = new RootBeanDefinition(AnnotationMethodHandlerExceptionResolver.class);
+ annExceptionResolver.setSource(source);
+ annExceptionResolver.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
+ annExceptionResolver.getPropertyValues().add("messageConverters", messageConverters);
+ annExceptionResolver.getPropertyValues().add("order", 0);
+ String annExceptionResolverName = registrar.registerWithGeneratedName(annExceptionResolver);
+
+ RootBeanDefinition responseStatusExceptionResolver = new RootBeanDefinition(
+ ResponseStatusExceptionResolver.class);
+ responseStatusExceptionResolver.setSource(source);
+ responseStatusExceptionResolver.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
+ responseStatusExceptionResolver.getPropertyValues().add("order", 1);
+ String responseStatusExceptionResolverName = registrar
+ .registerWithGeneratedName(responseStatusExceptionResolver);
+
+ RootBeanDefinition defaultExceptionResolver = new RootBeanDefinition(DefaultHandlerExceptionResolver.class);
+ defaultExceptionResolver.setSource(source);
+ defaultExceptionResolver.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
+ defaultExceptionResolver.getPropertyValues().add("order", 2);
+ String defaultExceptionResolverName = registrar.registerWithGeneratedName(defaultExceptionResolver);
+
+ CompositeComponentDefinition compDefinition = new CompositeComponentDefinition(spec.sourceName(), source);
+ compDefinition.addNestedComponent(new BeanComponentDefinition(annMappingDef, annMappingName));
+ compDefinition.addNestedComponent(new BeanComponentDefinition(annAdapterDef, annAdapterName));
+ compDefinition.addNestedComponent(new BeanComponentDefinition(annExceptionResolver, annExceptionResolverName));
+ compDefinition.addNestedComponent(new BeanComponentDefinition(responseStatusExceptionResolver,
+ responseStatusExceptionResolverName));
+ compDefinition.addNestedComponent(new BeanComponentDefinition(defaultExceptionResolver,
+ defaultExceptionResolverName));
+ compDefinition.addNestedComponent(new BeanComponentDefinition(mappedCsInterceptorDef, mappedInterceptorName));
+ registrar.registerComponent(compDefinition);
+ }
+
+ private Object getConversionService(MvcAnnotationDriven spec, ComponentRegistrar registrar) {
+ if (spec.conversionService() != null) {
+ return getBeanOrReference(spec.conversionService());
+ } else {
+ RootBeanDefinition conversionDef = new RootBeanDefinition(FormattingConversionServiceFactoryBean.class);
+ conversionDef.setSource(spec.source());
+ conversionDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
+ String conversionName = registrar.registerWithGeneratedName(conversionDef);
+ registrar.registerComponent(new BeanComponentDefinition(conversionDef, conversionName));
+ return new RuntimeBeanReference(conversionName);
+ }
+ }
+
+ private Object getValidator(MvcAnnotationDriven spec, ComponentRegistrar registrar) {
+ if (spec.validator() != null) {
+ return getBeanOrReference(spec.validator());
+ } else if (jsr303Present) {
+ RootBeanDefinition validatorDef = new RootBeanDefinition(LocalValidatorFactoryBean.class);
+ validatorDef.setSource(spec.source());
+ validatorDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
+ String validatorName = registrar.registerWithGeneratedName(validatorDef);
+ registrar.registerComponent(new BeanComponentDefinition(validatorDef, validatorName));
+ return new RuntimeBeanReference(validatorName);
+ } else {
+ return null;
+ }
+ }
+
+ private Object getMessageCodesResolver(MvcAnnotationDriven spec, ComponentRegistrar registrar) {
+ if (spec.messageCodesResolver() != null) {
+ return getBeanOrReference(spec.messageCodesResolver());
+ } else {
+ return null;
+ }
+ }
+
+ private ManagedList super Object> getMessageConverters(MvcAnnotationDriven spec, ComponentRegistrar registrar) {
+ ManagedList super Object> messageConverters = new ManagedList();
+ Object source = spec.source();
+ messageConverters.setSource(source);
+ messageConverters.addAll(spec.messageConverters());
+ if (spec.shouldRegisterDefaultMessageConverters()) {
+ messageConverters.add(createConverterBeanDefinition(ByteArrayHttpMessageConverter.class, source));
+ RootBeanDefinition stringConverterDef = createConverterBeanDefinition(StringHttpMessageConverter.class,
+ source);
+ stringConverterDef.getPropertyValues().add("writeAcceptCharset", false);
+ messageConverters.add(stringConverterDef);
+ messageConverters.add(createConverterBeanDefinition(ResourceHttpMessageConverter.class, source));
+ messageConverters.add(createConverterBeanDefinition(SourceHttpMessageConverter.class, source));
+ messageConverters.add(createConverterBeanDefinition(XmlAwareFormHttpMessageConverter.class, source));
+ if (jaxb2Present) {
+ messageConverters
+ .add(createConverterBeanDefinition(Jaxb2RootElementHttpMessageConverter.class, source));
+ }
+ if (jacksonPresent) {
+ messageConverters.add(createConverterBeanDefinition(MappingJacksonHttpMessageConverter.class, source));
+ }
+ if (romePresent) {
+ messageConverters.add(createConverterBeanDefinition(AtomFeedHttpMessageConverter.class, source));
+ messageConverters.add(createConverterBeanDefinition(RssChannelHttpMessageConverter.class, source));
+ }
+ }
+ return messageConverters;
+ }
+
+ @SuppressWarnings("rawtypes")
+ private RootBeanDefinition createConverterBeanDefinition(Class extends HttpMessageConverter> converterClass,
+ Object source) {
+ RootBeanDefinition beanDefinition = new RootBeanDefinition(converterClass);
+ beanDefinition.setSource(source);
+ beanDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
+ return beanDefinition;
+ }
+
+ private Object getBeanOrReference(Object bean) {
+ if (bean != null && bean instanceof String) {
+ return new RuntimeBeanReference((String) bean);
+ } else {
+ return bean;
+ }
+ }
+
+}
diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/config/MvcDefaultServletHandler.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/config/MvcDefaultServletHandler.java
new file mode 100644
index 00000000000..50f96fd5148
--- /dev/null
+++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/config/MvcDefaultServletHandler.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright 2002-2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springframework.web.servlet.config;
+
+import org.springframework.beans.factory.parsing.SimpleProblemCollector;
+import org.springframework.context.config.AbstractFeatureSpecification;
+import org.springframework.context.config.FeatureSpecificationExecutor;
+import org.springframework.web.servlet.handler.SimpleUrlHandlerMapping;
+import org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter;
+import org.springframework.web.servlet.resource.DefaultServletHttpRequestHandler;
+
+/**
+ * Specifies the Spring MVC "default-servlet-handler" container feature. The
+ * feature provides the following fine-grained configuration:
+ *
+ *
+ * {@link DefaultServletHttpRequestHandler} for serving static files by
+ * forwarding to the Servlet container's "default" Servlet.
+ * {@link SimpleUrlHandlerMapping} to map the above request handler to "/**"
+ * {@link HttpRequestHandlerAdapter} to enable the DispatcherServlet to be
+ * able to invoke the above request handler.
+ *
+ *
+ * @author Rossen Stoyanchev
+ * @since 3.1
+ */
+public class MvcDefaultServletHandler extends AbstractFeatureSpecification {
+
+ private static final Class extends FeatureSpecificationExecutor> EXECUTOR_TYPE = MvcDefaultServletHandlerExecutor.class;
+
+ private String defaultServletName;
+
+ /**
+ * Creates an instance of MvcDefaultServletHandler without.
+ * If this constructor is used the {@link DefaultServletHttpRequestHandler}
+ * will try to auto-detect the container's default Servlet at startup time
+ * using a list of known names.
+ *
+ *
If the default Servlet cannot be detected because of using an
+ * unknown container or because it has been manually configured, an
+ * alternate constructor provided here can be used to specify the
+ * servlet name explicitly.
+ */
+ public MvcDefaultServletHandler() {
+ super(EXECUTOR_TYPE);
+ }
+
+ /**
+ * The name of the default Servlet to forward to for static resource requests.
+ * The {@link DefaultServletHttpRequestHandler} will try to auto-detect the
+ * container's default Servlet at startup time using a list of known names.
+ * However if the default Servlet cannot be detected because of using an unknown
+ * container or because it has been manually configured, you can use this
+ * constructor to set the servlet name explicitly.
+ *
+ * @param defaultServletName the name of the default servlet
+ */
+ public MvcDefaultServletHandler(String defaultServletName) {
+ this();
+ this.defaultServletName = defaultServletName;
+ }
+
+ String defaultServletName() {
+ return this.defaultServletName;
+ }
+
+ @Override
+ protected void doValidate(SimpleProblemCollector reporter) {
+ }
+
+}
diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/config/MvcDefaultServletHandlerExecutor.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/config/MvcDefaultServletHandlerExecutor.java
new file mode 100644
index 00000000000..475e3a83ef8
--- /dev/null
+++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/config/MvcDefaultServletHandlerExecutor.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright 2002-2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.web.servlet.config;
+
+import java.util.Map;
+
+import org.springframework.beans.factory.config.BeanDefinition;
+import org.springframework.beans.factory.parsing.BeanComponentDefinition;
+import org.springframework.beans.factory.parsing.ComponentRegistrar;
+import org.springframework.beans.factory.support.BeanDefinitionRegistry;
+import org.springframework.beans.factory.support.ManagedMap;
+import org.springframework.beans.factory.support.RootBeanDefinition;
+import org.springframework.context.config.AbstractSpecificationExecutor;
+import org.springframework.context.config.ExecutorContext;
+import org.springframework.util.StringUtils;
+import org.springframework.web.servlet.handler.SimpleUrlHandlerMapping;
+import org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter;
+import org.springframework.web.servlet.resource.DefaultServletHttpRequestHandler;
+
+/**
+ * Executes {@link MvcDefaultServletHandler} specifications, creating and
+ * registering bean definitions as appropriate based on the configuration
+ * within.
+ *
+ * @author Jeremy Grelle
+ * @author Rossen Stoyanchev
+ * @since 3.1
+ */
+final class MvcDefaultServletHandlerExecutor extends AbstractSpecificationExecutor {
+
+ private static final String HANDLER_ADAPTER_BEAN_NAME = "org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter";
+
+ @Override
+ protected void doExecute(MvcDefaultServletHandler spec, ExecutorContext executorContext) {
+ BeanDefinitionRegistry registry = executorContext.getRegistry();
+ ComponentRegistrar registrar = executorContext.getRegistrar();
+ Object source = spec.source();
+
+ if (!registry.containsBeanDefinition(HANDLER_ADAPTER_BEAN_NAME)) {
+ RootBeanDefinition handlerAdapterDef = new RootBeanDefinition(HttpRequestHandlerAdapter.class);
+ handlerAdapterDef.setSource(source);
+ handlerAdapterDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
+ registry.registerBeanDefinition(HANDLER_ADAPTER_BEAN_NAME, handlerAdapterDef);
+ registrar.registerComponent(new BeanComponentDefinition(handlerAdapterDef, HANDLER_ADAPTER_BEAN_NAME));
+ }
+
+ RootBeanDefinition defaultServletHandlerDef = new RootBeanDefinition(DefaultServletHttpRequestHandler.class);
+ defaultServletHandlerDef.setSource(source);
+ defaultServletHandlerDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
+ if (StringUtils.hasText(spec.defaultServletName())) {
+ defaultServletHandlerDef.getPropertyValues().add("defaultServletName", spec.defaultServletName());
+ }
+ String defaultServletHandlerName = registrar.registerWithGeneratedName(defaultServletHandlerDef);
+ registry.registerBeanDefinition(defaultServletHandlerName, defaultServletHandlerDef);
+ registrar.registerComponent(new BeanComponentDefinition(defaultServletHandlerDef, defaultServletHandlerName));
+
+ Map urlMap = new ManagedMap();
+ urlMap.put("/**", defaultServletHandlerName);
+ RootBeanDefinition handlerMappingDef = new RootBeanDefinition(SimpleUrlHandlerMapping.class);
+ handlerMappingDef.setSource(source);
+ handlerMappingDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
+ handlerMappingDef.getPropertyValues().add("urlMap", urlMap);
+ String handlerMappingBeanName = registrar.registerWithGeneratedName(handlerMappingDef);
+ registry.registerBeanDefinition(handlerMappingBeanName, handlerMappingDef);
+ registrar.registerComponent(new BeanComponentDefinition(handlerMappingDef, handlerMappingBeanName));
+ }
+
+}
diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/config/MvcResources.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/config/MvcResources.java
new file mode 100644
index 00000000000..8a1ee991894
--- /dev/null
+++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/config/MvcResources.java
@@ -0,0 +1,179 @@
+/*
+ * Copyright 2002-2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.web.servlet.config;
+
+import org.springframework.beans.factory.parsing.SimpleProblemCollector;
+import org.springframework.context.config.AbstractFeatureSpecification;
+import org.springframework.context.config.FeatureSpecificationExecutor;
+import org.springframework.core.Ordered;
+import org.springframework.core.io.Resource;
+import org.springframework.util.StringUtils;
+import org.springframework.web.servlet.handler.SimpleUrlHandlerMapping;
+import org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter;
+import org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping;
+import org.springframework.web.servlet.resource.ResourceHttpRequestHandler;
+
+/**
+ * Specifies the Spring MVC "resources" container feature. The
+ * feature provides the following fine-grained configuration:
+ *
+ *
+ * {@link ResourceHttpRequestHandler} to serve static resources from a
+ * list of web-root relative, classpath, or other locations.
+ * {@link SimpleUrlHandlerMapping} to map the above request handler to a
+ * a specific path pattern (e.g. "/resources/**").
+ * {@link HttpRequestHandlerAdapter} to enable the DispatcherServlet to be
+ * able to invoke the above request handler.
+ *
+ *
+ * @author Rossen Stoynchev
+ * @since 3.1
+ */
+public final class MvcResources extends AbstractFeatureSpecification {
+
+ private static final Class extends FeatureSpecificationExecutor> EXECUTOR_TYPE = MvcResourcesExecutor.class;
+
+ private Object[] locations;
+
+ private String mapping;
+
+ private Object cachePeriod;
+
+ private Object order = Ordered.LOWEST_PRECEDENCE - 1;
+
+ /**
+ * Create an MvcResources specification instance. See alternate constructor
+ * you prefer to use {@link Resource} instances instead of {@code String}-based
+ * resource locations.
+ *
+ * @param mapping - the URL path pattern within the current Servlet context to
+ * use to identify resource requests (e.g. "/resources/**").
+ * @param locations - locations of resources containing static content to be
+ * served. Each location must point to a valid directory. Locations will be
+ * checked in the order specified. For example if "/" and
+ * "classpath:/META-INF/public-web-resources/" are configured resources will
+ * be served from the Web root and from any JAR on the classpath that contains
+ * a /META-INF/public-web-resources/ directory, with resources under the Web root
+ * taking precedence.
+ */
+ public MvcResources(String mapping, String... locations) {
+ super(EXECUTOR_TYPE);
+ this.locations = locations;
+ this.mapping = mapping;
+ }
+
+ /**
+ * Create an MvcResources specification instance. See alternate constructor
+ * defined here if you prefer to use String-based path patterns.
+ *
+ * @param mapping - the URL path pattern within the current Servlet context to
+ * use to identify resource requests (e.g. "/resources/**").
+ * @param resources - Spring {@link Resource} objects containing static
+ * content to be served. Resources will be checked in the order specified.
+ */
+ public MvcResources(String mapping, Resource... resources) {
+ super(EXECUTOR_TYPE);
+ this.locations = resources;
+ this.mapping = mapping;
+ }
+
+ /**
+ * The period of time resources should be cached for in seconds.
+ * The default is to not send any cache headers but rather to rely on
+ * last-modified timestamps only.
+ * Set this to 0 in order to send cache headers that prevent caching,
+ * or to a positive number of seconds in order to send cache headers
+ * with the given max-age value.
+ *
+ * @param cachePeriod the cache period in seconds
+ */
+ public MvcResources cachePeriod(Integer cachePeriod) {
+ this.cachePeriod = cachePeriod;
+ return this;
+ }
+
+ /**
+ * Specify a cachePeriod as a String. An alternative to {@link #cachePeriod(Integer)}.
+ * The String must represent an Integer after placeholder and SpEL expression
+ * resolution.
+ *
+ * @param cachePeriod the cache period in seconds
+ */
+ public MvcResources cachePeriod(String cachePeriod) {
+ this.cachePeriod = cachePeriod;
+ return this;
+ }
+
+ /**
+ * Specify a cachePeriod as a String. An alternative to {@link #cachePeriod(Integer)}.
+ * The String must represent an Integer after placeholder and SpEL expression
+ * resolution.
+ *
Sets the order for the SimpleUrlHandlerMapping used to match resource
+ * requests relative to order value for other HandlerMapping instances
+ * such as the {@link DefaultAnnotationHandlerMapping} used to match
+ * controller requests.
+ *
+ * @param order the order to use. The default value is
+ * {@link Ordered#LOWEST_PRECEDENCE} - 1.
+ */
+ public MvcResources order(Integer order) {
+ this.order = order;
+ return this;
+ }
+
+ /**
+ * Specify an order as a String. An alternative to {@link #order(Integer)}.
+ * The String must represent an Integer after placeholder and SpEL expression
+ * resolution.
+ *
+ * @param order the order to use. The default value is
+ * {@link Ordered#LOWEST_PRECEDENCE} - 1.
+ */
+ public MvcResources order(String order) {
+ this.order = order;
+ return this;
+ }
+
+ // Package private accessors
+
+ Object cachePeriod() {
+ return cachePeriod;
+ }
+
+ Object[] locations() {
+ return this.locations;
+ }
+
+ String mapping() {
+ return mapping;
+ }
+
+ Object order() {
+ return order;
+ }
+
+ @Override
+ protected void doValidate(SimpleProblemCollector problems) {
+ if (!StringUtils.hasText(mapping)) {
+ problems.error("Mapping is required");
+ }
+ if (locations == null || locations.length == 0) {
+ problems.error("At least one location is required");
+ }
+ }
+
+}
diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/config/MvcResourcesExecutor.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/config/MvcResourcesExecutor.java
new file mode 100644
index 00000000000..34add4cbdfb
--- /dev/null
+++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/config/MvcResourcesExecutor.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright 2002-2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.web.servlet.config;
+
+import java.util.Map;
+
+import org.springframework.beans.factory.config.BeanDefinition;
+import org.springframework.beans.factory.parsing.BeanComponentDefinition;
+import org.springframework.beans.factory.parsing.ComponentRegistrar;
+import org.springframework.beans.factory.support.BeanDefinitionRegistry;
+import org.springframework.beans.factory.support.ManagedMap;
+import org.springframework.beans.factory.support.RootBeanDefinition;
+import org.springframework.context.config.AbstractSpecificationExecutor;
+import org.springframework.context.config.ExecutorContext;
+import org.springframework.web.servlet.handler.SimpleUrlHandlerMapping;
+import org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter;
+import org.springframework.web.servlet.resource.ResourceHttpRequestHandler;
+
+/**
+ * Executes {@link MvcResources} specifications, creating and registering
+ * bean definitions as appropriate based on the configuration within.
+ *
+ * @author Keith Donald
+ * @author Jeremy Grelle
+ * @author Rossen Stoyanchev
+ * @since 3.1
+ */
+final class MvcResourcesExecutor extends AbstractSpecificationExecutor {
+
+ private static final String HANDLER_ADAPTER_BEAN_NAME = "org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter";
+
+ @Override
+ protected void doExecute(MvcResources spec, ExecutorContext executorContext) {
+ BeanDefinitionRegistry registry = executorContext.getRegistry();
+ ComponentRegistrar registrar = executorContext.getRegistrar();
+ Object source = spec.source();
+
+ if (!registry.containsBeanDefinition(HANDLER_ADAPTER_BEAN_NAME)) {
+ RootBeanDefinition handlerAdapterDef = new RootBeanDefinition(HttpRequestHandlerAdapter.class);
+ handlerAdapterDef.setSource(source);
+ handlerAdapterDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
+ registry.registerBeanDefinition(HANDLER_ADAPTER_BEAN_NAME, handlerAdapterDef);
+ registrar.registerComponent(new BeanComponentDefinition(handlerAdapterDef, HANDLER_ADAPTER_BEAN_NAME));
+ }
+
+ RootBeanDefinition resourceHandlerDef = new RootBeanDefinition(ResourceHttpRequestHandler.class);
+ resourceHandlerDef.setSource(source);
+ resourceHandlerDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
+ resourceHandlerDef.getPropertyValues().add("locations", spec.locations());
+ if (spec.cachePeriod() != null) {
+ resourceHandlerDef.getPropertyValues().add("cacheSeconds", spec.cachePeriod());
+ }
+ String resourceHandlerBeanName = registrar.registerWithGeneratedName(resourceHandlerDef);
+ registry.registerBeanDefinition(resourceHandlerBeanName, resourceHandlerDef);
+ registrar.registerComponent(new BeanComponentDefinition(resourceHandlerDef, resourceHandlerBeanName));
+
+ Map urlMap = new ManagedMap();
+ urlMap.put(spec.mapping(), resourceHandlerBeanName);
+ RootBeanDefinition handlerMappingDef = new RootBeanDefinition(SimpleUrlHandlerMapping.class);
+ handlerMappingDef.setSource(source);
+ handlerMappingDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
+ handlerMappingDef.getPropertyValues().add("urlMap", urlMap);
+ if (spec.order() != null) {
+ handlerMappingDef.getPropertyValues().add("order", spec.order());
+ }
+ String beanName = registrar.registerWithGeneratedName(handlerMappingDef);
+ registry.registerBeanDefinition(beanName, handlerMappingDef);
+ registrar.registerComponent(new BeanComponentDefinition(handlerMappingDef, beanName));
+ }
+
+}
diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/config/MvcViewControllers.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/config/MvcViewControllers.java
new file mode 100644
index 00000000000..5ef0b9ff049
--- /dev/null
+++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/config/MvcViewControllers.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright 2002-2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springframework.web.servlet.config;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.springframework.beans.factory.parsing.SimpleProblemCollector;
+import org.springframework.context.config.AbstractFeatureSpecification;
+import org.springframework.context.config.FeatureSpecificationExecutor;
+import org.springframework.util.StringUtils;
+import org.springframework.web.servlet.handler.SimpleUrlHandlerMapping;
+import org.springframework.web.servlet.mvc.ParameterizableViewController;
+import org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter;
+
+/**
+ * Specifies the Spring MVC "View Controllers" container feature. The
+ * feature allows specifying one or more path to view name mappings.
+ * It sets up the following fine-grained configuration:
+ *
+ *
+ * {@link ParameterizableViewController} for each path/view name pair.
+ * {@link SimpleUrlHandlerMapping} mapping each view controller to its path.
+ * {@link SimpleControllerHandlerAdapter} to enable the DispatcherServlet
+ * to invoke the view controllers.
+ *
+ *
+ * @author Rossen Stoyanchev
+ * @author Chris Beams
+ * @since 3.1
+ */
+public final class MvcViewControllers extends AbstractFeatureSpecification {
+
+ private static final Class extends FeatureSpecificationExecutor> EXECUTOR_TYPE = MvcViewControllersExecutor.class;
+
+ private Map mappings = new HashMap();
+
+ public MvcViewControllers(String path) {
+ this(path, null);
+ }
+
+ public MvcViewControllers(String path, String viewName) {
+ super(EXECUTOR_TYPE);
+ this.mappings.put(path, viewName);
+ }
+
+ public MvcViewControllers viewController(String path) {
+ return this.viewController(path, null);
+ }
+
+ public MvcViewControllers viewController(String path, String viewName) {
+ this.mappings.put(path, viewName);
+ return this;
+ }
+
+ Map mappings() {
+ return Collections.unmodifiableMap(mappings);
+ }
+
+ @Override
+ protected void doValidate(SimpleProblemCollector problems) {
+ if (mappings.size() == 0) {
+ problems.error("At least one ViewController must be defined");
+ }
+ for (String path : mappings.keySet()) {
+ if (!StringUtils.hasText(path)) {
+ problems.error("The path attribute in a ViewController is required");
+ }
+ String viewName = mappings.get(path);
+ if (viewName != null && viewName.isEmpty()) {
+ problems.error("The view name in a ViewController may be null but not empty.");
+ }
+ }
+ }
+
+}
diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/config/MvcViewControllersExecutor.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/config/MvcViewControllersExecutor.java
new file mode 100644
index 00000000000..46304241ffb
--- /dev/null
+++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/config/MvcViewControllersExecutor.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright 2002-2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */package org.springframework.web.servlet.config;
+
+import java.util.Map;
+
+import org.springframework.beans.factory.config.BeanDefinition;
+import org.springframework.beans.factory.parsing.BeanComponentDefinition;
+import org.springframework.beans.factory.parsing.ComponentRegistrar;
+import org.springframework.beans.factory.support.BeanDefinitionRegistry;
+import org.springframework.beans.factory.support.ManagedMap;
+import org.springframework.beans.factory.support.RootBeanDefinition;
+import org.springframework.context.config.AbstractSpecificationExecutor;
+import org.springframework.context.config.ExecutorContext;
+import org.springframework.web.servlet.handler.SimpleUrlHandlerMapping;
+import org.springframework.web.servlet.mvc.ParameterizableViewController;
+import org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter;
+
+/**
+ * Executes {@link MvcViewControllers} specification, creating and registering
+ * bean definitions as appropriate based on the configuration within.
+ *
+ * @author Keith Donald
+ * @author Christian Dupuis
+ * @author Rossen Stoyanchev
+ * @since 3.1
+ */
+final class MvcViewControllersExecutor extends AbstractSpecificationExecutor {
+
+ private static final String HANDLER_ADAPTER_BEAN_NAME = "org.springframework.web.servlet.config.viewControllerHandlerAdapter";
+
+ private static final String HANDLER_MAPPING_BEAN_NAME = "org.springframework.web.servlet.config.viewControllerHandlerMapping";
+
+ @Override
+ protected void doExecute(MvcViewControllers spec, ExecutorContext executorContext) {
+ BeanDefinitionRegistry registry = executorContext.getRegistry();
+ ComponentRegistrar registrar = executorContext.getRegistrar();
+ Object source = spec.source();
+
+ if (!registry.containsBeanDefinition(HANDLER_ADAPTER_BEAN_NAME)) {
+ RootBeanDefinition handlerAdapterDef = new RootBeanDefinition(SimpleControllerHandlerAdapter.class);
+ handlerAdapterDef.setSource(source);
+ handlerAdapterDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
+ registry.registerBeanDefinition(HANDLER_ADAPTER_BEAN_NAME, handlerAdapterDef);
+ registrar.registerComponent(new BeanComponentDefinition(handlerAdapterDef, HANDLER_ADAPTER_BEAN_NAME));
+ }
+
+ BeanDefinition handlerMappingBeanDef = null;
+ if (!registry.containsBeanDefinition(HANDLER_MAPPING_BEAN_NAME)) {
+ RootBeanDefinition beanDef = new RootBeanDefinition(SimpleUrlHandlerMapping.class);
+ beanDef.setSource(source);
+ beanDef.getPropertyValues().add("order", "1");
+ beanDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
+ registry.registerBeanDefinition(HANDLER_MAPPING_BEAN_NAME, beanDef);
+ registrar.registerComponent(new BeanComponentDefinition(beanDef, HANDLER_MAPPING_BEAN_NAME));
+ handlerMappingBeanDef = beanDef;
+ } else {
+ handlerMappingBeanDef = registry.getBeanDefinition(HANDLER_MAPPING_BEAN_NAME);
+ }
+
+ for (Map.Entry entry : spec.mappings().entrySet()) {
+ RootBeanDefinition viewControllerDef = new RootBeanDefinition(ParameterizableViewController.class);
+ viewControllerDef.setSource(source);
+ if (entry.getValue() != null) {
+ viewControllerDef.getPropertyValues().add("viewName", entry.getValue());
+ }
+ if (!handlerMappingBeanDef.getPropertyValues().contains("urlMap")) {
+ handlerMappingBeanDef.getPropertyValues().add("urlMap", new ManagedMap());
+ }
+ @SuppressWarnings("unchecked")
+ Map urlMap = (Map) handlerMappingBeanDef
+ .getPropertyValues().getPropertyValue("urlMap").getValue();
+ urlMap.put(entry.getKey(), viewControllerDef);
+ }
+ }
+
+}
diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/config/ResourcesBeanDefinitionParser.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/config/ResourcesBeanDefinitionParser.java
index 03654ab9e5b..a054634a063 100644
--- a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/config/ResourcesBeanDefinitionParser.java
+++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/config/ResourcesBeanDefinitionParser.java
@@ -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,89 +16,72 @@
package org.springframework.web.servlet.config;
-import java.util.Map;
-
-import org.w3c.dom.Element;
-
import org.springframework.beans.factory.config.BeanDefinition;
-import org.springframework.beans.factory.parsing.BeanComponentDefinition;
-import org.springframework.beans.factory.support.ManagedMap;
-import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.beans.factory.xml.BeanDefinitionParser;
import org.springframework.beans.factory.xml.ParserContext;
-import org.springframework.core.Ordered;
+import org.springframework.context.config.ExecutorContext;
import org.springframework.util.StringUtils;
-import org.springframework.web.servlet.handler.SimpleUrlHandlerMapping;
-import org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter;
-import org.springframework.web.servlet.resource.ResourceHttpRequestHandler;
+import org.w3c.dom.Element;
/**
* {@link org.springframework.beans.factory.xml.BeanDefinitionParser} that parses a
- * {@code resources} element to register a {@link ResourceHttpRequestHandler}.
- * Will also register a {@link SimpleUrlHandlerMapping} for mapping resource requests,
- * and a {@link HttpRequestHandlerAdapter} if necessary.
+ * {@code resources} element.
*
- * @author Keith Donald
- * @author Jeremy Grelle
+ * @author Rossen Stoyanchev
* @since 3.0.4
+ * @see MvcResources
+ * @see MvcResourcesExecutor
*/
-class ResourcesBeanDefinitionParser extends AbstractHttpRequestHandlerBeanDefinitionParser implements BeanDefinitionParser {
+class ResourcesBeanDefinitionParser implements BeanDefinitionParser {
- @Override
- public void doParse(Element element, ParserContext parserContext) {
- Object source = parserContext.extractSource(element);
- registerResourceMappings(parserContext, element, source);
+ /**
+ * Parses the {@code } tag
+ */
+ public BeanDefinition parse(Element element, ParserContext parserContext) {
+ MvcResources spec = createSpecification(element, parserContext);
+ if (spec != null) {
+ spec.execute(createExecutorContext(parserContext));
+ }
+ return null;
}
-
- private void registerResourceMappings(ParserContext parserContext, Element element, Object source) {
- String resourceHandlerName = registerResourceHandler(parserContext, element, source);
- if (resourceHandlerName == null) {
- return;
+
+ private MvcResources createSpecification(Element element, ParserContext parserContext) {
+ String mapping = element.getAttribute("mapping");
+ if (!StringUtils.hasText(mapping)) {
+ parserContext.getReaderContext().error("The 'mapping' attribute is required.",
+ parserContext.extractSource(element));
+ return null;
}
-
- Map urlMap = new ManagedMap();
- String resourceRequestPath = element.getAttribute("mapping");
- if (!StringUtils.hasText(resourceRequestPath)) {
- parserContext.getReaderContext().error("The 'mapping' attribute is required.", parserContext.extractSource(element));
- return;
+ String[] locations = StringUtils.commaDelimitedListToStringArray(element.getAttribute("location"));
+ if (locations.length == 0) {
+ parserContext.getReaderContext().error("The 'location' attribute is required.",
+ parserContext.extractSource(element));
+ return null;
}
- urlMap.put(resourceRequestPath, resourceHandlerName);
-
- RootBeanDefinition handlerMappingDef = new RootBeanDefinition(SimpleUrlHandlerMapping.class);
- handlerMappingDef.setSource(source);
- handlerMappingDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
- handlerMappingDef.getPropertyValues().add("urlMap", urlMap);
-
- String order = element.getAttribute("order");
- // use a default of near-lowest precedence, still allowing for even lower precedence in other mappings
- handlerMappingDef.getPropertyValues().add("order", StringUtils.hasText(order) ? order : Ordered.LOWEST_PRECEDENCE - 1);
-
- String beanName = parserContext.getReaderContext().generateBeanName(handlerMappingDef);
- parserContext.getRegistry().registerBeanDefinition(beanName, handlerMappingDef);
- parserContext.registerComponent(new BeanComponentDefinition(handlerMappingDef, beanName));
+ MvcResources spec = new MvcResources(mapping, locations);
+ if (element.hasAttribute("cache-period")) {
+ spec.cachePeriod(element.getAttribute("cache-period"));
+ }
+ if (element.hasAttribute("order")) {
+ spec.order(element.getAttribute("order"));
+ }
+ spec.source(parserContext.extractSource(element));
+ spec.sourceName(element.getTagName());
+ return spec;
}
-
- private String registerResourceHandler(ParserContext parserContext, Element element, Object source) {
- String locationAttr = element.getAttribute("location");
- if (!StringUtils.hasText(locationAttr)) {
- parserContext.getReaderContext().error("The 'location' attribute is required.", parserContext.extractSource(element));
- return null;
- }
- RootBeanDefinition resourceHandlerDef = new RootBeanDefinition(ResourceHttpRequestHandler.class);
- resourceHandlerDef.setSource(source);
- resourceHandlerDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
- resourceHandlerDef.getPropertyValues().add("locations", StringUtils.commaDelimitedListToStringArray(locationAttr));
-
- String cacheSeconds = element.getAttribute("cache-period");
- if (StringUtils.hasText(cacheSeconds)) {
- resourceHandlerDef.getPropertyValues().add("cacheSeconds", cacheSeconds);
- }
-
- String beanName = parserContext.getReaderContext().generateBeanName(resourceHandlerDef);
- parserContext.getRegistry().registerBeanDefinition(beanName, resourceHandlerDef);
- parserContext.registerComponent(new BeanComponentDefinition(resourceHandlerDef, beanName));
- return beanName;
+ /**
+ * 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 ExecutorContext createExecutorContext(ParserContext parserContext) {
+ ExecutorContext executorContext = new ExecutorContext();
+ executorContext.setRegistry(parserContext.getRegistry());
+ executorContext.setRegistrar(parserContext);
+ executorContext.setProblemReporter(parserContext.getReaderContext().getProblemReporter());
+ return executorContext;
}
}
diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/config/ViewControllerBeanDefinitionParser.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/config/ViewControllerBeanDefinitionParser.java
index 36b985e99c3..e50fae09f4d 100644
--- a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/config/ViewControllerBeanDefinitionParser.java
+++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/config/ViewControllerBeanDefinitionParser.java
@@ -16,88 +16,49 @@
package org.springframework.web.servlet.config;
-import java.util.Map;
-
-import org.w3c.dom.Element;
-
import org.springframework.beans.factory.config.BeanDefinition;
-import org.springframework.beans.factory.parsing.BeanComponentDefinition;
-import org.springframework.beans.factory.support.ManagedMap;
-import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.beans.factory.xml.BeanDefinitionParser;
import org.springframework.beans.factory.xml.ParserContext;
-import org.springframework.web.servlet.handler.SimpleUrlHandlerMapping;
+import org.springframework.context.config.ExecutorContext;
import org.springframework.web.servlet.mvc.ParameterizableViewController;
-import org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter;
+import org.w3c.dom.Element;
/**
* {@link org.springframework.beans.factory.xml.BeanDefinitionParser} that parses a
* {@code view-controller} element to register a {@link ParameterizableViewController}.
- * Will also register a {@link SimpleUrlHandlerMapping} for view controllers.
*
- * @author Keith Donald
- * @author Christian Dupuis
+ * @author Rossen Stoyanchev
* @since 3.0
+ * @see MvcViewControllers
+ * @see MvcViewControllersExecutor
*/
class ViewControllerBeanDefinitionParser implements BeanDefinitionParser {
- private static final String HANDLER_ADAPTER_BEAN_NAME =
- "org.springframework.web.servlet.config.viewControllerHandlerAdapter";
-
- private static final String HANDLER_MAPPING_BEAN_NAME =
- "org.springframework.web.servlet.config.viewControllerHandlerMapping";
-
-
+ /**
+ * Parses the {@code } tag.
+ */
public BeanDefinition parse(Element element, ParserContext parserContext) {
- Object source = parserContext.extractSource(element);
-
- // Register handler adapter
- registerHanderAdapter(parserContext, source);
-
- // Register handler mapping
- BeanDefinition handlerMappingDef = registerHandlerMapping(parserContext, source);
-
- // Create view controller bean definition
- RootBeanDefinition viewControllerDef = new RootBeanDefinition(ParameterizableViewController.class);
- viewControllerDef.setSource(source);
- if (element.hasAttribute("view-name")) {
- viewControllerDef.getPropertyValues().add("viewName", element.getAttribute("view-name"));
- }
- Map urlMap;
- if (handlerMappingDef.getPropertyValues().contains("urlMap")) {
- urlMap = (Map) handlerMappingDef.getPropertyValues().getPropertyValue("urlMap").getValue();
- }
- else {
- urlMap = new ManagedMap();
- handlerMappingDef.getPropertyValues().add("urlMap", urlMap);
- }
- urlMap.put(element.getAttribute("path"), viewControllerDef);
+ String path = element.getAttribute("path");
+ String viewName = element.getAttribute("view-name");
+ new MvcViewControllers(path, viewName.isEmpty() ? null : viewName)
+ .source(parserContext.extractSource(element))
+ .sourceName(element.getTagName())
+ .execute(createExecutorContext(parserContext));
return null;
}
-
- private void registerHanderAdapter(ParserContext parserContext, Object source) {
- if (!parserContext.getRegistry().containsBeanDefinition(HANDLER_ADAPTER_BEAN_NAME)) {
- RootBeanDefinition handlerAdapterDef = new RootBeanDefinition(SimpleControllerHandlerAdapter.class);
- handlerAdapterDef.setSource(source);
- handlerAdapterDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
- parserContext.getRegistry().registerBeanDefinition(HANDLER_ADAPTER_BEAN_NAME, handlerAdapterDef);
- parserContext.registerComponent(new BeanComponentDefinition(handlerAdapterDef, HANDLER_ADAPTER_BEAN_NAME));
- }
- }
-
- private BeanDefinition registerHandlerMapping(ParserContext parserContext, Object source) {
- if (!parserContext.getRegistry().containsBeanDefinition(HANDLER_MAPPING_BEAN_NAME)) {
- RootBeanDefinition handlerMappingDef = new RootBeanDefinition(SimpleUrlHandlerMapping.class);
- handlerMappingDef.setSource(source);
- handlerMappingDef.getPropertyValues().add("order", "1");
- handlerMappingDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
- parserContext.getRegistry().registerBeanDefinition(HANDLER_MAPPING_BEAN_NAME, handlerMappingDef);
- parserContext.registerComponent(new BeanComponentDefinition(handlerMappingDef, HANDLER_MAPPING_BEAN_NAME));
- return handlerMappingDef;
- }
- else {
- return parserContext.getRegistry().getBeanDefinition(HANDLER_MAPPING_BEAN_NAME);
- }
+
+ /**
+ * 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 ExecutorContext createExecutorContext(ParserContext parserContext) {
+ ExecutorContext executorContext = new ExecutorContext();
+ executorContext.setRegistry(parserContext.getRegistry());
+ executorContext.setRegistrar(parserContext);
+ executorContext.setProblemReporter(parserContext.getReaderContext().getProblemReporter());
+ return executorContext;
}
}
diff --git a/org.springframework.web.servlet/src/main/resources/org/springframework/web/servlet/config/spring-mvc-3.1.xsd b/org.springframework.web.servlet/src/main/resources/org/springframework/web/servlet/config/spring-mvc-3.1.xsd
index 7a53516a12e..b788c933144 100644
--- a/org.springframework.web.servlet/src/main/resources/org/springframework/web/servlet/config/spring-mvc-3.1.xsd
+++ b/org.springframework.web.servlet/src/main/resources/org/springframework/web/servlet/config/spring-mvc-3.1.xsd
@@ -23,7 +23,8 @@
@@ -36,6 +37,13 @@
+
+
+
+
+
diff --git a/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/config/AnnotationDrivenBeanDefinitionParserTests.java b/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/config/AnnotationDrivenBeanDefinitionParserTests.java
index 11f60904e8c..1c2be54e33a 100644
--- a/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/config/AnnotationDrivenBeanDefinitionParserTests.java
+++ b/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/config/AnnotationDrivenBeanDefinitionParserTests.java
@@ -64,8 +64,15 @@ public class AnnotationDrivenBeanDefinitionParserTests {
@Test
public void testMessageConverters() {
loadBeanDefinitions("mvc-config-message-converters.xml");
- verifyMessageConverters(appContext.getBean(AnnotationMethodHandlerAdapter.class));
- verifyMessageConverters(appContext.getBean(AnnotationMethodHandlerExceptionResolver.class));
+ verifyMessageConverters(appContext.getBean(AnnotationMethodHandlerAdapter.class), true);
+ verifyMessageConverters(appContext.getBean(AnnotationMethodHandlerExceptionResolver.class), true);
+ }
+
+ @Test
+ public void testMessageConvertersWithoutDefaultRegistrations() {
+ loadBeanDefinitions("mvc-config-message-converters-defaults-off.xml");
+ verifyMessageConverters(appContext.getBean(AnnotationMethodHandlerAdapter.class), false);
+ verifyMessageConverters(appContext.getBean(AnnotationMethodHandlerExceptionResolver.class), false);
}
@Test
@@ -88,12 +95,18 @@ public class AnnotationDrivenBeanDefinitionParserTests {
appContext.refresh();
}
- private void verifyMessageConverters(Object bean) {
+ private void verifyMessageConverters(Object bean, boolean hasDefaultRegistrations) {
assertNotNull(bean);
Object converters = new DirectFieldAccessor(bean).getPropertyValue("messageConverters");
assertNotNull(converters);
assertTrue(converters instanceof HttpMessageConverter>[]);
- assertEquals(2, ((HttpMessageConverter>[]) converters).length);
+ if (hasDefaultRegistrations) {
+ assertTrue("Default converters are registered in addition to custom ones",
+ ((HttpMessageConverter>[]) converters).length > 2);
+ } else {
+ assertTrue("Default converters should not be registered",
+ ((HttpMessageConverter>[]) converters).length == 2);
+ }
assertTrue(((HttpMessageConverter>[]) converters)[0] instanceof StringHttpMessageConverter);
assertTrue(((HttpMessageConverter>[]) converters)[1] instanceof ResourceHttpMessageConverter);
}
diff --git a/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/config/MvcAnnotationDrivenFeatureTests.java b/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/config/MvcAnnotationDrivenFeatureTests.java
new file mode 100644
index 00000000000..a1f3d633cca
--- /dev/null
+++ b/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/config/MvcAnnotationDrivenFeatureTests.java
@@ -0,0 +1,101 @@
+/*
+ * Copyright 2002-2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springframework.web.servlet.config;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import org.junit.Test;
+import org.springframework.beans.DirectFieldAccessor;
+import org.springframework.context.annotation.AnnotationConfigApplicationContext;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Feature;
+import org.springframework.context.annotation.FeatureConfiguration;
+import org.springframework.format.support.DefaultFormattingConversionService;
+import org.springframework.format.support.FormattingConversionService;
+import org.springframework.http.converter.HttpMessageConverter;
+import org.springframework.http.converter.StringHttpMessageConverter;
+import org.springframework.validation.MessageCodesResolver;
+import org.springframework.validation.Validator;
+import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;
+import org.springframework.web.bind.support.ConfigurableWebBindingInitializer;
+import org.springframework.web.bind.support.WebArgumentResolver;
+import org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter;
+
+/**
+ * Integration tests for the {@link MvcAnnotationDriven} feature specification.
+ * @author Rossen Stoyanchev
+ * @author Chris Beams
+ * @since 3.1
+ */
+public class MvcAnnotationDrivenFeatureTests {
+
+ @Test
+ public void testMessageCodesResolver() {
+ AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
+ ctx.register(MvcFeature.class, MvcBeans.class);
+ ctx.refresh();
+ AnnotationMethodHandlerAdapter adapter = ctx.getBean(AnnotationMethodHandlerAdapter.class);
+ assertNotNull(adapter);
+ Object initializer = new DirectFieldAccessor(adapter).getPropertyValue("webBindingInitializer");
+ assertNotNull(initializer);
+ MessageCodesResolver resolver = ((ConfigurableWebBindingInitializer) initializer).getMessageCodesResolver();
+ assertNotNull(resolver);
+ assertEquals("test.foo.bar", resolver.resolveMessageCodes("foo", "bar")[0]);
+ Object argResolvers = new DirectFieldAccessor(adapter).getPropertyValue("customArgumentResolvers");
+ assertNotNull(argResolvers);
+ WebArgumentResolver[] argResolversArray = (WebArgumentResolver[]) argResolvers;
+ assertEquals(1, argResolversArray.length);
+ assertTrue(argResolversArray[0] instanceof TestWebArgumentResolver);
+ Object converters = new DirectFieldAccessor(adapter).getPropertyValue("messageConverters");
+ assertNotNull(converters);
+ HttpMessageConverter>[] convertersArray = (HttpMessageConverter>[]) converters;
+ assertTrue("Default converters are registered in addition to the custom one", convertersArray.length > 1);
+ assertTrue(convertersArray[0] instanceof StringHttpMessageConverter);
+ }
+
+}
+
+@FeatureConfiguration
+class MvcFeature {
+ @Feature
+ public MvcAnnotationDriven annotationDriven(MvcBeans mvcBeans) {
+ return new MvcAnnotationDriven()
+ .conversionService(mvcBeans.conversionService())
+ .messageCodesResolver(mvcBeans.messageCodesResolver())
+ .validator(mvcBeans.validator())
+ .messageConverters(new StringHttpMessageConverter())
+ .argumentResolvers(new TestWebArgumentResolver());
+ }
+}
+
+@Configuration
+class MvcBeans {
+ @Bean
+ public FormattingConversionService conversionService() {
+ return new DefaultFormattingConversionService();
+ }
+ @Bean
+ public Validator validator() {
+ return new LocalValidatorFactoryBean();
+ }
+ @Bean MessageCodesResolver messageCodesResolver() {
+ return new TestMessageCodesResolver();
+ }
+}
+
diff --git a/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/config/MvcDefaultServletHandlerTests.java b/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/config/MvcDefaultServletHandlerTests.java
new file mode 100644
index 00000000000..74317c6bb88
--- /dev/null
+++ b/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/config/MvcDefaultServletHandlerTests.java
@@ -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.web.servlet.config;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+
+import org.junit.Test;
+import org.springframework.beans.DirectFieldAccessor;
+import org.springframework.context.annotation.AnnotationConfigApplicationContext;
+import org.springframework.context.annotation.Feature;
+import org.springframework.context.annotation.FeatureConfiguration;
+import org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter;
+import org.springframework.web.servlet.resource.DefaultServletHttpRequestHandler;
+
+/**
+ * Test fixture for {@link MvcDefaultServletHandler} feature specification.
+ * @author Rossen Stoyanchev
+ * @since 3.1
+ */
+public class MvcDefaultServletHandlerTests {
+
+ @Test
+ public void testDefaultServletHandler() {
+ AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
+ ctx.register(MvcDefaultServletHandlerFeature.class);
+ ctx.refresh();
+ HttpRequestHandlerAdapter adapter = ctx.getBean(HttpRequestHandlerAdapter.class);
+ assertNotNull(adapter);
+ DefaultServletHttpRequestHandler handler = ctx.getBean(DefaultServletHttpRequestHandler.class);
+ assertNotNull(handler);
+ String defaultServletHandlerName = (String) new DirectFieldAccessor(handler)
+ .getPropertyValue("defaultServletName");
+ assertEquals("foo", defaultServletHandlerName);
+ }
+
+ @FeatureConfiguration
+ private static class MvcDefaultServletHandlerFeature {
+
+ @SuppressWarnings("unused")
+ @Feature
+ public MvcDefaultServletHandler defaultServletHandler() {
+ return new MvcDefaultServletHandler("foo");
+ }
+
+ }
+
+}
diff --git a/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/config/MvcResourcesTests.java b/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/config/MvcResourcesTests.java
new file mode 100644
index 00000000000..bf71da49afa
--- /dev/null
+++ b/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/config/MvcResourcesTests.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright 2002-2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springframework.web.servlet.config;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import java.util.List;
+
+import org.junit.Test;
+import org.springframework.beans.DirectFieldAccessor;
+import org.springframework.context.annotation.AnnotationConfigApplicationContext;
+import org.springframework.context.annotation.Feature;
+import org.springframework.context.annotation.FeatureConfiguration;
+import org.springframework.core.io.Resource;
+import org.springframework.web.servlet.handler.SimpleUrlHandlerMapping;
+import org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter;
+import org.springframework.web.servlet.resource.ResourceHttpRequestHandler;
+
+/**
+ * Test fixture for {@link MvcResources} feature specification.
+ * @author Rossen Stoyanchev
+ * @since 3.1
+ */
+public class MvcResourcesTests {
+
+ @Test
+ public void testResources() {
+ AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
+ ctx.register(MvcResourcesFeature.class);
+ ctx.refresh();
+ HttpRequestHandlerAdapter adapter = ctx.getBean(HttpRequestHandlerAdapter.class);
+ assertNotNull(adapter);
+ ResourceHttpRequestHandler handler = ctx.getBean(ResourceHttpRequestHandler.class);
+ assertNotNull(handler);
+ @SuppressWarnings("unchecked")
+ List locations = (List) new DirectFieldAccessor(handler).getPropertyValue("locations");
+ assertNotNull(locations);
+ assertEquals(2, locations.size());
+ assertEquals("foo", locations.get(0).getFilename());
+ assertEquals("bar", locations.get(1).getFilename());
+ SimpleUrlHandlerMapping mapping = ctx.getBean(SimpleUrlHandlerMapping.class);
+ assertEquals(1, mapping.getOrder());
+ assertSame(handler, mapping.getHandlerMap().get("/resources/**"));
+ }
+
+ @Test
+ public void testInvalidResources() {
+ AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
+ ctx.register(InvalidMvcResourcesFeature.class);
+ try {
+ ctx.refresh();
+ fail("Invalid feature spec should not validate");
+ } catch (RuntimeException e) {
+ assertTrue(e.getCause().getMessage().contains("Mapping is required"));
+ // TODO : should all problems be in the message ?
+ }
+ }
+
+ @FeatureConfiguration
+ private static class MvcResourcesFeature {
+
+ @SuppressWarnings("unused")
+ @Feature
+ public MvcResources resources() {
+ return new MvcResources("/resources/**", new String[] { "/foo", "/bar" }).cachePeriod(86400).order(1);
+ }
+
+ }
+
+ @FeatureConfiguration
+ private static class InvalidMvcResourcesFeature {
+
+ @SuppressWarnings("unused")
+ @Feature
+ public MvcResources resources() {
+ return new MvcResources(" ", new String[] {});
+ }
+
+ }
+
+}
diff --git a/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/config/MvcViewControllersTests.java b/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/config/MvcViewControllersTests.java
new file mode 100644
index 00000000000..fdb27d71557
--- /dev/null
+++ b/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/config/MvcViewControllersTests.java
@@ -0,0 +1,131 @@
+/*
+ * Copyright 2002-2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springframework.web.servlet.config;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import java.util.Map;
+
+import org.junit.Test;
+import org.springframework.beans.factory.parsing.FailFastProblemReporter;
+import org.springframework.context.annotation.AnnotationConfigApplicationContext;
+import org.springframework.context.annotation.Feature;
+import org.springframework.context.annotation.FeatureConfiguration;
+import org.springframework.web.servlet.handler.SimpleUrlHandlerMapping;
+import org.springframework.web.servlet.mvc.ParameterizableViewController;
+import org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter;
+
+/**
+ * Test fixture for {@link MvcViewControllers} feature specification.
+ * @author Rossen Stoyanchev
+ * @since 3.1
+ */
+public class MvcViewControllersTests {
+
+ @Test
+ public void testMvcViewControllers() {
+ AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
+ ctx.register(MvcViewControllersFeature.class);
+ ctx.refresh();
+ SimpleControllerHandlerAdapter adapter = ctx.getBean(SimpleControllerHandlerAdapter.class);
+ assertNotNull(adapter);
+ SimpleUrlHandlerMapping handler = ctx.getBean(SimpleUrlHandlerMapping.class);
+ assertNotNull(handler);
+ Map urlMap = handler.getUrlMap();
+ assertNotNull(urlMap);
+ assertEquals(2, urlMap.size());
+ ParameterizableViewController controller = (ParameterizableViewController) urlMap.get("/");
+ assertNotNull(controller);
+ assertEquals("home", controller.getViewName());
+ controller = (ParameterizableViewController) urlMap.get("/account");
+ assertNotNull(controller);
+ assertNull(controller.getViewName());
+ }
+
+ @Test
+ public void testEmptyPath() {
+ AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
+ ctx.register(EmptyPathViewControllersFeature.class);
+ try {
+ ctx.refresh();
+ fail("expected exception");
+ } catch (Exception ex) {
+ assertTrue(ex.getCause().getMessage().contains("path attribute"));
+ }
+ }
+
+ @Test
+ public void testEmptyViewName() {
+ AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
+ ctx.register(EmptyViewNameViewControllersFeature.class);
+ try {
+ ctx.refresh();
+ fail("expected exception");
+ } catch (Exception ex) {
+ assertTrue(ex.getCause().getMessage().contains("not empty"));
+ }
+ }
+
+ @Test
+ public void testNullViewName() {
+ FailFastProblemReporter problemReporter = new FailFastProblemReporter();
+ assertThat(new MvcViewControllers("/some/path").validate(problemReporter), is(true));
+ }
+
+
+ @FeatureConfiguration
+ private static class MvcViewControllersFeature {
+
+ @SuppressWarnings("unused")
+ @Feature
+ public MvcViewControllers mvcViewControllers() {
+ return new MvcViewControllers("/", "home").viewController("/account");
+ }
+
+ }
+
+
+ @FeatureConfiguration
+ private static class EmptyViewNameViewControllersFeature {
+
+ @SuppressWarnings("unused")
+ @Feature
+ public MvcViewControllers mvcViewControllers() {
+ return new MvcViewControllers("/some/path", "");
+ }
+
+ }
+
+
+ @FeatureConfiguration
+ private static class EmptyPathViewControllersFeature {
+
+ @SuppressWarnings("unused")
+ @Feature
+ public MvcViewControllers mvcViewControllers() {
+ return new MvcViewControllers("", "someViewName");
+ }
+
+ }
+
+}
+
diff --git a/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/annotation/Spr7766Tests.java b/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/annotation/Spr7766Tests.java
index 117f5d70b0c..93a18beb330 100644
--- a/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/annotation/Spr7766Tests.java
+++ b/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/annotation/Spr7766Tests.java
@@ -1,10 +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.web.servlet.mvc.annotation;
import java.awt.Color;
import org.junit.Test;
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.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
@@ -16,7 +32,7 @@ public class Spr7766Tests {
public void test() throws Exception {
AnnotationMethodHandlerAdapter adapter = new AnnotationMethodHandlerAdapter();
ConfigurableWebBindingInitializer binder = new ConfigurableWebBindingInitializer();
- GenericConversionService service = ConversionServiceFactory.createDefaultConversionService();
+ GenericConversionService service = new DefaultConversionService();
service.addConverter(new ColorConverter());
binder.setConversionService(service);
adapter.setWebBindingInitializer(binder);
diff --git a/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/annotation/Spr7839Tests.java b/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/annotation/Spr7839Tests.java
index 5ed4d0b3f29..26012a43790 100644
--- a/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/annotation/Spr7839Tests.java
+++ b/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/annotation/Spr7839Tests.java
@@ -1,3 +1,19 @@
+/*
+ * Copyright 2002-2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
package org.springframework.web.servlet.mvc.annotation;
import static org.junit.Assert.assertEquals;
@@ -9,7 +25,7 @@ import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
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.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
@@ -30,7 +46,7 @@ public class Spr7839Tests {
@Before
public void setUp() {
ConfigurableWebBindingInitializer binder = new ConfigurableWebBindingInitializer();
- GenericConversionService service = ConversionServiceFactory.createDefaultConversionService();
+ GenericConversionService service = new DefaultConversionService();
service.addConverter(new Converter() {
public NestedBean convert(String source) {
return new NestedBean(source);
diff --git a/org.springframework.web.servlet/src/test/resources/org/springframework/web/servlet/config/mvc-config-message-converters-defaults-off.xml b/org.springframework.web.servlet/src/test/resources/org/springframework/web/servlet/config/mvc-config-message-converters-defaults-off.xml
new file mode 100644
index 00000000000..22344c79966
--- /dev/null
+++ b/org.springframework.web.servlet/src/test/resources/org/springframework/web/servlet/config/mvc-config-message-converters-defaults-off.xml
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/org.springframework.web/src/test/java/org/springframework/http/MediaTypeTests.java b/org.springframework.web/src/test/java/org/springframework/http/MediaTypeTests.java
index dab8d8ed78a..ffd07d479f2 100644
--- a/org.springframework.web/src/test/java/org/springframework/http/MediaTypeTests.java
+++ b/org.springframework.web/src/test/java/org/springframework/http/MediaTypeTests.java
@@ -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,12 @@
package org.springframework.http;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Collections;
@@ -23,11 +29,9 @@ import java.util.Comparator;
import java.util.List;
import java.util.Random;
-import static org.junit.Assert.*;
import org.junit.Test;
-
import org.springframework.core.convert.ConversionService;
-import org.springframework.core.convert.support.ConversionServiceFactory;
+import org.springframework.core.convert.support.DefaultConversionService;
/**
* @author Arjen Poutsma
@@ -490,7 +494,7 @@ public class MediaTypeTests {
@Test
public void testWithConversionService() {
- ConversionService conversionService = ConversionServiceFactory.createDefaultConversionService();
+ ConversionService conversionService = new DefaultConversionService();
assertTrue(conversionService.canConvert(String.class, MediaType.class));
MediaType mediaType = MediaType.parseMediaType("application/xml");
assertEquals(mediaType, conversionService.convert("application/xml", MediaType.class));
diff --git a/spring-framework-reference/src/mvc.xml b/spring-framework-reference/src/mvc.xml
index c49e1ef2248..c9e983ce74e 100644
--- a/spring-framework-reference/src/mvc.xml
+++ b/spring-framework-reference/src/mvc.xml
@@ -3368,8 +3368,10 @@ public class SimpleController {
- This list of HttpMessageConverters used can be replaced through
- the mvc:message-converters sub-element of mvc:annotation-driven.
+ You can provide your own HttpMessageConverters through the
+ mvc:message-converters sub-element of mvc:annotation-driven.
+ Message converters you provide will take precedence over the
+ ones registered by default.