Spring源码笔记(一):追根刨底IOC
很多人信奉“不去造轮子”,看似性价比很高的一句话,却不知道这句话是有前提的,那就是“你得知道轮子的内部结构”,否则就是自我麻痹
关于Spring,作为一个javaer必须要了解的框架,我觉得我们有必要对其追根刨底。
废话不多说,干货才是硬道理。
1、首先,我们从main函数入口。
2、进入SpringApplication类内部,调用构造方法和run方法。
3、在构造方法中,初始化系统资源,如:primarySources,咱们的启动类;如webApplicationType,这个参数在Spring Boot 1.x的时候,类型是一个boolean值,在当前版本,有三个值,NONE,SERVLET,REACTIVE。三个参数来源于WebAplicationType枚举类。这里我们返回的是SERVLET,代表启动Servlet Web服务器。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
public enum WebApplicationType { /** * The application should not run as a web application and should not start an * embedded web server. */ NONE, /** * The application should run as a servlet-based web application and should start an * embedded servlet web server. */ SERVLET, /** * The application should run as a reactive web application and should start an * embedded reactive web server. */ REACTIVE; ...... } |
4、紧接着,我们进入run方法,让我们看一下run方法大概是个什么造型,其中最为主要的有createApplicationContext、refreshContext等。
5、当我们进入createApplicationContext内部,Spring Boot为我们创建容器,这时候我们使用在构造初始化完成的参数:webApplicationType创建相对应的容器,这里通过反射创建容器org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext。
6、接下来看一下这个容器的继承结构,为什么要了解一下其继承结构呢?因为在后续的使用过程中面向接口编程,我们需要知道其继承结构,才能找到对应执行的源码。
7、接下来继续回到run方法中,继续往下走,准备执行一个非常重要的方法:refresh。
8、我们沿着源码继续往深处走。
1 2 3 4 5 6 7 8 |
/** * Refresh the underlying {@link ApplicationContext}. * @param applicationContext the application context to refresh */ protected void refresh(ApplicationContext applicationContext) { Assert.isInstanceOf(AbstractApplicationContext.class, applicationContext); ((AbstractApplicationContext) applicationContext).refresh(); } |
这里调用我们容器的refresh方法,而容器是AnnotationConfigServletWebServerApplicationContext,该容器没有实现refresh方法,这时候在之前了解到的继承结构就有派场了,父类ServletWebSerApplicationContext实现了方法。
9、继续往上走,AbstractApplicationContext,经过一系列准备,我们终于来到核心地带。
10、AbstractApplicationContext存在spring-context包下,接下来我们来分析分析容器的初始化过程。这里obtainFreshBeanFactory方法会创建一个beanFactory,如果存在旧的则销毁旧的再创建新的。
11、在obtainFreshBeanFactory方法中,会调用父类的refreshBeanFactory方法,我们再次利用继承结构,我们会进入父类GenericApplicationContext当中,而不是AbstractRefreshableApplicationContext。在以前,如果是基于xml方式去生成bean,我们经常会用到FileSystemXmlApplicationContext或ClassPathXmlApplicationContext去当我们的容器,这个时候就会进入父类AbstractRefresnableApplicationContext当中去初始化容器。
12、在GenericApplicationContext当中,其实我们早就已经初始化容器了,在之前没有提及,我们在初始化AnnotationConfigServletWebServerApplicationContext容器的时候,这个容器的父类继承ServletWebServerApplicationContext,而它又继承GenericWebApplicationContext,而它又继承GenericApplicationContext。
所以我们我们此时已经拥有了beanFactory,它的实现是DefaultListableBeanFactory,接下来看源码。
1 2 |
public class AnnotationConfigServletWebServerApplicationContext extends ServletWebServerApplicationContext implements AnnotationConfigRegistry { |
1 2 |
public class ServletWebServerApplicationContext extends GenericWebApplicationContext implements ConfigurableWebServerApplicationContext { |
1 2 |
public class GenericWebApplicationContext extends GenericApplicationContext implements ConfigurableWebApplicationContext, ThemeSource { |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
public class GenericApplicationContext extends AbstractApplicationContext implements BeanDefinitionRegistry { private final DefaultListableBeanFactory beanFactory; /** * Create a new GenericApplicationContext. * @see #registerBeanDefinition * @see #refresh */ public GenericApplicationContext() { this.beanFactory = new DefaultListableBeanFactory(); } } |
13、我们验证一下这个beanFactory的类型,其实在之前基于XML的方式也是DefaultListableBeanFactory,所以这个容器类就变得至关重要了。
14、接下来,我们要进入invokeBeanFactoryPostProcessors方法进行初始化IOC容器。若是以基于XML配置的,IOC容器初始化则是在obtainFreshBeanFactory内。
15、执行invokeBeanFactoryPostProcessors会做一层封装,然后进入PostProcessorRegistrationDelegate的invokeBeanFactoryPostProcessors。
1 2 3 4 5 6 7 8 9 |
protected void invokeBeanFactoryPostProcessors(ConfigurableListableBeanFactory beanFactory) { PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(beanFactory, getBeanFactoryPostProcessors()); // Detect a LoadTimeWeaver and prepare for weaving, if found in the meantime // (e.g. through an @Bean method registered by ConfigurationClassPostProcessor) if (beanFactory.getTempClassLoader() == null && beanFactory.containsBean(LOAD_TIME_WEAVER_BEAN_NAME)) { beanFactory.addBeanPostProcessor(new LoadTimeWeaverAwareProcessor(beanFactory)); beanFactory.setTempClassLoader(new ContextTypeMatchClassLoader(beanFactory.getBeanClassLoader())); } } |
16、在这个方法里,经过一系列配置后,进入invokeBeanDefinitionRegistryPostProcessors。
17、我们继续往深处走
18、我们看到postProcessor的类型为ConfigurationClassPostProcessor,所以在进入postProcessBeanDefinitionRegistry我们也需要进入该类,然后逐步分析源码。
19、我们继续深入,没有办法,封装的很严实,很深。
20、我们马上就要注册bean实现IOC容器的初始化了,但是在这之前,我们需要扫描所有的类。这里我们传入的candidates,是我们的启动类DemoApplication,在这个类上,有@SpringBootApplication注解。
在这里,Spring会去扫描Bean并注册。
21、继续往下走,我们传进来的configCandidates是我们的启动类集合,只有一个,bean被BeanDefinition封装这,而BeanDefinition又被BeanDefinitionHolder封装着,继续进入parse方法。
22、继续往下走,进入processConfigurationClass方法。
23、这个时候Spring才准备开始扫描指定包下的所有类,继续前进。
24、在这里,Spring又进行一系列准备,继续深入parse方法。
25、Spring继续进行一系列准备操作,这次是对包的一个过滤操作,最后执行doScan方法。在Spring中,以do开始的都是小弟,都是干活的,我们传入的包basePackages,是我们指定的包,如果没有指定,那就将启动类所在目录作为扫描包,Spring将扫描该包下的所有类(带注解或不带注解,都会被扫描,在后续会对不是Spring注解的进行一个过滤,不进行注册。)。
26、在doScan里,Spring会扫描所有的类,然后解析,是需要注册的Bean,在这里进行注册,完成IOC容器beanDefinitionMap的一个初始化过程。具体来看,findCandidateComponents方法是找到所有候选组件,也就是可进行注册的Bean,也就是说,在这个方法里,Spring返回的BeanDefinition集合,都是需要进行注册的,在方法内部已经完成筛选。
27、这里又做了一层过滤,我们继续往下走。
28、在这里就是准备resources,准备解析class,解析完成后做一个筛选,然后返回给IOC进行注册。
这里获取metadataReaderFactory,类型是CachingMetadataReaderFactory,看名字也能看出来,优先使用缓存。
代码如下,继承结构如下。
1 2 3 4 5 6 |
public final MetadataReaderFactory getMetadataReaderFactory() { if (this.metadataReaderFactory == null) { this.metadataReaderFactory = new CachingMetadataReaderFactory(); } return this.metadataReaderFactory; } |
29、缓存得不到,我们就去父类获取metadataReader,再放入缓存,以便下次直接从缓存获取,而它的父类在上面提到,父类是SimpleMetadaReaderFactory。
30、进入父类,new了一个SimpleMetadataReader,继续深入,看构造方法。
31、在这个构造方法里,初始化了这个resource所对应的class,然后去解析这个class。继续看accept方法。
在这个里面就不用再看了,基本上已经到底了,这里就是ClassReader如何去解析了。
好,现在我们蓦然回首,你会发现,这条路崎岖不堪。
跑题了,我们言归正传。
32、
当这个类被解析完毕后,被visitor装载,而在解析的下面几行代码,将visitor赋值给SimpleMetadaReader对象的两个属性里,所以我们在第28的时候,这个metadataReader是SimpleMetadataReader对象,且两个成员属性是一样的,都是这个visitor,它是AnnotationMetadataReadingVisitor类的对象,装载着这个resource对应class的所有信息,尤其是注解(因为我们本身就是基于注解开发)。
33、
我们看到第28,我们获取到metadaReader后会对它进行判定,判断是否为候选组件,也就是说有没有注解(当然,Spring做了比这更多的事情)。
在这里,this.excludeFilters是一个List,包含是三个TypeFilter,其中有一个是我们的启动类DemoApplication,因为在beanFactory已经注册过了,所以这里不需要再重复注册。
this.includeFilters是包含的规则,它也是一个List,里面两个TypeFilter,分别是Component和ManageBean,前者是Spring提供的,后者是Java自带的。也就是说只要在扫描包内类上标志这两个注解,都会提供给IOC注册。
但是问题来了,我们平时用的@Controller,@RestController,@Service,@Repository,@Configuration等等这些注解也不是@Component啊?里面怎么搞?
别着急,我们先来看一下Controller注解
1 2 3 4 5 |
@Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented @Component public @interface Controller { |
1 2 3 4 5 |
@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Indexed public @interface Component { |
Spring的@Component上有@Indexed,代表可被继承,所以@Controller继承@Component,自然而然只需要判断@Component就可以达到Spring想要的效果了。
34、我们回到26,我们得到了candidates,它是需要注册的bean集合,后续进行遍历。
这里,我们先思考一个问题,我们知道IOC容器是一个Map,
1 2 |
private final Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap(256); |
如果Bean名称重复,它是如何解决的?
在第26,余下的部分代码没有截图,这里奉上
35、在这个方法里,Spring解决bean名称冲突的问题
36、Spring到底如何比较两个名称一致的bean?
里面进行三个判断,如果满足其中一个,则会返回true,然后在外面一层,则不会抛异常,返回false,然后跳过该bean的注册。
第一个:
beanDefinition是否是ScannedGenericBeanDefinition。细心的读者会发现,我们目前所生成的BeanDefinition就是ScannedGenericBeanDefinition,为什么,请看第28,在这里,有这样一行代码:
1 |
ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader); |
如果是Spring自己扫描的,则就是一个这样的Bean,如果是其他方式注册的,就可能不属于ScannedGenericBeanDefinition
false,再取反,true,进入方法,返回false,跳过本次注册。
第二个第三个:比较source和比较两个对象。
37、在上一步说了Spring如何解决两个对象bean名称一致的问题,以及如何比较旧bean和新bean。
我们看第35的图发现,如果bean不一致,但是名称一致,会抛一个异常,我们来做一个【测试】
1 2 3 |
@RestController public class DemoController { } |
1 2 3 |
@Service(value = "demoController") public class DemoUtil { } |
38、我们继续看第34这张图的代码,当Spring判断为可进行注册的bean,则往下走,BeanDefinitionHolder封装着BeanDefinition,然后进行注册。
在DefaultListableBeanFactory中,还有一个重要的参数,allowBeanDefinitionOverriding:从名称上看代表允许覆盖BeanDefinition,没错,就是这样的。
首先,它的默认值是true,代表可以可以覆盖。如果为true,发现两个名称一致的bean,后者会把前者覆盖,如果为false,发现两个名称一致的bean,则会抛一个异常:BeanDefinitionOverrideException。
这个时候可能就会问,之前不是已经筛选过了吗?为什么这里还需要做一层?之前是注册(register),这次是IOC容器(beanDefinitionMap)。
Spring源码不是一朝一夕就能读完的,本文篇幅有些许的长,后续的源码分析中会小步快跑,感谢阅读。
你的关注,是我的荣幸
你要做多大的事情,就该承受多大的压力。
似水流年
看起来 好高端
228
如此好文章一定要留下名啊
27wy.cn
@228 哈哈~相互学习相互学习,不对的地方多指点~
asdasd
dahnsdadas
Elainejer
移动互联时代,展会营销还仅仅局限在实体展台现场获客已经远远不够。有远见的企业纷纷探索新技术,打造线上线下联动式展会营销,来倍增营销引流、获客回报。
芃酷信息推出革命性3D智慧展览云,基于微信打造与现实展台呼应的真3D线上展厅,并集成了支持线上到线下的一体化展会营销最佳实践,帮助企业创新展前预热、展中引流与互动、展后延展。
登陆 http://www.fancy-xr.com/ 或致电 400-157-1327 了解全球500强企业如何通过芃酷3D慧展云创新展会营销!
27wy.cn
@Elainejer 第一个中文广告,准了