Spring源码之component-scan

类继承InitializingBean并在类上标注@Component时bean被实例化两次

前提

  1. spring启动时有两个容器一个是spring容器,一个是我们在xml写的DispatchServlet的Servlet容器。

  2. 写项目的时候把配置文件分为 spring-beans.xml, springmvc-bean.xml。 这么 写是为了区分spring管理的bean跟springmvc管理的bean,其中都设置了自动扫描包:


spring-beans.xml:

    <context:component-scan base-package="com.***">

        <context:exclude-filter type="annotation"

		expression="org.springframework.stereotype.Controller" />

	</context:component-scan>

springmvc-bean.xml:

	<context:component-scan base-package="com.***">

		<context:include-filter type="annotation"

			expression="org.springframework.stereotype.Controller" /

	</context:component-scan>

  1. 类具体使用
@Component
public class *** implements  InitializingBean{
	@Override
       public void afterPropertiesSet() throws Exception {
           。。。
      }      
}
  1. @Component标注了@Controller @Service @ Respority

问题点

  • 上面第三点被@Component标注的类在spring启动时会调用两次afterPropertiesSet方法。

分析

  • 这与spring启动时有两个容器有关系,在 spring-beans.xml, springmvc-bean.xml中分别扫描并注册了***类一次,导致最终这个类会被调用两次。

  • component-scan源码会先看排除exclude-filte的注解,然后在把include-filter的注解一个个加进去。如果是默认的话会把@Component注解标注的@Controller @Service @ Respority都加进来。所以`<context:include-filter type=“annotation”

     	expression="org.springframework.stereotype.Controller" `
    

    这种写法并没有起到只加入Controller的作用,如果想只加入@Controller注解正确写法为

	<context:component-scan base-package="***">
			<context:exclude-filter type="annotation"
				expression="org.springframework.stereotype.Service" />
			<context:exclude-filter type="annotation"
				expression="org.springframework.stereotype.Repository" />
			<context:include-filter type="annotation"
				expression="org.springframework.stereotype.Controller" />
	</context:component-scan>

解决方案

  • 使用在spring-beans.xml中写入<bean代替@Component注解的方式。

对标spring源码

  • <context:component-scan这样非标准或者称为自定义的元素标签时 spring会通过spring.handlers文件中的对应关系 先找到spring-context包,再去找spring.handlers。根据下面文件去找ContextNamespaceHandler
http\://www.springframework.org/schema/context=org.springframework.context.config.ContextNamespaceHandler
http\://www.springframework.org/schema/jee=org.springframework.ejb.config.JeeNamespaceHandler
http\://www.springframework.org/schema/lang=org.springframework.scripting.config.LangNamespaceHandler
http\://www.springframework.org/schema/task=org.springframework.scheduling.config.TaskNamespaceHandler
http\://www.springframework.org/schema/cache=org.springframework.cache.config.CacheNamespaceHandler
ContextNamespaceHandler
  • init方法有注册对应解析器,可以看出是ComponentScanBeanDefinitionParser这个解析器
public void init() {
		registerBeanDefinitionParser("property-placeholder", new PropertyPlaceholderBeanDefinitionParser());
		registerBeanDefinitionParser("property-override", new PropertyOverrideBeanDefinitionParser());
		registerBeanDefinitionParser("annotation-config", new AnnotationConfigBeanDefinitionParser());
		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
  • (ComponentScanBeanDefinitionParser继承自BeanDefinitionParser)BeanDefinitionParser是个接口有个parse方法,所以看下parse函数:
//element 代表的是完整的<context:component-scan>标签
//parserContext 解析的上下文环境 能拿到一些诸如readerContext registry等变量
public BeanDefinition parse(Element element, ParserContext parserContext) {
		String basePackage = element.getAttribute(BASE_PACKAGE_ATTRIBUTE);
		basePackage = parserContext.getReaderContext().getEnvironment().resolvePlaceholders(basePackage);
		String[] basePackages = StringUtils.tokenizeToStringArray(basePackage,
				ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
		// Actually scan for bean definitions and register them.
		ClassPathBeanDefinitionScanner scanner = configureScanner(parserContext, element);
		 //@Component @Named @ManagedBean (@Controller @Service @Repository @Configuration)都会被注册 
		Set<BeanDefinitionHolder> beanDefinitions = scanner.doScan(basePackages);
		// 注册组件以及一些我们常见的注解处理器(BPP) 如@Resouce @Autowired @Configuration @Value @Required            @PostConstruct
       //那么以后看到属性注入(IOC/DI)的时候 就不会奇怪 这些注解的处理器(BPP)是在哪里注册的了 在这里!
		registerComponents(parserContext.getReaderContext(), beanDefinitions, element);
		return null;
	}
  • scanner的创建以及设置
protected ClassPathBeanDefinitionScanner configureScanner(ParserContext parserContext, Element element) {
		XmlReaderContext readerContext = parserContext.getReaderContext();
    boolean useDefaultFilters = true;
    if (element.hasAttribute(USE_DEFAULT_FILTERS_ATTRIBUTE)) {
        useDefaultFilters = Boolean.valueOf(element.getAttribute(USE_DEFAULT_FILTERS_ATTRIBUTE));
    }
    // 生成scanner的时候 在其父类构造函数中调用了`registerDefaultFilters()`方法中加入了3个注解类型的typeFilter 分别是@Component 
    // @ManagedBean @Named 意思也就是看类上面有没有这三个注解或者注解的元注解中是否含有这三个注解之一
    // 这里虽然没有加入如@Service的注解 但看@Service的定义会发现@Service注解也被@Component给标注了
    ClassPathBeanDefinitionScanner scanner = createScanner(readerContext, useDefaultFilters);
    scanner.setResourceLoader(readerContext.getResourceLoader());
    scanner.setEnvironment(parserContext.getDelegate().getEnvironment());
    // 利用spring 启动的时候根据xml文件或者默认配置(如果没有指定的话)生成的BeanDefinition规则
    scanner.setBeanDefinitionDefaults(parserContext.getDelegate().getBeanDefinitionDefaults());
    scanner.setAutowireCandidatePatterns(parserContext.getDelegate().getAutowireCandidatePatterns());

    if (element.hasAttribute(RESOURCE_PATTERN_ATTRIBUTE)) {
        scanner.setResourcePattern(element.getAttribute(RESOURCE_PATTERN_ATTRIBUTE));
    }

    try {
        // 扫描注解定义的bean 需要定义一种beanName的生成规则  一般是驼峰命名法
        parseBeanNameGenerator(element, scanner);
    }
    catch (Exception ex) {
        readerContext.error(ex.getMessage(), readerContext.extractSource(element), ex.getCause());
    }

    try {
    // bean的scope singleton?prototype?
        parseScope(element, scanner);
    }
    catch (Exception ex) {
        readerContext.error(ex.getMessage(), readerContext.extractSource(element), ex.getCause());
    }
    // 根据xml配置生成includeFilter excludeFilter 在创建scanner类的时候 就硬编码进去了3个注解类型
    // 的typeFilter到includeFilter里面
    // 类必须满足以下条件才会被注册 因为@Component等是被硬编码进去的 所以只有下面一种情况才会被注册了
    // 不被excludeFilter匹配 并且被includeFilter匹配
    parseTypeFilters(element, scanner, readerContext, parserContext);
    return scanner;
}
- .registerComponents()方法中除了组件外 还有一个比较重要的事情 就是注册我们后面属性填充(IOC)需要用到的一些BPP 例如@Resouce @Autowired等
ClassPathBeanDefinitionScanner
  • 看下doScan 方法我们发现spirng中真正干活的一般都是以do开头 前面的那么多只是为了做铺垫 单一职责
 protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
    Assert.notEmpty(basePackages, "At least one base package must be specified");
    Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<BeanDefinitionHolder>();
    for (String basePackage : basePackages) {
        //寻找被@Component或者@Named注解标注的类 或者 类的注解的元注解中含有这两个注解 例如常见的@Respsitory @Controller @Service
        Set<BeanDefinition> candidates = findCandidateComponents(basePackage);
        // 对于生成的AnnotatedBeanDefinition做一些转化工作 例如前面说到的应用bd(BeanDefinition)默认配置 
        // 处理@Primary @Lazy @DependsOn @Role注解等
        // 转化成BeanDefinitionHolder对象
        for (BeanDefinition candidate : candidates) {
            ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate);
            candidate.setScope(scopeMetadata.getScopeName());
            String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry);
            if (candidate instanceof AbstractBeanDefinition) {
                postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName);
            }
            if (candidate instanceof AnnotatedBeanDefinition) {
                AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate);
            }
            if (checkCandidate(beanName, candidate)) {
                BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);
                definitionHolder = AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
                beanDefinitions.add(definitionHolder);
                //注册到容器中
                registerBeanDefinition(definitionHolder, this.registry);
            }
        }
    }
    return beanDefinitions;
}
ClassPathScanningCandidateComponentProvider
  • findCandidateComponents
public Set<BeanDefinition> findCandidateComponents(String basePackage) {
    Set<BeanDefinition> candidates = new LinkedHashSet<BeanDefinition>();
    try {
        String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +
                resolveBasePackage(basePackage) + "/" + this.resourcePattern;
        //这里的resouces代表的就是路径下的各个类了
        Resource[] resources = this.resourcePatternResolver.getResources(packageSearchPath);
        boolean traceEnabled = logger.isTraceEnabled();
        boolean debugEnabled = logger.isDebugEnabled();
        for (Resource resource : resources) {
            if (traceEnabled) {
                logger.trace("Scanning " + resource);
            }
            if (resource.isReadable()) {
                try {
// 这一步的作用就是能够不必加载class但能拿到class各种属性 并且这里也不能载入class(有很多是单例模式) 还没到那一步
// 只是为了做匹配 生成bd而已
                    MetadataReader metadataReader = this.metadataReaderFactory.getMetadataReader(resource);
// 根据includeFilters excludeFilters做匹配 因为篇幅的限制 不详谈 有兴趣的可以研究一下typeFilter的实现
                    if (isCandidateComponent(metadataReader)) {
                        ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader);
                        sbd.setResource(resource);
                        sbd.setSource(resource);
                        if (isCandidateComponent(sbd)) {
                            if (debugEnabled) {
                                logger.debug("Identified candidate component class: " + resource);
                            }
                            candidates.add(sbd);
                        }
                        else {
                            if (debugEnabled) {
                                logger.debug("Ignored because not a concrete top-level class: " + resource);
                            }
                        }
                    }
                    else {
                        if (traceEnabled) {
                            logger.trace("Ignored because not matching any filter: " + resource);
                        }
                    }
                }
                catch (Throwable ex) {
                    throw new BeanDefinitionStoreException(
                            "Failed to read candidate component class: " + resource, ex);
                }
            }
            else {
                if (traceEnabled) {
                    logger.trace("Ignored because not readable: " + resource);
                }
            }
        }
    }
    catch (IOException ex) {
        throw new BeanDefinitionStoreException("I/O failure during classpath scanning", ex);
    }
    return candidates;
}
  • isCandidateComponent方法,这里的源码能够解决我们的问题,到底exclude-filter, include-filter是如何起作用的。
protected boolean isCandidateComponent(MetadataReader metadataReader) throws IOException {
		for (TypeFilter tf : this.excludeFilters) {
			if (tf.match(metadataReader, this.metadataReaderFactory)) {
				return false;
			}
		}
		for (TypeFilter tf : this.includeFilters) {
			if (tf.match(metadataReader, this.metadataReaderFactory)) {
				return isConditionMatch(metadataReader);
			}
		}
		return false;
	}

参考文章

  1. component-scan做了些什么:源码解读:https://blog.csdn.net/xiaoshuai1127/article/details/75090053
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值