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 49d0ce2ffb..800f146f95 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 @@ -267,18 +267,32 @@ class OnBeanCondition extends SpringBootCondition implements ConfigurationCondit collect(attributes, "annotation", this.annotations); collect(attributes, "ignored", this.ignoredTypes); collect(attributes, "ignoredType", this.ignoredTypes); - if (this.types.isEmpty() && this.names.isEmpty()) { - addDeducedBeanType(context, metadata, this.types); - } this.strategy = (SearchStrategy) metadata .getAnnotationAttributes(annotationType.getName()).get("search"); - validate(); + BeanTypeDeductionException deductionException = null; + try { + if (this.types.isEmpty() && this.names.isEmpty()) { + addDeducedBeanType(context, metadata, this.types); + } + } + catch (BeanTypeDeductionException ex) { + deductionException = ex; + } + validate(deductionException); } - protected void validate() { - Assert.isTrue(hasAtLeastOne(this.types, this.names, this.annotations), - annotationName() + " annotations must " - + "specify at least one bean (type, name or annotation)"); + protected void validate(BeanTypeDeductionException ex) { + if (!hasAtLeastOne(this.types, this.names, this.annotations)) { + String message = annotationName() + + " did not specify a bean using type, name or annotation"; + if (ex == null) { + throw new IllegalStateException(message); + } + else { + throw new IllegalStateException(message + " and the attempt to deduce" + + " the bean's type failed", ex); + } + } } private boolean hasAtLeastOne(List... lists) { @@ -337,12 +351,9 @@ class OnBeanCondition extends SpringBootCondition implements ConfigurationCondit }); } catch (Throwable ex) { - // swallow exception and continue - if (logger.isDebugEnabled()) { - logger.debug("Unable to deduce bean type for " - + methodMetadata.getDeclaringClassName() + "." - + methodMetadata.getMethodName(), ex); - } + throw new BeanTypeDeductionException( + methodMetadata.getDeclaringClassName(), + methodMetadata.getMethodName(), ex); } } @@ -404,11 +415,20 @@ class OnBeanCondition extends SpringBootCondition implements ConfigurationCondit } @Override - protected void validate() { + protected void validate(BeanTypeDeductionException ex) { Assert.isTrue(getTypes().size() == 1, annotationName() + " annotations must " + "specify only one type (got " + getTypes() + ")"); - } } + static final class BeanTypeDeductionException extends RuntimeException { + + private BeanTypeDeductionException(String className, String beanMethodName, + Throwable cause) { + super("Failed to deduce bean type for " + className + "." + beanMethodName, + cause); + } + + } + } diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/condition/OnBeanConditionTypeDeductionFailureTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/condition/OnBeanConditionTypeDeductionFailureTests.java new file mode 100644 index 0000000000..13d5434a59 --- /dev/null +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/condition/OnBeanConditionTypeDeductionFailureTests.java @@ -0,0 +1,100 @@ +/* + * Copyright 2012-2016 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 com.fasterxml.jackson.databind.ObjectMapper; +import org.junit.Test; +import org.junit.runner.RunWith; + +import org.springframework.boot.autoconfigure.condition.OnBeanCondition.BeanTypeDeductionException; +import org.springframework.boot.testutil.ClassPathExclusions; +import org.springframework.boot.testutil.FilteredClassPathRunner; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.context.annotation.ImportSelector; +import org.springframework.core.type.AnnotationMetadata; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.Assert.fail; + +/** + * Tests for {@link OnBeanCondition} when deduction of the bean's type fails + * + * @author Andy Wilkinson + */ +@RunWith(FilteredClassPathRunner.class) +@ClassPathExclusions("jackson-core-*.jar") +public class OnBeanConditionTypeDeductionFailureTests { + + @Test + public void conditionalOnMissingBeanWithDeducedTypeThatIsPartiallyMissingFromClassPath() { + try { + new AnnotationConfigApplicationContext(ImportingConfiguration.class).close(); + fail("Context refresh was successful"); + } + catch (Exception ex) { + ex.printStackTrace(); + Throwable beanTypeDeductionException = findBeanTypeDeductionException(ex); + assertThat(beanTypeDeductionException) + .hasMessage("Failed to deduce bean type for " + + OnMissingBeanConfiguration.class.getName() + + ".objectMapper"); + assertThat(beanTypeDeductionException) + .hasCauseInstanceOf(NoClassDefFoundError.class); + } + } + + private Throwable findBeanTypeDeductionException(Throwable ex) { + Throwable candidate = ex; + while (candidate != null) { + if (candidate instanceof BeanTypeDeductionException) { + return candidate; + } + candidate = candidate.getCause(); + } + return null; + } + + @Configuration + @Import(OnMissingBeanImportSelector.class) + static class ImportingConfiguration { + + } + + @Configuration + static class OnMissingBeanConfiguration { + + @Bean + @ConditionalOnMissingBean + public ObjectMapper objectMapper() { + return new ObjectMapper(); + } + + } + + static class OnMissingBeanImportSelector implements ImportSelector { + + @Override + public String[] selectImports(AnnotationMetadata importingClassMetadata) { + return new String[] { OnMissingBeanConfiguration.class.getName() }; + } + + } + +}