mirror of
https://github.com/spring-projects/spring-boot.git
synced 2024-08-29 03:06:45 +08:00
Update On(Missing)Bean Condition logic
Update OnBeanCondition and OnMissingBeanCondition to work better with @Configuration classes and to support an optional considerHierarchy annotation value. The class value for conditions can now also be inferred when used on @Bean methods.
This commit is contained in:
parent
7d7dc5107d
commit
3536fc68f5
@ -16,18 +16,24 @@
|
|||||||
|
|
||||||
package org.springframework.bootstrap.context.annotation;
|
package org.springframework.bootstrap.context.annotation;
|
||||||
|
|
||||||
|
import java.lang.reflect.Method;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import org.apache.commons.logging.Log;
|
import org.apache.commons.logging.Log;
|
||||||
import org.apache.commons.logging.LogFactory;
|
import org.apache.commons.logging.LogFactory;
|
||||||
import org.springframework.beans.factory.BeanFactoryUtils;
|
import org.springframework.beans.factory.BeanFactoryUtils;
|
||||||
import org.springframework.context.annotation.Condition;
|
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
import org.springframework.context.annotation.ConditionContext;
|
import org.springframework.context.annotation.ConditionContext;
|
||||||
|
import org.springframework.context.annotation.ConfigurationCondition;
|
||||||
import org.springframework.core.type.AnnotatedTypeMetadata;
|
import org.springframework.core.type.AnnotatedTypeMetadata;
|
||||||
|
import org.springframework.core.type.MethodMetadata;
|
||||||
import org.springframework.util.Assert;
|
import org.springframework.util.Assert;
|
||||||
import org.springframework.util.ClassUtils;
|
import org.springframework.util.ClassUtils;
|
||||||
import org.springframework.util.MultiValueMap;
|
import org.springframework.util.MultiValueMap;
|
||||||
|
import org.springframework.util.ReflectionUtils;
|
||||||
|
import org.springframework.util.ReflectionUtils.MethodCallback;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Base for {@link OnBeanCondition} and {@link OnMissingBeanCondition}.
|
* Base for {@link OnBeanCondition} and {@link OnMissingBeanCondition}.
|
||||||
@ -35,65 +41,95 @@ import org.springframework.util.MultiValueMap;
|
|||||||
* @author Phillip Webb
|
* @author Phillip Webb
|
||||||
* @author Dave Syer
|
* @author Dave Syer
|
||||||
*/
|
*/
|
||||||
abstract class AbstractOnBeanCondition implements Condition {
|
abstract class AbstractOnBeanCondition implements ConfigurationCondition {
|
||||||
|
|
||||||
protected Log logger = LogFactory.getLog(getClass());
|
protected Log logger = LogFactory.getLog(getClass());
|
||||||
|
|
||||||
private List<String> beanClasses;
|
|
||||||
|
|
||||||
private List<String> beanNames;
|
|
||||||
|
|
||||||
protected abstract Class<?> annotationClass();
|
protected abstract Class<?> annotationClass();
|
||||||
|
|
||||||
protected List<String> getBeanClasses() {
|
@Override
|
||||||
return this.beanClasses;
|
public ConfigurationPhase getConfigurationPhase() {
|
||||||
}
|
return ConfigurationPhase.REGISTER_BEAN;
|
||||||
|
|
||||||
protected List<String> getBeanNames() {
|
|
||||||
return this.beanNames;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
|
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
|
||||||
|
MultiValueMap<String, Object> attributes = metadata.getAllAnnotationAttributes(
|
||||||
|
annotationClass().getName(), true);
|
||||||
|
final List<String> beanClasses = collect(attributes, "value");
|
||||||
|
final List<String> 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 e) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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<String> beanClasses, List<String> beanNames) throws LinkageError {
|
||||||
|
|
||||||
String checking = ConditionLogUtils.getPrefix(this.logger, metadata);
|
String checking = ConditionLogUtils.getPrefix(this.logger, metadata);
|
||||||
|
|
||||||
MultiValueMap<String, Object> attributes = metadata.getAllAnnotationAttributes(
|
Boolean considerHierarchy = (Boolean) metadata.getAnnotationAttributes(
|
||||||
annotationClass().getName(), true);
|
annotationClass().getName()).get("considerHierarchy");
|
||||||
this.beanClasses = collect(attributes, "value");
|
considerHierarchy = (considerHierarchy == null ? false : considerHierarchy);
|
||||||
this.beanNames = collect(attributes, "name");
|
|
||||||
Assert.isTrue(this.beanClasses.size() > 0 || this.beanNames.size() > 0, "@"
|
|
||||||
+ ClassUtils.getShortName(annotationClass())
|
|
||||||
+ " annotations must specify at least one bean");
|
|
||||||
|
|
||||||
List<String> beanClassesFound = new ArrayList<String>();
|
List<String> beanClassesFound = new ArrayList<String>();
|
||||||
List<String> beanNamesFound = new ArrayList<String>();
|
List<String> beanNamesFound = new ArrayList<String>();
|
||||||
|
|
||||||
for (String beanClass : this.beanClasses) {
|
for (String beanClass : beanClasses) {
|
||||||
try {
|
try {
|
||||||
// eagerInit set to false to prevent early instantiation (some
|
// eagerInit set to false to prevent early instantiation (some
|
||||||
// factory beans will not be able to determine their object type at this
|
// factory beans will not be able to determine their object type at this
|
||||||
// stage, so those are not eligible for matching this condition)
|
// stage, so those are not eligible for matching this condition)
|
||||||
String[] beans = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(
|
ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
|
||||||
context.getBeanFactory(),
|
Class<?> type = ClassUtils.forName(beanClass, context.getClassLoader());
|
||||||
ClassUtils.forName(beanClass, context.getClassLoader()), false,
|
String[] beans = (considerHierarchy ? BeanFactoryUtils
|
||||||
false);
|
.beanNamesForTypeIncludingAncestors(beanFactory, type, false,
|
||||||
|
false) : beanFactory.getBeanNamesForType(type, false,
|
||||||
|
false));
|
||||||
if (beans.length != 0) {
|
if (beans.length != 0) {
|
||||||
beanClassesFound.add(beanClass);
|
beanClassesFound.add(beanClass);
|
||||||
}
|
}
|
||||||
} catch (ClassNotFoundException ex) {
|
} catch (ClassNotFoundException ex) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (String beanName : this.beanNames) {
|
for (String beanName : beanNames) {
|
||||||
if (context.getBeanFactory().containsBeanDefinition(beanName)) {
|
if (considerHierarchy ? context.getBeanFactory().containsBean(beanName)
|
||||||
|
: context.getBeanFactory().containsLocalBean(beanName)) {
|
||||||
beanNamesFound.add(beanName);
|
beanNamesFound.add(beanName);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean result = evaluate(beanClassesFound, beanNamesFound);
|
boolean result = evaluate(beanClassesFound, beanNamesFound);
|
||||||
if (this.logger.isDebugEnabled()) {
|
if (this.logger.isDebugEnabled()) {
|
||||||
logFoundResults(checking, "class", this.beanClasses, beanClassesFound);
|
logFoundResults(checking, "class", beanClasses, beanClassesFound);
|
||||||
logFoundResults(checking, "name", this.beanNames, beanClassesFound);
|
logFoundResults(checking, "name", beanNames, beanClassesFound);
|
||||||
this.logger.debug(checking + "Match result is: " + result);
|
this.logger.debug(checking + "Match result is: " + result);
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
|
@ -52,4 +52,9 @@ public @interface ConditionalOnBean {
|
|||||||
*/
|
*/
|
||||||
String[] name() default {};
|
String[] name() default {};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If the application context hierarchy (parent contexts) should be considered.
|
||||||
|
*/
|
||||||
|
boolean considerHierarchy() default true;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -52,4 +52,9 @@ public @interface ConditionalOnMissingBean {
|
|||||||
*/
|
*/
|
||||||
String[] name() default {};
|
String[] name() default {};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If the application context hierarchy (parent contexts) should be considered.
|
||||||
|
*/
|
||||||
|
boolean considerHierarchy() default true;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -21,13 +21,19 @@ import org.springframework.context.annotation.AnnotationConfigApplicationContext
|
|||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
|
||||||
|
import static org.hamcrest.Matchers.equalTo;
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
import static org.junit.Assert.assertFalse;
|
import static org.junit.Assert.assertFalse;
|
||||||
|
import static org.junit.Assert.assertThat;
|
||||||
import static org.junit.Assert.assertTrue;
|
import static org.junit.Assert.assertTrue;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* Tests for {@link OnMissingBeanCondition}.
|
||||||
|
*
|
||||||
* @author Dave Syer
|
* @author Dave Syer
|
||||||
|
* @author Phillip Webb
|
||||||
*/
|
*/
|
||||||
|
@SuppressWarnings("resource")
|
||||||
public class OnMissingBeanConditionTests {
|
public class OnMissingBeanConditionTests {
|
||||||
|
|
||||||
private AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
|
private AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
|
||||||
@ -49,6 +55,35 @@ public class OnMissingBeanConditionTests {
|
|||||||
assertEquals("foo", this.context.getBean("foo"));
|
assertEquals("foo", this.context.getBean("foo"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void hierarchyConsidered() throws Exception {
|
||||||
|
this.context.register(FooConfiguration.class);
|
||||||
|
this.context.refresh();
|
||||||
|
AnnotationConfigApplicationContext childContext = new AnnotationConfigApplicationContext();
|
||||||
|
childContext.setParent(this.context);
|
||||||
|
childContext.register(HierarchyConsidered.class);
|
||||||
|
childContext.refresh();
|
||||||
|
assertFalse(childContext.containsLocalBean("bar"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void hierarchyNotConsidered() throws Exception {
|
||||||
|
this.context.register(FooConfiguration.class);
|
||||||
|
this.context.refresh();
|
||||||
|
AnnotationConfigApplicationContext childContext = new AnnotationConfigApplicationContext();
|
||||||
|
childContext.setParent(this.context);
|
||||||
|
childContext.register(HierarchyNotConsidered.class);
|
||||||
|
childContext.refresh();
|
||||||
|
assertTrue(childContext.containsLocalBean("bar"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void impliedOnBeanMethod() throws Exception {
|
||||||
|
this.context.register(ExampleBeanConfiguration.class, ImpliedOnBeanMethod.class);
|
||||||
|
this.context.refresh();
|
||||||
|
assertThat(this.context.getBeansOfType(ExampleBean.class).size(), equalTo(1));
|
||||||
|
}
|
||||||
|
|
||||||
@Configuration
|
@Configuration
|
||||||
@ConditionalOnMissingBean(name = "foo")
|
@ConditionalOnMissingBean(name = "foo")
|
||||||
protected static class OnBeanNameConfiguration {
|
protected static class OnBeanNameConfiguration {
|
||||||
@ -66,4 +101,43 @@ public class OnMissingBeanConditionTests {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
@ConditionalOnMissingBean(name = "foo")
|
||||||
|
protected static class HierarchyConsidered {
|
||||||
|
@Bean
|
||||||
|
public String bar() {
|
||||||
|
return "bar";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
@ConditionalOnMissingBean(name = "foo", considerHierarchy = false)
|
||||||
|
protected static class HierarchyNotConsidered {
|
||||||
|
@Bean
|
||||||
|
public String bar() {
|
||||||
|
return "bar";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
protected static class ExampleBeanConfiguration {
|
||||||
|
@Bean
|
||||||
|
public ExampleBean exampleBean() {
|
||||||
|
return new ExampleBean();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
protected static class ImpliedOnBeanMethod {
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
@ConditionalOnMissingBean
|
||||||
|
public ExampleBean exampleBean2() {
|
||||||
|
return new ExampleBean();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class ExampleBean {
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user