为了账号安全,请及时绑定邮箱和手机立即绑定

手把手带你阅读源码,看看IoC容器的实现

标签:
Java

【干货点】 此处是 【好好面试】 系列文的第13篇文章。 现在网上大把说IoC的文章,可是手把手调试源码的却不多,也很少会描述IoC容器的实现步骤,现在就让稀饭下雪手把手调试源码看看IoC是怎么个东西,顺便记住几个关键知识点,以后面试无忧;不懂如何看源码的兄弟们要跟上啦,可以仔细看看我的调试骚法

必读大前提

依稀记得,在很多年前,我在简历技能栏目上写着熟悉spring几个字,后来面试官瞄了一眼,熟悉spring?不错,说说看IoC容器的实现。

我当时就懵逼了,说实话,我一直都知道IoC是什么,原理是什么,我还可以用类比的方式和你说这个鬼东西,可是你让我说说IoC容器的实现,我还真TM一时回答不上来,因为我没有看过源码。而这也是我写这篇文章的初衷,为了能够说清楚这个过程,我将整个SpringBoot的启动过程和IoC容器的实现过程都仔仔细细看了一遍。

看源码后的总结

仔细浏览了源码后,得出了一个总结,同时也是**【面试热点】** ,IoC容器的实现可以分为以下几步:

  • 1、Resource的定位

使用过SpringBoot的都知道,关于组件的扫描是从主类所在的包开始扫描的,通过阅读源码我们可以看到,在prepareContext()方法中,SpringBoot会先将主类,也就是使用了注解@SpringBootApplication的类,解析成BeanDefinition,之后在invokeBeanFactoryPostProcessors()方法中解析主类的BeanDefinition从而获取basePackage的路径,而这个路径也就是我们所说的定位了,当然了,此处不讨论使用了注解@Import的情况。

  • 2、BeanDefinition的载入

在取得了Resource的定位后,自然而然下一步肯定是将各种BeanDefinition的载入到内存中了,这里看源码其实就调试了很久,但是其实流程极其简单。所谓的载入内存中,用脚指头想想都知道其实就是通过Resource的定位,拼接了一下路径变成:classpath*:org/springframework/boot/demo/**/*.class这样的形式,之后再使用了一个比较屌的类PathMatchingResourcePatternResolver,将该路径下的所有.class的文件加载进来,之后再遍历判断是否有@Component注解,不过不要以为可以直接通过@Component注解来反调源码,年轻的我也是这么做的,不过发现其实不是哟,想知道就继续看。如果有注解的话,就是该步骤要装载的BeanDefinition了。

  • 3、注册BeanDefinition

所谓的注册,听起来很高大上,将类注册入IoC容器内,但是其实很简单,其实就是将其put到一个CurrentHashMap中罢了,是的,IoC容器其实就是使用这个Map来存放这些BeanDefinition数据的。

【温馨提示】:如果不知道BeanDefinition是什么的同学就该面壁了哟,说明Spring了解确实浅,同时也没有看我之前的文章,可以看看这篇Spring之Bean的动态注册

其实看到这里也差不多可以了,毕竟大致描述下过程的话确实就是上面三个了,有兴趣看源码解析的可以继续。后面我会将调试的流程跟着源码描述出来,最好是跟着代码调试,不然会晕圈的。

资源的定位

依旧按照之前套路,直接先定位到资源定位的地方,再通过调试页面,看看程序做了什么,我们可以直接跳到 ComponentScanAnnotationParser类中的parse函数,首先先解释一波什么是ComponentScanAnnotationParser ,所谓的ComponentScanAnnotationParser其实只是Spring的一个内部工具,它会基于某个类上的 @ComponentScan 注解属性分析指定包(package)以获取其中的BeanDefiniiton

接下来我们看看其中的parse函数,跟着调试的同学可以直接给这个函数的最后一段代码加个、断点,启动项目,就可以看到项目的运行流程了,如下所示
图片描述

// 通过调试可以看到componentScan携带者basePackages这个数据,而declaringClass是
// "com.nuofankj.demo.DemoApplication",也就是包路径+主类
public Set<BeanDefinitionHolder> parse(AnnotationAttributes componentScan, final String declaringClass) {
		ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(this.registry,
				componentScan.getBoolean("useDefaultFilters"), this.environment, this.resourceLoader);

		Class<? extends BeanNameGenerator> generatorClass = componentScan.getClass("nameGenerator");
		boolean useInheritedGenerator = (BeanNameGenerator.class == generatorClass);
		scanner.setBeanNameGenerator(useInheritedGenerator ? this.beanNameGenerator :
				BeanUtils.instantiateClass(generatorClass));

		ScopedProxyMode scopedProxyMode = componentScan.getEnum("scopedProxy");
		if (scopedProxyMode != ScopedProxyMode.DEFAULT) {
			scanner.setScopedProxyMode(scopedProxyMode);
		}
		else {
			Class<? extends ScopeMetadataResolver> resolverClass = componentScan.getClass("scopeResolver");
			scanner.setScopeMetadataResolver(BeanUtils.instantiateClass(resolverClass));
		}

		scanner.setResourcePattern(componentScan.getString("resourcePattern"));

		for (AnnotationAttributes filter : componentScan.getAnnotationArray("includeFilters")) {
			for (TypeFilter typeFilter : typeFiltersFor(filter)) {
				scanner.addIncludeFilter(typeFilter);
			}
		}
		for (AnnotationAttributes filter : componentScan.getAnnotationArray("excludeFilters")) {
			for (TypeFilter typeFilter : typeFiltersFor(filter)) {
				scanner.addExcludeFilter(typeFilter);
			}
		}

		boolean lazyInit = componentScan.getBoolean("lazyInit");
		if (lazyInit) {
			scanner.getBeanDefinitionDefaults().setLazyInit(true);
		}
		// 【重点】真正存放资源位置的地方
		Set<String> basePackages = new LinkedHashSet<>();
		String[] basePackagesArray = componentScan.getStringArray("basePackages");
		for (String pkg : basePackagesArray) {
			String[] tokenized = StringUtils.tokenizeToStringArray(this.environment.resolvePlaceholders(pkg),
					ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
			Collections.addAll(basePackages, tokenized);
		}
		for (Class<?> clazz : componentScan.getClassArray("basePackageClasses")) {
			basePackages.add(ClassUtils.getPackageName(clazz));
		}
		// 【重点】该次运行,由于没有是默认启动,因此最终basePackages存放的是declaringClass的包路径,这点可以直接看
		// ClassUtils.getPackageName(declaringClass)ll
		if (basePackages.isEmpty()) {
			basePackages.add(ClassUtils.getPackageName(declaringClass));
		}

		scanner.addExcludeFilter(new AbstractTypeHierarchyTraversingFilter(false, false) {
			@Override
			protected boolean matchClassName(String className) {
				return declaringClass.equals(className);
			}
		});
		// 【重点】StringUtils.toStringArray(basePackages) 断点可以看到打印出来的是主类的包名【com.nuofankj.demo】
		return scanner.doScan(StringUtils.toStringArray(basePackages));
	}

接下来让我们看看 scanner.doScan 中做了什么

protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
    Assert.notEmpty(basePackages, "At least one base package must be specified");
    Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<>();
    for (String basePackage : basePackages) {
        // 【重点】从指定的包中扫描需要装载的Bean
        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);
                // 【重点】将该 Bean 注册进 IoC容器(beanDefinitionMap)
                registerBeanDefinition(definitionHolder, this.registry);
            }
        }
    }
    return beanDefinitions;
}

上面中有标注了两个比较重要的方法
Set candidates = findCandidateComponents(basePackage); 作用是从basePackage中扫描类并解析成BeanDefinition,邓拿到所有符合条件的类后在 **registerBeanDefinition(definitionHolder, this.registry);**中将该类注册进IoC容器。也就是说在这个方法中完成了IoC容器初始化过程的第二三步,BeanDefinition的载入,和BeanDefinition的注册。

到这里,资源的定位到这里就结束了。

BeanDefinition的载入

我们接着上面所说的函数scanCandidateComponents

private Set<BeanDefinition> scanCandidateComponents(String basePackage) {
    Set<BeanDefinition> candidates = new LinkedHashSet<>();
    try {
        // 【重要】拼接扫描路径,最终生成的是 classpath*:com/nuofankj/demo/**/*.class
        String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +
                resolveBasePackage(basePackage) + '/' + this.resourcePattern;
        // 从上面拼接出来的packageSearchPath 路径中扫描所有的类
        Resource[] resources = getResourcePatternResolver().getResources(packageSearchPath);
        boolean traceEnabled = logger.isTraceEnabled();
        boolean debugEnabled = logger.isDebugEnabled();
        for (Resource resource : resources) {
            if (traceEnabled) {
                logger.trace("Scanning " + resource);
            }
            if (resource.isReadable()) {
                try {
                    MetadataReader metadataReader = getMetadataReaderFactory().getMetadataReader(resource);
                    // 【重要】这里判断该类是不是 @Component 注解标注的类
                    if (isCandidateComponent(metadataReader)) {
                        // 将该类封装成ScannedGenericBeanDefinition,这是BeanDefinition接口的实现类
                        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;
}

看源码和注释我们可以看到,packageSearchPath 是通过拼接出来的,而会在getResources(packageSearchPath)方法中扫描到了该路径下的所有的类,之后遍历这些Resources,判断是否是@Component 注解标注的类,并且不是需要排除掉的类。之后便将扫描到的类,解析成ScannedGenericBeanDefinition,该类是BeanDefinition接口的实现类。
到这里 IoC容器的BeanDefinition载入就结束了。

BeanDefinition的注册

载入结束了,接下来让我们看看如何进行BeanDefinition的注册,重新回到 registerBeanDefinition(definitionHolder, this.registry) ,根据调试可以看到,最终会进入以下函数

    public static void registerBeanDefinition(BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry) throws BeanDefinitionStoreException {
        String beanName = definitionHolder.getBeanName();
        // 【重点】
        registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition());
        String[] aliases = definitionHolder.getAliases();
        if (aliases != null) {
            String[] var4 = aliases;
            int var5 = aliases.length;

            for(int var6 = 0; var6 < var5; ++var6) {
                String alias = var4[var6];
                registry.registerAlias(beanName, alias);
            }
        }

    }

我们先看看什么是BeanDefinitionRegistry,该类的作用主要是向注册表中注册 BeanDefinition 实例,完成 注册的过程。继续看下重点函数 registerBeanDefinition

@Override
public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition)
        throws BeanDefinitionStoreException {

    Assert.hasText(beanName, "Bean name must not be empty");
    Assert.notNull(beanDefinition, "BeanDefinition must not be null");

    if (beanDefinition instanceof AbstractBeanDefinition) {
        try {
            ((AbstractBeanDefinition) beanDefinition).validate();
        } catch (BeanDefinitionValidationException ex) {
            throw new BeanDefinitionStoreException(beanDefinition.getResourceDescription(), beanName,
                    "Validation of bean definition failed", ex);
        }
    }
    // 【重点】可以看到这里,采用ConcurrentHashMap存放bean,看到这里便可以知道了,其实IoC容器的最底层就是一个ConcurrentHashMap,只是它被放到了某个对象中,通过看源码可以知道这个对象是DefaultListableBeanFactory
    BeanDefinition existingDefinition = this.beanDefinitionMap.get(beanName);
    if (existingDefinition != null) {
        // 【重点】如果该类不允许 Overriding 直接抛出异常
        if (!isAllowBeanDefinitionOverriding()) {
            throw new BeanDefinitionStoreException(beanDefinition.getResourceDescription(), beanName,
                    "Cannot register bean definition [" + beanDefinition + "] for bean '" + beanName +
                            "': There is already [" + existingDefinition + "] bound.");
        } else if (existingDefinition.getRole() < beanDefinition.getRole()) {
            // e.g. was ROLE_APPLICATION, now overriding with ROLE_SUPPORT or ROLE_INFRASTRUCTURE
            if (logger.isWarnEnabled()) {
                logger.warn("Overriding user-defined bean definition for bean '" + beanName +
                        "' with a framework-generated bean definition: replacing [" +
                        existingDefinition + "] with [" + beanDefinition + "]");
            }
        } else if (!beanDefinition.equals(existingDefinition)) {
            if (logger.isInfoEnabled()) {
                logger.info("Overriding bean definition for bean '" + beanName +
                        "' with a different definition: replacing [" + existingDefinition +
                        "] with [" + beanDefinition + "]");
            }
        } else {
            if (logger.isDebugEnabled()) {
                logger.debug("Overriding bean definition for bean '" + beanName +
                        "' with an equivalent definition: replacing [" + existingDefinition +
                        "] with [" + beanDefinition + "]");
            }
        }
        // 【重点】注册进beanDefinitionMap
        this.beanDefinitionMap.put(beanName, beanDefinition);
    } else {
        if (hasBeanCreationStarted()) {
            // Cannot modify startup-time collection elements anymore (for stable iteration)
            synchronized (this.beanDefinitionMap) {
                this.beanDefinitionMap.put(beanName, beanDefinition);
                List<String> updatedDefinitions = new ArrayList<>(this.beanDefinitionNames.size() + 1);
                updatedDefinitions.addAll(this.beanDefinitionNames);
                updatedDefinitions.add(beanName);
                this.beanDefinitionNames = updatedDefinitions;
                if (this.manualSingletonNames.contains(beanName)) {
                    Set<String> updatedSingletons = new LinkedHashSet<>(this.manualSingletonNames);
                    updatedSingletons.remove(beanName);
                    this.manualSingletonNames = updatedSingletons;
                }
            }
        } else {
            // 【重点】如果仍处于启动注册阶段,注册进beanDefinitionMap
            this.beanDefinitionMap.put(beanName, beanDefinition);
            this.beanDefinitionNames.add(beanName);
            this.manualSingletonNames.remove(beanName);
        }
        this.frozenBeanDefinitionNames = null;
    }

    if (existingDefinition != null || containsSingleton(beanName)) {
        resetBeanDefinition(beanName);
    }
}

通过一步步调试,我们可以看到最终走到了DefaultListableBeanFactory,首先介绍下什么是DefaultListableBeanFactory,该类可以说就是IoC容器本器了,调试源码看到最后,其实IoC容器就是一个ConcurrentHashMap。
那么DefaultListableBeanFactory是什么时候构建的呢?我们可以看到
图片描述
通过obtainFreshBeanFactory构建的,内部函数调试后看了下没什么,不影响整体流程,就不深入讲解了,有兴趣可以自己跟着调试看看。

吹水几分钟

文章总结的我都调到前面了,所以总结就不说了,说说看最近更文速度变慢的原因,主要还是状态不好,忙是一直都忙的,毕竟身处游戏行业,每天9点或者10点上班,23点左右才下班,不过状态不好的原因主要还是情感问题,所以无心学习导致无心写文章。其次是发现最近容易脖子酸,职业病呀,关注我的应该大部分是程序员,如果有好的建议,可以提下哈,我说的是职业病预防,当然了,如果你要给我介绍女朋友我也是不介意的 ○( ^皿^)っHiahiahia…

点击查看更多内容
TA 点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
JAVA开发工程师
手记
粉丝
108
获赞与收藏
437

关注作者,订阅最新文章

阅读免费教程

  • 推荐
  • 评论
  • 收藏
  • 共同学习,写下你的评论
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消