diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/AbstractOnBeanCondition.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/AbstractOnBeanCondition.java deleted file mode 100644 index 2d89f303abd..00000000000 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/AbstractOnBeanCondition.java +++ /dev/null @@ -1,184 +0,0 @@ -/* - * Copyright 2012-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.boot.autoconfigure.condition; - -import java.lang.reflect.Method; -import java.util.ArrayList; -import java.util.List; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.springframework.beans.factory.BeanFactory; -import org.springframework.beans.factory.BeanFactoryUtils; -import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.ConditionContext; -import org.springframework.context.annotation.ConfigurationCondition; -import org.springframework.core.type.AnnotatedTypeMetadata; -import org.springframework.core.type.MethodMetadata; -import org.springframework.util.Assert; -import org.springframework.util.ClassUtils; -import org.springframework.util.MultiValueMap; -import org.springframework.util.ReflectionUtils; -import org.springframework.util.ReflectionUtils.MethodCallback; - -/** - * Base for {@link OnBeanCondition} and {@link OnMissingBeanCondition}. - * - * @author Phillip Webb - * @author Dave Syer - */ -abstract class AbstractOnBeanCondition implements ConfigurationCondition { - - private final Log logger = LogFactory.getLog(getClass()); - - protected abstract Class annotationClass(); - - @Override - public ConfigurationPhase getConfigurationPhase() { - return ConfigurationPhase.REGISTER_BEAN; - } - - @Override - public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) { - MultiValueMap attributes = metadata.getAllAnnotationAttributes( - annotationClass().getName(), true); - final List beanClasses = collect(attributes, "value"); - final List beanNames = collect(attributes, "name"); - - if (beanClasses.size() == 0) { - if (metadata instanceof MethodMetadata - && metadata.isAnnotated(Bean.class.getName())) { - try { - final MethodMetadata methodMetadata = (MethodMetadata) metadata; - // We should be safe to load at this point since we are in the - // REGISTER_BEAN phase - Class configClass = ClassUtils.forName( - methodMetadata.getDeclaringClassName(), - context.getClassLoader()); - ReflectionUtils.doWithMethods(configClass, new MethodCallback() { - @Override - public void doWith(Method method) - throws IllegalArgumentException, IllegalAccessException { - if (methodMetadata.getMethodName().equals(method.getName())) { - beanClasses.add(method.getReturnType().getName()); - } - } - }); - } - catch (Exception ex) { - // swallow exception and continue - } - } - } - - Assert.isTrue(beanClasses.size() > 0 || beanNames.size() > 0, - "@" + ClassUtils.getShortName(annotationClass()) - + " annotations must specify at least one bean"); - - return matches(context, metadata, beanClasses, beanNames); - } - - protected boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata, - List beanClasses, List beanNames) throws LinkageError { - - String checking = ConditionLogUtils.getPrefix(this.logger, metadata); - - SearchStrategy search = (SearchStrategy) metadata.getAnnotationAttributes( - annotationClass().getName()).get("search"); - search = search == null ? SearchStrategy.ALL : search; - - boolean considerHierarchy = search == SearchStrategy.ALL; - boolean parentOnly = search == SearchStrategy.PARENTS; - - List beanClassesFound = new ArrayList(); - List beanNamesFound = new ArrayList(); - - // eagerInit set to false to prevent early instantiation (some - // factory beans will not be able to determine their object type at this - // stage, so those are not eligible for matching this condition) - ConfigurableListableBeanFactory beanFactory = context.getBeanFactory(); - if (parentOnly) { - BeanFactory parent = beanFactory.getParentBeanFactory(); - if (!(parent instanceof ConfigurableListableBeanFactory)) { - throw new IllegalStateException( - "Cannot use parentOnly if parent is not ConfigurableListableBeanFactory"); - } - beanFactory = (ConfigurableListableBeanFactory) parent; - } - for (String beanClass : beanClasses) { - try { - Class type = ClassUtils.forName(beanClass, context.getClassLoader()); - String[] beans = (considerHierarchy ? BeanFactoryUtils - .beanNamesForTypeIncludingAncestors(beanFactory, type, false, - false) : beanFactory.getBeanNamesForType(type, false, - false)); - if (beans.length != 0) { - beanClassesFound.add(beanClass); - } - } - catch (ClassNotFoundException ex) { - // swallow exception and continue - } - } - for (String beanName : beanNames) { - if (considerHierarchy ? beanFactory.containsBean(beanName) : beanFactory - .containsLocalBean(beanName)) { - beanNamesFound.add(beanName); - } - } - - boolean result = evaluate(beanClassesFound, beanNamesFound); - if (this.logger.isDebugEnabled()) { - logFoundResults(checking, "class", beanClasses, beanClassesFound); - logFoundResults(checking, "name", beanNames, beanClassesFound); - this.logger.debug(checking + "Match result is: " + result); - } - return result; - } - - private void logFoundResults(String prefix, String type, List candidates, - List found) { - if (!candidates.isEmpty()) { - this.logger.debug(prefix + "Looking for beans with " + type + ": " - + candidates); - if (found.isEmpty()) { - this.logger.debug(prefix + "Found no beans"); - } - else { - this.logger.debug(prefix + "Found beans with " + type + ": " + found); - } - } - } - - protected boolean evaluate(List beanClassesFound, List beanNamesFound) { - return !beanClassesFound.isEmpty() || !beanNamesFound.isEmpty(); - } - - @SuppressWarnings({ "unchecked", "rawtypes" }) - private List collect(MultiValueMap attributes, String key) { - List collected = new ArrayList(); - List valueList = (List) attributes.get(key); - for (String[] valueArray : valueList) { - for (String value : valueArray) { - collected.add(value); - } - } - return collected; - } - -} diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/ConditionalOnMissingBean.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/ConditionalOnMissingBean.java index 1c25286001b..a4ebe596194 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/ConditionalOnMissingBean.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/ConditionalOnMissingBean.java @@ -35,7 +35,7 @@ import org.springframework.context.annotation.Conditional; @Target({ ElementType.TYPE, ElementType.METHOD }) @Retention(RetentionPolicy.RUNTIME) @Documented -@Conditional(OnMissingBeanCondition.class) +@Conditional(OnBeanCondition.class) public @interface ConditionalOnMissingBean { /** diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/OnBeanCondition.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/OnBeanCondition.java index f15568a10d0..2f0bc82dd7b 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/OnBeanCondition.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/OnBeanCondition.java @@ -16,18 +16,212 @@ package org.springframework.boot.autoconfigure.condition; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.BeanFactoryUtils; +import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; +import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Condition; +import org.springframework.context.annotation.ConditionContext; +import org.springframework.context.annotation.ConfigurationCondition; +import org.springframework.core.style.ToStringCreator; +import org.springframework.core.type.AnnotatedTypeMetadata; +import org.springframework.core.type.MethodMetadata; +import org.springframework.util.Assert; +import org.springframework.util.ClassUtils; +import org.springframework.util.MultiValueMap; +import org.springframework.util.ReflectionUtils; +import org.springframework.util.ReflectionUtils.MethodCallback; /** - * {@link Condition} that checks that specific beans are present. + * {@link Condition} that checks for the presence or absence of specific beans. * * @author Phillip Webb - * @see ConditionalOnBean + * @author Dave Syer */ -class OnBeanCondition extends AbstractOnBeanCondition { +class OnBeanCondition implements ConfigurationCondition { + + private static final String[] NO_BEANS = {}; + + private final Log logger = LogFactory.getLog(getClass()); + + @Override + public ConfigurationPhase getConfigurationPhase() { + return ConfigurationPhase.REGISTER_BEAN; + } @Override - protected Class annotationClass() { - return ConditionalOnBean.class; + public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) { + + String checking = ConditionLogUtils.getPrefix(this.logger, metadata); + + if (metadata.isAnnotated(ConditionalOnBean.class.getName())) { + BeanSearchSpec spec = new BeanSearchSpec(context, metadata, + ConditionalOnBean.class); + List matching = getMatchingBeans(context, spec); + if (matching.isEmpty()) { + if (this.logger.isDebugEnabled()) { + this.logger.debug(checking + " @ConditionalOnBean " + spec + + " found no beans (search terminated with matches=false)"); + } + return false; + } + } + + if (metadata.isAnnotated(ConditionalOnMissingBean.class.getName())) { + BeanSearchSpec spec = new BeanSearchSpec(context, metadata, + ConditionalOnMissingBean.class); + List matching = getMatchingBeans(context, spec); + if (!matching.isEmpty()) { + if (this.logger.isDebugEnabled()) { + this.logger.debug(checking + " @ConditionalOnMissingBean " + spec + + " found the following " + matching + + " (search terminated with matches=false)"); + } + return false; + } + } + + return true; + } + + private List getMatchingBeans(ConditionContext context, BeanSearchSpec beans) { + + ConfigurableListableBeanFactory beanFactory = context.getBeanFactory(); + if (beans.getStrategy() == SearchStrategy.PARENTS) { + BeanFactory parent = beanFactory.getParentBeanFactory(); + Assert.isInstanceOf(ConfigurableListableBeanFactory.class, parent, + "Unable to use SearchStrategy.PARENTS"); + beanFactory = (ConfigurableListableBeanFactory) parent; + } + + List beanNames = new ArrayList(); + boolean considerHierarchy = beans.getStrategy() == SearchStrategy.ALL; + + for (String type : beans.getTypes()) { + beanNames.addAll(Arrays.asList(getBeanNamesForType(beanFactory, type, + context.getClassLoader(), considerHierarchy))); + } + + for (String beanName : beans.getNames()) { + if (containsBean(beanFactory, beanName, considerHierarchy)) { + beanNames.add(beanName); + } + } + + return beanNames; + } + + private boolean containsBean(ConfigurableListableBeanFactory beanFactory, + String beanName, boolean considerHierarchy) { + if (considerHierarchy) { + return beanFactory.containsBean(beanName); + } + return beanFactory.containsLocalBean(beanName); + } + + private String[] getBeanNamesForType(ConfigurableListableBeanFactory beanFactory, + String type, ClassLoader classLoader, boolean considerHierarchy) + throws LinkageError { + // eagerInit set to false to prevent early instantiation (some + // factory beans will not be able to determine their object type at this + // stage, so those are not eligible for matching this condition) + try { + Class typeClass = ClassUtils.forName(type, classLoader); + if (considerHierarchy) { + return BeanFactoryUtils.beanNamesForTypeIncludingAncestors(beanFactory, + typeClass, false, false); + } + return beanFactory.getBeanNamesForType(typeClass, false, false); + } + catch (ClassNotFoundException ex) { + return NO_BEANS; + } + } + + private static class BeanSearchSpec { + + private List names = new ArrayList(); + private List types = new ArrayList(); + private SearchStrategy strategy; + + public BeanSearchSpec(ConditionContext context, AnnotatedTypeMetadata metadata, + Class annotationType) { + MultiValueMap attributes = metadata + .getAllAnnotationAttributes(annotationType.getName(), true); + collect(attributes, "name", this.names); + collect(attributes, "value", this.types); + if (this.types.isEmpty() && this.names.isEmpty()) { + addDeducedBeanType(context, metadata, this.types); + } + Assert.isTrue(!this.types.isEmpty() || !this.names.isEmpty(), "@" + + ClassUtils.getShortName(annotationType) + + " annotations must specify at least one bean"); + this.strategy = (SearchStrategy) metadata.getAnnotationAttributes( + annotationType.getName()).get("search"); + } + + @SuppressWarnings({ "unchecked", "rawtypes" }) + private void collect(MultiValueMap attributes, String key, + List destination) { + List valueList = (List) attributes.get(key); + for (String[] valueArray : valueList) { + for (String value : valueArray) { + destination.add(value); + } + } + } + + private void addDeducedBeanType(ConditionContext context, + AnnotatedTypeMetadata metadata, final List beanTypes) { + if (metadata instanceof MethodMetadata + && metadata.isAnnotated(Bean.class.getName())) { + try { + final MethodMetadata methodMetadata = (MethodMetadata) metadata; + // We should be safe to load at this point since we are in the + // REGISTER_BEAN phase + Class configClass = ClassUtils.forName( + methodMetadata.getDeclaringClassName(), + context.getClassLoader()); + ReflectionUtils.doWithMethods(configClass, new MethodCallback() { + @Override + public void doWith(Method method) + throws IllegalArgumentException, IllegalAccessException { + if (methodMetadata.getMethodName().equals(method.getName())) { + beanTypes.add(method.getReturnType().getName()); + } + } + }); + } + catch (Exception ex) { + // swallow exception and continue + } + } + } + + public SearchStrategy getStrategy() { + return (this.strategy != null ? this.strategy : SearchStrategy.ALL); + } + + public List getNames() { + return this.names; + } + + public List getTypes() { + return this.types; + } + + @Override + public String toString() { + return new ToStringCreator(this).append("names", this.names) + .append("types", this.types).append("strategy", this.strategy) + .toString(); + } } } diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/OnMissingBeanCondition.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/OnMissingBeanCondition.java deleted file mode 100644 index f999cc416eb..00000000000 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/OnMissingBeanCondition.java +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright 2012-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.boot.autoconfigure.condition; - -import java.util.List; - -import org.springframework.context.annotation.Condition; - -/** - * {@link Condition} that checks that specific beans are missing. - * - * @author Phillip Webb - * @see ConditionalOnMissingBean - */ -class OnMissingBeanCondition extends AbstractOnBeanCondition { - - @Override - protected Class annotationClass() { - return ConditionalOnMissingBean.class; - } - - @Override - protected boolean evaluate(List beanClassesFound, List beanNamesFound) { - return !super.evaluate(beanClassesFound, beanNamesFound); - } -} diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/condition/OnBeanConditionTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/condition/OnBeanConditionTests.java index 1dcbf8789b5..97ee6c412b0 100644 --- a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/condition/OnBeanConditionTests.java +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/condition/OnBeanConditionTests.java @@ -18,7 +18,6 @@ package org.springframework.boot.autoconfigure.condition; import org.junit.Ignore; import org.junit.Test; -import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -29,6 +28,8 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; /** + * Tests for {@link ConditionalOnBean}. + * * @author Dave Syer */ @Ignore diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/condition/OnMissingBeanConditionTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/condition/OnMissingBeanConditionTests.java index 59d72f418f8..cbf10cb8c91 100644 --- a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/condition/OnMissingBeanConditionTests.java +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/condition/OnMissingBeanConditionTests.java @@ -17,9 +17,6 @@ package org.springframework.boot.autoconfigure.condition; import org.junit.Test; -import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; -import org.springframework.boot.autoconfigure.condition.OnMissingBeanCondition; -import org.springframework.boot.autoconfigure.condition.SearchStrategy; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -31,7 +28,7 @@ import static org.junit.Assert.assertThat; import static org.junit.Assert.assertTrue; /** - * Tests for {@link OnMissingBeanCondition}. + * Tests for {@link ConditionalOnMissingBean}. * * @author Dave Syer * @author Phillip Webb