spring中扫描bean的源码解析

 2019-11-02 21:06  阅读(1015)
文章分类:Spring boot

spring的扫描bean我们一般都会这么配:、

<context:component-scan base-package="com.dubbo.single"/>

这样就可以扫描到,com/dubbo/single包下的所有bean,具体是源码是怎么操作的,我们看下源码,前面的源码我们就不做介绍了,如果有不懂bean是如何加载的再去搜集相关资料,我们直接从扫描开始介绍起。

当解析了xml文件后,会有一个BeanDefinitionParserDelegate类执行parseCustomElement方法,如下:

public BeanDefinition parseCustomElement(Element ele, BeanDefinition containingBd) {
            String namespaceUri = getNamespaceURI(ele);
            NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);
            if (handler == null) {
                error("Unable to locate Spring NamespaceHandler for XML schema namespace [" + namespaceUri + "]", ele);
                return null;
            }
            return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd));
        }

标粗的这句会根据你写在xml中的命名空间获取所有的转换类,并且在下面的parse方法中,看使用哪个parse类:

public BeanDefinition parse(Element element, ParserContext parserContext) {
            return findParserForElement(element, parserContext).parse(element, parserContext);
        }

根据我们配置的
context
:component-scan ,用来解析的parse类是:ComponentScanBeanDefinitionParse类,执行它的parse方法:

public BeanDefinition parse(Element element, ParserContext parserContext) {
            String[] basePackages = StringUtils.tokenizeToStringArray(element.getAttribute(BASE_PACKAGE_ATTRIBUTE),
                    ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);

            // Actually scan for bean definitions and register them.
            ClassPathBeanDefinitionScanner scanner = configureScanner(parserContext, element);
            Set<BeanDefinitionHolder> beanDefinitions = scanner.doScan(basePackages);
            registerComponents(parserContext.getReaderContext(), beanDefinitions, element);

            return null;
        }

我们重点看下doScan方法:

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) {
                Set<BeanDefinition> candidates = findCandidateComponents(basePackage);
                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;
        }

我们看下标粗的findCandidateComponents(basePackage)方法:

public Set<BeanDefinition> findCandidateComponents(String basePackage) {
            Set<BeanDefinition> candidates = new LinkedHashSet<BeanDefinition>();
            try {
                String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +
                        resolveBasePackage(basePackage) + "/" + this.resourcePattern;
                Resource[] resources = this.resourcePatternResolver.getResources(packageSearchPath);

上面标粗的getResources方法做了扫描:

public Resource[] getResources(String locationPattern) throws IOException {
            return this.resourcePatternResolver.getResources(locationPattern);
        }

public Resource[] getResources(String locationPattern) throws IOException {
            Assert.notNull(locationPattern, "Location pattern must not be null");
            if (locationPattern.startsWith(CLASSPATH_ALL_URL_PREFIX)) {
                // a class path resource (multiple resources for same name possible)
                if (getPathMatcher().isPattern(locationPattern.substring(CLASSPATH_ALL_URL_PREFIX.length()))) {
                    // a class path resource pattern
                    return findPathMatchingResources(locationPattern);
                }

上面的if语句会判断如果路径是以“classpath*”开头则进入执行findPathMatchingResources方法.

String CLASSPATH_ALL_URL_PREFIX = "classpath*:";

protected Resource[] findPathMatchingResources(String locationPattern) throws IOException {
            String rootDirPath = determineRootDir(locationPattern);
            String subPattern = locationPattern.substring(rootDirPath.length());
            Resource[] rootDirResources = getResources(rootDirPath);
            Set<Resource> result = new LinkedHashSet<Resource>(16);
            for (Resource rootDirResource : rootDirResources) {
                rootDirResource = resolveRootDirResource(rootDirResource);
                if (isJarResource(rootDirResource)) {
                    result.addAll(doFindPathMatchingJarResources(rootDirResource, subPattern));
                }
                else if (rootDirResource.getURL().getProtocol().startsWith(ResourceUtils.URL_PROTOCOL_VFS)) {
                    result.addAll(VfsResourceMatchingDelegate.findMatchingResources(rootDirResource, subPattern, getPathMatcher()));
                }
                else {
                    result.addAll(doFindPathMatchingFileResources(rootDirResource, subPattern));
                }
            }
            if (logger.isDebugEnabled()) {
                logger.debug("Resolved location pattern [" + locationPattern + "] to resources " + result);
            }
            return result.toArray(new Resource[result.size()]);
        }

最后会执行我们标粗的逐句doFindPathMatchingFileResources方法,举例说明,比如我们原来配置是

<context:component-scan base-package="com.dubbo.single"/>

rootDirResource为:“classpath*:com/dubbo/single/”,subPattern为:“**/*.class”,然后我们进入方法查看:

protected Set<Resource> doFindPathMatchingFileResources(Resource rootDirResource, String subPattern)
                throws IOException {

            File rootDir;
            try {
                rootDir = rootDirResource.getFile().getAbsoluteFile();
            }
            catch (IOException ex) {
                if (logger.isWarnEnabled()) {
                    logger.warn("Cannot search for matching files underneath " + rootDirResource +
                            " because it does not correspond to a directory in the file system", ex);
                }
                return Collections.emptySet();
            }
            return doFindMatchingFileSystemResources(rootDir, subPattern);
        }

方法中获取绝对路径:rootDir如下:“D:\workspaces\dubbo-parent\dubbo-single\target\classes\com\dubbo\single”

protected Set<Resource> doFindMatchingFileSystemResources(File rootDir, String subPattern) throws IOException {
            if (logger.isDebugEnabled()) {
                logger.debug("Looking for matching resources in directory tree [" + rootDir.getPath() + "]");
            }
            Set<File> matchingFiles = retrieveMatchingFiles(rootDir, subPattern);
            Set<Resource> result = new LinkedHashSet<Resource>(matchingFiles.size());
            for (File file : matchingFiles) {
                result.add(new FileSystemResource(file));
            }
            return result;
        }

再最后进入了PathMatchingResourcePatternResolver的doRetriesMatchingFiles进行了递归查询所有.class文件:

protected void doRetrieveMatchingFiles(String fullPattern, File dir, Set<File> result) throws IOException {
            if (logger.isDebugEnabled()) {
                logger.debug("Searching directory [" + dir.getAbsolutePath() +
                        "] for files matching pattern [" + fullPattern + "]");
            }
            File[] dirContents = dir.listFiles();
            if (dirContents == null) {
                if (logger.isWarnEnabled()) {
                    logger.warn("Could not retrieve contents of directory [" + dir.getAbsolutePath() + "]");
                }
                return;
            }
            for (File content : dirContents) {
                String currPath = StringUtils.replace(content.getAbsolutePath(), File.separator, "/");
                if (content.isDirectory() && getPathMatcher().matchStart(fullPattern, currPath + "/")) {
                    if (!content.canRead()) {
                        if (logger.isDebugEnabled()) {
                            logger.debug("Skipping subdirectory [" + dir.getAbsolutePath() +
                                    "] because the application is not allowed to read the directory");
                        }
                    }
                    else {
                        doRetrieveMatchingFiles(fullPattern, content, result);
                    }
                }
                if (getPathMatcher().match(fullPattern, currPath)) {
                    result.add(content);
                }
            }
        }

最后将扫描拿到的bean进行注册。

注意接口类就算加了注解也不会被spring所加载,在这里会进行过滤:

在ClassPathScanningCandidateComponentProvider类的方法findCandidateComponents中,获取到bean集合后会执行此方法:

if (isCandidateComponent(sbd)) {
                                    if (debugEnabled) {
                                        logger.debug("Identified candidate component class: " + resource);
                                    }
                                    candidates.add(sbd);
                                }

在此处会对接口类进行过滤。


来源:http://ddrv.cn

点赞(0)
版权归原创作者所有,任何形式转载请联系作者; Java 技术驿站 >> spring中扫描bean的源码解析

相关推荐