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:
John Blum
2021-03-22 19:52:27 -07:00
parent 08350ab997
commit 3b5208c213
2 changed files with 196 additions and 0 deletions

View File

@@ -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 {};
}

View File

@@ -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;
}
}