Add relative ordering to @AutoConfiguration
The relative ordering is implemented with @AliasFor annotations on the @AutoConfiguration annotation. The production code already works without changes, only the test code had to be modified. It now uses AnnotationMetadata which already knows how to deal with @AliasFor instead of using the reflection API directly. See gh-29907
This commit is contained in:
parent
57cd34be88
commit
9e9049ca7f
|
|
@ -25,8 +25,11 @@ import java.lang.annotation.Target;
|
||||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||||
import org.springframework.boot.context.annotation.ImportCandidates;
|
import org.springframework.boot.context.annotation.ImportCandidates;
|
||||||
|
import org.springframework.context.annotation.AnnotationBeanNameGenerator;
|
||||||
|
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
|
||||||
import org.springframework.context.annotation.Conditional;
|
import org.springframework.context.annotation.Conditional;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.core.annotation.AliasFor;
|
||||||
import org.springframework.core.io.support.SpringFactoriesLoader;
|
import org.springframework.core.io.support.SpringFactoriesLoader;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -55,6 +58,52 @@ import org.springframework.core.io.support.SpringFactoriesLoader;
|
||||||
@Retention(RetentionPolicy.RUNTIME)
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
@Documented
|
@Documented
|
||||||
@Configuration(proxyBeanMethods = false)
|
@Configuration(proxyBeanMethods = false)
|
||||||
|
@AutoConfigureBefore
|
||||||
|
@AutoConfigureAfter
|
||||||
public @interface AutoConfiguration {
|
public @interface AutoConfiguration {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Explicitly specify the name of the Spring bean definition associated with the
|
||||||
|
* {@code @AutoConfiguration} class. If left unspecified (the common case), a bean
|
||||||
|
* name will be automatically generated.
|
||||||
|
* <p>
|
||||||
|
* The custom name applies only if the {@code @AutoConfiguration} class is picked up
|
||||||
|
* via component scanning or supplied directly to an
|
||||||
|
* {@link AnnotationConfigApplicationContext}. If the {@code @AutoConfiguration} class
|
||||||
|
* is registered as a traditional XML bean definition, the name/id of the bean element
|
||||||
|
* will take precedence.
|
||||||
|
* @return the explicit component name, if any (or empty String otherwise)
|
||||||
|
* @see AnnotationBeanNameGenerator
|
||||||
|
*/
|
||||||
|
@AliasFor(annotation = Configuration.class)
|
||||||
|
String value() default "";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The auto-configure classes that should have not yet been applied.
|
||||||
|
* @return the classes
|
||||||
|
*/
|
||||||
|
@AliasFor(annotation = AutoConfigureBefore.class, attribute = "value")
|
||||||
|
Class<?>[] before() default {};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The names of the auto-configure classes that should have not yet been applied.
|
||||||
|
* @return the class names
|
||||||
|
*/
|
||||||
|
@AliasFor(annotation = AutoConfigureBefore.class, attribute = "name")
|
||||||
|
String[] beforeName() default {};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The auto-configure classes that should have already been applied.
|
||||||
|
* @return the classes
|
||||||
|
*/
|
||||||
|
@AliasFor(annotation = AutoConfigureAfter.class, attribute = "value")
|
||||||
|
Class<?>[] after() default {};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The names of the auto-configure classes that should have already been applied.
|
||||||
|
* @return the class names
|
||||||
|
*/
|
||||||
|
@AliasFor(annotation = AutoConfigureAfter.class, attribute = "name")
|
||||||
|
String[] afterName() default {};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright 2012-2020 the original author or authors.
|
* Copyright 2012-2022 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright 2012-2020 the original author or authors.
|
* Copyright 2012-2022 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright 2012-2019 the original author or authors.
|
* Copyright 2012-2022 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
|
@ -21,6 +21,7 @@ import java.util.Arrays;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.LinkedHashSet;
|
import java.util.LinkedHashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.Properties;
|
import java.util.Properties;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
|
|
@ -28,6 +29,7 @@ import org.junit.jupiter.api.BeforeEach;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
import org.springframework.core.Ordered;
|
import org.springframework.core.Ordered;
|
||||||
|
import org.springframework.core.type.AnnotationMetadata;
|
||||||
import org.springframework.core.type.classreading.CachingMetadataReaderFactory;
|
import org.springframework.core.type.classreading.CachingMetadataReaderFactory;
|
||||||
import org.springframework.core.type.classreading.MetadataReader;
|
import org.springframework.core.type.classreading.MetadataReader;
|
||||||
import org.springframework.core.type.classreading.MetadataReaderFactory;
|
import org.springframework.core.type.classreading.MetadataReaderFactory;
|
||||||
|
|
@ -43,6 +45,7 @@ import static org.mockito.Mockito.mock;
|
||||||
*
|
*
|
||||||
* @author Phillip Webb
|
* @author Phillip Webb
|
||||||
* @author Andy Wilkinson
|
* @author Andy Wilkinson
|
||||||
|
* @author Moritz Halbritter
|
||||||
*/
|
*/
|
||||||
class AutoConfigurationSorterTests {
|
class AutoConfigurationSorterTests {
|
||||||
|
|
||||||
|
|
@ -54,8 +57,14 @@ class AutoConfigurationSorterTests {
|
||||||
|
|
||||||
private static final String A = AutoConfigureA.class.getName();
|
private static final String A = AutoConfigureA.class.getName();
|
||||||
|
|
||||||
|
private static final String A2 = AutoConfigureA2.class.getName();
|
||||||
|
|
||||||
|
private static final String A3 = AutoConfigureA3.class.getName();
|
||||||
|
|
||||||
private static final String B = AutoConfigureB.class.getName();
|
private static final String B = AutoConfigureB.class.getName();
|
||||||
|
|
||||||
|
private static final String B2 = AutoConfigureB2.class.getName();
|
||||||
|
|
||||||
private static final String C = AutoConfigureC.class.getName();
|
private static final String C = AutoConfigureC.class.getName();
|
||||||
|
|
||||||
private static final String D = AutoConfigureD.class.getName();
|
private static final String D = AutoConfigureD.class.getName();
|
||||||
|
|
@ -64,15 +73,17 @@ class AutoConfigurationSorterTests {
|
||||||
|
|
||||||
private static final String W = AutoConfigureW.class.getName();
|
private static final String W = AutoConfigureW.class.getName();
|
||||||
|
|
||||||
|
private static final String W2 = AutoConfigureW2.class.getName();
|
||||||
|
|
||||||
private static final String X = AutoConfigureX.class.getName();
|
private static final String X = AutoConfigureX.class.getName();
|
||||||
|
|
||||||
private static final String Y = AutoConfigureY.class.getName();
|
private static final String Y = AutoConfigureY.class.getName();
|
||||||
|
|
||||||
|
private static final String Y2 = AutoConfigureY2.class.getName();
|
||||||
|
|
||||||
private static final String Z = AutoConfigureZ.class.getName();
|
private static final String Z = AutoConfigureZ.class.getName();
|
||||||
|
|
||||||
private static final String A2 = AutoConfigureA2.class.getName();
|
private static final String Z2 = AutoConfigureZ2.class.getName();
|
||||||
|
|
||||||
private static final String W2 = AutoConfigureW2.class.getName();
|
|
||||||
|
|
||||||
private AutoConfigurationSorter sorter;
|
private AutoConfigurationSorter sorter;
|
||||||
|
|
||||||
|
|
@ -95,12 +106,24 @@ class AutoConfigurationSorterTests {
|
||||||
assertThat(actual).containsExactly(C, B, A);
|
assertThat(actual).containsExactly(C, B, A);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void byAutoConfigureAfterAliasFor() {
|
||||||
|
List<String> actual = this.sorter.getInPriorityOrder(Arrays.asList(A3, B2, C));
|
||||||
|
assertThat(actual).containsExactly(C, B2, A3);
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void byAutoConfigureBefore() {
|
void byAutoConfigureBefore() {
|
||||||
List<String> actual = this.sorter.getInPriorityOrder(Arrays.asList(X, Y, Z));
|
List<String> actual = this.sorter.getInPriorityOrder(Arrays.asList(X, Y, Z));
|
||||||
assertThat(actual).containsExactly(Z, Y, X);
|
assertThat(actual).containsExactly(Z, Y, X);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void byAutoConfigureBeforeAliasFor() {
|
||||||
|
List<String> actual = this.sorter.getInPriorityOrder(Arrays.asList(X, Y2, Z2));
|
||||||
|
assertThat(actual).containsExactly(Z2, Y2, X);
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void byAutoConfigureAfterDoubles() {
|
void byAutoConfigureAfterDoubles() {
|
||||||
List<String> actual = this.sorter.getInPriorityOrder(Arrays.asList(A, B, C, E));
|
List<String> actual = this.sorter.getInPriorityOrder(Arrays.asList(A, B, C, E));
|
||||||
|
|
@ -170,29 +193,47 @@ class AutoConfigurationSorterTests {
|
||||||
for (String className : classNames) {
|
for (String className : classNames) {
|
||||||
Class<?> type = ClassUtils.forName(className, null);
|
Class<?> type = ClassUtils.forName(className, null);
|
||||||
properties.put(type.getName(), "");
|
properties.put(type.getName(), "");
|
||||||
AutoConfigureOrder order = type.getDeclaredAnnotation(AutoConfigureOrder.class);
|
AnnotationMetadata annotationMetadata = AnnotationMetadata.introspect(type);
|
||||||
if (order != null) {
|
addAutoConfigureOrder(properties, className, annotationMetadata);
|
||||||
properties.put(className + ".AutoConfigureOrder", String.valueOf(order.value()));
|
addAutoConfigureBefore(properties, className, annotationMetadata);
|
||||||
}
|
addAutoConfigureAfter(properties, className, annotationMetadata);
|
||||||
AutoConfigureBefore autoConfigureBefore = type.getDeclaredAnnotation(AutoConfigureBefore.class);
|
|
||||||
if (autoConfigureBefore != null) {
|
|
||||||
properties.put(className + ".AutoConfigureBefore",
|
|
||||||
merge(autoConfigureBefore.value(), autoConfigureBefore.name()));
|
|
||||||
}
|
|
||||||
AutoConfigureAfter autoConfigureAfter = type.getDeclaredAnnotation(AutoConfigureAfter.class);
|
|
||||||
if (autoConfigureAfter != null) {
|
|
||||||
properties.put(className + ".AutoConfigureAfter",
|
|
||||||
merge(autoConfigureAfter.value(), autoConfigureAfter.name()));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return AutoConfigurationMetadataLoader.loadMetadata(properties);
|
return AutoConfigurationMetadataLoader.loadMetadata(properties);
|
||||||
}
|
}
|
||||||
|
|
||||||
private String merge(Class<?>[] value, String[] name) {
|
private void addAutoConfigureAfter(Properties properties, String className, AnnotationMetadata annotationMetadata) {
|
||||||
Set<String> items = new LinkedHashSet<>();
|
Map<String, Object> autoConfigureAfter = annotationMetadata
|
||||||
for (Class<?> type : value) {
|
.getAnnotationAttributes(AutoConfigureAfter.class.getName(), true);
|
||||||
items.add(type.getName());
|
if (autoConfigureAfter != null) {
|
||||||
|
properties.put(className + ".AutoConfigureAfter",
|
||||||
|
merge((String[]) autoConfigureAfter.get("value"), (String[]) autoConfigureAfter.get("name")));
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addAutoConfigureBefore(Properties properties, String className,
|
||||||
|
AnnotationMetadata annotationMetadata) {
|
||||||
|
Map<String, Object> autoConfigureBefore = annotationMetadata
|
||||||
|
.getAnnotationAttributes(AutoConfigureBefore.class.getName(), true);
|
||||||
|
if (autoConfigureBefore != null) {
|
||||||
|
properties.put(className + ".AutoConfigureBefore",
|
||||||
|
merge((String[]) autoConfigureBefore.get("value"), (String[]) autoConfigureBefore.get("name")));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addAutoConfigureOrder(Properties properties, String className, AnnotationMetadata annotationMetadata) {
|
||||||
|
Map<String, Object> autoConfigureOrder = annotationMetadata
|
||||||
|
.getAnnotationAttributes(AutoConfigureOrder.class.getName());
|
||||||
|
if (autoConfigureOrder != null) {
|
||||||
|
Integer order = (Integer) autoConfigureOrder.get("order");
|
||||||
|
if (order != null) {
|
||||||
|
properties.put(className + ".AutoConfigureOrder", String.valueOf(order));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String merge(String[] value, String[] name) {
|
||||||
|
Set<String> items = new LinkedHashSet<>();
|
||||||
|
Collections.addAll(items, value);
|
||||||
Collections.addAll(items, name);
|
Collections.addAll(items, name);
|
||||||
return StringUtils.collectionToCommaDelimitedString(items);
|
return StringUtils.collectionToCommaDelimitedString(items);
|
||||||
}
|
}
|
||||||
|
|
@ -222,11 +263,21 @@ class AutoConfigurationSorterTests {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@AutoConfiguration(after = AutoConfigureB2.class)
|
||||||
|
static class AutoConfigureA3 {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
@AutoConfigureAfter({ AutoConfigureC.class, AutoConfigureD.class, AutoConfigureE.class })
|
@AutoConfigureAfter({ AutoConfigureC.class, AutoConfigureD.class, AutoConfigureE.class })
|
||||||
static class AutoConfigureB {
|
static class AutoConfigureB {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@AutoConfiguration(after = { AutoConfigureC.class, AutoConfigureD.class, AutoConfigureE.class })
|
||||||
|
static class AutoConfigureB2 {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
static class AutoConfigureC {
|
static class AutoConfigureC {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -259,11 +310,21 @@ class AutoConfigurationSorterTests {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@AutoConfiguration(before = AutoConfigureX.class)
|
||||||
|
static class AutoConfigureY2 {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
@AutoConfigureBefore(AutoConfigureY.class)
|
@AutoConfigureBefore(AutoConfigureY.class)
|
||||||
static class AutoConfigureZ {
|
static class AutoConfigureZ {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@AutoConfiguration(before = AutoConfigureY2.class)
|
||||||
|
static class AutoConfigureZ2 {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
static class SkipCycleMetadataReaderFactory extends CachingMetadataReaderFactory {
|
static class SkipCycleMetadataReaderFactory extends CachingMetadataReaderFactory {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue