带你一行一行分析SpringBoot原码解析
一.说明
1.本次源码解析是基于2.3.3.RELEASE版本的
2.本文主要分析Spring的自动配置
二.原码分析
1.创建一个普通的springboot项目如下:
只有一个配置文件和一个启动类。
配置文件中只配了一个redis,配置其他组件都行,这里以redis为例展开说明自动注入。
2.打开启动类
对于springboot来说,最强大的地方就是没有复杂的配置文件,创建springboot后只有一个启动类,那就从启动类入手,Ctrl + 左键 点击
@SpringBootApplication 注解,进入如下所示:
@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited @SpringBootConfiguration @EnableAutoConfiguration @ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class), @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) }) public @interface SpringBootApplication {
可以看到,
@SpringBootApplication注解上面又有七个注解,其实,这种包含了很多注解的注解就是组合注解。前四个是元注解(在JDK 1.5中提供了4个标准的用来对注解类型进行注解的注解类,我们称之为 meta-annotation(元注解)),我们先分析前四个注解:
-
@Target : 描述注解的使用范围,括号里有个ElementType.TYPE,点进去,如下:
public enum ElementType { /** Class, interface (including annotation type), or enum declaration */ TYPE, /** Field declaration (includes enum constants) */ FIELD, /** Method declaration */ METHOD, /** Formal parameter declaration */ PARAMETER, /** Constructor declaration */ CONSTRUCTOR, /** Local variable declaration */ LOCAL_VARIABLE, /** Annotation type declaration */ ANNOTATION_TYPE, /** Package declaration */ PACKAGE, /** * Type parameter declaration * * @since 1.8 */ TYPE_PARAMETER, /** * Use of a type * * @since 1.8 */ TYPE_USE }
Target注解用来说明那些被它所注解的注解类可修饰的对象范围:注解可以用于修饰 packages、types(类、接口、枚举、注解类)、类成员(方法、构造方法、成员变量、枚举值)、方法参数和本地变量(如循环变量、catch参数),在定义注解类时使用了@Target 能够更加清晰的知道它能够被用来修饰哪些对象,它的取值范围定义在ElementType 枚举中。
-
@Retention 同理,点击去 RetentionPolicy类,它的主要作用是:用来限定那些被它所注解的注解类在注解到其他类上以后,可被保留到何时,一共有三种策略,定义在RetentionPolicy枚举中。
public enum RetentionPolicy { SOURCE, // 源文件保留 CLASS, // 编译期保留,默认值 RUNTIME // 运行期保留,可通过反射去获取注解信息
- @Documented ,它的作用是:描述在使用 javadoc 工具为类生成帮助文档时是否要保留其注解信息
-
@Inherited,这是个比较重要的注解,它表示注解会被子类自动继承。
接下来就剩三个注解了,其中@EnableAutoConfiguration是最核心的注解,我们放到最后面说,先说其他两个注解:
- @SpringBootConfiguration : 它的作用就是:继承自@Configuration,二者功能也一致,标注当前类是配置类, 并会将当前类内声明的一个或多个以@Bean注解标记的方法的实例纳入到spring容器中,并且实例名就是方法名。可以点击去看下,如下:其实,@Configuration和 @SpringBootConfiguration 是具有相同功能的。
@Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented @Configuration public @interface SpringBootConfiguration { @AliasFor( annotation = Configuration.class ) boolean proxyBeanMethods() default true; }
-
@ComponentScan,也是比较复杂的,点进去,如下图:
这也就是为什么,所有的代码都要放在启动类所在的包及子包里面。
接下来就是最重要的注解了,@EnableAutoConfiguration ,先点进去,如下:
@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited @AutoConfigurationPackage @Import(AutoConfigurationImportSelector.class) public @interface EnableAutoConfiguration { String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration"; /** * Exclude specific auto-configuration classes such that they will never be applied. * @return the classes to exclude */ Class<?>[] exclude() default {}; /** * Exclude specific auto-configuration class names such that they will never be * applied. * @return the class names to exclude * @since 1.3.0 */ String[] excludeName() default {}; }
前四个注解就不说了,
@AutoConfigurationPackage的作用是导入自定义的类的,
@Import(AutoConfigurationImportSelector.class)是导入框架本身的一些类的
在这里,这两个注解仅仅是找到需要导入的类,并没有实例化,实例化依然需要spring容器去做。
先看@AutoConfigurationPackage,点进去,如下图:
有个Registrar,点进去,如下图:
从方法名可以看出是个注册的方法,打上断点,启动,当代码停住后,Alt + F8,查看new PackageImports(metadata).getPackageNames().toArray(new String[0])的值,如下图:
从上图可以看出,这个方法其实就是就是把自定义包下的类扫描并注册到容器中。
再看@Import(AutoConfigurationImportSelector.class),它是注入框架本身使用的和自动配置相关的类,点击去,找到getCandidateConfigurations方法。
注释的意思的是;找到可能的自动配置的类名,进入loadFactoryNames方法,
继续进入loadSpringFactories,
图中有个FACTORIES_RESOURCE_LOCATION,点进去,
原来是一个文件,也就是说,springboot框架本省要导入的类就在这个文件中,那么这个spring.factories在哪呢?
接下来打开pom文件,找到spring-boot-starter-web,点进去,
找到spring-boot-starter,点进去
就能发现有一个spring-boot-autoconfigure,表示自动配置,如图。
打开pom文件引入的包,在工程窗口,如下图:
找到:spring-boot-autoconfigure,如下图:
发现有一个spring.properties文件,打开:
发现,类似于redis这种组件所对应的类就在这个文件中。
到此为止,springboot只是将可能用到的类加载进来了,但是仅仅知识加载了类名,怎么能根据我们在yml文件中的配置来使用呢?也就是说,springboot怎么能知道我们要使用哪些类,不使用哪些类呢,比如我们现在要是用redis,首先我们需要在yml文件中配置redis的连接信息,如下图:
想到这里,我们就应该想到,springboo肯定是通过加载这个yml文件开读取的,接下来跟原码:
打开启动类,进入run方法,只要是run方法,就一直往下走,直到org.springframework.context.ConfigurableApplicationContext这个方法,如下图:
进入:prepareEnvironment方法这个方法表示环境的准备,如下图;
进入environmentPrepared,表示添加监听:
进入environmentPrepared方法,表示初始化:继续往下走,方法顺序为
multicastEvent->
multicastEvent->
multicastEvent->
invokeListener(listener, event))->
doInvokeListener——>如图:
进入onApplicationEvent接口的配置文件实现类 ConfigFileApplicationListener
进入实现方法:
再按下顺序往下走:
onApplicationEnvironmentPreparedEvent ->
postProcessEnvironment->如图:
再进入postProcessEnvironment的实现类:ConfigFileApplicationListener
实现方法如下:
再进入addPropertySources方法:
在进入load方法:
再进入load方法:
再进入load方法:
点击getFileExtensions()进入接口,
这个接口有两个实现类,分别是properties和yml,太熟悉了,这不就是配置文件吗,这个接口其实就是配置文件的扩展名,
重新进入load方法:
进入loadForFileExtension方法:
再进入load方法:
再进入loadDocuments方法:
再进入load接口方法:
发现又有两个实现类,进入yml的实现类:
····················终于完了,这是最后一个方法了,在犯法最后一行打上断点:启动:
把配置文件中的所有信息都加载进来了。
但是。。。,这也只是把配置文件加载进来了,那么redis怎么起作用呢?
打开前面说的spring.properties文件找到 RedisAutoConfiguration类,进入这个类,如下
要使redis起作用,图中的四个注解不可少,进入注解括号中的RedisProperties类:
发现,又是熟悉的感觉,这不就对饮配置文件中的属性吗?
再看RedisTemplate<Object, Object> template = new RedisTemplate<>(); 这行代码,直接就new了一个对象,这就是创建了一个RedisTemplate,因此,我们就可以使用RedisTemplate来操作redis了。
所以springboot,也就是通过这种方式进行类的自动装配的。