• 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

61.22
today-context/src/main/java/infra/context/annotation/ComponentScanBeanDefinitionParser.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;
19

20
import org.jspecify.annotations.Nullable;
21
import org.w3c.dom.Element;
22
import org.w3c.dom.Node;
23
import org.w3c.dom.NodeList;
24

25
import java.util.function.Consumer;
26
import java.util.regex.Pattern;
27

28
import infra.beans.BeanUtils;
29
import infra.beans.factory.config.BeanDefinition;
30
import infra.beans.factory.config.BeanDefinitionHolder;
31
import infra.beans.factory.parsing.BeanComponentDefinition;
32
import infra.beans.factory.parsing.CompositeComponentDefinition;
33
import infra.beans.factory.support.BeanNameGenerator;
34
import infra.beans.factory.xml.BeanDefinitionParser;
35
import infra.beans.factory.xml.ParserContext;
36
import infra.beans.factory.xml.XmlReaderContext;
37
import infra.context.ConfigurableApplicationContext;
38
import infra.core.type.filter.AnnotationTypeFilter;
39
import infra.core.type.filter.AspectJTypeFilter;
40
import infra.core.type.filter.AssignableTypeFilter;
41
import infra.core.type.filter.RegexPatternTypeFilter;
42
import infra.core.type.filter.TypeFilter;
43
import infra.util.ClassUtils;
44
import infra.util.ReflectionUtils;
45
import infra.util.StringUtils;
46

47
/**
48
 * Parser for the {@code <context:component-scan/>} element.
49
 *
50
 * @author Mark Fisher
51
 * @author Ramnivas Laddad
52
 * @author Juergen Hoeller
53
 * @author <a href="https://github.com/TAKETODAY">Harry Yang</a>
54
 * @since 4.0 2022/3/7 18:04
55
 */
56
@SuppressWarnings("NullAway")
57
public class ComponentScanBeanDefinitionParser implements BeanDefinitionParser {
3✔
58

59
  private static final String BASE_PACKAGE_ATTRIBUTE = "base-package";
60

61
  private static final String RESOURCE_PATTERN_ATTRIBUTE = "resource-pattern";
62

63
  private static final String USE_DEFAULT_FILTERS_ATTRIBUTE = "use-default-filters";
64

65
  private static final String ANNOTATION_CONFIG_ATTRIBUTE = "annotation-config";
66

67
  private static final String NAME_GENERATOR_ATTRIBUTE = "name-generator";
68

69
  private static final String SCOPE_RESOLVER_ATTRIBUTE = "scope-resolver";
70

71
  private static final String SCOPED_PROXY_ATTRIBUTE = "scoped-proxy";
72

73
  private static final String EXCLUDE_FILTER_ELEMENT = "exclude-filter";
74

75
  private static final String INCLUDE_FILTER_ELEMENT = "include-filter";
76

77
  private static final String FILTER_TYPE_ATTRIBUTE = "type";
78

79
  private static final String FILTER_EXPRESSION_ATTRIBUTE = "expression";
80

81
  @Override
82
  @Nullable
83
  public BeanDefinition parse(Element element, ParserContext parserContext) {
84
    String basePackage = element.getAttribute(BASE_PACKAGE_ATTRIBUTE);
4✔
85
    basePackage = parserContext.getReaderContext().getEnvironment().resolvePlaceholders(basePackage);
6✔
86
    String[] basePackages = StringUtils.tokenizeToStringArray(basePackage,
4✔
87
            ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
88

89
    // Actually scan for bean definitions and register them.
90
    ClassPathBeanDefinitionScanner scanner = configureScanner(parserContext, element);
5✔
91
    registerComponents(parserContext.getReaderContext(), scanner, basePackages, element);
7✔
92

93
    return null;
2✔
94
  }
95

96
  protected ClassPathBeanDefinitionScanner configureScanner(ParserContext parserContext, Element element) {
97
    boolean useDefaultFilters = true;
2✔
98
    if (element.hasAttribute(USE_DEFAULT_FILTERS_ATTRIBUTE)) {
4!
99
      useDefaultFilters = Boolean.parseBoolean(element.getAttribute(USE_DEFAULT_FILTERS_ATTRIBUTE));
5✔
100
    }
101

102
    // Delegate bean definition registration to scanner class.
103
    ClassPathBeanDefinitionScanner scanner = createScanner(parserContext.getReaderContext(), useDefaultFilters);
6✔
104
    scanner.setBeanDefinitionDefaults(parserContext.getDelegate().getBeanDefinitionDefaults());
5✔
105
    scanner.setAutowireCandidatePatterns(parserContext.getDelegate().getAutowireCandidatePatterns());
5✔
106

107
    if (element.hasAttribute(RESOURCE_PATTERN_ATTRIBUTE)) {
4✔
108
      scanner.setResourcePattern(element.getAttribute(RESOURCE_PATTERN_ATTRIBUTE));
5✔
109
    }
110

111
    try {
112
      parseBeanNameGenerator(element, scanner);
4✔
113
    }
114
    catch (Exception ex) {
×
115
      parserContext.getReaderContext().error(ex.getMessage(), parserContext.extractSource(element), ex.getCause());
×
116
    }
1✔
117

118
    try {
119
      parseScope(element, scanner);
4✔
120
    }
121
    catch (Exception ex) {
×
122
      parserContext.getReaderContext().error(ex.getMessage(), parserContext.extractSource(element), ex.getCause());
×
123
    }
1✔
124

125
    parseTypeFilters(element, scanner, parserContext);
5✔
126

127
    return scanner;
2✔
128
  }
129

130
  protected ClassPathBeanDefinitionScanner createScanner(XmlReaderContext readerContext, boolean useDefaultFilters) {
131
    return new ClassPathBeanDefinitionScanner(readerContext.getRegistry(), useDefaultFilters,
7✔
132
            readerContext.getEnvironment(), readerContext.getResourceLoader());
4✔
133
  }
134

135
  protected void registerComponents(
136
          XmlReaderContext readerContext, ClassPathBeanDefinitionScanner scanner, String[] basePackages, Element element) {
137

138
    Object source = readerContext.extractSource(element);
4✔
139
    CompositeComponentDefinition compositeDef = new CompositeComponentDefinition(element.getTagName(), source);
7✔
140

141
    scanner.scan(new Consumer<BeanDefinitionHolder>() {
17✔
142
      @Override
143
      public void accept(BeanDefinitionHolder holder) {
144
        compositeDef.addNestedComponent(new BeanComponentDefinition(holder));
7✔
145
      }
1✔
146
    }, basePackages);
147

148
    // Register annotation config processors, if necessary.
149
    boolean annotationConfig = true;
2✔
150
    if (element.hasAttribute(ANNOTATION_CONFIG_ATTRIBUTE)) {
4!
151
      annotationConfig = Boolean.parseBoolean(element.getAttribute(ANNOTATION_CONFIG_ATTRIBUTE));
5✔
152
    }
153
    if (annotationConfig) {
2✔
154
      AnnotationConfigUtils.registerAnnotationConfigProcessors(readerContext.getRegistry(), definition -> {
6✔
155
        definition.setSource(source);
3✔
156
        compositeDef.addNestedComponent(new BeanComponentDefinition(definition));
6✔
157
      });
1✔
158
    }
159

160
    readerContext.fireComponentRegistered(compositeDef);
3✔
161
  }
1✔
162

163
  protected void parseBeanNameGenerator(Element element, ClassPathBeanDefinitionScanner scanner) {
164
    if (element.hasAttribute(NAME_GENERATOR_ATTRIBUTE)) {
4!
165
      BeanNameGenerator beanNameGenerator = instantiateUserDefinedStrategy(
×
166
              element.getAttribute(NAME_GENERATOR_ATTRIBUTE), BeanNameGenerator.class,
×
167
              scanner.getResourceLoader().getClassLoader());
×
168
      scanner.setBeanNameGenerator(beanNameGenerator);
×
169
    }
170
  }
1✔
171

172
  protected void parseScope(Element element, ClassPathBeanDefinitionScanner scanner) {
173
    // Register ScopeMetadataResolver if class name provided.
174
    if (element.hasAttribute(SCOPE_RESOLVER_ATTRIBUTE)) {
4!
175
      if (element.hasAttribute(SCOPED_PROXY_ATTRIBUTE)) {
×
176
        throw new IllegalArgumentException(
×
177
                "Cannot define both 'scope-resolver' and 'scoped-proxy' on <component-scan> tag");
178
      }
179
      ScopeMetadataResolver scopeMetadataResolver = instantiateUserDefinedStrategy(
×
180
              element.getAttribute(SCOPE_RESOLVER_ATTRIBUTE), ScopeMetadataResolver.class,
×
181
              scanner.getResourceLoader().getClassLoader());
×
182
      scanner.setScopeMetadataResolver(scopeMetadataResolver);
×
183
    }
184

185
    if (element.hasAttribute(SCOPED_PROXY_ATTRIBUTE)) {
4!
186
      String mode = element.getAttribute(SCOPED_PROXY_ATTRIBUTE);
×
187
      switch (mode) {
×
188
        case "no" -> scanner.setScopedProxyMode(ScopedProxyMode.NO);
×
189
        case "interfaces" -> scanner.setScopedProxyMode(ScopedProxyMode.INTERFACES);
×
190
        case "targetClass" -> scanner.setScopedProxyMode(ScopedProxyMode.TARGET_CLASS);
×
191
        default -> throw new IllegalArgumentException("scoped-proxy only supports 'no', 'interfaces' and 'targetClass'");
×
192
      }
193
    }
194
  }
1✔
195

196
  protected void parseTypeFilters(Element element, ClassPathBeanDefinitionScanner scanner, ParserContext parserContext) {
197
    // Parse exclude and include filter elements.
198
    ClassLoader classLoader = scanner.getResourceLoader().getClassLoader();
4✔
199
    NodeList nodeList = element.getChildNodes();
3✔
200
    int length = nodeList.getLength();
3✔
201
    for (int i = 0; i < length; i++) {
7✔
202
      Node node = nodeList.item(i);
4✔
203
      if (node.getNodeType() == Node.ELEMENT_NODE) {
4✔
204
        String localName = parserContext.getDelegate().getLocalName(node);
5✔
205
        try {
206
          if (INCLUDE_FILTER_ELEMENT.equals(localName)) {
4✔
207
            TypeFilter typeFilter = createTypeFilter((Element) node, classLoader, parserContext);
7✔
208
            scanner.addIncludeFilter(typeFilter);
3✔
209
          }
1✔
210
          else if (EXCLUDE_FILTER_ELEMENT.equals(localName)) {
4!
211
            TypeFilter typeFilter = createTypeFilter((Element) node, classLoader, parserContext);
7✔
212
            scanner.addExcludeFilter(typeFilter);
3✔
213
          }
214
        }
215
        catch (ClassNotFoundException ex) {
×
216
          parserContext.getReaderContext().warning(
×
217
                  "Ignoring non-present type filter class: " + ex, parserContext.extractSource(element));
×
218
        }
219
        catch (Exception ex) {
×
220
          parserContext.getReaderContext().error(
×
221
                  ex.getMessage(), parserContext.extractSource(element), ex.getCause());
×
222
        }
1✔
223
      }
224
    }
225
  }
1✔
226

227
  protected TypeFilter createTypeFilter(Element element, @Nullable ClassLoader classLoader,
228
          ParserContext parserContext) throws ClassNotFoundException {
229

230
    String filterType = element.getAttribute(FILTER_TYPE_ATTRIBUTE);
4✔
231
    String expression = element.getAttribute(FILTER_EXPRESSION_ATTRIBUTE);
4✔
232
    expression = parserContext.getReaderContext().getEnvironment().resolvePlaceholders(expression);
6✔
233
    switch (filterType) {
8!
234
      case "annotation":
235
        return new AnnotationTypeFilter(ClassUtils.forName(expression, classLoader));
7✔
236
      case "assignable":
237
        return new AssignableTypeFilter(ClassUtils.forName(expression, classLoader));
7✔
238
      case "aspectj":
239
        return new AspectJTypeFilter(expression, classLoader);
6✔
240
      case "regex":
241
        return new RegexPatternTypeFilter(Pattern.compile(expression));
×
242
      case "custom":
243
        Class<?> filterClass = ClassUtils.forName(expression, classLoader);
4✔
244
        if (!TypeFilter.class.isAssignableFrom(filterClass)) {
4!
245
          throw new IllegalArgumentException(
×
246
                  "Class is not assignable to [%s]: %s".formatted(TypeFilter.class.getName(), expression));
×
247
        }
248
        return (TypeFilter) BeanUtils.newInstance(filterClass);
4✔
249
      default:
250
        throw new IllegalArgumentException("Unsupported filter type: " + filterType);
×
251
    }
252
  }
253

254
  private <T> T instantiateUserDefinedStrategy(
255
          String className, Class<T> strategyType, @Nullable ClassLoader classLoader) {
256

257
    T result;
258
    try {
259
      result = ReflectionUtils.accessibleConstructor(ClassUtils.<T>forName(className, classLoader)).newInstance();
×
260
    }
261
    catch (ClassNotFoundException ex) {
×
262
      throw new IllegalArgumentException("Class [%s] for strategy [%s] not found".formatted(className, strategyType.getName()), ex);
×
263
    }
264
    catch (Throwable ex) {
×
265
      throw new IllegalArgumentException("Unable to instantiate class [%s] for strategy [%s]: a zero-argument constructor is required"
×
266
              .formatted(className, strategyType.getName()), ex);
×
267
    }
×
268

269
    if (!strategyType.isAssignableFrom(result.getClass())) {
×
270
      throw new IllegalArgumentException("Provided class name must be an implementation of " + strategyType);
×
271
    }
272
    return result;
×
273
  }
274

275
}
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