Simplify OnBeanCondition
Merge OnMissingBeanCondition into OnBeanCondition and simplify code. Polish on bean conditions
This commit is contained in:
@@ -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<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 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<String> beanClasses, List<String> 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<String> beanClassesFound = new ArrayList<String>();
|
||||
List<String> beanNamesFound = new ArrayList<String>();
|
||||
|
||||
// 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<String> beanClassesFound, List<String> beanNamesFound) {
|
||||
return !beanClassesFound.isEmpty() || !beanNamesFound.isEmpty();
|
||||
}
|
||||
|
||||
@SuppressWarnings({ "unchecked", "rawtypes" })
|
||||
private List<String> collect(MultiValueMap<String, Object> attributes, String key) {
|
||||
List<String> collected = new ArrayList<String>();
|
||||
List<String[]> valueList = (List) attributes.get(key);
|
||||
for (String[] valueArray : valueList) {
|
||||
for (String value : valueArray) {
|
||||
collected.add(value);
|
||||
}
|
||||
}
|
||||
return collected;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
/**
|
||||
|
||||
@@ -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
|
||||
protected Class<?> annotationClass() {
|
||||
return ConditionalOnBean.class;
|
||||
public ConfigurationPhase getConfigurationPhase() {
|
||||
return ConfigurationPhase.REGISTER_BEAN;
|
||||
}
|
||||
|
||||
@Override
|
||||
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<String> 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<String> 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<String> 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<String> beanNames = new ArrayList<String>();
|
||||
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<String> names = new ArrayList<String>();
|
||||
private List<String> types = new ArrayList<String>();
|
||||
private SearchStrategy strategy;
|
||||
|
||||
public BeanSearchSpec(ConditionContext context, AnnotatedTypeMetadata metadata,
|
||||
Class<?> annotationType) {
|
||||
MultiValueMap<String, Object> 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<String, Object> attributes, String key,
|
||||
List<String> destination) {
|
||||
List<String[]> 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<String> 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<String> getNames() {
|
||||
return this.names;
|
||||
}
|
||||
|
||||
public List<String> 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<String> beanClassesFound, List<String> beanNamesFound) {
|
||||
return !super.evaluate(beanClassesFound, beanNamesFound);
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user