Skip to content

Commit 7872f61

Browse files
committed
Add @autoConfiguration annotation support to the autoconfigure-processor
See gh-29907
1 parent 9149ae5 commit 7872f61

File tree

7 files changed

+301
-73
lines changed

7 files changed

+301
-73
lines changed

spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/AutoConfigurationSorterTests.java

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,15 @@ void byAutoConfigureAfterAliasFor() {
112112
assertThat(actual).containsExactly(C, B2, A3);
113113
}
114114

115+
@Test
116+
void byAutoConfigureAfterAliasForWithProperties() throws Exception {
117+
MetadataReaderFactory readerFactory = new CachingMetadataReaderFactory();
118+
this.autoConfigurationMetadata = getAutoConfigurationMetadata(A3, B2, C);
119+
this.sorter = new AutoConfigurationSorter(readerFactory, this.autoConfigurationMetadata);
120+
List<String> actual = this.sorter.getInPriorityOrder(Arrays.asList(A3, B2, C));
121+
assertThat(actual).containsExactly(C, B2, A3);
122+
}
123+
115124
@Test
116125
void byAutoConfigureBefore() {
117126
List<String> actual = this.sorter.getInPriorityOrder(Arrays.asList(X, Y, Z));
@@ -124,6 +133,15 @@ void byAutoConfigureBeforeAliasFor() {
124133
assertThat(actual).containsExactly(Z2, Y2, X);
125134
}
126135

136+
@Test
137+
void byAutoConfigureBeforeAliasForWithProperties() throws Exception {
138+
MetadataReaderFactory readerFactory = new CachingMetadataReaderFactory();
139+
this.autoConfigurationMetadata = getAutoConfigurationMetadata(X, Y2, Z2);
140+
this.sorter = new AutoConfigurationSorter(readerFactory, this.autoConfigurationMetadata);
141+
List<String> actual = this.sorter.getInPriorityOrder(Arrays.asList(X, Y2, Z2));
142+
assertThat(actual).containsExactly(Z2, Y2, X);
143+
}
144+
127145
@Test
128146
void byAutoConfigureAfterDoubles() {
129147
List<String> actual = this.sorter.getInPriorityOrder(Arrays.asList(A, B, C, E));
@@ -273,7 +291,7 @@ static class AutoConfigureB {
273291

274292
}
275293

276-
@AutoConfiguration(after = { AutoConfigureC.class, AutoConfigureD.class, AutoConfigureE.class })
294+
@AutoConfiguration(after = { AutoConfigureC.class })
277295
static class AutoConfigureB2 {
278296

279297
}

spring-boot-project/spring-boot-tools/spring-boot-autoconfigure-processor/src/main/java/org/springframework/boot/autoconfigureprocessor/AutoConfigureAnnotationProcessor.java

Lines changed: 111 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2020 the original author or authors.
2+
* Copyright 2012-2022 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -51,6 +51,7 @@
5151
*
5252
* @author Madhura Bhave
5353
* @author Phillip Webb
54+
* @author Moritz Halbritter
5455
* @since 1.5.0
5556
*/
5657
@SupportedAnnotationTypes({ "org.springframework.boot.autoconfigure.condition.ConditionalOnClass",
@@ -59,46 +60,45 @@
5960
"org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication",
6061
"org.springframework.boot.autoconfigure.AutoConfigureBefore",
6162
"org.springframework.boot.autoconfigure.AutoConfigureAfter",
62-
"org.springframework.boot.autoconfigure.AutoConfigureOrder" })
63+
"org.springframework.boot.autoconfigure.AutoConfigureOrder",
64+
"org.springframework.boot.autoconfigure.AutoConfiguration" })
6365
public class AutoConfigureAnnotationProcessor extends AbstractProcessor {
6466

6567
protected static final String PROPERTIES_PATH = "META-INF/spring-autoconfigure-metadata.properties";
6668

67-
private final Map<String, String> annotations;
68-
69-
private final Map<String, ValueExtractor> valueExtractors;
70-
7169
private final Map<String, String> properties = new TreeMap<>();
7270

73-
public AutoConfigureAnnotationProcessor() {
74-
Map<String, String> annotations = new LinkedHashMap<>();
75-
addAnnotations(annotations);
76-
this.annotations = Collections.unmodifiableMap(annotations);
77-
Map<String, ValueExtractor> valueExtractors = new LinkedHashMap<>();
78-
addValueExtractors(valueExtractors);
79-
this.valueExtractors = Collections.unmodifiableMap(valueExtractors);
80-
}
71+
private final List<PropertyGenerator> propertyGenerators;
8172

82-
protected void addAnnotations(Map<String, String> annotations) {
83-
annotations.put("ConditionalOnClass", "org.springframework.boot.autoconfigure.condition.ConditionalOnClass");
84-
annotations.put("ConditionalOnBean", "org.springframework.boot.autoconfigure.condition.ConditionalOnBean");
85-
annotations.put("ConditionalOnSingleCandidate",
86-
"org.springframework.boot.autoconfigure.condition.ConditionalOnSingleCandidate");
87-
annotations.put("ConditionalOnWebApplication",
88-
"org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication");
89-
annotations.put("AutoConfigureBefore", "org.springframework.boot.autoconfigure.AutoConfigureBefore");
90-
annotations.put("AutoConfigureAfter", "org.springframework.boot.autoconfigure.AutoConfigureAfter");
91-
annotations.put("AutoConfigureOrder", "org.springframework.boot.autoconfigure.AutoConfigureOrder");
73+
public AutoConfigureAnnotationProcessor() {
74+
this.propertyGenerators = Collections.unmodifiableList(getPropertyGenerators());
9275
}
9376

94-
private void addValueExtractors(Map<String, ValueExtractor> attributes) {
95-
attributes.put("ConditionalOnClass", new OnClassConditionValueExtractor());
96-
attributes.put("ConditionalOnBean", new OnBeanConditionValueExtractor());
97-
attributes.put("ConditionalOnSingleCandidate", new OnBeanConditionValueExtractor());
98-
attributes.put("ConditionalOnWebApplication", ValueExtractor.allFrom("type"));
99-
attributes.put("AutoConfigureBefore", ValueExtractor.allFrom("value", "name"));
100-
attributes.put("AutoConfigureAfter", ValueExtractor.allFrom("value", "name"));
101-
attributes.put("AutoConfigureOrder", ValueExtractor.allFrom("value"));
77+
protected List<PropertyGenerator> getPropertyGenerators() {
78+
List<PropertyGenerator> generators = new ArrayList<>();
79+
generators.add(PropertyGenerator.of("ConditionalOnClass",
80+
"org.springframework.boot.autoconfigure.condition.ConditionalOnClass",
81+
new OnClassConditionValueExtractor()));
82+
generators.add(PropertyGenerator.of("ConditionalOnBean",
83+
"org.springframework.boot.autoconfigure.condition.ConditionalOnBean",
84+
new OnBeanConditionValueExtractor()));
85+
generators.add(PropertyGenerator.of("ConditionalOnSingleCandidate",
86+
"org.springframework.boot.autoconfigure.condition.ConditionalOnSingleCandidate",
87+
new OnBeanConditionValueExtractor()));
88+
generators.add(PropertyGenerator.of("ConditionalOnWebApplication",
89+
"org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication",
90+
ValueExtractor.allFrom("type")));
91+
generators.add(PropertyGenerator.of("AutoConfigureBefore",
92+
"org.springframework.boot.autoconfigure.AutoConfigureBefore", ValueExtractor.allFrom("value", "name"),
93+
"org.springframework.boot.autoconfigure.AutoConfiguration",
94+
ValueExtractor.allFrom("before", "beforeName")));
95+
generators.add(PropertyGenerator.of("AutoConfigureAfter",
96+
"org.springframework.boot.autoconfigure.AutoConfigureAfter", ValueExtractor.allFrom("value", "name"),
97+
"org.springframework.boot.autoconfigure.AutoConfiguration",
98+
ValueExtractor.allFrom("after", "afterName")));
99+
generators.add(PropertyGenerator.of("AutoConfigureOrder",
100+
"org.springframework.boot.autoconfigure.AutoConfigureOrder", ValueExtractor.allFrom("value")));
101+
return generators;
102102
}
103103

104104
@Override
@@ -108,8 +108,8 @@ public SourceVersion getSupportedSourceVersion() {
108108

109109
@Override
110110
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
111-
for (Map.Entry<String, String> entry : this.annotations.entrySet()) {
112-
process(roundEnv, entry.getKey(), entry.getValue());
111+
for (PropertyGenerator generator : this.propertyGenerators) {
112+
process(roundEnv, generator);
113113
}
114114
if (roundEnv.processingOver()) {
115115
try {
@@ -122,22 +122,24 @@ public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment
122122
return false;
123123
}
124124

125-
private void process(RoundEnvironment roundEnv, String propertyKey, String annotationName) {
126-
TypeElement annotationType = this.processingEnv.getElementUtils().getTypeElement(annotationName);
127-
if (annotationType != null) {
128-
for (Element element : roundEnv.getElementsAnnotatedWith(annotationType)) {
129-
processElement(element, propertyKey, annotationName);
125+
private void process(RoundEnvironment roundEnv, PropertyGenerator generator) {
126+
for (String annotationName : generator.getSupportedAnnotations()) {
127+
TypeElement annotationType = this.processingEnv.getElementUtils().getTypeElement(annotationName);
128+
if (annotationType != null) {
129+
for (Element element : roundEnv.getElementsAnnotatedWith(annotationType)) {
130+
processElement(element, generator, annotationName);
131+
}
130132
}
131133
}
132134
}
133135

134-
private void processElement(Element element, String propertyKey, String annotationName) {
136+
private void processElement(Element element, PropertyGenerator generator, String annotationName) {
135137
try {
136138
String qualifiedName = Elements.getQualifiedName(element);
137139
AnnotationMirror annotation = getAnnotation(element, annotationName);
138140
if (qualifiedName != null && annotation != null) {
139-
List<Object> values = getValues(propertyKey, annotation);
140-
this.properties.put(qualifiedName + "." + propertyKey, toCommaDelimitedString(values));
141+
List<Object> values = getValues(generator, annotationName, annotation);
142+
generator.applyToProperties(this.properties, qualifiedName, values);
141143
this.properties.put(qualifiedName, "");
142144
}
143145
}
@@ -157,17 +159,8 @@ private AnnotationMirror getAnnotation(Element element, String type) {
157159
return null;
158160
}
159161

160-
private String toCommaDelimitedString(List<Object> list) {
161-
StringBuilder result = new StringBuilder();
162-
for (Object item : list) {
163-
result.append((result.length() != 0) ? "," : "");
164-
result.append(item);
165-
}
166-
return result.toString();
167-
}
168-
169-
private List<Object> getValues(String propertyKey, AnnotationMirror annotation) {
170-
ValueExtractor extractor = this.valueExtractors.get(propertyKey);
162+
private List<Object> getValues(PropertyGenerator generator, String annotationName, AnnotationMirror annotation) {
163+
ValueExtractor extractor = generator.getValueExtractor(annotationName);
171164
if (extractor == null) {
172165
return Collections.emptyList();
173166
}
@@ -190,7 +183,7 @@ private void writeProperties() throws IOException {
190183
}
191184

192185
@FunctionalInterface
193-
private interface ValueExtractor {
186+
interface ValueExtractor {
194187

195188
List<Object> getValues(AnnotationMirror annotation);
196189

@@ -245,7 +238,7 @@ public List<Object> getValues(AnnotationMirror annotation) {
245238

246239
}
247240

248-
private static class OnBeanConditionValueExtractor extends AbstractValueExtractor {
241+
static class OnBeanConditionValueExtractor extends AbstractValueExtractor {
249242

250243
@Override
251244
public List<Object> getValues(AnnotationMirror annotation) {
@@ -263,7 +256,7 @@ public List<Object> getValues(AnnotationMirror annotation) {
263256

264257
}
265258

266-
private static class OnClassConditionValueExtractor extends NamedValuesExtractor {
259+
static class OnClassConditionValueExtractor extends NamedValuesExtractor {
267260

268261
OnClassConditionValueExtractor() {
269262
super("value", "name");
@@ -287,4 +280,66 @@ private boolean isSpringClass(String type) {
287280

288281
}
289282

283+
static final class PropertyGenerator {
284+
285+
private final String keyName;
286+
287+
/**
288+
* Maps from annotation class name -> {@link ValueExtractor}.
289+
*/
290+
private final Map<String, ValueExtractor> valueExtractors;
291+
292+
private PropertyGenerator(String keyName, Map<String, ValueExtractor> valueExtractors) {
293+
this.keyName = keyName;
294+
this.valueExtractors = valueExtractors;
295+
}
296+
297+
Set<String> getSupportedAnnotations() {
298+
return Collections.unmodifiableSet(this.valueExtractors.keySet());
299+
}
300+
301+
ValueExtractor getValueExtractor(String annotation) {
302+
return this.valueExtractors.get(annotation);
303+
}
304+
305+
void applyToProperties(Map<String, String> properties, String className, List<Object> annotationValues) {
306+
mergeProperties(properties, className + "." + this.keyName, toCommaDelimitedString(annotationValues));
307+
}
308+
309+
private void mergeProperties(Map<String, String> properties, String key, String value) {
310+
String existingKey = properties.get(key);
311+
if (existingKey == null || existingKey.isEmpty()) {
312+
properties.put(key, value);
313+
}
314+
else if (!value.isEmpty()) {
315+
properties.put(key, existingKey + "," + value);
316+
}
317+
}
318+
319+
private String toCommaDelimitedString(List<Object> list) {
320+
if (list.isEmpty()) {
321+
return "";
322+
}
323+
StringBuilder result = new StringBuilder();
324+
for (Object item : list) {
325+
result.append((result.length() != 0) ? "," : "");
326+
result.append(item);
327+
}
328+
return result.toString();
329+
}
330+
331+
static PropertyGenerator of(String keyName, String annotation, ValueExtractor valueExtractor) {
332+
return new PropertyGenerator(keyName, Collections.singletonMap(annotation, valueExtractor));
333+
}
334+
335+
static PropertyGenerator of(String keyName, String annotation1, ValueExtractor valueExtractor1,
336+
String annotation2, ValueExtractor valueExtractor2) {
337+
Map<String, ValueExtractor> valueExtractors = new LinkedHashMap<>();
338+
valueExtractors.put(annotation1, valueExtractor1);
339+
valueExtractors.put(annotation2, valueExtractor2);
340+
return new PropertyGenerator(keyName, valueExtractors);
341+
}
342+
343+
}
344+
290345
}

spring-boot-project/spring-boot-tools/spring-boot-autoconfigure-processor/src/test/java/org/springframework/boot/autoconfigureprocessor/AutoConfigureAnnotationProcessorTests.java

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2020 the original author or authors.
2+
* Copyright 2012-2022 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -33,6 +33,7 @@
3333
* Tests for {@link AutoConfigureAnnotationProcessor}.
3434
*
3535
* @author Madhura Bhave
36+
* @author Moritz Halbritter
3637
*/
3738
class AutoConfigureAnnotationProcessorTests {
3839

@@ -97,6 +98,32 @@ void annotatedClassWithOrder() throws Exception {
9798
"123");
9899
}
99100

101+
@Test
102+
void annotatedClassWithAutoConfiguration() throws Exception {
103+
Properties properties = compile(TestAutoConfigurationConfiguration.class);
104+
assertThat(properties).containsEntry(
105+
"org.springframework.boot.autoconfigureprocessor.TestAutoConfigurationConfiguration", "");
106+
assertThat(properties).containsEntry(
107+
"org.springframework.boot.autoconfigureprocessor.TestAutoConfigurationConfiguration.AutoConfigureBefore",
108+
"java.io.InputStream,test.before1,test.before2");
109+
assertThat(properties).containsEntry(
110+
"org.springframework.boot.autoconfigureprocessor.TestAutoConfigurationConfiguration.AutoConfigureAfter",
111+
"java.io.OutputStream,test.after1,test.after2");
112+
}
113+
114+
@Test
115+
void annotatedClassWithAutoConfigurationMerged() throws Exception {
116+
Properties properties = compile(TestMergedAutoConfigurationConfiguration.class);
117+
assertThat(properties).containsEntry(
118+
"org.springframework.boot.autoconfigureprocessor.TestMergedAutoConfigurationConfiguration", "");
119+
assertThat(properties).containsEntry(
120+
"org.springframework.boot.autoconfigureprocessor.TestMergedAutoConfigurationConfiguration.AutoConfigureBefore",
121+
"java.io.InputStream,test.before1,test.before2,java.io.ObjectInputStream,test.before3,test.before4");
122+
assertThat(properties).containsEntry(
123+
"org.springframework.boot.autoconfigureprocessor.TestMergedAutoConfigurationConfiguration.AutoConfigureAfter",
124+
"java.io.OutputStream,test.after1,test.after2,java.io.ObjectOutputStream,test.after3,test.after4");
125+
}
126+
100127
@Test // gh-19370
101128
void propertiesAreFullRepeatable() throws Exception {
102129
String first = new String(
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
/*
2+
* Copyright 2012-2022 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.boot.autoconfigureprocessor;
18+
19+
import java.lang.annotation.Documented;
20+
import java.lang.annotation.ElementType;
21+
import java.lang.annotation.Retention;
22+
import java.lang.annotation.RetentionPolicy;
23+
import java.lang.annotation.Target;
24+
25+
import org.springframework.core.annotation.AliasFor;
26+
27+
/**
28+
* Alternative to Spring Boot's {@code @AutoConfiguration} for testing (removes the need
29+
* for a dependency on the real annotation).
30+
*
31+
* @author Moritz Halbritter
32+
*/
33+
@Target(ElementType.TYPE)
34+
@Retention(RetentionPolicy.RUNTIME)
35+
@Documented
36+
@TestAutoConfigureBefore
37+
@TestAutoConfigureAfter
38+
public @interface TestAutoConfiguration {
39+
40+
@AliasFor(annotation = TestAutoConfigureBefore.class, attribute = "value")
41+
Class<?>[] before() default {};
42+
43+
@AliasFor(annotation = TestAutoConfigureBefore.class, attribute = "name")
44+
String[] beforeName() default {};
45+
46+
@AliasFor(annotation = TestAutoConfigureAfter.class, attribute = "value")
47+
Class<?>[] after() default {};
48+
49+
@AliasFor(annotation = TestAutoConfigureAfter.class, attribute = "name")
50+
String[] afterName() default {};
51+
52+
}

0 commit comments

Comments
 (0)