From 8e445f3a211395d43895f747d04158ff7f9c0913 Mon Sep 17 00:00:00 2001 From: Phillip Webb Date: Wed, 20 Feb 2013 09:38:43 -0800 Subject: [PATCH 1/3] Extend AnnotationMetadata and MethodMetadata Update AnnotationMetadata and MethodMetadata to extend from a new AnnotatedTypeMetadata base interface containing the methods that are common to both. Also introduce new getAllAnnotationAttributes methods providing MultiValueMap access to both annotation and meta-annotation attributes. Existing classreading and standard implementations have been refactored to support the new interface. --- .../core/type/AnnotatedElementUtils.java | 160 ++++++++++++++++++ .../core/type/AnnotatedTypeMetadata.java | 97 +++++++++++ .../core/type/AnnotationMetadata.java | 43 +---- .../core/type/MethodMetadata.java | 27 +-- .../core/type/StandardAnnotationMetadata.java | 108 +++--------- .../core/type/StandardMethodMetadata.java | 49 +++--- .../AnnotationAttributesReadingVisitor.java | 47 +++-- .../AnnotationMetadataReadingVisitor.java | 69 +++----- .../AnnotationReadingVisitorUtils.java | 92 ++++++++++ .../MethodMetadataReadingVisitor.java | 36 +++- .../core/type/AnnotationMetadataTests.java | 56 +++++- 11 files changed, 537 insertions(+), 247 deletions(-) create mode 100644 spring-core/src/main/java/org/springframework/core/type/AnnotatedElementUtils.java create mode 100644 spring-core/src/main/java/org/springframework/core/type/AnnotatedTypeMetadata.java create mode 100644 spring-core/src/main/java/org/springframework/core/type/classreading/AnnotationReadingVisitorUtils.java diff --git a/spring-core/src/main/java/org/springframework/core/type/AnnotatedElementUtils.java b/spring-core/src/main/java/org/springframework/core/type/AnnotatedElementUtils.java new file mode 100644 index 00000000000..ca817f48bd6 --- /dev/null +++ b/spring-core/src/main/java/org/springframework/core/type/AnnotatedElementUtils.java @@ -0,0 +1,160 @@ +/* + * Copyright 2002-2013 the original author 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.type; + +import java.lang.annotation.Annotation; +import java.lang.reflect.AnnotatedElement; +import java.util.HashSet; +import java.util.LinkedHashSet; +import java.util.Map; +import java.util.Set; + +import org.springframework.core.annotation.AnnotationUtils; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; + +/** + * Internal utility class used to collect all annotation values including those declared + * on meta-annotations. + * + * @author Phillip Webb + * @since 4.0 + */ +class AnnotatedElementUtils { + + public static Set getMetaAnnotationTypes(AnnotatedElement element, + String annotationType) { + final Set types = new LinkedHashSet(); + process(element, annotationType, new Processor() { + public Object process(Annotation annotation, int depth) { + if (depth > 0) { + types.add(annotation.annotationType().getName()); + } + return null; + } + }); + return (types.size() == 0 ? null : types); + } + + public static boolean hasMetaAnnotationTypes(AnnotatedElement element, + String annotationType) { + return Boolean.TRUE.equals( + process(element, annotationType, new Processor() { + public Boolean process(Annotation annotation, int depth) { + if (depth > 0) { + return true; + } + return null; + } + })); + } + + public static boolean isAnnotated(AnnotatedElement element, String annotationType) { + return Boolean.TRUE.equals( + process(element, annotationType, new Processor() { + public Boolean process(Annotation annotation, int depth) { + return true; + } + })); + } + + public static Map getAnnotationAttributes(AnnotatedElement element, + String annotationType, final boolean classValuesAsString, + final boolean nestedAnnotationsAsMap) { + return process(element, annotationType, new Processor>() { + public Map process(Annotation annotation, int depth) { + return AnnotationUtils.getAnnotationAttributes(annotation, + classValuesAsString, nestedAnnotationsAsMap); + } + }); + } + + public static MultiValueMap getAllAnnotationAttributes( + AnnotatedElement element, final String annotationType, + final boolean classValuesAsString, final boolean nestedAnnotationsAsMap) { + final MultiValueMap attributes = new LinkedMultiValueMap(); + process(element, annotationType, new Processor() { + public Object process(Annotation annotation, int depth) { + if (annotation.annotationType().getName().equals(annotationType)) { + for (Map.Entry entry : AnnotationUtils.getAnnotationAttributes( + annotation, classValuesAsString, nestedAnnotationsAsMap).entrySet()) { + attributes.add(entry.getKey(), entry.getValue()); + } + } + return null; + } + }); + return (attributes.size() == 0 ? null : attributes); + } + + /** + * Process all annotations of the specified annotation type and recursively all + * meta-annotations on the specified type. + * @param element the annotated element + * @param annotationType the annotation type to find. Only items of the specified type + * or meta-annotations of the specified type will be processed + * @param processor the processor + * @return the result of the processor + */ + private static T process(AnnotatedElement element, String annotationType, + Processor processor) { + return recursivelyProcess(element, annotationType, processor, + new HashSet(), 0); + } + + private static T recursivelyProcess(AnnotatedElement element, + String annotationType, Processor processor, Set visited, + int depth) { + T result = null; + if (visited.add(element)) { + for (Annotation annotation : element.getAnnotations()) { + if (annotation.annotationType().getName().equals(annotationType) || depth > 0) { + result = result != null ? result : + processor.process(annotation, depth); + result = result != null ? result : + recursivelyProcess(annotation.annotationType(), annotationType, + processor, visited, depth + 1); + } + } + for (Annotation annotation : element.getAnnotations()) { + result = result != null ? result : + recursivelyProcess(annotation.annotationType(), annotationType, + processor, visited, depth); + } + + } + return result; + } + + /** + * Callback interface used to process an annotation. + * @param the result type + */ + private static interface Processor { + + /** + * Called to process the annotation. + * @param annotation the annotation to process + * @param depth the depth of the annotation relative to the initial match. For + * example a matched annotation will have a depth of 0, a meta-annotation 1 + * and a meta-meta-annotation 2 + * @return the result of the processing or {@code null} to continue + */ + T process(Annotation annotation, int depth); + } + +} diff --git a/spring-core/src/main/java/org/springframework/core/type/AnnotatedTypeMetadata.java b/spring-core/src/main/java/org/springframework/core/type/AnnotatedTypeMetadata.java new file mode 100644 index 00000000000..4cef1d48b30 --- /dev/null +++ b/spring-core/src/main/java/org/springframework/core/type/AnnotatedTypeMetadata.java @@ -0,0 +1,97 @@ +/* + * Copyright 2002-2013 the original author 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.type; + +import java.util.Map; + +import org.springframework.util.MultiValueMap; + +/** + * Defines access to the annotations of a specific type ({@link AnnotationMetadata class} + * or {@link MethodMetadata method}), in a form that does not necessarily require the + * class-loading. + * + * @author Juergen Hoeller + * @author Mark Fisher + * @author Mark Pollack + * @author Chris Beams + * @author Phillip Webb + * @since 4.0 + * @see AnnotationMetadata + * @see MethodMetadata + */ +public interface AnnotatedTypeMetadata { + + /** + * Determine whether the underlying type has an annotation or + * meta-annotation of the given type defined. + *

If this method returns {@code true}, then + * {@link #getAnnotationAttributes} will return a non-null Map. + * @param annotationType the annotation type to look for + * @return whether a matching annotation is defined + */ + boolean isAnnotated(String annotationType); + + /** + * Retrieve the attributes of the annotation of the given type, + * if any (i.e. if defined on the underlying class, as direct + * annotation or as meta-annotation). + * @param annotationType the annotation type to look for + * @return a Map of attributes, with the attribute name as key (e.g. "value") + * and the defined attribute value as Map value. This return value will be + * {@code null} if no matching annotation is defined. + */ + Map getAnnotationAttributes(String annotationType); + + /** + * Retrieve the attributes of the annotation of the given type, + * if any (i.e. if defined on the underlying class, as direct + * annotation or as meta-annotation). + * @param annotationType the annotation type to look for + * @param classValuesAsString whether to convert class references to String + * class names for exposure as values in the returned Map, instead of Class + * references which might potentially have to be loaded first + * @return a Map of attributes, with the attribute name as key (e.g. "value") + * and the defined attribute value as Map value. This return value will be + * {@code null} if no matching annotation is defined. + */ + Map getAnnotationAttributes(String annotationType, boolean classValuesAsString); + + /** + * Retrieve all attributes of all annotations of the given type, if any (i.e. if + * defined on the underlying method, as direct annotation or as meta-annotation). + * @param annotationType the annotation type to look for + * @return a MultiMap of attributes, with the attribute name as key (e.g. "value") and + * a list of the defined attribute values as Map value. This return value will + * be {@code null} if no matching annotation is defined. + */ + MultiValueMap getAllAnnotationAttributes(String annotationType); + + /** + * Retrieve all attributes of all annotations of the given type, if any (i.e. if + * defined on the underlying method, as direct annotation or as meta-annotation). + * @param annotationType the annotation type to look for + * @param classValuesAsString whether to convert class references to String + * @return a MultiMap of attributes, with the attribute name as key (e.g. "value") and + * a list of the defined attribute values as Map value. This return value will + * be {@code null} if no matching annotation is defined. + * @see #getAllAnnotationAttributes(String) + */ + MultiValueMap getAllAnnotationAttributes(String annotationType, + boolean classValuesAsString); + +} diff --git a/spring-core/src/main/java/org/springframework/core/type/AnnotationMetadata.java b/spring-core/src/main/java/org/springframework/core/type/AnnotationMetadata.java index beeb7d935a0..435e934415d 100644 --- a/spring-core/src/main/java/org/springframework/core/type/AnnotationMetadata.java +++ b/spring-core/src/main/java/org/springframework/core/type/AnnotationMetadata.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author 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,7 +16,6 @@ package org.springframework.core.type; -import java.util.Map; import java.util.Set; /** @@ -25,11 +24,13 @@ import java.util.Set; * * @author Juergen Hoeller * @author Mark Fisher + * @author Phillip Webb * @since 2.5 * @see StandardAnnotationMetadata * @see org.springframework.core.type.classreading.MetadataReader#getAnnotationMetadata() + * @see AnnotatedTypeMetadata */ -public interface AnnotationMetadata extends ClassMetadata { +public interface AnnotationMetadata extends ClassMetadata, AnnotatedTypeMetadata { /** * Return the names of all annotation types defined on the underlying class. @@ -61,42 +62,6 @@ public interface AnnotationMetadata extends ClassMetadata { */ boolean hasMetaAnnotation(String metaAnnotationType); - /** - * Determine whether the underlying class has an annotation or - * meta-annotation of the given type defined. - *

This is equivalent to a "hasAnnotation || hasMetaAnnotation" - * check. If this method returns {@code true}, then - * {@link #getAnnotationAttributes} will return a non-null Map. - * @param annotationType the annotation type to look for - * @return whether a matching annotation is defined - */ - boolean isAnnotated(String annotationType); - - /** - * Retrieve the attributes of the annotation of the given type, - * if any (i.e. if defined on the underlying class, as direct - * annotation or as meta-annotation). - * @param annotationType the annotation type to look for - * @return a Map of attributes, with the attribute name as key (e.g. "value") - * and the defined attribute value as Map value. This return value will be - * {@code null} if no matching annotation is defined. - */ - Map getAnnotationAttributes(String annotationType); - - /** - * Retrieve the attributes of the annotation of the given type, - * if any (i.e. if defined on the underlying class, as direct - * annotation or as meta-annotation). - * @param annotationType the annotation type to look for - * @param classValuesAsString whether to convert class references to String - * class names for exposure as values in the returned Map, instead of Class - * references which might potentially have to be loaded first - * @return a Map of attributes, with the attribute name as key (e.g. "value") - * and the defined attribute value as Map value. This return value will be - * {@code null} if no matching annotation is defined. - */ - Map getAnnotationAttributes(String annotationType, boolean classValuesAsString); - /** * Determine whether the underlying class has any methods that are * annotated (or meta-annotated) with the given annotation type. diff --git a/spring-core/src/main/java/org/springframework/core/type/MethodMetadata.java b/spring-core/src/main/java/org/springframework/core/type/MethodMetadata.java index 17d7bab24cb..585965ca32e 100644 --- a/spring-core/src/main/java/org/springframework/core/type/MethodMetadata.java +++ b/spring-core/src/main/java/org/springframework/core/type/MethodMetadata.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author 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,8 +16,6 @@ package org.springframework.core.type; -import java.util.Map; - /** * Interface that defines abstract access to the annotations of a specific * class, in a form that does not require that class to be loaded yet. @@ -25,11 +23,13 @@ import java.util.Map; * @author Juergen Hoeller * @author Mark Pollack * @author Chris Beams + * @author Phillip Webb * @since 3.0 * @see StandardMethodMetadata * @see AnnotationMetadata#getAnnotatedMethods + * @see AnnotatedTypeMetadata */ -public interface MethodMetadata { +public interface MethodMetadata extends AnnotatedTypeMetadata { /** * Return the name of the method. @@ -57,23 +57,4 @@ public interface MethodMetadata { */ boolean isOverridable(); - /** - * Determine whether the underlying method has an annotation or - * meta-annotation of the given type defined. - * @param annotationType the annotation type to look for - * @return whether a matching annotation is defined - */ - boolean isAnnotated(String annotationType); - - /** - * Retrieve the attributes of the annotation of the given type, - * if any (i.e. if defined on the underlying method, as direct - * annotation or as meta-annotation). - * @param annotationType the annotation type to look for - * @return a Map of attributes, with the attribute name as key (e.g. "value") - * and the defined attribute value as Map value. This return value will be - * {@code null} if no matching annotation is defined. - */ - Map getAnnotationAttributes(String annotationType); - } diff --git a/spring-core/src/main/java/org/springframework/core/type/StandardAnnotationMetadata.java b/spring-core/src/main/java/org/springframework/core/type/StandardAnnotationMetadata.java index 37473c5a77c..e0c609624cd 100644 --- a/spring-core/src/main/java/org/springframework/core/type/StandardAnnotationMetadata.java +++ b/spring-core/src/main/java/org/springframework/core/type/StandardAnnotationMetadata.java @@ -23,7 +23,7 @@ import java.util.Map; import java.util.Set; import org.springframework.core.annotation.AnnotationAttributes; -import org.springframework.core.annotation.AnnotationUtils; +import org.springframework.util.MultiValueMap; /** * {@link AnnotationMetadata} implementation that uses standard reflection @@ -32,6 +32,7 @@ import org.springframework.core.annotation.AnnotationUtils; * @author Juergen Hoeller * @author Mark Fisher * @author Chris Beams + * @author Phillip Webb * @since 2.5 */ public class StandardAnnotationMetadata extends StandardClassMetadata implements AnnotationMetadata { @@ -74,21 +75,7 @@ public class StandardAnnotationMetadata extends StandardClassMetadata implements } public Set getMetaAnnotationTypes(String annotationType) { - Annotation[] anns = getIntrospectedClass().getAnnotations(); - for (Annotation ann : anns) { - if (ann.annotationType().getName().equals(annotationType)) { - Set types = new LinkedHashSet(); - Annotation[] metaAnns = ann.annotationType().getAnnotations(); - for (Annotation metaAnn : metaAnns) { - types.add(metaAnn.annotationType().getName()); - for (Annotation metaMetaAnn : metaAnn.annotationType().getAnnotations()) { - types.add(metaMetaAnn.annotationType().getName()); - } - } - return types; - } - } - return null; + return AnnotatedElementUtils.getMetaAnnotationTypes(getIntrospectedClass(), annotationType); } public boolean hasAnnotation(String annotationType) { @@ -102,75 +89,38 @@ public class StandardAnnotationMetadata extends StandardClassMetadata implements } public boolean hasMetaAnnotation(String annotationType) { - Annotation[] anns = getIntrospectedClass().getAnnotations(); - for (Annotation ann : anns) { - Annotation[] metaAnns = ann.annotationType().getAnnotations(); - for (Annotation metaAnn : metaAnns) { - if (metaAnn.annotationType().getName().equals(annotationType)) { - return true; - } - for (Annotation metaMetaAnn : metaAnn.annotationType().getAnnotations()) { - if (metaMetaAnn.annotationType().getName().equals(annotationType)) { - return true; - } - } - } - } - return false; + return AnnotatedElementUtils.hasMetaAnnotationTypes(getIntrospectedClass(), annotationType); } public boolean isAnnotated(String annotationType) { - Annotation[] anns = getIntrospectedClass().getAnnotations(); - for (Annotation ann : anns) { - if (ann.annotationType().getName().equals(annotationType)) { - return true; - } - for (Annotation metaAnn : ann.annotationType().getAnnotations()) { - if (metaAnn.annotationType().getName().equals(annotationType)) { - return true; - } - } - } - return false; + return AnnotatedElementUtils.isAnnotated(getIntrospectedClass(), annotationType); } public Map getAnnotationAttributes(String annotationType) { return this.getAnnotationAttributes(annotationType, false); } - public Map getAnnotationAttributes(String annotationType, boolean classValuesAsString) { - Annotation[] anns = getIntrospectedClass().getAnnotations(); - for (Annotation ann : anns) { - if (ann.annotationType().getName().equals(annotationType)) { - return AnnotationUtils.getAnnotationAttributes( - ann, classValuesAsString, this.nestedAnnotationsAsMap); - } - } - for (Annotation ann : anns) { - for (Annotation metaAnn : ann.annotationType().getAnnotations()) { - if (metaAnn.annotationType().getName().equals(annotationType)) { - return AnnotationUtils.getAnnotationAttributes( - metaAnn, classValuesAsString, this.nestedAnnotationsAsMap); - } - } - } - return null; + public Map getAnnotationAttributes(String annotationType, + boolean classValuesAsString) { + return AnnotatedElementUtils.getAnnotationAttributes(getIntrospectedClass(), + annotationType, classValuesAsString, this.nestedAnnotationsAsMap); + } + + public MultiValueMap getAllAnnotationAttributes(String annotationType) { + return getAllAnnotationAttributes(annotationType, false); + } + + public MultiValueMap getAllAnnotationAttributes( + String annotationType, boolean classValuesAsString) { + return AnnotatedElementUtils.getAllAnnotationAttributes(getIntrospectedClass(), + annotationType, classValuesAsString, this.nestedAnnotationsAsMap); } public boolean hasAnnotatedMethods(String annotationType) { Method[] methods = getIntrospectedClass().getDeclaredMethods(); for (Method method : methods) { - for (Annotation ann : method.getAnnotations()) { - if (ann.annotationType().getName().equals(annotationType)) { - return true; - } - else { - for (Annotation metaAnn : ann.annotationType().getAnnotations()) { - if (metaAnn.annotationType().getName().equals(annotationType)) { - return true; - } - } - } + if (AnnotatedElementUtils.isAnnotated(method, annotationType)) { + return true; } } return false; @@ -180,19 +130,9 @@ public class StandardAnnotationMetadata extends StandardClassMetadata implements Method[] methods = getIntrospectedClass().getDeclaredMethods(); Set annotatedMethods = new LinkedHashSet(); for (Method method : methods) { - for (Annotation ann : method.getAnnotations()) { - if (ann.annotationType().getName().equals(annotationType)) { - annotatedMethods.add(new StandardMethodMetadata(method, this.nestedAnnotationsAsMap)); - break; - } - else { - for (Annotation metaAnn : ann.annotationType().getAnnotations()) { - if (metaAnn.annotationType().getName().equals(annotationType)) { - annotatedMethods.add(new StandardMethodMetadata(method, this.nestedAnnotationsAsMap)); - break; - } - } - } + if (AnnotatedElementUtils.isAnnotated(method, annotationType)) { + annotatedMethods.add(new StandardMethodMetadata(method, + this.nestedAnnotationsAsMap)); } } return annotatedMethods; diff --git a/spring-core/src/main/java/org/springframework/core/type/StandardMethodMetadata.java b/spring-core/src/main/java/org/springframework/core/type/StandardMethodMetadata.java index c6564c643f7..221cbdb8bec 100644 --- a/spring-core/src/main/java/org/springframework/core/type/StandardMethodMetadata.java +++ b/spring-core/src/main/java/org/springframework/core/type/StandardMethodMetadata.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author 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,13 +16,12 @@ package org.springframework.core.type; -import java.lang.annotation.Annotation; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.Map; -import org.springframework.core.annotation.AnnotationUtils; import org.springframework.util.Assert; +import org.springframework.util.MultiValueMap; /** * {@link MethodMetadata} implementation that uses standard reflection @@ -31,6 +30,7 @@ import org.springframework.util.Assert; * @author Juergen Hoeller * @author Mark Pollack * @author Chris Beams + * @author Phillip Webb * @since 3.0 */ public class StandardMethodMetadata implements MethodMetadata { @@ -89,35 +89,26 @@ public class StandardMethodMetadata implements MethodMetadata { } public boolean isAnnotated(String annotationType) { - Annotation[] anns = this.introspectedMethod.getAnnotations(); - for (Annotation ann : anns) { - if (ann.annotationType().getName().equals(annotationType)) { - return true; - } - for (Annotation metaAnn : ann.annotationType().getAnnotations()) { - if (metaAnn.annotationType().getName().equals(annotationType)) { - return true; - } - } - } - return false; + return AnnotatedElementUtils.isAnnotated(this.introspectedMethod, annotationType); } public Map getAnnotationAttributes(String annotationType) { - Annotation[] anns = this.introspectedMethod.getAnnotations(); - for (Annotation ann : anns) { - if (ann.annotationType().getName().equals(annotationType)) { - return AnnotationUtils.getAnnotationAttributes( - ann, true, nestedAnnotationsAsMap); - } - for (Annotation metaAnn : ann.annotationType().getAnnotations()) { - if (metaAnn.annotationType().getName().equals(annotationType)) { - return AnnotationUtils.getAnnotationAttributes( - metaAnn, true, this.nestedAnnotationsAsMap); - } - } - } - return null; + return getAnnotationAttributes(annotationType, false); + } + + public Map getAnnotationAttributes(String annotationType, boolean classValuesAsString) { + return AnnotatedElementUtils.getAnnotationAttributes(this.introspectedMethod, + annotationType, classValuesAsString, this.nestedAnnotationsAsMap); + } + + public MultiValueMap getAllAnnotationAttributes(String annotationType) { + return getAllAnnotationAttributes(annotationType, false); + } + + public MultiValueMap getAllAnnotationAttributes( + String annotationType, boolean classValuesAsString) { + return AnnotatedElementUtils.getAllAnnotationAttributes(this.introspectedMethod, + annotationType, classValuesAsString, this.nestedAnnotationsAsMap); } } diff --git a/spring-core/src/main/java/org/springframework/core/type/classreading/AnnotationAttributesReadingVisitor.java b/spring-core/src/main/java/org/springframework/core/type/classreading/AnnotationAttributesReadingVisitor.java index 0b89eeec783..b9a1586666c 100644 --- a/spring-core/src/main/java/org/springframework/core/type/classreading/AnnotationAttributesReadingVisitor.java +++ b/spring-core/src/main/java/org/springframework/core/type/classreading/AnnotationAttributesReadingVisitor.java @@ -34,13 +34,14 @@ import org.springframework.asm.SpringAsmInfo; import org.springframework.asm.Type; import org.springframework.core.annotation.AnnotationAttributes; import org.springframework.core.annotation.AnnotationUtils; +import org.springframework.util.MultiValueMap; import org.springframework.util.ObjectUtils; import org.springframework.util.ReflectionUtils; - /** * @author Chris Beams * @author Juergen Hoeller + * @author Phillip Webb * @since 3.1.1 */ abstract class AbstractRecursiveAnnotationVisitor extends AnnotationVisitor { @@ -51,12 +52,14 @@ abstract class AbstractRecursiveAnnotationVisitor extends AnnotationVisitor { protected final ClassLoader classLoader; + public AbstractRecursiveAnnotationVisitor(ClassLoader classLoader, AnnotationAttributes attributes) { super(SpringAsmInfo.ASM_VERSION); this.classLoader = classLoader; this.attributes = attributes; } + public void visit(String attributeName, Object attributeValue) { this.attributes.put(attributeName, attributeValue); } @@ -108,12 +111,14 @@ final class RecursiveAnnotationArrayVisitor extends AbstractRecursiveAnnotationV private final List allNestedAttributes = new ArrayList(); + public RecursiveAnnotationArrayVisitor( String attributeName, AnnotationAttributes attributes, ClassLoader classLoader) { super(classLoader, attributes); this.attributeName = attributeName; } + @Override public void visit(String attributeName, Object attributeValue) { Object newValue = attributeValue; @@ -155,12 +160,14 @@ class RecursiveAnnotationAttributesVisitor extends AbstractRecursiveAnnotationVi private final String annotationType; + public RecursiveAnnotationAttributesVisitor( String annotationType, AnnotationAttributes attributes, ClassLoader classLoader) { super(classLoader, attributes); this.annotationType = annotationType; } + public final void visitEnd() { try { Class annotationClass = this.classLoader.loadClass(this.annotationType); @@ -214,18 +221,20 @@ class RecursiveAnnotationAttributesVisitor extends AbstractRecursiveAnnotationVi * * @author Juergen Hoeller * @author Chris Beams + * @author Phillip Webb * @since 3.0 */ final class AnnotationAttributesReadingVisitor extends RecursiveAnnotationAttributesVisitor { private final String annotationType; - private final Map attributesMap; + private final MultiValueMap attributesMap; private final Map> metaAnnotationMap; + public AnnotationAttributesReadingVisitor( - String annotationType, Map attributesMap, + String annotationType, MultiValueMap attributesMap, Map> metaAnnotationMap, ClassLoader classLoader) { super(annotationType, new AnnotationAttributes(), classLoader); @@ -234,29 +243,33 @@ final class AnnotationAttributesReadingVisitor extends RecursiveAnnotationAttrib this.metaAnnotationMap = metaAnnotationMap; } + @Override public void doVisitEnd(Class annotationClass) { super.doVisitEnd(annotationClass); - this.attributesMap.put(this.annotationType, this.attributes); - registerMetaAnnotations(annotationClass); - } - - private void registerMetaAnnotations(Class annotationClass) { - // Register annotations that the annotation type is annotated with. + List attributes = this.attributesMap.get(this.annotationType); + if (attributes == null) { + this.attributesMap.add(this.annotationType, this.attributes); + } + else { + attributes.add(0, this.attributes); + } Set metaAnnotationTypeNames = new LinkedHashSet(); for (Annotation metaAnnotation : annotationClass.getAnnotations()) { - metaAnnotationTypeNames.add(metaAnnotation.annotationType().getName()); - if (!this.attributesMap.containsKey(metaAnnotation.annotationType().getName())) { - this.attributesMap.put(metaAnnotation.annotationType().getName(), - AnnotationUtils.getAnnotationAttributes(metaAnnotation, true, true)); - } - for (Annotation metaMetaAnnotation : metaAnnotation.annotationType().getAnnotations()) { - metaAnnotationTypeNames.add(metaMetaAnnotation.annotationType().getName()); - } + recusivelyCollectMetaAnnotations(metaAnnotationTypeNames, metaAnnotation); } if (this.metaAnnotationMap != null) { this.metaAnnotationMap.put(annotationClass.getName(), metaAnnotationTypeNames); } } + private void recusivelyCollectMetaAnnotations(Set visited, Annotation annotation) { + if (visited.add(annotation.annotationType().getName())) { + this.attributesMap.add(annotation.annotationType().getName(), + AnnotationUtils.getAnnotationAttributes(annotation, true, true)); + for (Annotation metaMetaAnnotation : annotation.annotationType().getAnnotations()) { + recusivelyCollectMetaAnnotations(visited, metaMetaAnnotation); + } + } + } } diff --git a/spring-core/src/main/java/org/springframework/core/type/classreading/AnnotationMetadataReadingVisitor.java b/spring-core/src/main/java/org/springframework/core/type/classreading/AnnotationMetadataReadingVisitor.java index 72bf34bbbcb..5a2f959fb2d 100644 --- a/spring-core/src/main/java/org/springframework/core/type/classreading/AnnotationMetadataReadingVisitor.java +++ b/spring-core/src/main/java/org/springframework/core/type/classreading/AnnotationMetadataReadingVisitor.java @@ -41,6 +41,7 @@ import org.springframework.util.MultiValueMap; * @author Juergen Hoeller * @author Mark Fisher * @author Costin Leau + * @author Phillip Webb * @since 2.5 */ public class AnnotationMetadataReadingVisitor extends ClassMetadataReadingVisitor implements AnnotationMetadata { @@ -51,7 +52,7 @@ public class AnnotationMetadataReadingVisitor extends ClassMetadataReadingVisito protected final Map> metaAnnotationMap = new LinkedHashMap>(4); - protected final Map attributeMap = new LinkedHashMap(4); + protected final MultiValueMap attributeMap = new LinkedMultiValueMap(4); protected final MultiValueMap methodMetadataMap = new LinkedMultiValueMap(); @@ -105,60 +106,30 @@ public class AnnotationMetadataReadingVisitor extends ClassMetadataReadingVisito } public AnnotationAttributes getAnnotationAttributes(String annotationType, boolean classValuesAsString) { - AnnotationAttributes raw = this.attributeMap.get(annotationType); - return convertClassValues(raw, classValuesAsString); + List attributes = this.attributeMap.get(annotationType); + AnnotationAttributes raw = (attributes == null ? null : attributes.get(0)); + return AnnotationReadingVisitorUtils.convertClassValues(this.classLoader, raw, + classValuesAsString); } - private AnnotationAttributes convertClassValues(AnnotationAttributes original, boolean classValuesAsString) { - if (original == null) { + public MultiValueMap getAllAnnotationAttributes(String annotationType) { + return getAllAnnotationAttributes(annotationType, false); + } + + public MultiValueMap getAllAnnotationAttributes( + String annotationType, boolean classValuesAsString) { + MultiValueMap allAttributes = new LinkedMultiValueMap(); + List attributes = this.attributeMap.get(annotationType); + if (attributes == null) { return null; } - AnnotationAttributes result = new AnnotationAttributes(original.size()); - for (Map.Entry entry : original.entrySet()) { - try { - Object value = entry.getValue(); - if (value instanceof AnnotationAttributes) { - value = convertClassValues((AnnotationAttributes) value, classValuesAsString); - } - else if (value instanceof AnnotationAttributes[]) { - AnnotationAttributes[] values = (AnnotationAttributes[])value; - for (int i = 0; i < values.length; i++) { - values[i] = convertClassValues(values[i], classValuesAsString); - } - } - else if (value instanceof Type) { - value = (classValuesAsString ? ((Type) value).getClassName() : - this.classLoader.loadClass(((Type) value).getClassName())); - } - else if (value instanceof Type[]) { - Type[] array = (Type[]) value; - Object[] convArray = (classValuesAsString ? new String[array.length] : new Class[array.length]); - for (int i = 0; i < array.length; i++) { - convArray[i] = (classValuesAsString ? array[i].getClassName() : - this.classLoader.loadClass(array[i].getClassName())); - } - 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) { - // Class not found - can't resolve class reference in annotation attribute. + for (AnnotationAttributes raw : attributes) { + for (Map.Entry entry : AnnotationReadingVisitorUtils.convertClassValues( + this.classLoader, raw, classValuesAsString).entrySet()) { + allAttributes.add(entry.getKey(), entry.getValue()); } } - return result; + return allAttributes; } public boolean hasAnnotatedMethods(String annotationType) { diff --git a/spring-core/src/main/java/org/springframework/core/type/classreading/AnnotationReadingVisitorUtils.java b/spring-core/src/main/java/org/springframework/core/type/classreading/AnnotationReadingVisitorUtils.java new file mode 100644 index 00000000000..82189cc29fc --- /dev/null +++ b/spring-core/src/main/java/org/springframework/core/type/classreading/AnnotationReadingVisitorUtils.java @@ -0,0 +1,92 @@ +/* + * Copyright 2002-2013 the original author 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.type.classreading; + +import java.util.Map; + +import org.springframework.asm.Type; +import org.springframework.core.annotation.AnnotationAttributes; + +/** + * Internal utility class used when reading annotations. + * + * @author Juergen Hoeller + * @author Mark Fisher + * @author Costin Leau + * @author Phillip Webb + * @since 4.0 + */ +abstract class AnnotationReadingVisitorUtils { + + public static AnnotationAttributes convertClassValues(ClassLoader classLoader, + AnnotationAttributes original, boolean classValuesAsString) { + + if (original == null) { + return null; + } + + AnnotationAttributes result = new AnnotationAttributes(original.size()); + for (Map.Entry entry : original.entrySet()) { + try { + Object value = entry.getValue(); + if (value instanceof AnnotationAttributes) { + value = convertClassValues(classLoader, (AnnotationAttributes) value, + classValuesAsString); + } + else if (value instanceof AnnotationAttributes[]) { + AnnotationAttributes[] values = (AnnotationAttributes[])value; + for (int i = 0; i < values.length; i++) { + values[i] = convertClassValues(classLoader, values[i], + classValuesAsString); + } + } + else if (value instanceof Type) { + value = (classValuesAsString ? ((Type) value).getClassName() : + classLoader.loadClass(((Type) value).getClassName())); + } + else if (value instanceof Type[]) { + Type[] array = (Type[]) value; + Object[] convArray = (classValuesAsString ? new String[array.length] : new Class[array.length]); + for (int i = 0; i < array.length; i++) { + convArray[i] = (classValuesAsString ? array[i].getClassName() : + classLoader.loadClass(array[i].getClassName())); + } + 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) { + // Class not found - can't resolve class reference in annotation attribute. + } + } + return result; + } + +} diff --git a/spring-core/src/main/java/org/springframework/core/type/classreading/MethodMetadataReadingVisitor.java b/spring-core/src/main/java/org/springframework/core/type/classreading/MethodMetadataReadingVisitor.java index 4b39bbaea7b..b7e9a1e1209 100644 --- a/spring-core/src/main/java/org/springframework/core/type/classreading/MethodMetadataReadingVisitor.java +++ b/spring-core/src/main/java/org/springframework/core/type/classreading/MethodMetadataReadingVisitor.java @@ -16,7 +16,7 @@ package org.springframework.core.type.classreading; -import java.util.LinkedHashMap; +import java.util.List; import java.util.Map; import org.springframework.asm.AnnotationVisitor; @@ -26,6 +26,7 @@ import org.springframework.asm.SpringAsmInfo; import org.springframework.asm.Type; import org.springframework.core.annotation.AnnotationAttributes; import org.springframework.core.type.MethodMetadata; +import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; /** @@ -37,6 +38,7 @@ import org.springframework.util.MultiValueMap; * @author Mark Pollack * @author Costin Leau * @author Chris Beams + * @author Phillip Webb * @since 3.0 */ public class MethodMetadataReadingVisitor extends MethodVisitor implements MethodMetadata { @@ -51,7 +53,7 @@ public class MethodMetadataReadingVisitor extends MethodVisitor implements Metho protected final MultiValueMap methodMetadataMap; - protected final Map attributeMap = new LinkedHashMap(2); + protected final MultiValueMap attributeMap = new LinkedMultiValueMap(2); public MethodMetadataReadingVisitor(String name, int access, String declaringClassName, ClassLoader classLoader, @@ -93,8 +95,34 @@ public class MethodMetadataReadingVisitor extends MethodVisitor implements Metho return this.attributeMap.containsKey(annotationType); } - public AnnotationAttributes getAnnotationAttributes(String annotationType) { - return this.attributeMap.get(annotationType); + public Map getAnnotationAttributes(String annotationType) { + return getAnnotationAttributes(annotationType, false); + } + + public Map getAnnotationAttributes(String annotationType, + boolean classValuesAsString) { + List attributes = this.attributeMap.get(annotationType); + return (attributes == null ? null : AnnotationReadingVisitorUtils.convertClassValues( + this.classLoader, attributes.get(0), classValuesAsString)); + } + + public MultiValueMap getAllAnnotationAttributes(String annotationType) { + return getAllAnnotationAttributes(annotationType, false); + } + + public MultiValueMap getAllAnnotationAttributes( + String annotationType, boolean classValuesAsString) { + if(!this.attributeMap.containsKey(annotationType)) { + return null; + } + MultiValueMap allAttributes = new LinkedMultiValueMap(); + for (AnnotationAttributes annotationAttributes : this.attributeMap.get(annotationType)) { + for (Map.Entry entry : AnnotationReadingVisitorUtils.convertClassValues( + this.classLoader, annotationAttributes, classValuesAsString).entrySet()) { + allAttributes.add(entry.getKey(), entry.getValue()); + } + } + return allAttributes; } public String getDeclaringClassName() { diff --git a/spring-core/src/test/java/org/springframework/core/type/AnnotationMetadataTests.java b/spring-core/src/test/java/org/springframework/core/type/AnnotationMetadataTests.java index cb6808114c0..870ccd3beea 100644 --- a/spring-core/src/test/java/org/springframework/core/type/AnnotationMetadataTests.java +++ b/spring-core/src/test/java/org/springframework/core/type/AnnotationMetadataTests.java @@ -18,7 +18,9 @@ package org.springframework.core.type; import static org.hamcrest.CoreMatchers.instanceOf; import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.Matchers.equalTo; import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertThat; import static org.junit.Assert.assertTrue; @@ -29,10 +31,12 @@ import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; import java.util.Set; import org.junit.Test; - import org.springframework.core.annotation.AnnotationAttributes; import org.springframework.core.type.classreading.MetadataReader; import org.springframework.core.type.classreading.MetadataReaderFactory; @@ -45,6 +49,7 @@ import org.springframework.stereotype.Component; * * @author Juergen Hoeller * @author Chris Beams + * @author Phillip Webb */ public class AnnotationMetadataTests { @@ -93,7 +98,7 @@ public class AnnotationMetadataTests { assertThat(metadata.hasAnnotation(Component.class.getName()), is(true)); assertThat(metadata.hasAnnotation(Scope.class.getName()), is(true)); assertThat(metadata.hasAnnotation(SpecialAttr.class.getName()), is(true)); - assertThat(metadata.getAnnotationTypes().size(), is(3)); + assertThat(metadata.getAnnotationTypes().size(), is(5)); assertThat(metadata.getAnnotationTypes().contains(Component.class.getName()), is(true)); assertThat(metadata.getAnnotationTypes().contains(Scope.class.getName()), is(true)); assertThat(metadata.getAnnotationTypes().contains(SpecialAttr.class.getName()), is(true)); @@ -104,6 +109,15 @@ public class AnnotationMetadataTests { AnnotationAttributes scopeAttrs = (AnnotationAttributes) metadata.getAnnotationAttributes(Scope.class.getName()); assertThat(scopeAttrs.size(), is(1)); assertThat(scopeAttrs.getString("value"), is("myScope")); + + Set methods = metadata.getAnnotatedMethods(DirectAnnotation.class.getName()); + MethodMetadata method = methods.iterator().next(); + assertEquals("direct", method.getAnnotationAttributes(DirectAnnotation.class.getName()).get("value")); + List allMeta = method.getAllAnnotationAttributes(DirectAnnotation.class.getName()).get("value"); + assertThat(new HashSet(allMeta), is(equalTo(new HashSet(Arrays.asList("direct", "meta"))))); + + assertTrue(metadata.isAnnotated(IsAnnotatedAnnotation.class.getName())); + { // perform tests with classValuesAsString = false (the default) AnnotationAttributes specialAttrs = (AnnotationAttributes) metadata.getAnnotationAttributes(SpecialAttr.class.getName()); assertThat(specialAttrs.size(), is(6)); @@ -137,6 +151,10 @@ public class AnnotationMetadataTests { assertTrue(optionalArray[0].getEnum("anEnum").equals(SomeEnum.DEFAULT)); assertArrayEquals(new Class[]{Void.class}, (Class[])optionalArray[0].get("classArray")); assertArrayEquals(new Class[]{Void.class}, optionalArray[0].getClassArray("classArray")); + + assertEquals("direct", metadata.getAnnotationAttributes(DirectAnnotation.class.getName()).get("value")); + allMeta = metadata.getAllAnnotationAttributes(DirectAnnotation.class.getName()).get("value"); + assertThat(new HashSet(allMeta), is(equalTo(new HashSet(Arrays.asList("direct", "meta"))))); } { // perform tests with classValuesAsString = true AnnotationAttributes specialAttrs = (AnnotationAttributes) metadata.getAnnotationAttributes(SpecialAttr.class.getName(), true); @@ -161,6 +179,10 @@ public class AnnotationMetadataTests { AnnotationAttributes[] optionalArray = specialAttrs.getAnnotationArray("optionalArray"); assertArrayEquals(new String[]{Void.class.getName()}, (String[])optionalArray[0].get("classArray")); assertArrayEquals(new String[]{Void.class.getName()}, optionalArray[0].getStringArray("classArray")); + + assertEquals(metadata.getAnnotationAttributes(DirectAnnotation.class.getName()).get("value"), "direct"); + allMeta = metadata.getAllAnnotationAttributes(DirectAnnotation.class.getName()).get("value"); + assertThat(new HashSet(allMeta), is(equalTo(new HashSet(Arrays.asList("direct", "meta"))))); } } @@ -201,6 +223,29 @@ public class AnnotationMetadataTests { NestedAnno[] optionalArray() default {@NestedAnno(value="optional", anEnum=SomeEnum.DEFAULT, classArray=Void.class)}; } + @Target({ ElementType.TYPE, ElementType.METHOD }) + @Retention(RetentionPolicy.RUNTIME) + public @interface DirectAnnotation { + String value(); + } + + @Target({ ElementType.TYPE }) + @Retention(RetentionPolicy.RUNTIME) + public @interface IsAnnotatedAnnotation { + } + + @Target(ElementType.TYPE) + @Retention(RetentionPolicy.RUNTIME) + @DirectAnnotation("meta") + @IsAnnotatedAnnotation + public @interface MetaAnnotation { + } + + @Target({ ElementType.TYPE, ElementType.METHOD }) + @Retention(RetentionPolicy.RUNTIME) + @MetaAnnotation + public @interface MetaMetaAnnotation { + } @Component("myName") @Scope("myScope") @@ -211,6 +256,8 @@ public class AnnotationMetadataTests { @NestedAnno(value = "na1", anEnum = SomeEnum.LABEL2, classArray = {Number.class}) }) @SuppressWarnings({"serial", "unused"}) + @DirectAnnotation("direct") + @MetaMetaAnnotation private static class AnnotatedComponent implements Serializable { @TestAutowired @@ -219,6 +266,11 @@ public class AnnotationMetadataTests { public void doSleep() { } + + @DirectAnnotation("direct") + @MetaMetaAnnotation + public void meta() { + } } } From b257253a2b6890ef532f27a662c441fc4fd21f3b Mon Sep 17 00:00:00 2001 From: Phillip Webb Date: Wed, 20 Feb 2013 10:32:53 -0800 Subject: [PATCH 2/3] Support for @Conditional configuration Introduce new @Conditional annotation that can be used to filter which @Configuration classes or methods are loaded. @Conditional can be used directly or as a meta-annotation. Condition implementations are provided via the 'Condition' interface and are free to filter based on any criteria available at the time that they run. The ConditionalContext provides access to the BeanDefinitionRegistry, Environment and ConfigurableListableBeanFactory along with a ResourceLoader and ClassLoader. The existing @Profile annotation has been refactored as a @Conditional with the added benefit that it can now be used as a method level annotation. --- .../AnnotatedBeanDefinitionReader.java | 14 +- .../ClassPathBeanDefinitionScanner.java | 7 +- .../context/annotation/Condition.java | 52 +++++ .../context/annotation/ConditionContext.java | 70 ++++++ .../context/annotation/Conditional.java | 58 +++++ .../ConditionalAnnotationHelper.java | 212 ++++++++++++++++++ .../annotation/ConfigurationClass.java | 38 ++-- ...onfigurationClassBeanDefinitionReader.java | 12 +- .../annotation/ConfigurationClassParser.java | 30 ++- .../ConfigurationClassPostProcessor.java | 10 +- .../context/annotation/Profile.java | 11 +- .../context/annotation/ProfileCondition.java | 42 ++++ .../ConfigurationClassWithConditionTests.java | 170 ++++++++++++++ 13 files changed, 681 insertions(+), 45 deletions(-) create mode 100644 spring-context/src/main/java/org/springframework/context/annotation/Condition.java create mode 100644 spring-context/src/main/java/org/springframework/context/annotation/ConditionContext.java create mode 100644 spring-context/src/main/java/org/springframework/context/annotation/Conditional.java create mode 100644 spring-context/src/main/java/org/springframework/context/annotation/ConditionalAnnotationHelper.java create mode 100644 spring-context/src/main/java/org/springframework/context/annotation/ProfileCondition.java create mode 100644 spring-context/src/test/java/org/springframework/context/annotation/ConfigurationClassWithConditionTests.java diff --git a/spring-context/src/main/java/org/springframework/context/annotation/AnnotatedBeanDefinitionReader.java b/spring-context/src/main/java/org/springframework/context/annotation/AnnotatedBeanDefinitionReader.java index cfbc4237141..b660b0355b2 100644 --- a/spring-context/src/main/java/org/springframework/context/annotation/AnnotatedBeanDefinitionReader.java +++ b/spring-context/src/main/java/org/springframework/context/annotation/AnnotatedBeanDefinitionReader.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author 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,9 @@ import org.springframework.beans.factory.support.AutowireCandidateQualifier; import org.springframework.beans.factory.support.BeanDefinitionReaderUtils; import org.springframework.beans.factory.support.BeanDefinitionRegistry; import org.springframework.beans.factory.support.BeanNameGenerator; -import org.springframework.core.annotation.AnnotationAttributes; import org.springframework.core.env.Environment; import org.springframework.core.env.EnvironmentCapable; import org.springframework.core.env.StandardEnvironment; -import org.springframework.core.type.AnnotationMetadata; import org.springframework.util.Assert; /** @@ -39,6 +37,7 @@ import org.springframework.util.Assert; * @author Juergen Hoeller * @author Chris Beams * @author Sam Brannen + * @author Phillip Webb * @since 3.0 * @see AnnotationConfigApplicationContext#register */ @@ -134,12 +133,9 @@ public class AnnotatedBeanDefinitionReader { public void registerBean(Class annotatedClass, String name, Class... qualifiers) { AnnotatedGenericBeanDefinition abd = new AnnotatedGenericBeanDefinition(annotatedClass); - AnnotationMetadata metadata = abd.getMetadata(); - if (metadata.isAnnotated(Profile.class.getName())) { - AnnotationAttributes profile = MetadataUtils.attributesFor(metadata, Profile.class); - if (!this.environment.acceptsProfiles(profile.getStringArray("value"))) { - return; - } + if (ConditionalAnnotationHelper.shouldSkip(abd, this.registry, + this.environment, this.beanNameGenerator)) { + return; } ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(abd); abd.setScope(scopeMetadata.getScopeName()); diff --git a/spring-context/src/main/java/org/springframework/context/annotation/ClassPathBeanDefinitionScanner.java b/spring-context/src/main/java/org/springframework/context/annotation/ClassPathBeanDefinitionScanner.java index b3f9450f983..a016b885545 100644 --- a/spring-context/src/main/java/org/springframework/context/annotation/ClassPathBeanDefinitionScanner.java +++ b/spring-context/src/main/java/org/springframework/context/annotation/ClassPathBeanDefinitionScanner.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -52,6 +52,7 @@ import org.springframework.util.PatternMatchUtils; * @author Mark Fisher * @author Juergen Hoeller * @author Chris Beams + * @author Phillip Webb * @since 2.5 * @see AnnotationConfigApplicationContext#scan * @see org.springframework.stereotype.Component @@ -298,6 +299,10 @@ public class ClassPathBeanDefinitionScanner extends ClassPathScanningCandidateCo * bean definition has been found for the specified name */ protected boolean checkCandidate(String beanName, BeanDefinition beanDefinition) throws IllegalStateException { + if (ConditionalAnnotationHelper.shouldSkip(beanDefinition, getRegistry(), + getEnvironment(), this.beanNameGenerator)) { + return false; + } if (!this.registry.containsBeanDefinition(beanName)) { return true; } diff --git a/spring-context/src/main/java/org/springframework/context/annotation/Condition.java b/spring-context/src/main/java/org/springframework/context/annotation/Condition.java new file mode 100644 index 00000000000..69543830aab --- /dev/null +++ b/spring-context/src/main/java/org/springframework/context/annotation/Condition.java @@ -0,0 +1,52 @@ +/* + * Copyright 2002-2013 the original author 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.jruby.internal.runtime.methods.MethodMethod; +import org.springframework.beans.factory.config.BeanFactoryPostProcessor; +import org.springframework.core.type.AnnotatedTypeMetadata; +import org.springframework.core.type.AnnotationMetadata; + +/** + * A single {@code condition} that must be {@linkplain #matches matched} in order + * for a component to be registered. + * + *

Conditions are checked immediately before a component bean-definition is due to be + * registered and are free to veto registration based on any criteria that can be + * determined at that point. + * + *

Conditions must follow the same restrictions as {@link BeanFactoryPostProcessor} + * and take care to never interact with bean instances. + * + * @author Phillip Webb + * @since 4.0 + * @see Conditional + * @see ConditionContext + */ +public interface Condition { + + /** + * Determine if the condition matches. + * @param context the condition context + * @param metadata meta-data of the {@link AnnotationMetadata class} or + * {@link MethodMethod method} being checked. + * @return {@code true} if the condition matches and the component can be registered + * or {@code false} to veto registration. + */ + boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata); + +} diff --git a/spring-context/src/main/java/org/springframework/context/annotation/ConditionContext.java b/spring-context/src/main/java/org/springframework/context/annotation/ConditionContext.java new file mode 100644 index 00000000000..564586501c1 --- /dev/null +++ b/spring-context/src/main/java/org/springframework/context/annotation/ConditionContext.java @@ -0,0 +1,70 @@ +/* + * Copyright 2002-2013 the original author 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.ConfigurableListableBeanFactory; +import org.springframework.beans.factory.support.BeanDefinitionRegistry; +import org.springframework.core.env.Environment; +import org.springframework.core.io.ResourceLoader; + +/** + * Context information for use by {@link Condition}s. + * + * @author Phillip Webb + * @since 4.0 + */ +public interface ConditionContext { + + /** + * Returns the {@link BeanDefinitionRegistry} that will hold the bean definition + * should the condition match. + * @return the registry (never {@code null}) + */ + BeanDefinitionRegistry getRegistry(); + + /** + * Return the {@link Environment} for which the current application is running or + * {@code null} if no environment is available. + * @return the environment or {@code null} + */ + Environment getEnvironment(); + + /** + * Returns the {@link ConfigurableListableBeanFactory} that will hold the bean + * definition should the condition match. If a + * {@link ConfigurableListableBeanFactory} is unavailable an + * {@link IllegalStateException} will be thrown. + * @return the bean factory + * @throws IllegalStateException if the bean factory could not be obtained + */ + ConfigurableListableBeanFactory getBeanFactory() throws IllegalStateException; + + /** + * Returns the {@link ResourceLoader} currently being used or {@code null} if the + * resource loader cannot be obtained. + * @return a resource loader or {@code null} + */ + ResourceLoader getResourceLoader(); + + /** + * Returns the {@link ClassLoader} that should be used to load additional classes + * or {@code null} if the default classloader should be used. + * @return the classloader or {@code null} + */ + ClassLoader getClassLoader(); + +} diff --git a/spring-context/src/main/java/org/springframework/context/annotation/Conditional.java b/spring-context/src/main/java/org/springframework/context/annotation/Conditional.java new file mode 100644 index 00000000000..9a987142547 --- /dev/null +++ b/spring-context/src/main/java/org/springframework/context/annotation/Conditional.java @@ -0,0 +1,58 @@ +/* + * Copyright 2002-2013 the original author 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; + +/** + * Indicates that a component is is only eligible for registration when all + * {@linkplain #value() specified conditions} match. + * + *

A condition is any state that can be determined programmatically + * immediately before the bean is due to be created (see {@link Condition} for details). + * + *

The {@code @Conditional} annotation may be used in any of the following ways: + *

    + *
  • as a type-level annotation on any class directly or indirectly annotated with + * {@code @Component}, including {@link Configuration @Configuration} classes
  • + *
  • as a meta-annotation, for the purpose of composing custom stereotype + * annotations
  • + *
  • as a method-level annotation on any {@link Bean @Bean} method
  • + *
+ * + *

If a {@code @Configuration} class is marked with {@code @Conditional}, all of the + * {@code @Bean} methods and {@link Import @Import} annotations associated with that class + * will be subject to the conditions. + * + * @author Phillip Webb + * @since 4.0 + * @see Condition + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ ElementType.TYPE, ElementType.METHOD }) +public @interface Conditional { + + /** + * All {@link Condition}s that must {@linkplain Condition#matches match} in order for + * the component to be registered. + */ + Class[] value(); + +} diff --git a/spring-context/src/main/java/org/springframework/context/annotation/ConditionalAnnotationHelper.java b/spring-context/src/main/java/org/springframework/context/annotation/ConditionalAnnotationHelper.java new file mode 100644 index 00000000000..acd766ea48b --- /dev/null +++ b/spring-context/src/main/java/org/springframework/context/annotation/ConditionalAnnotationHelper.java @@ -0,0 +1,212 @@ +/* + * Copyright 2002-2013 the original author 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.Collections; +import java.util.List; + +import org.springframework.beans.BeanUtils; +import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition; +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; +import org.springframework.beans.factory.support.BeanDefinitionRegistry; +import org.springframework.beans.factory.support.BeanNameGenerator; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.core.env.Environment; +import org.springframework.core.env.EnvironmentCapable; +import org.springframework.core.io.ResourceLoader; +import org.springframework.core.type.AnnotatedTypeMetadata; +import org.springframework.util.Assert; +import org.springframework.util.ClassUtils; +import org.springframework.util.MultiValueMap; + +/** + * Helper class used to determine if registration should be skipped based due to a + * {@code @Conditional} annotation. + * + * @author Phillip Webb + * @since 4.0 + * @see Conditional + */ +abstract class ConditionalAnnotationHelper { + + private static final String CONDITIONAL_ANNOTATION = Conditional.class.getName(); + + + public static boolean shouldSkip(BeanDefinition beanDefinition, + BeanDefinitionRegistry registry, Environment environment, + BeanNameGenerator beanNameGenerator) { + if (hasCondition(getMetadata(beanDefinition))) { + ConditionContextImpl context = new ConditionContextImpl(registry, + environment, beanNameGenerator); + return shouldSkip(getMetadata(beanDefinition), context); + } + return false; + } + + public static boolean shouldSkip(BeanMethod beanMethod, + BeanDefinitionRegistry registry, Environment environment, + BeanNameGenerator beanNameGenerator) { + if (hasCondition(getMetadata(beanMethod))) { + ConditionContextImpl context = new ConditionContextImpl(registry, + environment, beanNameGenerator); + return shouldSkip(getMetadata(beanMethod), context); + } + return false; + } + + public static boolean shouldSkip(ConfigurationClass configurationClass, + BeanDefinitionRegistry registry, Environment environment, + BeanNameGenerator beanNameGenerator) { + if (hasCondition(configurationClass)) { + ConditionContextImpl context = new ConditionContextImpl(registry, + environment, beanNameGenerator); + return shouldSkip(configurationClass, context); + } + return false; + } + + public static boolean shouldSkip(ConfigurationClass configClass, + ConditionContextImpl context) { + if (configClass == null) { + return false; + } + return shouldSkip(configClass.getMetadata(), context); + } + + private static boolean shouldSkip(AnnotatedTypeMetadata metadata, + ConditionContextImpl context) { + if (metadata != null) { + for (String[] conditionClasses : getConditionClasses(metadata)) { + for (String conditionClass : conditionClasses) { + if (!getCondition(conditionClass, context.getClassLoader()).matches( + context, metadata)) { + return true; + } + } + } + } + return false; + } + + private static AnnotatedTypeMetadata getMetadata(BeanMethod beanMethod) { + return (beanMethod == null ? null : beanMethod.getMetadata()); + } + + private static AnnotatedTypeMetadata getMetadata(BeanDefinition beanDefinition) { + if (beanDefinition != null && beanDefinition instanceof AnnotatedBeanDefinition) { + return ((AnnotatedBeanDefinition) beanDefinition).getMetadata(); + } + return null; + } + + private static boolean hasCondition(ConfigurationClass configurationClass) { + if (configurationClass == null) { + return false; + } + return hasCondition(configurationClass.getMetadata()) + || hasCondition(configurationClass.getImportedBy()); + } + + private static boolean hasCondition(AnnotatedTypeMetadata metadata) { + return (metadata != null) && metadata.isAnnotated(CONDITIONAL_ANNOTATION); + } + + @SuppressWarnings("unchecked") + private static List getConditionClasses(AnnotatedTypeMetadata metadata) { + MultiValueMap attributes = metadata.getAllAnnotationAttributes( + CONDITIONAL_ANNOTATION, true); + Object values = attributes == null ? null : attributes.get("value"); + return (List) (values == null ? Collections.emptyList() : values); + } + + private static Condition getCondition(String conditionClassName, + ClassLoader classloader) { + Class conditionClass = ClassUtils.resolveClassName(conditionClassName, + classloader); + return (Condition) BeanUtils.instantiateClass(conditionClass); + } + + + /** + * Implementation of a {@link ConditionContext}. + */ + private static class ConditionContextImpl implements ConditionContext { + + private BeanDefinitionRegistry registry; + + private ConfigurableListableBeanFactory beanFactory; + + private Environment environment; + + + public ConditionContextImpl(BeanDefinitionRegistry registry, + Environment environment, BeanNameGenerator beanNameGenerator) { + Assert.notNull(registry, "Registry must not be null"); + this.registry = registry; + this.beanFactory = deduceBeanFactory(registry); + this.environment = environment; + if (this.environment == null) { + this.environment = deduceEnvironment(registry); + } + } + + + private ConfigurableListableBeanFactory deduceBeanFactory(Object source) { + if (source instanceof ConfigurableListableBeanFactory) { + return (ConfigurableListableBeanFactory) source; + } + else if (source instanceof ConfigurableApplicationContext) { + return deduceBeanFactory(((ConfigurableApplicationContext) source).getBeanFactory()); + } + return null; + } + + private Environment deduceEnvironment(BeanDefinitionRegistry registry) { + if (registry instanceof EnvironmentCapable) { + return ((EnvironmentCapable) registry).getEnvironment(); + } + return null; + } + + public BeanDefinitionRegistry getRegistry() { + return this.registry; + } + + public Environment getEnvironment() { + return this.environment; + } + + public ConfigurableListableBeanFactory getBeanFactory() { + Assert.state(this.beanFactory != null, "Unable to locate the BeanFactory"); + return this.beanFactory; + } + + public ResourceLoader getResourceLoader() { + if (registry instanceof ResourceLoader) { + return (ResourceLoader) registry; + } + return null; + } + + public ClassLoader getClassLoader() { + ResourceLoader resourceLoader = getResourceLoader(); + return (resourceLoader == null ? null : resourceLoader.getClassLoader()); + } + } + +} diff --git a/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClass.java b/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClass.java index 0a5a6460924..6ed1f643093 100644 --- a/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClass.java +++ b/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClass.java @@ -41,6 +41,7 @@ import org.springframework.util.ClassUtils; * * @author Chris Beams * @author Juergen Hoeller + * @author Phillip Webb * @since 3.0 * @see BeanMethod * @see ConfigurationClassParser @@ -58,7 +59,7 @@ final class ConfigurationClass { private String beanName; - private final boolean imported; + private final ConfigurationClass importedBy; /** @@ -66,28 +67,28 @@ final class ConfigurationClass { * @param metadataReader reader used to parse the underlying {@link Class} * @param beanName must not be {@code null} * @throws IllegalArgumentException if beanName is null (as of Spring 3.1.1) - * @see ConfigurationClass#ConfigurationClass(Class, boolean) + * @see ConfigurationClass#ConfigurationClass(Class, ConfigurationClass) */ public ConfigurationClass(MetadataReader metadataReader, String beanName) { Assert.hasText(beanName, "bean name must not be null"); this.metadata = metadataReader.getAnnotationMetadata(); this.resource = metadataReader.getResource(); this.beanName = beanName; - this.imported = false; + this.importedBy = null; } /** * Create a new {@link ConfigurationClass} representing a class that was imported * using the {@link Import} annotation or automatically processed as a nested - * configuration class (if imported is {@code true}). + * configuration class (if importedBy is not {@code null}). * @param metadataReader reader used to parse the underlying {@link Class} - * @param imported whether the given configuration class is being imported + * @param importedBy the configuration class importing this one or {@code null} * @since 3.1.1 */ - public ConfigurationClass(MetadataReader metadataReader, boolean imported) { + public ConfigurationClass(MetadataReader metadataReader, ConfigurationClass importedBy) { this.metadata = metadataReader.getAnnotationMetadata(); this.resource = metadataReader.getResource(); - this.imported = imported; + this.importedBy = importedBy; } /** @@ -95,14 +96,14 @@ final class ConfigurationClass { * @param clazz the underlying {@link Class} to represent * @param beanName name of the {@code @Configuration} class bean * @throws IllegalArgumentException if beanName is null (as of Spring 3.1.1) - * @see ConfigurationClass#ConfigurationClass(Class, boolean) + * @see ConfigurationClass#ConfigurationClass(Class, ConfigurationClass) */ public ConfigurationClass(Class clazz, String beanName) { Assert.hasText(beanName, "bean name must not be null"); this.metadata = new StandardAnnotationMetadata(clazz, true); this.resource = new DescriptiveResource(clazz.toString()); this.beanName = beanName; - this.imported = false; + this.importedBy = null; } /** @@ -110,13 +111,13 @@ final class ConfigurationClass { * using the {@link Import} annotation or automatically processed as a nested * configuration class (if imported is {@code true}). * @param clazz the underlying {@link Class} to represent - * @param imported whether the given configuration class is being imported + * @param importedBy the configuration class importing this one or {@code null} * @since 3.1.1 */ - public ConfigurationClass(Class clazz, boolean imported) { + public ConfigurationClass(Class clazz, ConfigurationClass importedBy) { this.metadata = new StandardAnnotationMetadata(clazz, true); this.resource = new DescriptiveResource(clazz.toString()); - this.imported = imported; + this.importedBy = importedBy; } @@ -136,9 +137,20 @@ final class ConfigurationClass { * Return whether this configuration class was registered via @{@link Import} or * automatically registered due to being nested within another configuration class. * @since 3.1.1 + * @see #getImportedBy() */ public boolean isImported() { - return this.imported; + return this.importedBy != null; + } + + /** + * Returns the configuration class that imported this class or {@code null} if + * this configuration was not imported. + * @since 4.0 + * @see #isImported() + */ + public ConfigurationClass getImportedBy() { + return importedBy; } public void setBeanName(String beanName) { diff --git a/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassBeanDefinitionReader.java b/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassBeanDefinitionReader.java index 27ae171955c..eab2b0bd26b 100644 --- a/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassBeanDefinitionReader.java +++ b/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassBeanDefinitionReader.java @@ -16,6 +16,8 @@ package org.springframework.context.annotation; +import static org.springframework.context.annotation.MetadataUtils.attributesFor; + import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Arrays; @@ -26,7 +28,6 @@ import java.util.Set; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; - import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition; import org.springframework.beans.factory.annotation.AnnotatedGenericBeanDefinition; import org.springframework.beans.factory.annotation.Autowire; @@ -51,8 +52,6 @@ import org.springframework.core.type.MethodMetadata; import org.springframework.core.type.classreading.MetadataReaderFactory; import org.springframework.util.StringUtils; -import static org.springframework.context.annotation.MetadataUtils.*; - /** * Reads a given fully-populated set of ConfigurationClass instances, registering bean * definitions with the given {@link BeanDefinitionRegistry} based on its contents. @@ -63,6 +62,7 @@ import static org.springframework.context.annotation.MetadataUtils.*; * * @author Chris Beams * @author Juergen Hoeller + * @author Phillip Webb * @since 3.0 * @see ConfigurationClassParser */ @@ -118,7 +118,7 @@ class ConfigurationClassBeanDefinitionReader { * Read a particular {@link ConfigurationClass}, registering bean definitions for the * class itself, all its {@link Bean} methods */ - private void loadBeanDefinitionsForConfigurationClass(ConfigurationClass configClass) { + public void loadBeanDefinitionsForConfigurationClass(ConfigurationClass configClass) { if (configClass.isImported()) { registerBeanDefinitionForImportedConfigurationClass(configClass); } @@ -153,6 +153,10 @@ class ConfigurationClassBeanDefinitionReader { * with the BeanDefinitionRegistry based on its contents. */ private void loadBeanDefinitionsForBeanMethod(BeanMethod beanMethod) { + if (ConditionalAnnotationHelper.shouldSkip(beanMethod, this.registry, + this.environment, this.importBeanNameGenerator)) { + return; + } ConfigurationClass configClass = beanMethod.getConfigurationClass(); MethodMetadata metadata = beanMethod.getMetadata(); diff --git a/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassParser.java b/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassParser.java index 82fbbeba528..93731a00258 100644 --- a/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassParser.java +++ b/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassParser.java @@ -76,6 +76,7 @@ import static org.springframework.context.annotation.MetadataUtils.*; * * @author Chris Beams * @author Juergen Hoeller + * @author Phillip Webb * @since 3.0 * @see ConfigurationClassBeanDefinitionReader */ @@ -103,6 +104,8 @@ class ConfigurationClassParser { private final ComponentScanAnnotationParser componentScanParser; + private final BeanNameGenerator beanNameGenerator; + /** * Create a new {@link ConfigurationClassParser} instance that will be used @@ -117,6 +120,7 @@ class ConfigurationClassParser { this.environment = environment; this.resourceLoader = resourceLoader; this.registry = registry; + this.beanNameGenerator = componentScanBeanNameGenerator; this.componentScanParser = new ComponentScanAnnotationParser( resourceLoader, environment, componentScanBeanNameGenerator, registry); } @@ -144,11 +148,10 @@ class ConfigurationClassParser { protected void processConfigurationClass(ConfigurationClass configClass) throws IOException { AnnotationMetadata metadata = configClass.getMetadata(); - if (this.environment != null && metadata.isAnnotated(Profile.class.getName())) { - AnnotationAttributes profile = MetadataUtils.attributesFor(metadata, Profile.class); - if (!this.environment.acceptsProfiles(profile.getStringArray("value"))) { - return; - } + + if (ConditionalAnnotationHelper.shouldSkip(configClass, this.registry, + this.environment, this.beanNameGenerator)) { + return; } // recursively process the configuration class and its superclass hierarchy @@ -173,7 +176,7 @@ class ConfigurationClassParser { ConfigurationClass configClass, AnnotationMetadata metadata) throws IOException { // recursively process any member (nested) classes first - processMemberClasses(metadata); + processMemberClasses(configClass, metadata); // process any @PropertySource annotations AnnotationAttributes propertySource = attributesFor(metadata, org.springframework.context.annotation.PropertySource.class); @@ -256,11 +259,12 @@ class ConfigurationClassParser { * @param metadata the metadata representation of the containing class * @throws IOException if there is any problem reading metadata from a member class */ - private void processMemberClasses(AnnotationMetadata metadata) throws IOException { + private void processMemberClasses(ConfigurationClass configClass, + AnnotationMetadata metadata) throws IOException { if (metadata instanceof StandardAnnotationMetadata) { for (Class memberClass : ((StandardAnnotationMetadata) metadata).getIntrospectedClass().getDeclaredClasses()) { if (ConfigurationClassUtils.isConfigurationCandidate(new StandardAnnotationMetadata(memberClass))) { - processConfigurationClass(new ConfigurationClass(memberClass, true)); + processConfigurationClass(new ConfigurationClass(memberClass, configClass)); } } } @@ -269,7 +273,7 @@ class ConfigurationClassParser { MetadataReader reader = this.metadataReaderFactory.getMetadataReader(memberClassName); AnnotationMetadata memberClassMetadata = reader.getAnnotationMetadata(); if (ConfigurationClassUtils.isConfigurationCandidate(memberClassMetadata)) { - processConfigurationClass(new ConfigurationClass(reader, true)); + processConfigurationClass(new ConfigurationClass(reader, configClass)); } } } @@ -391,9 +395,11 @@ class ConfigurationClassParser { } else { // candidate class not an ImportSelector or ImportBeanDefinitionRegistrar -> process it as a @Configuration class - this.importStack.registerImport(importingClassMetadata.getClassName(), (candidate instanceof Class ? ((Class) candidate).getName() : (String) candidate)); - processConfigurationClass(candidateToCheck instanceof Class ? new ConfigurationClass((Class) candidateToCheck, true) : - new ConfigurationClass((MetadataReader) candidateToCheck, true)); + this.importStack.registerImport(importingClassMetadata.getClassName(), + (candidate instanceof Class ? ((Class) candidate).getName() : (String) candidate)); + processConfigurationClass((candidateToCheck instanceof Class ? + new ConfigurationClass((Class) candidateToCheck, configClass) : + new ConfigurationClass((MetadataReader) candidateToCheck, configClass))); } } } diff --git a/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassPostProcessor.java b/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassPostProcessor.java index 7afbc027337..933192970a6 100644 --- a/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassPostProcessor.java +++ b/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassPostProcessor.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -82,6 +82,7 @@ import static org.springframework.context.annotation.AnnotationConfigUtils.*; * * @author Chris Beams * @author Juergen Hoeller + * @author Phillip Webb * @since 3.0 */ public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPostProcessor, @@ -312,7 +313,12 @@ public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPo registry, this.sourceExtractor, this.problemReporter, this.metadataReaderFactory, this.resourceLoader, this.environment, this.importBeanNameGenerator); } - this.reader.loadBeanDefinitions(parser.getConfigurationClasses()); + for (ConfigurationClass configurationClass : parser.getConfigurationClasses()) { + if (!ConditionalAnnotationHelper.shouldSkip(configurationClass, registry, + this.environment, this.importBeanNameGenerator)) { + reader.loadBeanDefinitionsForConfigurationClass(configurationClass); + } + } // Register the ImportRegistry as a bean in order to support ImportAware @Configuration classes if (singletonRegistry != null) { diff --git a/spring-context/src/main/java/org/springframework/context/annotation/Profile.java b/spring-context/src/main/java/org/springframework/context/annotation/Profile.java index ab793599c01..27900952cdc 100644 --- a/spring-context/src/main/java/org/springframework/context/annotation/Profile.java +++ b/spring-context/src/main/java/org/springframework/context/annotation/Profile.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -38,8 +38,9 @@ import org.springframework.core.env.ConfigurableEnvironment; *

The {@code @Profile} annotation may be used in any of the following ways: *

    *
  • as a type-level annotation on any class directly or indirectly annotated with - * {@code @Component}, including {@link Configuration @Configuration} classes - *
  • as a meta-annotation, for the purpose of composing custom stereotype annotations + * {@code @Component}, including {@link Configuration @Configuration} classes
  • + *
  • as a meta-annotation, for the purpose of composing custom stereotype annotations
  • + *
  • as a method-level annotation on any {@link Bean @Bean} method
  • *
* *

If a {@code @Configuration} class is marked with {@code @Profile}, all of the @@ -65,6 +66,7 @@ import org.springframework.core.env.ConfigurableEnvironment; * {@code spring-beans} XSD (version 3.1 or greater) for details. * * @author Chris Beams + * @author Phillip Webb * @since 3.1 * @see ConfigurableEnvironment#setActiveProfiles * @see ConfigurableEnvironment#setDefaultProfiles @@ -72,7 +74,8 @@ import org.springframework.core.env.ConfigurableEnvironment; * @see AbstractEnvironment#DEFAULT_PROFILES_PROPERTY_NAME */ @Retention(RetentionPolicy.RUNTIME) -@Target(ElementType.TYPE) +@Target({ ElementType.TYPE, ElementType.METHOD }) +@Conditional(ProfileCondition.class) public @interface Profile { /** diff --git a/spring-context/src/main/java/org/springframework/context/annotation/ProfileCondition.java b/spring-context/src/main/java/org/springframework/context/annotation/ProfileCondition.java new file mode 100644 index 00000000000..424e20ae098 --- /dev/null +++ b/spring-context/src/main/java/org/springframework/context/annotation/ProfileCondition.java @@ -0,0 +1,42 @@ +/* + * Copyright 2002-2013 the original author 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.core.annotation.AnnotationAttributes; +import org.springframework.core.type.AnnotatedTypeMetadata; + +/** + * {@link Condition} that matches based on the value of a {@link Profile @Profile} + * annotation. + * + * @author Chris Beams + * @author Phillip Webb + * @since 4.0 + */ +class ProfileCondition implements Condition { + + public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) { + if (context.getEnvironment() != null && metadata.isAnnotated(Profile.class.getName())) { + AnnotationAttributes profile = AnnotationAttributes.fromMap(metadata.getAnnotationAttributes(Profile.class.getName())); + if (!context.getEnvironment().acceptsProfiles(profile.getStringArray("value"))) { + return false; + } + } + return true; + } + +} diff --git a/spring-context/src/test/java/org/springframework/context/annotation/ConfigurationClassWithConditionTests.java b/spring-context/src/test/java/org/springframework/context/annotation/ConfigurationClassWithConditionTests.java new file mode 100644 index 00000000000..9506b817c91 --- /dev/null +++ b/spring-context/src/test/java/org/springframework/context/annotation/ConfigurationClassWithConditionTests.java @@ -0,0 +1,170 @@ +/* + * Copyright 2002-2013 the original author 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.Matchers.equalTo; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.springframework.beans.factory.NoSuchBeanDefinitionException; +import org.springframework.core.annotation.AnnotationAttributes; +import org.springframework.core.type.AnnotatedTypeMetadata; +import org.springframework.stereotype.Component; + +/** + * Test for {@link Conditional} beans. + * + * @author Phillip Webb + */ +public class ConfigurationClassWithConditionTests { + + @Rule + public ExpectedException thrown = ExpectedException.none(); + + @Test + public void conditionalOnBeanMatch() throws Exception { + AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(); + ctx.register(BeanOneConfiguration.class, BeanTwoConfiguration.class); + ctx.refresh(); + assertTrue(ctx.containsBean("bean1")); + assertFalse(ctx.containsBean("bean2")); + } + + @Test + public void conditionalOnBeanNoMatch() throws Exception { + AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(); + ctx.register(BeanTwoConfiguration.class); + ctx.refresh(); + assertTrue(ctx.containsBean("bean2")); + } + + @Test + public void metaConditional() throws Exception { + AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(); + ctx.register(ConfigurationWithMetaCondition.class); + ctx.refresh(); + assertTrue(ctx.containsBean("bean")); + } + + @Test + public void nonConfigurationClass() throws Exception { + AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(); + ctx.register(NonConfigurationClass.class); + ctx.refresh(); + thrown.expect(NoSuchBeanDefinitionException.class); + assertNull(ctx.getBean(NonConfigurationClass.class)); + } + + @Test + public void methodConditional() throws Exception { + AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(); + ctx.register(ConditionOnMethodConfiguration.class); + ctx.refresh(); + thrown.expect(NoSuchBeanDefinitionException.class); + assertNull(ctx.getBean(ExampleBean.class)); + } + + @Configuration + static class BeanOneConfiguration { + @Bean + public ExampleBean bean1() { + return new ExampleBean(); + } + } + + @Configuration + @Conditional(NoBeanOneCondition.class) + static class BeanTwoConfiguration { + @Bean + public ExampleBean bean2() { + return new ExampleBean(); + } + } + + @Configuration + @MetaConditional("test") + static class ConfigurationWithMetaCondition { + @Bean + public ExampleBean bean() { + return new ExampleBean(); + } + } + + @Conditional(MetaConditionalFilter.class) + @Retention(RetentionPolicy.RUNTIME) + @Target(ElementType.TYPE) + public static @interface MetaConditional { + String value(); + } + + @Conditional(NeverCondition.class) + @Retention(RetentionPolicy.RUNTIME) + @Target({ElementType.TYPE, ElementType.METHOD}) + public static @interface Never { + } + + static class NoBeanOneCondition implements Condition { + + public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) { + return !context.getBeanFactory().containsBeanDefinition("bean1"); + } + } + + static class MetaConditionalFilter implements Condition { + + public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) { + AnnotationAttributes attributes = AnnotationAttributes.fromMap(metadata.getAnnotationAttributes(MetaConditional.class.getName())); + assertThat(attributes.getString("value"), equalTo("test")); + return true; + } + } + + static class NeverCondition implements Condition { + public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) { + return false; + } + } + + @Component + @Never + static class NonConfigurationClass { + } + + @Configuration + static class ConditionOnMethodConfiguration { + + @Bean + @Never + public ExampleBean bean1() { + return new ExampleBean(); + } + } + + static class ExampleBean { + } + +} From 7c7fdb07363791fb3c72f4946839f4c166196ebe Mon Sep 17 00:00:00 2001 From: Phillip Webb Date: Thu, 21 Feb 2013 16:47:00 -0800 Subject: [PATCH 3/3] Add support for DeferredImportSelector Add DeferredImportSelector interface that can be used to select imports after all @Configuration beans have been processed. --- .../annotation/ConfigurationClassParser.java | 78 +++++++++- .../ConfigurationClassPostProcessor.java | 15 +- .../annotation/DeferredImportSelector.java | 33 ++++ .../context/annotation/ImportSelector.java | 6 + .../annotation/ImportSelectorTests.java | 146 ++++++++++++++++++ 5 files changed, 262 insertions(+), 16 deletions(-) create mode 100644 spring-context/src/main/java/org/springframework/context/annotation/DeferredImportSelector.java create mode 100644 spring-context/src/test/java/org/springframework/context/annotation/ImportSelectorTests.java diff --git a/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassParser.java b/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassParser.java index 93731a00258..5b1736fd6f2 100644 --- a/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassParser.java +++ b/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassParser.java @@ -25,6 +25,8 @@ import java.util.Comparator; import java.util.HashMap; import java.util.Iterator; import java.util.LinkedHashSet; +import java.util.LinkedList; +import java.util.List; import java.util.Map; import java.util.Set; import java.util.Stack; @@ -32,19 +34,23 @@ import java.util.Stack; import org.springframework.beans.BeanUtils; import org.springframework.beans.factory.Aware; import org.springframework.beans.factory.BeanClassLoaderAware; +import org.springframework.beans.factory.BeanDefinitionStoreException; import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.BeanFactoryAware; +import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.BeanDefinitionHolder; import org.springframework.beans.factory.config.ConfigurableBeanFactory; import org.springframework.beans.factory.parsing.Location; import org.springframework.beans.factory.parsing.Problem; import org.springframework.beans.factory.parsing.ProblemReporter; +import org.springframework.beans.factory.support.AbstractBeanDefinition; import org.springframework.beans.factory.support.BeanDefinitionReader; import org.springframework.beans.factory.support.BeanDefinitionRegistry; import org.springframework.beans.factory.support.BeanNameGenerator; import org.springframework.context.ResourceLoaderAware; import org.springframework.core.NestedIOException; import org.springframework.core.annotation.AnnotationAttributes; +import org.springframework.core.annotation.AnnotationAwareOrderComparator; import org.springframework.core.env.CompositePropertySource; import org.springframework.core.env.Environment; import org.springframework.core.env.PropertySource; @@ -82,6 +88,15 @@ import static org.springframework.context.annotation.MetadataUtils.*; */ class ConfigurationClassParser { + private static final Comparator DEFERRED_IMPORT_COMPARATOR = + new Comparator() { + public int compare(DeferredImportSelectorHolder o1, + DeferredImportSelectorHolder o2) { + return AnnotationAwareOrderComparator.INSTANCE.compare( + o1.getImportSelector(), o2.getImportSelector()); + } + }; + private final MetadataReaderFactory metadataReaderFactory; private final ProblemReporter problemReporter; @@ -106,6 +121,8 @@ class ConfigurationClassParser { private final BeanNameGenerator beanNameGenerator; + private final List deferredImportSelectors = + new LinkedList(); /** * Create a new {@link ConfigurationClassParser} instance that will be used @@ -125,6 +142,23 @@ class ConfigurationClassParser { resourceLoader, environment, componentScanBeanNameGenerator, registry); } + public void parse(Set configCandidates) { + for (BeanDefinitionHolder holder : configCandidates) { + BeanDefinition bd = holder.getBeanDefinition(); + try { + if (bd instanceof AbstractBeanDefinition && ((AbstractBeanDefinition) bd).hasBeanClass()) { + parse(((AbstractBeanDefinition) bd).getBeanClass(), holder.getBeanName()); + } + else { + parse(bd.getBeanClassName(), holder.getBeanName()); + } + } + catch (IOException ex) { + throw new BeanDefinitionStoreException("Failed to load bean class: " + bd.getBeanClassName(), ex); + } + } + processDeferredImportSelectors(); + } /** * Parse the specified {@link Configuration @Configuration} class. @@ -142,7 +176,7 @@ class ConfigurationClassParser { * @param clazz the Class to parse * @param beanName must not be null (as of Spring 3.1.1) */ - public void parse(Class clazz, String beanName) throws IOException { + protected void parse(Class clazz, String beanName) throws IOException { processConfigurationClass(new ConfigurationClass(clazz, beanName)); } @@ -369,6 +403,21 @@ class ConfigurationClassParser { } } + private void processDeferredImportSelectors() { + Collections.sort(this.deferredImportSelectors, DEFERRED_IMPORT_COMPARATOR); + for (DeferredImportSelectorHolder deferredImport : this.deferredImportSelectors) { + try { + ConfigurationClass configClass = deferredImport.getConfigurationClass(); + String[] imports = deferredImport.getImportSelector().selectImports(configClass.getMetadata()); + processImport(configClass, Arrays.asList(imports), false); + } + catch (IOException ex) { + throw new BeanDefinitionStoreException("Failed to load bean class: ", ex); + } + } + deferredImportSelectors.clear(); + } + private void processImport(ConfigurationClass configClass, Collection classesToImport, boolean checkForCircularImports) throws IOException { if (checkForCircularImports && this.importStack.contains(configClass)) { this.problemReporter.error(new CircularImportProblem(configClass, this.importStack, configClass.getMetadata())); @@ -384,7 +433,12 @@ class ConfigurationClassParser { Class candidateClass = (candidate instanceof Class ? (Class) candidate : this.resourceLoader.getClassLoader().loadClass((String) candidate)); ImportSelector selector = BeanUtils.instantiateClass(candidateClass, ImportSelector.class); invokeAwareMethods(selector); - processImport(configClass, Arrays.asList(selector.selectImports(importingClassMetadata)), false); + if(selector instanceof DeferredImportSelector) { + this.deferredImportSelectors.add(new DeferredImportSelectorHolder( + configClass, (DeferredImportSelector) selector)); + } else { + processImport(configClass, Arrays.asList(selector.selectImports(importingClassMetadata)), false); + } } else if (checkAssignability(ImportBeanDefinitionRegistrar.class, candidateToCheck)) { // the candidate class is an ImportBeanDefinitionRegistrar -> delegate to it to register additional bean definitions @@ -539,4 +593,24 @@ class ConfigurationClassParser { } } + + private static class DeferredImportSelectorHolder { + + private ConfigurationClass configurationClass; + + private DeferredImportSelector importSelector; + + public DeferredImportSelectorHolder(ConfigurationClass configurationClass, DeferredImportSelector importSelector) { + this.configurationClass = configurationClass; + this.importSelector = importSelector; + } + + public ConfigurationClass getConfigurationClass() { + return configurationClass; + } + + public DeferredImportSelector getImportSelector() { + return importSelector; + } + } } diff --git a/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassPostProcessor.java b/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassPostProcessor.java index 933192970a6..dbd94f33103 100644 --- a/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassPostProcessor.java +++ b/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassPostProcessor.java @@ -276,20 +276,7 @@ public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPo ConfigurationClassParser parser = new ConfigurationClassParser( this.metadataReaderFactory, this.problemReporter, this.environment, this.resourceLoader, this.componentScanBeanNameGenerator, registry); - for (BeanDefinitionHolder holder : configCandidates) { - BeanDefinition bd = holder.getBeanDefinition(); - try { - if (bd instanceof AbstractBeanDefinition && ((AbstractBeanDefinition) bd).hasBeanClass()) { - parser.parse(((AbstractBeanDefinition) bd).getBeanClass(), holder.getBeanName()); - } - else { - parser.parse(bd.getBeanClassName(), holder.getBeanName()); - } - } - catch (IOException ex) { - throw new BeanDefinitionStoreException("Failed to load bean class: " + bd.getBeanClassName(), ex); - } - } + parser.parse(configCandidates); parser.validate(); // Handle any @PropertySource annotations diff --git a/spring-context/src/main/java/org/springframework/context/annotation/DeferredImportSelector.java b/spring-context/src/main/java/org/springframework/context/annotation/DeferredImportSelector.java new file mode 100644 index 00000000000..2c104126f9b --- /dev/null +++ b/spring-context/src/main/java/org/springframework/context/annotation/DeferredImportSelector.java @@ -0,0 +1,33 @@ +/* + * Copyright 2002-2013 the original author 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; + +/** + * A variation of {@link ImportSelector} that runs after all {@code @Configuration} beans + * have been processed. This type of selector can be particularly useful when the selected + * imports are {@code @Conditional}. + * + *

Implementations can also extend the {@link org.springframework.core.Ordered} + * interface or use the {@link org.springframework.core.annotation.Order} annotation to + * indicate a precedence against other {@link DeferredImportSelector}s. + * + * @author Phillip Webb + * @since 4.0 + */ +public interface DeferredImportSelector extends ImportSelector { + +} diff --git a/spring-context/src/main/java/org/springframework/context/annotation/ImportSelector.java b/spring-context/src/main/java/org/springframework/context/annotation/ImportSelector.java index 5fcb9b960ff..5d8848001df 100644 --- a/spring-context/src/main/java/org/springframework/context/annotation/ImportSelector.java +++ b/spring-context/src/main/java/org/springframework/context/annotation/ImportSelector.java @@ -32,8 +32,14 @@ import org.springframework.core.type.AnnotationMetadata; *

  • {@link org.springframework.context.ResourceLoaderAware ResourceLoaderAware}
  • * * + *

    ImportSelectors are usually processed in the same way as regular {@code @Import} + * annotations, however, it is also possible to defer selection of imports until all + * {@code @Configuration} classes have been processed (see {@link DeferredImportSelector} + * for details). + * * @author Chris Beams * @since 3.1 + * @see DeferredImportSelector * @see Import * @see ImportBeanDefinitionRegistrar * @see Configuration diff --git a/spring-context/src/test/java/org/springframework/context/annotation/ImportSelectorTests.java b/spring-context/src/test/java/org/springframework/context/annotation/ImportSelectorTests.java new file mode 100644 index 00000000000..abdfcdbf6d1 --- /dev/null +++ b/spring-context/src/test/java/org/springframework/context/annotation/ImportSelectorTests.java @@ -0,0 +1,146 @@ +/* + * Copyright 2002-2013 the original author 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.mockito.Matchers.any; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.inOrder; +import static org.mockito.Mockito.spy; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.junit.Test; +import org.mockito.InOrder; +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.support.DefaultListableBeanFactory; +import org.springframework.core.Ordered; +import org.springframework.core.annotation.Order; +import org.springframework.core.type.AnnotationMetadata; + +/** + * Tests for {@link ImportSelector} and {@link DeferredImportSelector}. + * + * @author Phillip Webb + */ +public class ImportSelectorTests { + + @Test + public void importSelectors() { + DefaultListableBeanFactory beanFactory = spy(new DefaultListableBeanFactory()); + AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext( + beanFactory); + context.register(Config.class); + context.refresh(); + context.getBean(Config.class); + InOrder ordered = inOrder(beanFactory); + ordered.verify(beanFactory).registerBeanDefinition(eq("a"), any(BeanDefinition.class)); + ordered.verify(beanFactory).registerBeanDefinition(eq("b"), any(BeanDefinition.class)); + ordered.verify(beanFactory).registerBeanDefinition(eq("d"), any(BeanDefinition.class)); + ordered.verify(beanFactory).registerBeanDefinition(eq("c"), any(BeanDefinition.class)); + } + + @Sample + @Configuration + static class Config { + } + + @Target(ElementType.TYPE) + @Retention(RetentionPolicy.RUNTIME) + @Import({ DeferredImportSelector1.class, DeferredImportSelector2.class, + ImportSelector1.class, ImportSelector2.class }) + public static @interface Sample { + } + + public static class ImportSelector1 implements ImportSelector { + + @Override + public String[] selectImports(AnnotationMetadata importingClassMetadata) { + return new String[] { ImportedSelector1.class.getName() }; + } + } + + public static class ImportSelector2 implements ImportSelector { + + @Override + public String[] selectImports(AnnotationMetadata importingClassMetadata) { + return new String[] { ImportedSelector2.class.getName() }; + } + } + + public static class DeferredImportSelector1 implements DeferredImportSelector, Ordered { + + @Override + public String[] selectImports(AnnotationMetadata importingClassMetadata) { + return new String[] { DeferredImportedSelector1.class.getName() }; + } + + @Override + public int getOrder() { + return Ordered.LOWEST_PRECEDENCE; + } + } + + @Order(Ordered.HIGHEST_PRECEDENCE) + public static class DeferredImportSelector2 implements DeferredImportSelector { + + @Override + public String[] selectImports(AnnotationMetadata importingClassMetadata) { + return new String[] { DeferredImportedSelector2.class.getName() }; + } + + } + + @Configuration + public static class ImportedSelector1 { + + @Bean + public String a() { + return "a"; + } + } + + @Configuration + public static class ImportedSelector2 { + + @Bean + public String b() { + return "b"; + } + } + + @Configuration + public static class DeferredImportedSelector1 { + + @Bean + public String c() { + return "c"; + } + } + + @Configuration + public static class DeferredImportedSelector2 { + + @Bean + public String d() { + return "d"; + } + } + +}