带你一行一行分析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,也就是通过这种方式进行类的自动装配的。

原文地址:https://www.cnblogs.com/xyzyj/p/13635280.html