Spring源码解析之context:component-scan标签解析

本文分析了Spring框架中使用注解声明bean的方式,并详细解析了<context:component-scan/>标签的工作原理,包括如何配置包扫描、BeanNameGenerator、ScopeMetadataResolver等。

阅读须知

  • Spring源码版本:4.3.8
  • 文章中使用/* */注释的方法会做深入分析

正文

现在我们使用Spring的时候,基本上很少使用标签配置的方式来声明bean了,都是使用注解的方式来声明bean,下面我们就来分析一下这部分的源码实现。
在使用注解之前,我们要首先配置一下<context:component-scan/>标签,我们就以这个标签的解析作为入口来分析,这里需要读者了解Spring自定义标签解析的流程,不了解的可以看一下笔者之前关于Spring自定义标签解析的的文章,这里给出传送门,锁定ContextNamespaceHandler:

public void init() {
    registerBeanDefinitionParser("property-placeholder", new PropertyPlaceholderBeanDefinitionParser());
    registerBeanDefinitionParser("property-override", new PropertyOverrideBeanDefinitionParser());
    registerBeanDefinitionParser("annotation-config", new AnnotationConfigBeanDefinitionParser());
    /* 注册<context:component-scan/>标签解析器 */
    registerBeanDefinitionParser("component-scan", new ComponentScanBeanDefinitionParser());
    registerBeanDefinitionParser("load-time-weaver", new LoadTimeWeaverBeanDefinitionParser());
    registerBeanDefinitionParser("spring-configured", new SpringConfiguredBeanDefinitionParser());
    registerBeanDefinitionParser("mbean-export", new MBeanExportBeanDefinitionParser());
    registerBeanDefinitionParser("mbean-server", new MBeanServerBeanDefinitionParser());
}

分析ComponentScanBeanDefinitionParser的parse方法:

public BeanDefinition parse(Element element, ParserContext parserContext) {
	// 获取base-package属性
    String basePackage = element.getAttribute(BASE_PACKAGE_ATTRIBUTE);
    // 处理包路径中的特殊符号,如${}
    basePackage = parserContext.getReaderContext().getEnvironment().resolvePlaceholders(basePackage);
    // 分割
    String[] basePackages = StringUtils.tokenizeToStringArray(basePackage,
            ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
    /* 配置Scanner */
    ClassPathBeanDefinitionScanner scanner = configureScanner(parserContext, element);
    /* 扫描 */
    Set<BeanDefinitionHolder> beanDefinitions = scanner.doScan(basePackages);
    /* 注册一些注解相关的PostProcessor,将BeanDefinition注册完成的事件通知给相关监听器 */
    registerComponents(parserContext.getReaderContext(), beanDefinitions, element);
    return null;
}

ComponentScanBeanDefinitionParser:

protected ClassPathBeanDefinitionScanner configureScanner(ParserContext parserContext, Element element) {
    boolean useDefaultFilters = true;
    // use-default-filters属性,默认为true
    if (element.hasAttribute(USE_DEFAULT_FILTERS_ATTRIBUTE)) {
        useDefaultFilters = Boolean.valueOf(element.getAttribute(USE_DEFAULT_FILTERS_ATTRIBUTE));
    }
    /* 创建Scanner */
    ClassPathBeanDefinitionScanner scanner = createScanner(parserContext.getReaderContext(), useDefaultFilters);
    scanner.setBeanDefinitionDefaults(parserContext.getDelegate().getBeanDefinitionDefaults());
    scanner.setAutowireCandidatePatterns(parserContext.getDelegate().getAutowireCandidatePatterns());
    // resource-pattern属性
    if (element.hasAttribute(RESOURCE_PATTERN_ATTRIBUTE)) {
        scanner.setResourcePattern(element.getAttribute(RESOURCE_PATTERN_ATTRIBUTE));
    }
    try {
        /* 解析name-generator属性 */
        parseBeanNameGenerator(element, scanner);
    }
    catch (Exception ex) {
        parserContext.getReaderContext().error(ex.getMessage(), parserContext.extractSource(element), ex.getCause());
    }
    try {
        /* 解析scope-resolver属性和scoped-proxy属性 */
        parseScope(element, scanner);
    }
    catch (Exception ex) {
        parserContext.getReaderContext().error(ex.getMessage(), parserContext.extractSource(element), ex.getCause());
    }
    /* 解析include-filter子标签和exclude-filter子标签 */
    parseTypeFilters(element, scanner, parserContext);
    return scanner;
}

ComponentScanBeanDefinitionParser:

protected ClassPathBeanDefinitionScanner createScanner(XmlReaderContext readerContext, boolean useDefaultFilters) {
    /* 创建ClassPathBeanDefinitionScanner */
    return new ClassPathBeanDefinitionScanner(readerContext.getRegistry(), useDefaultFilters,
            readerContext.getEnvironment(), readerContext.getResourceLoader());
}

ClassPathBeanDefinitionScanner:

public ClassPathBeanDefinitionScanner(BeanDefinitionRegistry registry, boolean useDefaultFilters,
        Environment environment, ResourceLoader resourceLoader) {
    Assert.notNull(registry, "BeanDefinitionRegistry must not be null");
    this.registry = registry;
    if (useDefaultFilters) {
        /* 注册默认的过滤器 */
        registerDefaultFilters();
    }
    setEnvironment(environment);
    setResourceLoader(resourceLoader);
}

ClassPathScanningCandidateComponentProvider:

protected void registerDefaultFilters() {
    // 添加默认的Component注解的过滤器
    this.includeFilters.add(new AnnotationTypeFilter(Component.class));
    ClassLoader cl = ClassPathScanningCandidateComponentProvider.class.getClassLoader();
    try {
        this.includeFilters.add(new AnnotationTypeFilter(
                ((Class<? extends Annotation>) ClassUtils.forName("javax.annotation.ManagedBean", cl)), false));
        logger.debug("JSR-250 'javax.annotation.ManagedBean' found and supported for component scanning");
    }
    catch (ClassNotFoundException ex) {
    }
    try {
        this.includeFilters.add(new AnnotationTypeFilter(
                ((Class<? extends Annotation>) ClassUtils.forName("javax.inject.Named", cl)), false));
        logger.debug("JSR-330 'javax.inject.Named' annotation found and supported for component scanning");
    }
    catch (ClassNotFoundException ex) {
    }
}

这里我们看到了默认的@Component注解过滤器的添加,而我们常用的@Service、@Controller等注解都使用了@Component注解,所以同样会匹配这个过滤器。

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component // @Service注解也使用了@Component
public @interface Service {
    String value() default "";
}

ClassPathBeanDefinitionScanner:

protected void parseBeanNameGenerator(Element element, ClassPathBeanDefinitionScanner scanner) {
    // 如果配置了name-generator属性则设置为自定义的BeanNameGenerator
    if (element.hasAttribute(NAME_GENERATOR_ATTRIBUTE)) {
        BeanNameGenerator beanNameGenerator = (BeanNameGenerator) instantiateUserDefinedStrategy(
                element.getAttribute(NAME_GENERATOR_ATTRIBUTE), BeanNameGenerator.class,
                scanner.getResourceLoader().getClassLoader());
        scanner.setBeanNameGenerator(beanNameGenerator);
    }
}

ClassPathBeanDefinitionScanner:

protected void parseScope(Element element, ClassPathBeanDefinitionScanner scanner) {
    // scope-resolver属性
    if (element.hasAttribute(SCOPE_RESOLVER_ATTRIBUTE)) {
        // 不能与scoped-proxy同时出现
        if (element.hasAttribute(SCOPED_PROXY_ATTRIBUTE)) {
            throw new IllegalArgumentException(
                    "Cannot define both 'scope-resolver' and 'scoped-proxy' on <component-scan> tag");
        }
        ScopeMetadataResolver scopeMetadataResolver = (ScopeMetadataResolver) instantiateUserDefinedStrategy(
                element.getAttribute(SCOPE_RESOLVER_ATTRIBUTE), ScopeMetadataResolver.class,
                scanner.getResourceLoader().getClassLoader());
        scanner.setScopeMetadataResolver(scopeMetadataResolver);
    }
    // scope-proxy属性
    if (element.hasAttribute(SCOPED_PROXY_ATTRIBUTE)) {
        String mode = element.getAttribute(SCOPED_PROXY_ATTRIBUTE);
        // targetClass、interfaces、no三个属性值的设置
        if ("targetClass".equals(mode)) {
            scanner.setScopedProxyMode(ScopedProxyMode.TARGET_CLASS);
        }
        else if ("interfaces".equals(mode)) {
            scanner.setScopedProxyMode(ScopedProxyMode.INTERFACES);
        }
        else if ("no".equals(mode)) {
            scanner.setScopedProxyMode(ScopedProxyMode.NO);
        }
        else {
            throw new IllegalArgumentException("scoped-proxy only supports 'no', 'interfaces' and 'targetClass'");
        }
    }
}

ClassPathBeanDefinitionScanner:

protected void parseTypeFilters(Element element, ClassPathBeanDefinitionScanner scanner, ParserContext parserContext) {
    ClassLoader classLoader = scanner.getResourceLoader().getClassLoader();
    NodeList nodeList = element.getChildNodes();
    for (int i = 0; i < nodeList.getLength(); i++) {
        Node node = nodeList.item(i);
        if (node.getNodeType() == Node.ELEMENT_NODE) {
            String localName = parserContext.getDelegate().getLocalName(node);
            try {
                // 子标签include-filter
                if (INCLUDE_FILTER_ELEMENT.equals(localName)) {
                    TypeFilter typeFilter = createTypeFilter((Element) node, classLoader, parserContext);
                    scanner.addIncludeFilter(typeFilter);
                }
                // 子标签exclude-filter
                else if (EXCLUDE_FILTER_ELEMENT.equals(localName)) {
                    TypeFilter typeFilter = createTypeFilter((Element) node, classLoader, parserContext);
                    scanner.addExcludeFilter(typeFilter);
                }
            }
            catch (Exception ex) {
                parserContext.getReaderContext().error(
                        ex.getMessage(), parserContext.extractSource(element), ex.getCause());
            }
        }
    }
}

到这配置解析的准备工作就完成了,我们会在后面的文章继续分析注解的扫描流程。

<think> 我们正在讨论Spring框架中的两个配置元素:<context:annotation-config>和<context:component-scan>。 用户之前的问题和回答历史中没有直接相关的内容,因此我们直接针对这个问题进行回答。 首先,这两个配置都是用来启用Spring注解驱动的功能,但它们的作用范围不同。 1. <context:annotation-config> 作用:用于激活已经在Spring容器中注册的bean(无论是通过XML配置还是通过编程方式注册)上的注解。 它处理的注解包括:@Autowired, @Resource, @Required, @Value等(以及JSR-250和JPA的注解,如果类路径中有相应的支持)。 注意:它不会去扫描和注册新的bean,仅仅是对已经存在的bean进行处理。 2. <context:component-scan> 作用:除了包含<context:annotation-config>的功能(即处理上述注解)之外,它还会自动扫描指定的包路径,将带有@Component(以及其派生注解如@Repository, @Service, @Controller)的类注册为Spring容器中的bean。 因此,它比<context:annotation-config>更强大,因为它可以自动发现并注册bean,同时也会激活注解配置。 因此,通常如果我们使用<context:component-scan>,就不需要再单独配置<context:annotation-config>了。 下面通过一个例子来说明: 假设我们有一个服务类和一个DAO类,都使用了注解。 使用<context:annotation-config>的情况: 我们需要在XML中显式定义bean,例如: <bean id="myService" class="com.example.MyService"/> <bean id="myDao" class="com.example.MyDao"/> 然后配置<context:annotation-config/>,这样Spring就会处理这些bean上的注解(比如在MyService中使用了@Autowired来注入myDao)。 使用<context:component-scan>的情况: 我们只需要配置: <context:component-scan base-package="com.example"/> 这样,Spring会扫描com.example包,将带有@Component注解的类自动注册为bean,并且也会处理这些bean上的自动装配注解。 总结: - <context:annotation-config>仅处理已注册bean上的注解,不扫描新bean- <context:component-scan>既扫描并注册bean,同时也处理这些bean上的注解(包含了<context:annotation-config>的功能)。 因此,在大多数现代Spring应用中,通常直接使用<context:component-scan>,因为它提供了更简洁的配置(减少XML配置)。 注意:在Spring 3.0及以上版本,<context:component-scan>已经包含了<context:annotation-config>的功能,所以同时配置两者不会有额外的好处,但也不会造成错误(只是冗余)。 引用说明:以上内容参考了Spring官方文档[^1]和常见的最佳实践。 相关问题: 1.Spring框架中,除了<context:component-scan>,还有哪些方式可以注册bean? 2. 如何自定义组件扫描的过滤器? 3. @Autowired和@Resource注解有什么区别? </think> 在Spring框架中,`<context:annotation-config>`和`<context:component-scan>`都是用于简化配置的核心功能,但它们的职责和覆盖范围有显著区别: ### 1. **`<context:annotation-config>`** - **功能**:仅**启用注解处理器** 激活对已注册Bean上的注解(如`@Autowired`, `@Resource`, `@Required`, `@Value`等)的处理,但它**不会自动发现或注册Bean**。 - **使用场景**: ```xml <!-- 显式定义Bean --> <bean id="myService" class="com.example.MyService"/> <bean id="myDao" class="com.example.MyDao"/> <!-- 启用已有Bean注解处理 --> <context:annotation-config/> ``` - ✅ 处理`MyService`中`@Autowired`注入`MyDao` - ❌ 无法自动扫描并注册新的Bean ### 2. **`<context:component-scan>`** - **功能**:**自动扫描注册Bean + 启用所有注解处理器** 扫描指定包路径,自动将带有`@Component`, `@Service`, `@Repository`, `@Controller`等注解的类注册为Spring Bean,同时**隐式包含`<context:annotation-config>`的所有功能**。 - **使用场景**: ```xml <!-- 自动扫描并注册Bean,同时启用注解处理 --> <context:component-scan base-package="com.example"/> ``` - ✅ 自动注册`com.example`包下所有带注解的类(如`@Service`) - ✅ 处理这些Bean中的依赖注入(如`@Autowired`) --- ### ⚡ 核心区别总结 | **特性** | `<context:annotation-config>` | `<context:component-scan>` | |-------------------------|-------------------------------|-------------------------------------| | **Bean注册** | ❌ 不注册新Bean | ✅ 自动扫描并注册Bean | | **注解处理** | ✅ 处理已存在Bean注解 | ✅ 处理所有Bean注解 | | **包含关系** | 独立功能 | **隐含`<context:annotation-config>`** | | **典型使用场景** | 配合显式XML定义的Bean使用 | 基于注解的自动配置 | --- ### 💡 最佳实践 - **优先使用`<context:component-scan>`**:现代Spring项目通常直接用它简化配置,避免冗余的XML定义[^1]。 - **避免同时使用两者**:`<context:component-scan>`已包含注解处理功能,同时配置会导致重复处理(无实际错误,但冗余)[^2]。 > 示例:若项目中所有Bean均通过注解(如`@Service`)定义,只需: > ```xml > <context:component-scan base-package="com.example"/> > ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值