Commit e5a253e6 authored by Andy Wilkinson's avatar Andy Wilkinson

Improve diagnostics when OnBeanCondition type deduction fails

When @ConditionalOnBean or @ConditionalOnMissingBean are used on a
@Bean method, they will, in the absence of any other configuration,
attempt to deduce the bean's type by examining the method's return
type. This deduction can fail. See gh-4841, gh-4934, and gh-5624
for some examples of possible failure causes. Previously, this
failure was only logged as a debug message leaving the user with a
misleading message suggesting that the @ConditionalOnBean or
@ConditionalOnMissingBean annotation was not configured correctly.

This commit improves the diagnostics by mention the possibility of
type deduction in the exception message and including the exception
that caused deduction to fail as the cause.

Closes gh-4934
parent a37ae5d5
...@@ -267,18 +267,32 @@ class OnBeanCondition extends SpringBootCondition implements ConfigurationCondit ...@@ -267,18 +267,32 @@ class OnBeanCondition extends SpringBootCondition implements ConfigurationCondit
collect(attributes, "annotation", this.annotations); collect(attributes, "annotation", this.annotations);
collect(attributes, "ignored", this.ignoredTypes); collect(attributes, "ignored", this.ignoredTypes);
collect(attributes, "ignoredType", this.ignoredTypes); collect(attributes, "ignoredType", this.ignoredTypes);
if (this.types.isEmpty() && this.names.isEmpty()) {
addDeducedBeanType(context, metadata, this.types);
}
this.strategy = (SearchStrategy) metadata this.strategy = (SearchStrategy) metadata
.getAnnotationAttributes(annotationType.getName()).get("search"); .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() { protected void validate(BeanTypeDeductionException ex) {
Assert.isTrue(hasAtLeastOne(this.types, this.names, this.annotations), if (!hasAtLeastOne(this.types, this.names, this.annotations)) {
annotationName() + " annotations must " String message = annotationName()
+ "specify at least one bean (type, name or annotation)"); + " 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) { private boolean hasAtLeastOne(List<?>... lists) {
...@@ -337,12 +351,9 @@ class OnBeanCondition extends SpringBootCondition implements ConfigurationCondit ...@@ -337,12 +351,9 @@ class OnBeanCondition extends SpringBootCondition implements ConfigurationCondit
}); });
} }
catch (Throwable ex) { catch (Throwable ex) {
// swallow exception and continue throw new BeanTypeDeductionException(
if (logger.isDebugEnabled()) { methodMetadata.getDeclaringClassName(),
logger.debug("Unable to deduce bean type for " methodMetadata.getMethodName(), ex);
+ methodMetadata.getDeclaringClassName() + "."
+ methodMetadata.getMethodName(), ex);
}
} }
} }
...@@ -404,11 +415,20 @@ class OnBeanCondition extends SpringBootCondition implements ConfigurationCondit ...@@ -404,11 +415,20 @@ class OnBeanCondition extends SpringBootCondition implements ConfigurationCondit
} }
@Override @Override
protected void validate() { protected void validate(BeanTypeDeductionException ex) {
Assert.isTrue(getTypes().size() == 1, annotationName() + " annotations must " Assert.isTrue(getTypes().size() == 1, annotationName() + " annotations must "
+ "specify only one type (got " + getTypes() + ")"); + "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);
} }
} }
} }
/*
* 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() };
}
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment