死磕Spring源码- spring Ioc 源码分析 (一)

1,AnnotationConfigApplicationContext 首先从这个类开始说明,因为Spring基于注解加载配置类是从这里开始的。

一:为我们做了什么?

首先AnnotationConfigApplicationContext 继承了GenericApplicationContext,所以AnnotationConfigApplicationContext 初始化的时候会走一下GenericApplicationContext的构造函数。去创建一个叫做this.beanFactory =new DefaultListableBeanFactory();这样的默认Bean工厂。



二:为什么要去构建一个DefaultListableBeanFactory对象呢?

因为,DefaultListableBeanFactory这个类是BeanFactory下实现功能最多的一个类。故此来构建它。


三:AnnotationConfigApplicationContext 构造方法都干了些什么?

首先看一下AnnotationConfigApplicationContext 的构造函数

第一步调用了自己的无参构造函数

这里面有两个东西,第一个是AnnotatedBeanDefinitionReader。注解Bean定义的读取器,下图中的构造器有个getOrCreateEnvironment方法,是获取或者创建一个环境,并且加入缓存。

回过头点开this,其中ConditionEvaluator 是@Condition注解的一个解析器。这个东西很重要。AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry)很重要,重要的不能在重要,敲黑板,划重点。。。。。

点击进去看下

registerAnnotationConfigProcessors是注解的配置处理器,点进去看一下public static Set<BeanDefinitionHolder> registerAnnotationConfigProcessors(BeanDefinitionRegistry registry, Object source) {}如下图可知,这个类主要的作用是,将各种各样不同类型的类定义,注入到容器当中,注意,此时是Bean的定义并不是真正的创建Bean的实例到IOC容器中。就比如,洗浴中心要招一批小姐姐,规定身高180以上,体重120以下,只是定义。仅此而已。如下图可知,第一个Bean的定义ConfigurationClassPostProcessor.class。它是通过internalAutowiredAnnotationProcessor名字进行判断,注入的类型是ConfigurationClassPostProcessor。

通过如下把Bean定义注册到容器中去。

beanDefs.add(registerPostProcessor(registry, def, "org.springframework.context.annotation.internalAutowiredAnnotationProcessor"));

还有如下下图所示,是解析Autowired自动配的

总结registerAnnotationConfigProcessors(registry, (Object)null);的作用就是处理各种内部的组件。比如,AutoWired、Required、jsr250标准,JPA等等等。。。Bean定义它是用来描述Bean的,是否是懒加载等等等。

回过头在看看AnnotationConfigApplicationContext构造方法中的第二句话this.scanner =new ClassPathBeanDefinitionScanner(this);这句话就比较牛逼了。

ClassPathBeanDefinitionScanner译为,类路径下Bean扫描器

如下如,我们一直进入方法最后一层在类:ClassPathBeanDefinitionScanner下的ClassPathBeanDefinitionScanner方法中可以看到useDefaultFilters为true执行this.registerDefaultFilters();加载默认的配置策略。这也是之前的文章讲过的。在包扫描中如果使用includeFilters包含条件的话如果useDefaultFilters设为false就只会采用自定义的包扫描规则,如果为true是全量加载。

进入this.registerDefaultFilters()方法中

如下图所示,我的默认扫描规则是包含@Component注解的类。比如@Controller、比如@Service、比如@Confingration等等等等注解,如果不符合JSR-250、JSR-330标准就会抛出异常。

至此如下图的两端代码分析结束,第一个的主要作用是,对内部Bean的定义。第二个是扫描策略。

二:接下来将第二个方法this.register(annotatedClasses);

抛出结论:这段代码就是将我们自己传入进来的MainConfig类注册到容器中去。如以下代码所示,进入方法最后找到类AnnotatedBeanDefinitionReader中的registerBean方法。前面大概的意思就是说看我们的类上有没有@Primary、@Lazy、这些个东西。最后通过BeanDefinitionReaderUtils.registerBeanDefinition(definitionHolder, this.registry);这个方法,将我们的类注册到容器中。

以上两个方法分析完毕,


三:this.refresh()

最重要,也是最难理解的一个方法。无论是Spring还是SpringBoot都是通过这个方法去带动的。故此非常非常之重要。并且这里面的功能多的吓死人。下面逐一分析。

点进该方法如下图所示,根据图中方法进行分析

1,上面说过ConfigurationClassPostProcessor这个类非常的重要,一定要记住它。点进来看一下,如下图所示。他是BeanDefinitionRegistryPostProcessor类型,我们先记住这个接口

2,回到refresh()方法中来,看一下如下图所示,调用了一个Bean工厂的后置处理器invokeBeanFactoryPostProcessors(beanFactory);

3,点进这个方法可以看一下,如下图所示,看第一个if判断的是beanFactory是不是Bean的注册器。其实这个 beanFactory就是我们一开始构建的那个DefaultListableBeanFactory类。那么点进这个类,会发现它属于bean的注册器。

 

4,既然是Bean的注册器,那么就会进入第一个if判断,然后将beanFactory转成Bean的注册器,下面有两个list。第一个list可以解释为普通的PostProcessors,就是非BeanDefinitionRegistryPostProcessor类型的list。此时看for循环,这个循环根本就不会进来,因为传入的beanFactoryPostProcessors是一个半成品,并未有值。是一个空对象而已。for循环里只是做了,对两种类型的分类。并且放到regularPostProcessors和registryProcessors里面去。

,

5,再往下看代码,如图所示,红框1内表示创建一个(当前正在注册的一个处理器)。再往下看红框2,实际上postProcessorNames数组就是我们之前在this()中加载的Bean的注册器。红框三中的currentRegistryProcessors.add表示往容器中第一次调用,放入了一个Bean并且放入单位缓存池中具体细节其他章节阐述。processedBeans.add(ppName);这句话代表的是将Bean的注册器放入方法第一行定义的Set中。其次后面的代码不重要,重要的是invokeBeanDefinitionRegistryPostProcessors(currentRegistryProcessors, registry);这句话

6,如果没有invokeBeanDefinitionRegistryPostProcessors(currentRegistryProcessors, registry);这句话,我们自定义的Bean根本就不会被注册进来。点击进入方法内。可以看到postProcessors他的类型就是前面讲的最重要的那个类型ConfigurationClassPostProcessor

7,同样点进去看看方法内,找到ConfigurationClassPostProcessor类中postProcessBeanDefinitionRegistry方法看看如何做的。我们主要看最后一行,处理配置Bean定义信息。直接点进去

8,进入方法看到红框内,在这个for循环里,找出我们自己的配置类。非系统的配置类。也就是我们前面提到的MainConfig.class,并且后面是判断关于 配置类上的@Order注解的排序,这个不重要继续往下看。

9,看下图中第二个红框内的代码,其他的看不懂不要紧,但至少能看懂两个东西,第一个是包扫描的生成器,起一个名字。第二个通过import注解导入的也要起个名字。

10,接着往下看,如下图所示通过注释可知,这段代码是对Bean的配置类的解析,创建一个Bean的解析器。这里使用了一个do-while循环,其中candidates集合表示候选的,alreadyParsed集合表示已解析的,但是长度设为了1.其作用就是使用了do-while循环,肯定先执行一次。节省内存。那么最核心的解析代码是parser.parse(candidates);点进去看一下。

11,点进parse方法,我们的MainClass是通过注解的形式定义的。所以走红框内的parse。点进去。

12,解析配置类

13,点进去以后,其他的都不用看直接看下面,doProcessConfigurationClass这个代码。为什么?因为Spring真正干活的类,基本都是以do开头的方法名(小技巧)点进去

 

14,点进去我们发现AnnotationConfigUtils.attributesForRepeatable这个方法,他是用来进行解析我们的Component注解的。将这个注解解析成一个对象如下图所示。

15,下面这段代码才是真正的对你的包扫描进行解析。上面那点,就好比,你发现了她是一个小姐姐,下面红框下的代码才是真正的对这个小姐姐进行分析,底盘扎实不扎实,发动机是否好用,变速箱是否换挡顺畅等等等一系列的信息。然后我们直接点进parse方法看一下

16,进入parse方法以后可以看到创建了一个扫描器。ClassPathBeanDefinitionScanner scanner =new ClassPathBeanDefinitionScanner(this.registry, componentScan.getBoolean("useDefaultFilters"), this.environment, this.resourceLoader);然后,把上面解析的Component的值搂出来,逐一的给扫描器赋值进行初始化,真正干活的方法是最后的的这句:return scanner.doScan(StringUtils.toStringArray(basePackages));

17,通过下面的代码可知找到候选的一个组件。并且给我们拼接路径,找到这个包下所有子包的类,并且转换成Resource,并且里面存储的就是我们自己想要的组件。然后循环resource,判断是不是可读的,然后转换成一个原信息的读取器。到这里仅仅是将我们的Bean定义扫描到容器中去,并不是Bean的实例化。接着判断是否有包含或者排除的组件。接下来往下执行,如果遇到@Controller的组件,就会进行赋值。因为@Controller是可读的。此方法执行完毕以后回到doScan方法。进行循环。

18,此时可以看到容器中并没有我们自己的组件,但是解析包扫描是找到的。然后接着往下看。原来是6个。现在还是6个

19,执行完registerBeanDefinition(definitionHolder, this.registry);注册Bean扫描器以后我们自己定义的组件就被扫描出来了。doScan执行完以后回到this.componentScanParser.parse这个地方。

20,在this.componentScanParser.parse的最下面又执行了一次parse,它作用是扫描比如在@Controller注解上有没有其他ComponentScan注解。正常的逻辑是不会有的。所以这个功能有点儿多余,不清楚为什么要这么设计。

21,代码继续往下走,执行这句话processImports。解析完可读的注解以后,在继续扫描类上面有没有import注解,如果有,就进去,并且找到import注解的value值。并且判断是否是导入的ImportSelector或者是ImportBeanDefinitionRegistrar。或者是DeferredImportSelector这种类型(这个东西很重要,自动注入,SpringBoot进行自动注入就是通过这个东西进行处理的)进行保存。保存之后就会有调用

22,processImports执行完以后往下看找到// Process individual @Bean methods,点击去之后发现,这个段代码的意思是,是解析@Bean标注的方法。最后执行完,回到parse方法。

23,这是我们新扫描出来的两个Bean的定义接着往下看,

24,这个方法很重要。如果说在你的@Bean注解标注的方法内创建了一个类的实例,在未执行此方法的时候,那么那个实例是不会被加紧Bean定义注册器的,只有执行了这个方法以后才会在Bean的注册器中被注册。可以推断,这个方法就是用来将@Bean标注的方法内实例化的对象搂出来,放入Bean自定义的注册器中。并且如果是Import导入的类,也会将该类加入Bean的自定注册器中去,点开源码看。

25,this.reader.loadBeanDefinitions(configClasses);点开源码发现。通过import注解导入的类会通过红框内的方法进行注册,点进去看一下

26,进入这个方法以后,这个方法获取了你的方法名,这也是为什么Bean的依赖注入如果不指定别名,就是默认的方法名的原因。还进行了一系列的判断,接着往下看。

27,在方法的最后调用了this.registry.registerBeanDefinition(beanName, beanDefToRegister);方法。这段代码最关键。执行完以后会发现,@Bean注解标注的方法内,如果通过new关键字实例化的对象,此时已经被加入Bean定义注册器当中了。是不是很神奇?

28,回过头总结一下

1:parser.parse(candidates);这句话仅仅是注册包扫描的。

2:this.reader.loadBeanDefinitions(configClasses);这段代码是加载通过@import、@ Bean倒进来的。同理,SpringBoot也是通过这个东东来扫描各大装配种类的。

后面的代码是判断有没有解析完。看看有没有,没有解析的类。一笔带过了。如果都解析过,do-while循环就结束了。至此下图所示的一个方法就分析完毕了。

封面图源网络,侵权删除)

如有收获,请帮忙转发,您的鼓励是作者最大的动力,谢谢!

一大波微服务、分布式、高并发、高可用的原创系列文章正在路上,

欢迎关注头条号:java小马哥

周一至周日早九点半!下午三点半!精品技术文章准时送上!!!

十余年BAT架构经验倾囊相授

 

java架构师|Java直播课|如何从程序员到架构师​​​​​​​

 

 

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值