Add (experimental) DependencyOf annotation type and associated DependencyOfBeanFactoryPostProcessor class.
The @DependencyOf annotation is the inverse of Spring's @DependsOn annotation enabling a bean to declare itself as a dependency for another bean in the Spring ApplicationContext (container).
This commit is contained in:
@@ -0,0 +1,104 @@
|
||||
/*
|
||||
* Copyright 2019 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
|
||||
*
|
||||
* https://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.data.gemfire.tests.extensions.spring.context.annotation;
|
||||
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Inherited;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
import org.springframework.context.annotation.DependsOn;
|
||||
import org.springframework.core.annotation.AliasFor;
|
||||
|
||||
/**
|
||||
* The {@link DependencyOf} annotation is the inverse of Spring's {@link DependsOn} annotation allowing a bean
|
||||
* to declare itself as a dependency of another bean in the Spring container.
|
||||
*
|
||||
* For example, with Spring's {@link DependsOn} annotation, a bean A can say that it {@literal depends on} bean B.
|
||||
* However, with the {@link DependencyOf} annotation, a bean B can say it is a required dependency for bean A,
|
||||
* or rather that bean A depends on bean B.
|
||||
*
|
||||
* Therefore, the following bean definitions for A & B are equivalent:
|
||||
*
|
||||
* <code>
|
||||
*
|
||||
* @Configuration
|
||||
* public class ConfigurationOne {
|
||||
*
|
||||
* @Bean
|
||||
* @DependsOn("b")
|
||||
* public A a() {
|
||||
* return new A();
|
||||
* }
|
||||
*
|
||||
* @Bean
|
||||
* public B b() {
|
||||
* return new B();
|
||||
* }
|
||||
* }
|
||||
* </code>
|
||||
*
|
||||
*
|
||||
* And...
|
||||
*
|
||||
* <code>
|
||||
* @Configuration
|
||||
* public class ConfigurationTwo {
|
||||
*
|
||||
* @Bean
|
||||
* public A a() {
|
||||
* return new A();
|
||||
* }
|
||||
*
|
||||
* @Bean
|
||||
* @DependencyOf("a")
|
||||
* public B b() {
|
||||
* return new B();
|
||||
* }
|
||||
* }
|
||||
* </code>
|
||||
*
|
||||
* One advantage of this approach is that bean A does not need to know all the beans it is possibly dependent on,
|
||||
* especially at runtime when additional collaborators or dependencies maybe added dynamically to the classpath,
|
||||
* Therefore, additional dependencies of A can be added to the configuration automatically, over time without
|
||||
* having to go back and modify the bean definition for A.
|
||||
*
|
||||
* This feature is experimental.
|
||||
*
|
||||
* @author John Blum
|
||||
* @see java.lang.annotation.Documented
|
||||
* @see java.lang.annotation.Inherited
|
||||
* @see java.lang.annotation.Retention
|
||||
* @see java.lang.annotation.Target
|
||||
* @see org.springframework.context.annotation.DependsOn
|
||||
* @since 0.0.23
|
||||
*/
|
||||
@Documented
|
||||
@Inherited
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target({ ElementType.ANNOTATION_TYPE, ElementType.TYPE })
|
||||
@SuppressWarnings("unused")
|
||||
public @interface DependencyOf {
|
||||
|
||||
@AliasFor("value")
|
||||
String[] beanNames() default {};
|
||||
|
||||
@AliasFor("beanNames")
|
||||
String[] value() default {};
|
||||
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
/*
|
||||
* Copyright 2019 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
|
||||
*
|
||||
* https://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.data.gemfire.tests.extensions.spring.context.annotation;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.util.Optional;
|
||||
|
||||
import org.apache.shiro.util.StringUtils;
|
||||
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.beans.factory.config.BeanDefinition;
|
||||
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
|
||||
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
|
||||
import org.springframework.core.annotation.AnnotationAttributes;
|
||||
import org.springframework.core.annotation.AnnotationUtils;
|
||||
import org.springframework.data.gemfire.util.ArrayUtils;
|
||||
import org.springframework.data.gemfire.util.SpringUtils;
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
* Spring {@link BeanFactoryPostProcessor} implementation used to post process {@link BeanDefinition BeanDefinitions}
|
||||
* annotated with {@link DependencyOf} annotations.
|
||||
*
|
||||
* @author John Blum
|
||||
* @see org.springframework.beans.factory.config.BeanDefinition
|
||||
* @see org.springframework.beans.factory.config.BeanFactoryPostProcessor
|
||||
* @see org.springframework.beans.factory.config.ConfigurableListableBeanFactory
|
||||
* @see org.springframework.data.gemfire.tests.extensions.spring.context.annotation.DependencyOf
|
||||
* @since 0.0.23
|
||||
*/
|
||||
public class DependencyOfBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
|
||||
|
||||
protected static final Class<? extends Annotation> DEPENDENCY_OF_TYPE = DependencyOf.class;
|
||||
|
||||
protected static final String VALUE_ATTRIBUTE_NAME = "value";
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
@Override
|
||||
public void postProcessBeanFactory(@NonNull ConfigurableListableBeanFactory beanFactory) throws BeansException {
|
||||
|
||||
String[] dependencyOfAnnotatedBeanNames =
|
||||
ArrayUtils.nullSafeArray(beanFactory.getBeanNamesForAnnotation(DEPENDENCY_OF_TYPE), String.class);
|
||||
|
||||
for (String beanName : dependencyOfAnnotatedBeanNames) {
|
||||
|
||||
Annotation dependencyOf = beanFactory.findAnnotationOnBean(beanName, DEPENDENCY_OF_TYPE);
|
||||
|
||||
Optional.ofNullable(dependencyOf)
|
||||
.map(this::getAnnotationAttributes)
|
||||
.map(this::getValueAttribute)
|
||||
.ifPresent(dependentBeanNames -> {
|
||||
for (String dependentBeanName : dependentBeanNames) {
|
||||
Optional.ofNullable(dependentBeanName)
|
||||
.filter(StringUtils::hasText)
|
||||
.map(beanFactory::getBeanDefinition)
|
||||
.ifPresent(dependentBeanDefinition ->
|
||||
SpringUtils.addDependsOn(dependentBeanDefinition, beanName));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private @Nullable AnnotationAttributes getAnnotationAttributes(@NonNull Annotation annotation) {
|
||||
|
||||
return annotation != null
|
||||
? AnnotationAttributes.fromMap(AnnotationUtils.getAnnotationAttributes(annotation))
|
||||
: null;
|
||||
}
|
||||
|
||||
private @Nullable String[] getValueAttribute(@NonNull AnnotationAttributes annotationAttributes) {
|
||||
|
||||
return annotationAttributes != null
|
||||
? annotationAttributes.getStringArray(VALUE_ATTRIBUTE_NAME)
|
||||
: null;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user