5w字SpringBoot源码分析

Posted 结构化思维wz

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了5w字SpringBoot源码分析相关的知识,希望对你有一定的参考价值。

Spring Boot源码分析

文章目录

SpringBoot版本-2.X

启动类分析

@SpringBootApplication 标注在某个类上说明这个类是 SpringBoot 的主配置类, SpringBoot 就应该运行这个类的main方法来启动 SpringBoot 应用;

@SpringBootApplication
public class StudyProjectApplication 

    public static void main(String[] args) 
        SpringApplication.run(StudyProjectApplication.class, args);
    


当我们点进去后发现:这是一个集成注解,为什么它能集成这么多的注解的功能呢?是在于它上面的 @Inherited 注解, @Inherited 表示自动继承注解类型。

@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 

    /**
     * 自动装配要排除的类,功能来自于 @EnableAutoConfiguration
     */
    @AliasFor(annotation = EnableAutoConfiguration.class)
    Class<?>[] exclude() default ;

    /**
     *  自动装配要排除的类名,功能来自于 @EnableAutoConfiguration
     */
    @AliasFor(annotation = EnableAutoConfiguration.class)
    String[] excludeName() default ;

    /**
     * 配置扫描的包,功能来自于 @ComponentScan
     */
    @AliasFor(annotation = ComponentScan.class, attribute = "basePackages")
    String[] scanBasePackages() default ;

    /**
     * 配置扫描的class,该class所在的包都会被扫描,功能来自于 @ComponentScan
     */
    @AliasFor(annotation = ComponentScan.class, attribute = "basePackageClasses")
    Class<?>[] scanBasePackageClasses() default ;

    /**
     * 是否启用 @Bean 方法代理,功能来自于 @Configuration
     */
    @AliasFor(annotation = Configuration.class)
    boolean proxyBeanMethods() default true;


@SpringBootApplication 是一个组合注解,包含了 @SpringBootConfiguration@EnableAutoConfiguration@ComponentScan 三个注解的功能;@SpringBootApplication 中也提供了一些配置属性,而这些属性来自于以上三个注解。

@SpringBootConfiguration

我们点进注解发现:它是 springboot 的配置类,标注在某个类上,表示这是一个 springboot的配置类。

这个注解比较简单,上面标记了 @Configuration,然后是一个属性 proxyBeanMethods(),它来自于 @Configuration。因此,@SpringBootConfiguration 并没有做什么,仅仅只是将 @Configuration 使用了 @Configuration 的功能。

@EnableAutoConfiguration

@EnableAutoConfiguration 主要 用来开启自动装配功能,在第二章SpringBoot自动装配原理中详细说明。

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
// 将被 @SpringBootApplication 标记的类所在的包,包装成 BasePackages,然后注册到 spring 容器中;
@AutoConfigurationPackage
// 将当前项目支持的自动配置类添加到 spring 容器中;
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration 

    String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";

    /**
     * 可自行定义排除自动装配的类
     */
    Class<?>[] exclude() default ;

    /**
     * 可自行定义排除自动装配的类名
     */
    String[] excludeName() default ;


从代码中可以看到,

  1. 该注解组合了 @AutoConfigurationPackage 注解的功能,该注解用来指定自动装配的包;
  2. 该注解通过 @Import 注解引入了一个类 AutoConfigurationImportSelector,这个类是自动装配的关键;
  3. 该注解提供了两个配置,用来排除指定的自动装配类,可以根据类来排除 (Class 对象),也可以根据类名 (包名.类名) 排除。

@ComponentScan

这个注解想必大家已经很熟悉了,它指定了包扫描路径,如果不指定,就扫描所在类的包。

@ComponentScan(excludeFilters =  @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
      @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) )

TypeExcludeFilter: 这个类表示在进行包扫描时,可以排除一些类。

AutoConfigurationExcludeFilter:用来排除自动配置类,也就是说,spring 在进行包扫描时,不会扫描自动配置类。核心源码如下:

public class AutoConfigurationExcludeFilter implements TypeFilter, BeanClassLoaderAware 

    private ClassLoader beanClassLoader;

    private volatile List<String> autoConfigurations;

    @Override
    public void setBeanClassLoader(ClassLoader beanClassLoader) 
        this.beanClassLoader = beanClassLoader;
    

    @Override
    public boolean match(MetadataReader metadataReader, 
            MetadataReaderFactory metadataReaderFactory) throws IOException 
        // isConfiguration(...):当前类是否被 @Configuration 标记
        // isAutoConfiguration(...):当前类是否为自动配置类
        return isConfiguration(metadataReader) && isAutoConfiguration(metadataReader);
    

    private boolean isConfiguration(MetadataReader metadataReader) 
        return metadataReader.getAnnotationMetadata().isAnnotated(Configuration.class.getName());
    

    private boolean isAutoConfiguration(MetadataReader metadataReader) 
        // 获取所有的自动配置类,然后判断当前类是否存在于其中
        return getAutoConfigurations().contains(metadataReader.getClassMetadata().getClassName());
    

    protected List<String> getAutoConfigurations() 
        if (this.autoConfigurations == null) 
            this.autoConfigurations = SpringFactoriesLoader
                    .loadFactoryNames(EnableAutoConfiguration.class, this.beanClassLoader);
        
        return this.autoConfigurations;
    


我们主要看 match(...) 方法,它的匹配的类为:

  1. @Configuration 标记;
  2. 是自动配置类。(从 isAutoConfiguration(...) 可以看到,在判断是否为自动配置类上,springboot 先使用 SpringFactoriesLoader 加载所有配置类,然后再判断传入的类是否为其中之一。)

满足以上两个条件,spring 就不会对其进行扫描处理。

SpringBoot启动流程

@SpringBootApplication
public class StudyProjectApplication 

    public static void main(String[] args) 
        SpringApplication.run(StudyProjectApplication.class, args);
    


我们进入这个run方法:

public class SpringApplication 
    ...
    // primarySource 就是我们传入的 StudyProjectApplication.class,
    // args 就是 main() 方法的参数
    public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) 
        // 将 primarySource 包装成数组,继续调用 run(...) 方法
        return run(new Class<?>[]  primarySource , args);
    

    // primarySources 就是我们传入的 StudyProjectApplication.class 包装成的数组,
    // args 就是 main() 方法的参数
    public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) 
        // 核心代码
        return new SpringApplication(primarySources).run(args);
    
    ...

分析关键代码: return new SpringApplication(primarySources).run(args);

这块代码需要拆开来看,可以拆成以下两部分:

  • 构造方法:SpringApplication#SpringApplication(Class<?>...)
  • 实例方法:SpringApplication#run(String...)

看来,这两个方法就是 springboot 的启动所有流程了,接下来我们就来分析这两个方法。

1. 准备SpringApplication

创建SpringApplication对象

 /**
     * 这里就最终调用的构造方法了
     * resourceLoader 为 null
     * primarySources 为 StudyProjectApplication.class
     */
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) 
        // 1. 将传入的resourceLoader设置到成员变量,这里的值为null
		this.resourceLoader = resourceLoader;
		Assert.notNull(primarySources, "PrimarySources must not be null");
        // 2. 将传入的primarySources设置到成员变量,这里的值为 StudyProjectApplication.class
		this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
        // 3. 当前的 web 应用类型,REACTIVE,NONE,SERVLET
		this.webApplicationType = WebApplicationType.deduceFromClasspath();
    
         // 4. 设置初始化器,getSpringFactoriesInstances:从 META-INF/spring.factories 中获取配置
		setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
         // 5. 设置监听器,getSpringFactoriesInstances:从 META-INF/spring.factories 中获取配置
		setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
         // 6. 返回包含main()方法的class
		this.mainApplicationClass = deduceMainApplicationClass();
	

获取当前 web 应用类型

this.webApplicationType = WebApplicationType.deduceFromClasspath();

WebApplicationType.deduceFromClasspath() 方法是用来推断当前项目是什么类型的,代码如下:

public enum WebApplicationType 
    // 不是 web 应用
    NONE,

    // servlet 类型的 web 应用
    SERVLET,

    // reactive 类型的 web 应用
    REACTIVE;

    ...

    private static final String[] SERVLET_INDICATOR_CLASSES =  
            "javax.servlet.Servlet",
            "org.springframework.web.context.ConfigurableWebApplicationContext" ;

    private static final String WEBMVC_INDICATOR_CLASS 
            = "org.springframework.web.servlet.DispatcherServlet";

    private static final String WEBFLUX_INDICATOR_CLASS 
            = "org.springframework.web.reactive.DispatcherHandler";

    private static final String JERSEY_INDICATOR_CLASS 
            = "org.glassfish.jersey.servlet.ServletContainer";

    static WebApplicationType deduceFromClasspath() 
        // classpath 中仅存在 WEBFLUX 相关类
        if (ClassUtils.isPresent(WEBFLUX_INDICATOR_CLASS, null) 
                && !ClassUtils.isPresent(WEBMVC_INDICATOR_CLASS, null)
                && !ClassUtils.isPresent(JERSEY_INDICATOR_CLASS, null)) 
            return WebApplicationType.REACTIVE;
        
        // classpath 不存在 SERVLET 相关类
        for (String className : SERVLET_INDICATOR_CLASSES) 
            if (!ClassUtils.isPresent(className, null)) 
                return WebApplicationType.NONE;
            
        
        // 默认 web 类型为 SERVLET
        // 也就是说,同时存在 WEBFLUX 与 SERVLET 相关类,最终返回的是 SERVLET
        return WebApplicationType.SERVLET;
    

    ...

可以看到,springboot 定义了三种项目类型:NONE(不是 web 应用)、SERVLET(servlet 类型的 web 应用)、REACTIVE(reactive 类型的 web 应用),WebApplicationType.deduceFromClasspath() 的执行流程如下:

  1. 如果 classpath 中仅存在 WEBFLUX 相关类,则表明当前项目是 reactive 类型的 web 应用,返回;
  2. 如果 classpath 中不存在 SERVLET 相关类,则表明当前项目不是 web 应用,返回;
  3. 如果以上条件都不满足,则表明当前项目是 servlet 类型的 web 应用。

由于 demo 引用了 spring-boot-starter-web 相关依赖,因此当前项目是 servlet 类型的 web 应用。

设置初始化器

setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));

这行代码分为两部分:

  • 获取 ApplicationContextInitializergetSpringFactoriesInstances(ApplicationContextInitializer.class)
  • 设置初始化器:setInitializers(...)
public class SpringApplication 
    ...

    // type 为 ApplicationContextInitializer.class
    private <T> Collection<T> getSpringFactoriesInstances(Class<T> type) 
        return getSpringFactoriesInstances(type, new Class<?>[] );
    


    /**
     * type 为 ApplicationContextInitializer.class
     * parameterTypes 为 ew Class<?>[] 
     * args 为 null
     */
    private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) 
        ClassLoader classLoader = getClassLoader();
        // 从 META-INF/spring.factories 加载内容
        Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
        // 实例化,使用的反射操作
        List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
        // 排序,比较的是 @Order 注解,或实现的 Orderd 接口
        AnnotationAwareOrderComparator.sort(instances);
        return instances;
    
    ...

先从 META-INF/spring.factories 获取内容,然后使用反射进行实例化,进行排序后再返回。那么最终会有多少个 ApplicationContextInitializer 加载进来呢?

通过调试,发现一共有 7 个:对这 7 个 ApplicationContextInitializer,说明如下:

  • ConfigurationWarningsApplicationContextInitializer:报告 IOC 容器的一些常见的错误配置
  • ContextIdApplicationContextInitializer:设置 Spring 应用上下文的 ID
  • DelegatingApplicationContextInitializer:加载 application.propertiescontext.initializer.classes 配置的类
  • RSocketPortInfoApplicationContextInitializer:将 RSocketServer 实际使用的监听端口写入到 Environment 环境属性中
  • ServerPortInfoApplicationContextInitializer:将内置 servlet 容器实际使用的监听端口写入到 Environment 环境属性中
  • SharedMetadataReaderFactoryContextInitializer:创建一个 SpringBootConfigurationClassPostProcessor 共用的 CachingMetadataReaderFactory 对象
  • ConditionEvaluationReportLoggingListener:将 ConditionEvaluationReport 写入日志

获取到 ApplicationContextInitializer,我们再来看看 setInitializers(...) 方法:

public class SpringApplication 
    ...
    public void setInitializers(Collection<? extends ApplicationContextInitializer<?>> initializers) 
        this.initializers = new ArrayList<>(initializers);
    
    ...

这是一个标准的 setter 方法,所做的就只是设置成员变量。

注意这里会把项目中所有的spring.factories中的key都存到内存里,以便后面快速找到对应的key

设置监听器

setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));

从形式上看,同 Initializer 一样,也是先从 META-INF/spring.factories 中加载 ApplicationListener,然后添加到成员变量中,这里我们直接看能获取到哪些 listener

这些Listener的作用是:

  • ClearCachesApplicationListener:应用上下文加载完成后对缓存做清除工作
  • ParentContextCloserApplicationListener:监听双亲应用上下文的关闭事件并往自己的子应用上下文中传播
  • CloudFoundryVcapEnvironmentPostProcessor:对 CloudFoundry 提供支持
  • FileEncodingApplicationListener:检测系统文件编码与应用环境编码是否一致,如果系统文件编码和应用环境的编码不同则终止应用启动
  • AnsiOutputApplicationListener:根据 spring.output.ansi.enabled 参数配置 AnsiOutput
  • ConfigFileApplicationListener:从常见的那些约定的位置读取配置文件
  • DelegatingApplicationListener:监听到事件后转发给 application.properties 中配置的 context.listener.classes 的监听器
  • ClasspathLoggingApplicationListener:对环境就绪事件 ApplicationEnvironmentPreparedEvent 和应用失败事件 ApplicationFailedEvent 做出响应
  • LoggingApplicationListener:配置 LoggingSystem,使用 logging.config 环境变量指定的配置或者缺省配置
  • LiquibaseServiceLocatorApplicationListener:使用一个可以和 SpringBoot 可执行 jar 包配合工作的版本替换 LiquibaseServiceLocator
  • BackgroundPreinitializer:使用一个后台线程尽早触发一些耗时的初始化任务

再来看看 SpringApplication#setListeners

public void setListeners(Collection<? extends ApplicationListener<?>> listeners) 
    this.listeners = new ArrayList<>(listeners);

推导主应用程序类

this.mainApplicationClass = deduceMainApplicationClass();

所谓主类,就是包含 main(String[]),也就是当前 spring 应用的启动类,SpringApplication#deduceMainApplicationClass 代码如下:

private Class<?> deduceMainApplicationClass() 
    try 
        // 获取调用栈
        StackTraceElement[] stackTrace = new RuntimeException().getStackTrace();
        // 遍历调用栈,找 到main方法
        for (StackTraceElement stackTraceElement : stackTrace) 
            if ("main".equals(stackTraceElement.getMethodName())RabbitMQ从概念到使用从Docker安装到RabbitMQ整合Springboot1.5w字保姆级教学

RabbitMQ从概念到使用从Docker安装到RabbitMQ整合Springboot1.5w字最全教学

01.搭建 springboot 源码分析环境

01.搭建 springboot 源码分析环境

Springboot源码分析之项目结构

5W字穿透 ELK(史上最全):elasticsearch +logstash+kibana