Java架构篇章-02Ioc容器源码解析

Ioc容器源码解析

我们都是常用 new 一个AnnotationConfigApplicationContext 来启动ioc容器,类似于下面的这种.但是具体的流程到底是怎么样的呢?

AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(MainConfig.class);

先看一个粗略的流程
请添加图片描述

从图上可以知道,AnnotationConfigApplicationContext的源码中先使用了一个this() ,我们发现他继承于父类GenericApplicationContext ,我们发现父类第一个就导入了DefaultListableBeanFactory。这是因为DefaultListableBeanFactory 是具有最全的功能的.

请添加图片描述

其实我们需要确定一个问题; Beanbean定义的区别是什么?有人说 bean 可以理解成鸡,而bean定义是鸡蛋。bean定义是用来描述bean的。而且还符合鸡和鸡蛋的关系。鸡生蛋→蛋生鸡.
但是我觉得这样理解更对 bean 可以看成一个正常人 而bean定义就是这个人身上的某些具体信息,通过这些信息的集合就能确认这一个人了
在这里插入图片描述

好有了这2个的理解我们马上就能开始手撕源码了

这是ioc刚刚开始的一小部分。比较粗略. 建议看下面的xmind文件
请添加图片描述
ioc容器源码解析.xmind

先看AnnotatedBeanDefinitionReader这个方法 :

这个registerAnnotationConfigProcessors(this.registry) 是往容器中注册内部各种组件(bean定义信息),经过这个处理,它已经是一个bean的定义了
请添加图片描述
点击进去 查看里面的registerAnnotationConfigProcessors(registry, null);

public static void registerAnnotationConfigProcessors(BeanDefinitionRegistry registry) {
   registerAnnotationConfigProcessors(registry, null);
}

然后我们会发现有很多的if判断条件
请添加图片描述

先说明一下,以下的类是非常重要的,因为源码就是按照ConfigurationClassPostProcessor这个类来讲解的

		if (!registry.containsBeanDefinition(CONFIGURATION_ANNOTATION_PROCESSOR_BEAN_NAME)) {
			RootBeanDefinition def = new RootBeanDefinition(ConfigurationClassPostProcessor.class);
			def.setSource(source);
			beanDefs.add(registerPostProcessor(registry, def, CONFIGURATION_ANNOTATION_PROCESSOR_BEAN_NAME));
		}

暂时再看下ClassPathBeanDefinitionScanner这个方法:(先进入3个this的方法):

请添加图片描述

我们可以发现如果useDefaultFilters是true 那么就执行registerDefaultFilters。而registerDefaultFilters最主要的作用就是包扫描和注册扫描

请添加图片描述

代码的第一行注册了一个this.includeFilters.add(new AnnotationTypeFilter(Component.class)) Component组件 ,通过注释可以知道,当Component组件注册进来时候,什么@Service啊,@Controller啊都会生效,因为这写注解里面都被标记上了@Component

This will implicitly register all annotations that have the @Component meta-annotation including the @Repository, @Service, and @Controller stereotype annotations.

请添加图片描述

当注册完了Component之后就是,注册JRS-250和JSR-330的注解,如果不支持就报异常.所以,这个第一个this() 最主要的就是两个作用:

  • ​ 就是往我们的容器中注入一系列的内部bean后置处理器

  • ​ 另一个就是指定我们的扫描策略

请添加图片描述

接下来看第二个register(annotatedClasses),可以发现它需要传入一个参数,这个参数正是我们配置类。点击进去4轮注册后,可以看到一个具体逻辑了.第一个就是判断配置类上面有没有标记@Primary 和@Lazy,然后就是将我们的配置类注册到容器中去

请添加图片描述

然后往下看第三个方法refresh(),也是一个最重要的方法,它里面有许许多多的方法

请添加图片描述
在将这些方法之前,我们先说一个简单的铺垫,BeanFactoryPostProcessor不知道大家了不了解,他的作用是在bean在解析之后,但还没有实例化之前,来注入一些东西(比如可以控制懒加载或非懒加载)。

代码demo-testbeanfacotoryPostProcessor

BeanDefinitionRegistryPostProcessor继承BeanFactoryPostProcessor 在bean定义没有加载之前 我们可以自己加载

代码demo-testbfpostprocessor
请添加图片描述

如果上面的xmind啊巴巴拉拉的一大堆没看懂那就记住下面的话。new 一个AnnotationConfigApplicationContext 会用到3个主要方法分别是this() 和register(annotatedClasses)和refresh()。而第三个refresh里面的方法太多了。我们等等说,但是前面两个方法的作用是容器中注入一系列的内置bean的后置处理器和指定容器的扫描策略(this的作用)已经注入我们自己的配置类和@注解到容器中(register(annotatedClasses)的作用)

先说一个老生常谈的问题 ,然后在看ioc的真正源码。这个问题就是Spring是如何发布事件的?ApplicationListener

代码Demo-testapplicationlistener

注解这行打断点,debug调试step into

AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(MainConfig.class);

在进入refresh()方法 → initApplicationEventMulticaster()方法,我们一点点来分析,先说下作用 先去创建多播器!主要看下面的注释

protected void initApplicationEventMulticaster() {
   
		ConfigurableListableBeanFactory beanFactory = getBeanFactory();
    /*判断IOC容器中包含applicationEventMulticaster 事件多播器的Bean的name,我们可以点进去看一下这个魔法值    public static final String APPLICATION_EVENT_MULTICASTER_BEAN_NAME = "applicationEventMulticaster";*/
		if (beanFactory.containsLocalBean(APPLICATION_EVENT_MULTICASTER_BEAN_NAME)) {
            /* 创建一个applicationEventMulticaster的bean放在IOC 容器中,bean的name 为applicationEventMulticaster*/
			this.applicationEventMulticaster =
					beanFactory.getBean(APPLICATION_EVENT_MULTICASTER_BEAN_NAME, ApplicationEventMulticaster.class);
			if (logger.isDebugEnabled()) {
				logger.debug("Using ApplicationEventMulticaster [" + this.applicationEventMulticaster + "]");
			}
		}
    /*容器中不包含一个beanName 为applicationEventMulticaster的多播器组件 就走这里*/
		else {
            /*既然不包含了 那么就创建多播器*/
			this.applicationEventMulticaster = new SimpleApplicationEventMulticaster(beanFactory);
            /*然后注入到容器中*/
			beanFactory.registerSingleton(APPLICATION_EVENT_MULTICASTER_BEAN_NAME, this.applicationEventMulticaster);
			if (logger.isDebugEnabled()) {
				logger.debug("Unable to locate ApplicationEventMulticaster with name '" +
						APPLICATION_EVENT_MULTICASTER_BEAN_NAME +
						"': using default [" + this.applicationEventMulticaster + "]");
			}
		}
	}

上面已经有了多播器了,那么就将监听到的注册到多播器上面呗,在进入refresh()方法 → registerListeners()方法

protected void registerListeners() {
		// 去容器中把applicationListener 捞取出来注册到多播器上去(系统的)
		for (ApplicationListener<?> listener : getApplicationListeners()) {
			getApplicationEventMulticaster().addApplicationListener(listener);
		}

		// 你可以自己debug一下 走到这里 看看值,会发现这里是我们自己实现了ApplicationListener 的组件
		String[] listenerBeanNames = getBeanNamesForType(ApplicationListener.class, true, false);
		for (String listenerBeanName : listenerBeanNames) {
			getApplicationEventMulticaster().addApplicationListenerBean(listenerBeanName);
		}

		//在这里之前,我们早期想发布的事件 由于没有多播器没有发布,在这里我们总算有了自己的多播器,可以在这里发布早期堆积的事件了.
		Set<ApplicationEvent> earlyEventsToProcess = this.earlyApplicationEvents;
		this.earlyApplicationEvents = null;
		if (earlyEventsToProcess != null) {
			for (ApplicationEvent earlyEvent : earlyEventsToProcess) {
				getApplicationEventMulticaster().multicastEvent(earlyEvent);
			}
		}
	}

好,有了前面的铺垫,这个是最后的铺垫了 主要是想让一个东西进入大家的眼睛里面

我们先ctrl进入AnnotationConfigApplicationContextthis();AnnotatedBeanDefinitionReader 是无参的那个 →this(registry, getOrCreateEnvironment(registry));AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);registerAnnotationConfigProcessors(registry, null);

请添加图片描述
我们发现ConfigurationClassPostProcessor实现BeanDefinitionRegistryPostProcessor,我们先记住这个东西,后面再讲解,他很强!在这里插入图片描述

我们正式开始手撕ioc的源码。下面的讲解的很浅。主要看的是思路。新手不需要纠结每个方法具体的作用。我们只需知道大概的几个方法内容和简单的思路就好了。先附上代码和总体的逻辑图

codeDemo-testspringiocstarter

请添加图片描述

现在new AnnotationConfigApplicationContext处打一个断点debug运行起来。
在这里插入图片描述点击F7 进入到点断的方法里面
在这里插入图片描述
点击3次见到熟悉的方法名,熟悉的配方.上面的xmind已经铺垫其实已经说明白了this()register(componentClasses)方法了,所以下面主要讲的是refresh的方法,我们顺便在refresh上面打一个断点。并且进入到refresh方法里面去.
在这里插入图片描述进入到refresh方法里面后我们会看到很多方法,那么本章呢,小主先讲解这个画了圈圈的方法invokeBeanFactoryPostProcessors(beanFactory);
在这里插入图片描述
英文不好的可以通过翻译知道 这个方法和bean工厂处理器有关系。
在这里插入图片描述F7进入到对应的方法里面来,方法的第一个就是PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(beanFactory, getBeanFactoryPostProcessors());通过翻译发现 是调用了beanFactory后置处理器。
在这里插入图片描述
F7查看方法具体内容。发现是一个if判断的逻辑
在这里插入图片描述并且通过注释发现 当某个条件成立的时候,会调用BeanDefinitionRegistryPostProcessors 方法,很明显这个方法的逻辑我们需要查看,不然歪果仁就不那么勤快的写注释了
在这里插入图片描述我们现在if这里打一个断点。我们可以看出首当其冲的if条件就是判断传入的beanFactory 是不是继承 BeanDefinitionRegistry,通过debug值可以看出传入的beanFactoryDefaultListableBeanFactory 是继承关系

在这里插入图片描述再来看第二条,把当前的beanFactory 转为BeanDefinitionRegistry bean注册器

		BeanDefinitionRegistry registry = (BeanDefinitionRegistry) beanFactory;

之后设置两个list 来区分具有BeanFactoryPostProcessorBeanDefinitionRegistryPostProcessor特性的

			List<BeanFactoryPostProcessor> regularPostProcessors = new ArrayList<>();
			List<BeanDefinitionRegistryPostProcessor> registryProcessors = new ArrayList<>();

接下来我们继续debug下去发现这个for 是不会执行的,因为beanFactoryPostProcessors是一个成品的东西,我们从debug到这一步是没有发现哪一步是形成这个东西的,所以肯定是不会执行的.硬要说作用:其实就是区分postProcessor ,按照属性放到上面一步中创建两个list当中
在这里插入图片描述
那么我们继续F8往下进行,我们会发现又来了一个for循环,通过查看debug的调试值我们可以发现一个熟悉的东西org.springframework.context.annotation.internalConfigurationAnnotationProcessor,上面不是有一个单独说了一下的东西。好,暂时不管他继续往下走
在这里插入图片描述继续F8,走到if条件里面,这个getBean方法是及其复杂的,我们先不讲,但是这个作用是:往容器中创建了一个bean,然后再把它放入到单例缓存池里面

currentRegistryProcessors.add(beanFactory.getBean(ppName, BeanDefinitionRegistryPostProcessor.class));

再继续F8往下走到这里(中间的方法暂时不太重要);下面这个方法是非常重要的,如果没有它,那么我们定义的bean方法是注册不到ioc容器中的!所以接下里我们就探究一下这个方法~.

invokeBeanDefinitionRegistryPostProcessors(currentRegistryProcessors, registry);

F7 进入方法里面:第一个for循环中就是看到我们说了重要的ConfigurationAnnotationProcessor方法。在脑子里加深这个方法的印象哈
在这里插入图片描述for循环中每次都执行了postProcessBeanDefinitionRegistry方法,7进入到postProcessBeanDefinitionRegistry方法里面,我们可以到一个新的方法processConfigBeanDefinitions处理配置bean定义信息方法,通过抛出的注释知道中间两个if判断不重要,直接忽略
在这里插入图片描述F7进入到processConfigBeanDefinitions方法当中:
通过画圈圈的方法名字知道,第一个是获取了bean定义的名字,然后在for循环出
在这里插入图片描述

debug发现 for循环当中的内容candidateNames;它存放了java框架中内部本身几个和我们自己注入的bean的配置类信息
在这里插入图片描述那么这个for循环循环什么东西我们已经知道了,但是for循环里面的作用是什么呢?我们接着debug往下走,会发现一直在for循环里面进行什么,但是当beanName使我们自己的配置类的时候,居然就跳出循环了.这个for循环的作用就是将我们自己注入bean的配置类筛选出来。
在这里插入图片描述

configCandidates.add(new BeanDefinitionHolder(beanDef, beanName));

接着debug往下走,通过注释和方法名我们知道了,如果我们的配置类是有不止一个的话就会为我们的配置类进行一个排序
在这里插入图片描述
继续往下看 这个是什么?这个注释说真的我翻译了,但是也没看懂翻译的是意思,但是bean name generation 我知道 是bean 名字生成器?

在这里插入图片描述方法里面还有魔法值呢:ctrl点进去看发现是名字生成器

AnnotationConfigUtils.CONFIGURATION_BEAN_NAME_GENERATOR);

在这里插入图片描述包括if判断里面的方法名都是什么bean名称生成器
在这里插入图片描述
在这里插入图片描述
到这里应该能明白了 这个方法就是给我们bean或者包扫描取名字的,弄清楚了就继续往下,通过万能的翻译我们发现是下面这个方法是解析每一个@configuration类
在这里插入图片描述
那么下面的代码必然就是具体的解析了,所以正常应该都要主要解析这个方法名了吧。先看打头的这两行,你会发现set里面赋值了长度.这个应该是可以理解的吧,代码既然走到这里,前面有了那么多的if判断和for循环 什么不为空 什么的之类的。这已经说明了我们传入了自己的配置类吧。那一定是存在的。所以这里一定是不为0的。所以直接用list的长度计算即可,包括下面的都不是用for循环了 直接用do while循环了

在这里插入图片描述好了既然知道了传入了什么(list里面也就是传入的配置类),也知道了具体的作用(解析)和要看那个方法名(parse相关的名字),那就打个断点debug看看吧.
在这里插入图片描述又是for循环又是if判断?别慌!!先看他循环什么东西?configCandidates?也就是我们自己的配置类。
在这里插入图片描述万能的翻译啊 !它告诉我 if的里面的代码是 获取他的bean定义,然后再判断它是那个类型的注解,如果判断成功那么就去对应的解析。那么是解析什么呢? 我们ctrl 进去看看第一个parse
在这里插入图片描述解析我们的配置类?貌似不明显啊 我们在点进去看下吧
在这里插入图片描述这么多? 怎么看?无从下手? 不知道你又没有发现小主的在看上面源码的思路 ? 那么就时看方法名 + 注释来判断逻辑。但是在偷偷告诉你下java 一般做事情的都是什么do…的方法!有没有!听懂掌声!
在这里插入图片描述这不是现成的嘛?我的天!!!!!那就打个断点,重新debug到这个断点下,在step into 这个doProcessConfigurationClass里面看看吧
在这里插入图片描述
惊喜!!!一坨代码!!!没事让我们来翻译翻译!师爷!来给我翻译翻译什么是惊喜!
在这里插入图片描述看到了师爷的惊喜翻译我们知道了,实际上这个doProcessConfigurationClass处理我们自己的注解信息比如这些:这些我们都有用过吧
在这里插入图片描述
那么我们就看几个常用的吧,比如这个@ComponentScan,打个断点debug,走起来,发现第一行就是在解析我们的配置类,解析成对象
在这里插入图片描述而随后判断不为空的情况下循环遍历出我们的对象,然后我又发现了一个熟悉的单词parse,F7 step into
在这里插入图片描述进入到了这个方法里面 我们发现了大量的get和set方法, 先说一下这个方法作用就是把componentScan里面[这里面还记得是什么吗? 是我们传入配置类的对象]的值取出来,赋值给下面的scanner[给扫描器赋值,对扫描器初始化].
在这里插入图片描述你问我怎么知道作用的?让你的师爷翻译翻译啊!
在这里插入图片描述所以其实就是在得到某一个东西,然后获这个东西里面的相关参数,然后在赋值给某个东西,差不多就是这样了,既然这么多get和set方法,那么我们就去找doXXX的方法吧。发现了!再doScan 设置断点debug然后step into进入

在这里插入图片描述经过不为空的判断后 开始循环遍历执行某些方法了。findCandidateComponents 寻找候选组件方法?一脸懵逼?,step into 看怎么使用就知道原因了,

在这里插入图片描述
进来后进过debug的流程发现 if判断为空走scanCandidateComponents那我们先点进去看下.
在这里插入图片描述首先看到的就是拼接拉,它再传一个包的路径然后拼接到了一起,扫描 **/*.class下面的,然后再转化为resource 可以看到resource 就是我们自己的资源信息
在这里插入图片描述

师爷的翻译告诉我们 for循环后开始判断是否是可读的类型,如果是就转换成源数据信息,然后再判断是不是候选组件,如果时就加入到candidates
在这里插入图片描述在这里插入图片描述一直debug回来走到这里,要注意的是 现在还没有实例化噢,现在还是bean的定义,一个可以从下图这个方法名就可以知道,他在给bean取名字呢,第二个我们可以看具体的值,在this-registry-beanDefinitionMap中我们可以发现 目前只有这几个,还没有从candidates里面传来的。
在这里插入图片描述那么什么时候才是bean能注册进来呢?往下看到这个registerBeanDefinition中我们可以发现map里面已经有了从candidates传来的了,所以流程就是这样子的

在这里插入图片描述一直debug往下走后 这个注入完成了,但是我们发现了下面还有一个parse?卧槽!我大意了,没有闪!源代码不讲武德!都解析完了为什么还要在解析一次?debug进去看看
在这里插入图片描述step into 进去后我们发现了熟悉的processConfigurationClass 方法。好的!原来流程和上面的一样。但是为什么呢?仔细看debug调试的值。他的操作对象居然变成了传过来的controller了。所以你明白了?它只是怕我们在这个controller上面再标一些注解。虽然正常人不会这么做。但是他要防止这种事情.
在这里插入图片描述
我们依旧debug 一直往下走.一值走到这里(走的有点多,有点耐心不慌哈)。Process individual @Bean methods,再set打一个断点.可以把之前的断点都删除掉,我们能发现@Bean 仅仅是把方法加进来了,其他什么都没做

在这里插入图片描述在这里插入图片描述

临时总结一下

我们已经说了@Bean @ComponentScan 两个个方法了噢。
@ComponentScan 最重要,里面有2个parse。 @Bean 其实撒谎也没干就仅仅加方法而已

接下来我们step into 回来
在这里插入图片描述看到loadBeanDefinitions,进入看具体方法,通过方法名字得知 - 加载bean定义信息从那个配置类加载的
在这里插入图片描述
进入去看,其实是再判断这个类是从哪里导入进来的
在这里插入图片描述然后我们看一下第一个方法

registerBeanDefinitionForImportedConfigurationClass(configClass);

就看圈起来的着两个的方法名就是知道了做了什么了吧 设置bean的名字 + 反手注册到bean定义里面去

在这里插入图片描述上面的仅仅是包扫描 下面的才是@XXX加到容器里面
在这里插入图片描述
继续往下看.这个代码就表示说明 java已经发生了解析了

if (registry.getBeanDefinitionCount() > candidateNames.length) {
    ......

在这里插入图片描述

这里看逻辑 就是说明 原本有多少bean要解析.然后循环解析几个

			String[] newCandidateNames = registry.getBeanDefinitionNames();
				Set<String> oldCandidateNames = new HashSet<>(Arrays.asList(candidateNames));
				Set<String> alreadyParsedClasses = new HashSet<>();
				for (ConfigurationClass configurationClass : alreadyParsed) {
					alreadyParsedClasses.add(configurationClass.getMetadata().getClassName());
				}

在这里插入图片描述

在这里插入图片描述
所以上面就是refresh 中 invokeBeanDefinitionRegistryPostProcessors这个的源码逻辑

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值