• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

TAKETODAY / today-infrastructure / 18154768944

01 Oct 2025 07:26AM UTC coverage: 81.882% (-0.005%) from 81.887%
18154768944

push

github

web-flow
Merge pull request #290 from TAKETODAY/dev/jspecify

jspecify

59788 of 78013 branches covered (76.64%)

Branch coverage included in aggregate %.

141239 of 167496 relevant lines covered (84.32%)

3.6 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

86.77
today-context/src/main/java/infra/context/annotation/config/AutoConfigurationImportSelector.java
1
/*
2
 * Copyright 2017 - 2025 the original author or authors.
3
 *
4
 * This program is free software: you can redistribute it and/or modify
5
 * it under the terms of the GNU General Public License as published by
6
 * the Free Software Foundation, either version 3 of the License, or
7
 * (at your option) any later version.
8
 *
9
 * This program is distributed in the hope that it will be useful,
10
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
 * GNU General Public License for more details.
13
 *
14
 * You should have received a copy of the GNU General Public License
15
 * along with this program. If not, see [https://www.gnu.org/licenses/]
16
 */
17

18
package infra.context.annotation.config;
19

20
import org.jspecify.annotations.Nullable;
21

22
import java.util.ArrayList;
23
import java.util.Arrays;
24
import java.util.Collection;
25
import java.util.Collections;
26
import java.util.HashSet;
27
import java.util.LinkedHashMap;
28
import java.util.LinkedHashSet;
29
import java.util.List;
30
import java.util.Set;
31
import java.util.concurrent.TimeUnit;
32
import java.util.function.Predicate;
33

34
import infra.beans.factory.BeanClassLoaderAware;
35
import infra.context.BootstrapContext;
36
import infra.context.BootstrapContextAware;
37
import infra.context.annotation.Configuration;
38
import infra.context.annotation.DeferredImportSelector;
39
import infra.context.properties.bind.Binder;
40
import infra.core.Ordered;
41
import infra.core.annotation.AnnotationAttributes;
42
import infra.core.env.ConfigurableEnvironment;
43
import infra.core.env.Environment;
44
import infra.core.type.AnnotationMetadata;
45
import infra.lang.Assert;
46
import infra.lang.TodayStrategies;
47
import infra.logging.Logger;
48
import infra.logging.LoggerFactory;
49
import infra.util.ClassUtils;
50
import infra.util.CollectionUtils;
51
import infra.util.StringUtils;
52

53
/**
54
 * {@link DeferredImportSelector} to handle {@link EnableAutoConfiguration
55
 * auto-configuration}. This class can also be subclassed if a custom variant of
56
 * {@link EnableAutoConfiguration @EnableAutoConfiguration} is needed.
57
 *
58
 * @author Phillip Webb
59
 * @author Andy Wilkinson
60
 * @author Stephane Nicoll
61
 * @author Madhura Bhave
62
 * @author <a href="https://github.com/TAKETODAY">Harry Yang</a>
63
 * @see EnableAutoConfiguration
64
 * @since 4.0 2022/2/1 02:37
65
 */
66
@SuppressWarnings("NullAway")
67
public class AutoConfigurationImportSelector implements DeferredImportSelector,
68
        BeanClassLoaderAware, BootstrapContextAware, Ordered, Predicate<String> {
69

70
  private static final Logger log = LoggerFactory.getLogger(AutoConfigurationImportSelector.class);
4✔
71

72
  private static final String PROPERTY_NAME_AUTOCONFIGURE_EXCLUDE = "infra.auto-configuration.exclude";
73

74
  static final int ORDER = Ordered.LOWEST_PRECEDENCE - 1;
75

76
  private final Class<?> autoConfigurationAnnotation;
77

78
  private ClassLoader beanClassLoader;
79

80
  protected BootstrapContext bootstrapContext;
81

82
  @Nullable
83
  private volatile ConfigurationClassFilter configurationClassFilter;
84

85
  @Nullable
86
  private volatile AutoConfigurationReplacements autoConfigurationReplacements;
87

88
  public AutoConfigurationImportSelector() {
89
    this(null);
3✔
90
  }
1✔
91

92
  protected AutoConfigurationImportSelector(@Nullable Class<?> autoConfigurationAnnotation) {
2✔
93
    this.autoConfigurationAnnotation = autoConfigurationAnnotation != null ? autoConfigurationAnnotation
5✔
94
            : AutoConfiguration.class;
2✔
95
  }
1✔
96

97
  @Override
98
  public String[] selectImports(AnnotationMetadata importMetadata) {
99
    if (isEnabled(importMetadata)) {
4!
100
      AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(importMetadata);
4✔
101
      return StringUtils.toStringArray(autoConfigurationEntry.configurations);
4✔
102
    }
103
    return NO_IMPORTS;
×
104
  }
105

106
  @Override
107
  public Predicate<String> getExclusionFilter() {
108
    return this;
2✔
109
  }
110

111
  @Override
112
  public boolean test(String configurationClassName) {
113
    return shouldExclude(configurationClassName);
4✔
114
  }
115

116
  private boolean shouldExclude(String configurationClassName) {
117
    return getConfigurationClassFilter()
4✔
118
            .filter(Collections.singletonList(configurationClassName))
2✔
119
            .isEmpty();
1✔
120
  }
121

122
  /**
123
   * Return the {@link AutoConfigurationEntry} based on the {@link AnnotationMetadata}
124
   * of the importing {@link Configuration @Configuration} class.
125
   *
126
   * @param annotationMetadata the annotation metadata of the configuration class
127
   * @return the auto-configurations that should be imported
128
   */
129
  protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
130
    if (isEnabled(annotationMetadata)) {
4!
131
      AnnotationAttributes attributes = getAttributes(annotationMetadata);
4✔
132
      List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
5✔
133
      configurations = removeDuplicates(configurations);
4✔
134
      Set<String> exclusions = getExclusions(annotationMetadata, attributes);
5✔
135
      checkExcludedClasses(configurations, exclusions);
4✔
136
      configurations.removeAll(exclusions);
4✔
137
      configurations = getConfigurationClassFilter().filter(configurations);
5✔
138
      fireAutoConfigurationImportEvents(configurations, exclusions);
4✔
139
      return new AutoConfigurationEntry(configurations, exclusions);
6✔
140
    }
141
    return AutoConfigurationEntry.empty();
×
142
  }
143

144
  @Override
145
  public Class<? extends Group> getImportGroup() {
146
    return AutoConfigurationGroup.class;
2✔
147
  }
148

149
  protected boolean isEnabled(AnnotationMetadata metadata) {
150
    if (getClass() == AutoConfigurationImportSelector.class) {
4✔
151
      Environment environment = getEnvironment();
3✔
152
      return environment != null && environment.getFlag(
10!
153
              EnableAutoConfiguration.ENABLED_OVERRIDE_PROPERTY, true);
154
    }
155
    return true;
2✔
156
  }
157

158
  /**
159
   * Return the appropriate {@link AnnotationAttributes} from the
160
   * {@link AnnotationMetadata}. By default this method will return attributes for
161
   * {@link #getAnnotationClass()}.
162
   *
163
   * @param metadata the annotation metadata
164
   * @return annotation attributes
165
   */
166
  @Nullable
167
  protected AnnotationAttributes getAttributes(AnnotationMetadata metadata) {
168
    String name = getAnnotationClass().getName();
4✔
169
    AnnotationAttributes attributes = AnnotationAttributes.fromMap(
5✔
170
            metadata.getAnnotationAttributes(name, true));
1✔
171
    if (attributes == null) {
2!
172
      throw new IllegalArgumentException("No auto-configuration attributes found. Is %s annotated with %s?"
×
173
              .formatted(metadata.getClassName(), ClassUtils.getShortName(name)));
×
174
    }
175
    return attributes;
2✔
176
  }
177

178
  /**
179
   * Return the source annotation class used by the selector.
180
   *
181
   * @return the annotation class
182
   */
183
  protected Class<?> getAnnotationClass() {
184
    return EnableAutoConfiguration.class;
2✔
185
  }
186

187
  /**
188
   * Return the auto-configuration class names that should be considered. By default
189
   * this method will load candidates using {@link TodayStrategies} with
190
   * {@link #getStrategyClass()}.
191
   *
192
   * @param metadata the source metadata
193
   * @param attributes the {@link #getAttributes(AnnotationMetadata) annotation attributes}
194
   * @return a list of candidate configurations
195
   */
196
  protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, @Nullable AnnotationAttributes attributes) {
197
    var configurations = ImportCandidates.load(autoConfigurationAnnotation, getBeanClassLoader()).getCandidates();
7✔
198
    configurations.addAll(TodayStrategies.findNames(getStrategyClass(), getBeanClassLoader()));
8✔
199

200
    if (CollectionUtils.isEmpty(configurations)) {
3!
201
      throw new IllegalArgumentException("No auto configuration classes found in META-INF/today.strategies " +
×
202
              "nor in META-INF/config/%s.imports If you are using a custom packaging, make sure that file is correct."
203
                      .formatted(autoConfigurationAnnotation.getName()));
×
204
    }
205

206
    return configurations;
2✔
207
  }
208

209
  /**
210
   * Return the class used by {@link TodayStrategies} to load configuration
211
   * candidates.
212
   *
213
   * @return the strategy class
214
   */
215
  protected Class<?> getStrategyClass() {
216
    return EnableAutoConfiguration.class;
2✔
217
  }
218

219
  private void checkExcludedClasses(List<String> configurations, Set<String> exclusions) {
220
    ArrayList<String> invalidExcludes = new ArrayList<>(exclusions.size());
6✔
221
    ClassLoader classLoader = getClass().getClassLoader();
4✔
222
    for (String exclusion : exclusions) {
10✔
223
      if (ClassUtils.isPresent(exclusion, classLoader)
6✔
224
              && !configurations.contains(exclusion)) {
2✔
225
        invalidExcludes.add(exclusion);
4✔
226
      }
227
    }
1✔
228
    if (!invalidExcludes.isEmpty()) {
3✔
229
      handleInvalidExcludes(invalidExcludes);
3✔
230
    }
231
  }
1✔
232

233
  /**
234
   * Handle any invalid excludes that have been specified.
235
   *
236
   * @param invalidExcludes the list of invalid excludes (will always have at least one
237
   * element)
238
   */
239
  protected void handleInvalidExcludes(List<String> invalidExcludes) {
240
    StringBuilder message = new StringBuilder();
4✔
241
    for (String exclude : invalidExcludes) {
10✔
242
      message.append("\t- ").append(exclude).append(String.format("%n"));
11✔
243
    }
1✔
244
    throw new IllegalStateException(String.format(
12✔
245
            "The following classes could not be excluded because they are not auto-configuration classes:%n%s",
246
            message));
247
  }
248

249
  /**
250
   * Return any exclusions that limit the candidate configurations.
251
   *
252
   * @param metadata the source metadata
253
   * @param attributes the {@link #getAttributes(AnnotationMetadata) annotation
254
   * attributes}
255
   * @return exclusions or an empty set
256
   */
257
  protected Set<String> getExclusions(AnnotationMetadata metadata, @Nullable AnnotationAttributes attributes) {
258
    LinkedHashSet<String> excluded = new LinkedHashSet<>();
4✔
259
    if (attributes != null) {
2!
260
      excluded.addAll(asList(attributes, "exclude"));
7✔
261
      excluded.addAll(asList(attributes, "excludeName"));
7✔
262
    }
263
    excluded.addAll(getExcludeAutoConfigurationsProperty());
5✔
264
    return getAutoConfigurationReplacements().replaceAll(excluded);
5✔
265
  }
266

267
  /**
268
   * Returns the auto-configurations excluded by the
269
   * {@code infra.auto-configuration.exclude} property.
270
   *
271
   * @return excluded auto-configurations
272
   */
273
  @SuppressWarnings("NullAway")
274
  protected List<String> getExcludeAutoConfigurationsProperty() {
275
    Environment environment = getEnvironment();
3✔
276
    if (environment == null) {
2✔
277
      return Collections.emptyList();
2✔
278
    }
279
    if (environment instanceof ConfigurableEnvironment) {
3!
280
      Binder binder = Binder.get((ConfigurableEnvironment) environment);
4✔
281
      return binder.bind(PROPERTY_NAME_AUTOCONFIGURE_EXCLUDE, String[].class)
6✔
282
              .map(Arrays::asList)
1✔
283
              .orElse(Collections.emptyList());
3✔
284
    }
285
    String[] excludes = environment.getProperty(PROPERTY_NAME_AUTOCONFIGURE_EXCLUDE, String[].class);
×
286
    return excludes != null ? Arrays.asList(excludes) : Collections.emptyList();
×
287
  }
288

289
  protected List<AutoConfigurationImportFilter> getAutoConfigurationImportFilters() {
290
    return TodayStrategies.find(AutoConfigurationImportFilter.class, getBeanClassLoader(), bootstrapContext);
7✔
291
  }
292

293
  private ConfigurationClassFilter getConfigurationClassFilter() {
294
    ConfigurationClassFilter configurationClassFilter = this.configurationClassFilter;
3✔
295
    if (configurationClassFilter == null) {
2✔
296
      List<AutoConfigurationImportFilter> filters = getAutoConfigurationImportFilters();
3✔
297
      configurationClassFilter = new ConfigurationClassFilter(getBeanClassLoader(), filters);
7✔
298
      this.configurationClassFilter = configurationClassFilter;
3✔
299
    }
300
    return configurationClassFilter;
2✔
301
  }
302

303
  private AutoConfigurationReplacements getAutoConfigurationReplacements() {
304
    AutoConfigurationReplacements autoConfigurationReplacements = this.autoConfigurationReplacements;
3✔
305
    if (autoConfigurationReplacements == null) {
2✔
306
      autoConfigurationReplacements = AutoConfigurationReplacements.load(autoConfigurationAnnotation, getBeanClassLoader());
6✔
307
      this.autoConfigurationReplacements = autoConfigurationReplacements;
3✔
308
    }
309
    return autoConfigurationReplacements;
2✔
310
  }
311

312
  protected final <T> List<T> removeDuplicates(List<T> list) {
313
    return new ArrayList<>(new LinkedHashSet<>(list));
8✔
314
  }
315

316
  protected final List<String> asList(AnnotationAttributes attributes, String name) {
317
    String[] value = attributes.getStringArray(name);
4✔
318
    return Arrays.asList(value);
3✔
319
  }
320

321
  private void fireAutoConfigurationImportEvents(List<String> configurations, Set<String> exclusions) {
322
    var listeners = getAutoConfigurationImportListeners();
3✔
323
    if (!listeners.isEmpty()) {
3!
324
      var event = new AutoConfigurationImportEvent(this, configurations, exclusions);
7✔
325
      for (AutoConfigurationImportListener listener : listeners) {
10✔
326
        listener.onAutoConfigurationImportEvent(event);
3✔
327
      }
1✔
328
    }
329
  }
1✔
330

331
  protected List<AutoConfigurationImportListener> getAutoConfigurationImportListeners() {
332
    return TodayStrategies.find(AutoConfigurationImportListener.class, getBeanClassLoader(), bootstrapContext);
7✔
333
  }
334

335
  @Override
336
  public void setBeanClassLoader(ClassLoader classLoader) {
337
    this.beanClassLoader = classLoader;
3✔
338
  }
1✔
339

340
  @Override
341
  public void setBootstrapContext(BootstrapContext bootstrapContext) {
342
    this.bootstrapContext = bootstrapContext;
3✔
343
  }
1✔
344

345
  protected ClassLoader getBeanClassLoader() {
346
    return this.beanClassLoader;
3✔
347
  }
348

349
  @Nullable
350
  protected final Environment getEnvironment() {
351
    if (bootstrapContext == null) {
3✔
352
      return null;
2✔
353
    }
354
    return bootstrapContext.getEnvironment();
4✔
355
  }
356

357
  @Override
358
  public int getOrder() {
359
    return ORDER;
×
360
  }
361

362
  private static class ConfigurationClassFilter {
363

364
    private final List<AutoConfigurationImportFilter> filters;
365

366
    private final AutoConfigurationMetadata autoConfigurationMetadata;
367

368
    ConfigurationClassFilter(ClassLoader classLoader, List<AutoConfigurationImportFilter> filters) {
2✔
369
      this.autoConfigurationMetadata = AutoConfigurationMetadata.load(classLoader);
4✔
370
      this.filters = filters;
3✔
371
    }
1✔
372

373
    List<String> filter(List<String> configurations) {
374
      long startTime = System.nanoTime();
2✔
375
      String[] candidates = StringUtils.toStringArray(configurations);
3✔
376
      boolean skipped = false;
2✔
377
      for (AutoConfigurationImportFilter filter : filters) {
11✔
378
        boolean[] match = filter.match(candidates, autoConfigurationMetadata);
6✔
379
        for (int i = 0; i < match.length; i++) {
8✔
380
          if (!match[i]) {
4✔
381
            candidates[i] = null;
4✔
382
            skipped = true;
2✔
383
          }
384
        }
385
      }
1✔
386
      if (!skipped) {
2✔
387
        return configurations;
2✔
388
      }
389
      ArrayList<String> result = new ArrayList<>(candidates.length);
6✔
390
      for (String candidate : candidates) {
16✔
391
        if (candidate != null) {
2✔
392
          result.add(candidate);
4✔
393
        }
394
      }
395
      if (log.isTraceEnabled()) {
3!
396
        int numberFiltered = configurations.size() - result.size();
×
397
        log.trace("Filtered {} auto configuration class in {} ms",
×
398
                numberFiltered, TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startTime));
×
399
      }
400
      return result;
2✔
401
    }
402

403
  }
404

405
  private static class AutoConfigurationGroup implements DeferredImportSelector.Group {
406

407
    private final BootstrapContext context;
408

409
    private final ClassLoader beanClassLoader;
410

411
    private AutoConfigurationMetadata autoConfigurationMetadata;
412

413
    private AutoConfigurationReplacements autoConfigurationReplacements;
414

415
    private final LinkedHashMap<String, AnnotationMetadata> entries = new LinkedHashMap<>();
5✔
416

417
    private final ArrayList<AutoConfigurationEntry> autoConfigurationEntries = new ArrayList<>();
5✔
418

419
    public AutoConfigurationGroup(BootstrapContext context, ClassLoader beanClassLoader) {
2✔
420
      this.context = context;
3✔
421
      this.beanClassLoader = beanClassLoader;
3✔
422
    }
1✔
423

424
    @Override
425
    public void process(AnnotationMetadata annotationMetadata, DeferredImportSelector selector) {
426
      if (selector instanceof AutoConfigurationImportSelector autoConfigSelector) {
6!
427

428
        var autoConfigurationReplacements = autoConfigSelector.getAutoConfigurationReplacements();
3✔
429
        Assert.state(this.autoConfigurationReplacements == null
4!
430
                        || this.autoConfigurationReplacements.equals(autoConfigurationReplacements),
3!
431
                "Auto-configuration replacements must be the same for each call to process");
432
        this.autoConfigurationReplacements = autoConfigurationReplacements;
3✔
433

434
        AutoConfigurationEntry entry = autoConfigSelector.getAutoConfigurationEntry(annotationMetadata);
4✔
435
        autoConfigurationEntries.add(entry);
5✔
436
        for (String importClassName : entry.configurations) {
11✔
437
          entries.putIfAbsent(importClassName, annotationMetadata);
6✔
438
        }
1✔
439
      }
1✔
440
      else {
441
        throw new IllegalStateException("Only %s implementations are supported, got %s"
×
442
                .formatted(AutoConfigurationImportSelector.class.getSimpleName(), selector.getClass().getName()));
×
443
      }
444
    }
1✔
445

446
    @Override
447
    public Iterable<Entry> selectImports() {
448
      if (autoConfigurationEntries.isEmpty()) {
4!
449
        return Collections.emptyList();
×
450
      }
451

452
      var processedConfigurations = new LinkedHashSet<String>();
4✔
453
      for (AutoConfigurationEntry entry : autoConfigurationEntries) {
11✔
454
        processedConfigurations.addAll(entry.configurations);
5✔
455
      }
1✔
456

457
      for (AutoConfigurationEntry entry : autoConfigurationEntries) {
11✔
458
        processedConfigurations.removeAll(entry.exclusions);
5✔
459
      }
1✔
460

461
      List<String> sortedConfigurations = sortAutoConfigurations(processedConfigurations, getAutoConfigurationMetadata());
6✔
462

463
      var entries = new ArrayList<Entry>(sortedConfigurations.size());
6✔
464
      for (String importClassName : sortedConfigurations) {
10✔
465
        Entry entry = new Entry(this.entries.get(importClassName), importClassName);
10✔
466
        entries.add(entry);
4✔
467
      }
1✔
468
      return entries;
2✔
469
    }
470

471
    private AutoConfigurationMetadata getAutoConfigurationMetadata() {
472
      if (this.autoConfigurationMetadata == null) {
3!
473
        this.autoConfigurationMetadata = AutoConfigurationMetadata.load(this.beanClassLoader);
5✔
474
      }
475
      return this.autoConfigurationMetadata;
3✔
476
    }
477

478
    private List<String> sortAutoConfigurations(Set<String> configurations, AutoConfigurationMetadata autoConfigurationMetadata) {
479
      return new AutoConfigurationSorter(context.getMetadataReaderFactory(), autoConfigurationMetadata, autoConfigurationReplacements::replace)
15✔
480
              .getInPriorityOrder(configurations);
1✔
481
    }
482

483
  }
484

485
  protected static class AutoConfigurationEntry {
486

487
    public final Set<String> exclusions;
488

489
    public final List<String> configurations;
490

491
    /**
492
     * Create an entry with the configurations that were contributed and their
493
     * exclusions.
494
     *
495
     * @param configurations the configurations that should be imported
496
     * @param exclusions the exclusions that were applied to the original list
497
     */
498
    AutoConfigurationEntry(Collection<String> configurations, Collection<String> exclusions) {
2✔
499
      this.configurations = new ArrayList<>(configurations);
6✔
500
      this.exclusions = new HashSet<>(exclusions);
6✔
501
    }
1✔
502

503
    static AutoConfigurationEntry empty() {
504
      return new AutoConfigurationEntry(Collections.emptyList(), Collections.emptyList());
×
505
    }
506

507
  }
508

509
}
510

STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc