27. 从零开始学springboot-运行原理

前言

SpringBoot作为目前最流行的 Java 开发框架,秉承“约定优于配置”原则,大大简化了 Spring MVC 繁琐的 XML 文件配置,基本实现零配置启动项目。
本文基于SpringBoot 2.1.4.RELEASE 版本,通过一步步追踪代码,详细探求 SpringBoot的运行原理。

图解

整个springboot运行流程可以由如下图表示(图来自互联网,版权归原作者所有)


1.png

入口类

首先让我们看一下最简单的 SpringBoot入口类

@SpringBootApplication
public class DemoApplication {
    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }
}

入口类的要求是最顶层包下面第一个含有 main 方法的类,使用注解 @SpringBootApplication 来启用SpringBoot特性,使用 SpringApplication.run 方法来启动 Spring Boot 项目。

首先我们来看下@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注解实际上是SpringBoot提供的一个复合注解,其中最重要的三个注解分别是:

  • @SpringBootConfiguration
  • @EnableAutoConfiguration
  • @ComponentScan

事实上,我们可以把入口类上的@SpringBootApplication这个注解替换为以上三个注解,效果是一样的。同学们可以自己试试。

下面我们分别阐述这三个注解都是干什么的。

@SpringBootConfiguration

@SpringBootConfiguration
追踪其实现代码

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
public @interface SpringBootConfiguration {

}

终于看到熟悉的@Configuration注解了,在Spring中,作用是配置Spring容器,也即 JavaConfig 形式的 Spring IoC 容器的配置类所使用。
这说明 @SpringBootConfiguration 也是来源于 @Configuration,二者功能都是将当前类标注为配置类,并将当前类里以 @Bean 注解标记的方法的实例注入到srping容器中,实例名即为方法名。

@EnableAutoConfiguration

@EnableAutoConfiguration
实现代码

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
        //....省略
}

@EnableAutoConfiguration注解作用是启用自动配置,可以帮助 SpringBoot 应用,将所有符合条件的 @Configuration 配置都加载到当前 IoC 容器之中

@AutoConfigurationPackage自动配置包,将SpringBootApplication主配置类所在包以及子包的所有子类扫描到spring容器

@Import(AutoConfigurationImportSelector.class)导入组件开启自动配置类导包的选择器。
我们可以追踪AutoConfigurationImportSelector这个类看看其实现

    @Override
    public String[] selectImports(AnnotationMetadata annotationMetadata) {
        if (!isEnabled(annotationMetadata)) {
            return NO_IMPORTS;
        }
        AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
                .loadMetadata(this.beanClassLoader);
        AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(
                autoConfigurationMetadata, annotationMetadata);
        return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
    }

对应的getAutoConfigurationEntry()实现

protected AutoConfigurationEntry getAutoConfigurationEntry(
            AutoConfigurationMetadata autoConfigurationMetadata,
            AnnotationMetadata annotationMetadata) {
        if (!isEnabled(annotationMetadata)) {
            return EMPTY_ENTRY;
        }
        AnnotationAttributes attributes = getAttributes(annotationMetadata);
        List configurations = getCandidateConfigurations(annotationMetadata,
                attributes);
        configurations = removeDuplicates(configurations);
        Set exclusions = getExclusions(annotationMetadata, attributes);
        checkExcludedClasses(configurations, exclusions);
        configurations.removeAll(exclusions);
        configurations = filter(configurations, autoConfigurationMetadata);
        fireAutoConfigurationImportEvents(configurations, exclusions);
        return new AutoConfigurationEntry(configurations, exclusions);
    }

getCandidateConfigurations()的实现

    protected List getCandidateConfigurations(AnnotationMetadata metadata,
            AnnotationAttributes attributes) {
        List configurations = SpringFactoriesLoader.loadFactoryNames(
                getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader());
        Assert.notEmpty(configurations,
                "No auto configuration classes found in META-INF/spring.factories. If you "
                        + "are using a custom packaging, make sure that file is correct.");
        return configurations;
    }

可以看出selectImports() 方法最终是通过调用SpringCore 包里 SpringFactoriesLoader 类的 loadFactoryNames()方法,读取了 ClassPath 下面的 META-INF/spring.factories 文件来获取所有导出类。而spring.factories 文件里关于 EnableAutoConfiguration 的配置其实就是一个键值对结构,我们看下对应的spring.factories文件内容

# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\
org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration,\
//.......省略

最后,我们概括下,@EnableAutoConfiguration注解实现了从 ClassPath下扫描所有的 META-INF/spring.factories 配置文件,并将spring.factories 文件中的 EnableAutoConfiguration 对应的配置项通过反射机制实例化为对应标注了 @Configuration 的形式的IoC容器配置类,然后注入IoC容器。

@ComponentScan

最后我们看下@ComponentScan注解
@ComponentScan 对应于XML配置形式中的 ,用于将一些标注了特定注解的bean定义批量采集注册到Spring的IoC容器之中,这些特定的注解大致包括:
@Controller
@Entity
@Component
@Service
@Repository
等等

对于该注解,还可以通过 basePackages 属性来更细粒度的控制该注解的自动扫描范围,比如:

@ComponentScan(basePackages = {"com.mrcoder.controller","com.mrcoder.entity"})

SpringApplication实例初始化过程分析

追踪入口类中的run方法

public static ConfigurableApplicationContext run(Class primarySource,
        String... args) {
    return run(new Class[] { primarySource }, args);
}

/**
  * 第一个参数 primarySource:加载的主要资源类
  * 第二个参数 args:传递给应用的应用参数
  */
public static ConfigurableApplicationContext run(Class[] primarySources,
        String[] args) {
    return new SpringApplication(primarySources).run(args);
}

可以看出,run方法先用主要资源类primarySources 来实例化一个 SpringApplication 对象,再调用这个SpringApplication对象的 run 方法。
进入SpringApplication()构造方法

public SpringApplication(Class... primarySources) {
    this(null, primarySources);
}

然后进入SpringApplication构造函数

@SuppressWarnings({ "unchecked", "rawtypes" })
public SpringApplication(ResourceLoader resourceLoader, Class... primarySources) {
    //1.资源初始化资源加载器为 null
    this.resourceLoader = resourceLoader;
    //2.断言主要加载资源类不能为 null,否则报错
    Assert.notNull(primarySources, "PrimarySources must not be null");
    //3.初始化主要加载资源类集合并去重
    this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
    //4.推断当前 WEB 应用类型
    this.webApplicationType = WebApplicationType.deduceFromClasspath();
    //5.设置应用上线文初始化器
    setInitializers((Collection) getSpringFactoriesInstances(
            ApplicationContextInitializer.class));
    //6.设置监听器            
    setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
    //7.推断主入口应用类
    this.mainApplicationClass = deduceMainApplicationClass();
}

对于1-3没有什么好讲的,大家都能看懂,
我们看看后几步。
第4步
这个就是根据类路径下是否有对应项目类型的类推断出不同的应用类型,进入该类可以看到

package org.springframework.boot;
import org.springframework.util.ClassUtils;
public enum WebApplicationType {
    //非 WEB 项目
    NONE,
    //SERVLET WEB 项目
    SERVLET,
    //响应式 WEB 项目
    REACTIVE;
    
    //省略......

第5步

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

ApplicationContextInitializer该类是干啥的呢?进入该类可以看到如下定义

package org.springframework.context;
public interface ApplicationContextInitializer {
    void initialize(C applicationContext);
}

用来初始化指定的 Spring 应用上下文,如注册属性资源、激活 Profiles 等。
接下来我们来分析下第5步使用的setInitializers()方法

public void setInitializers(
        Collection> initializers) {
    this.initializers = new ArrayList<>();
    this.initializers.addAll(initializers);
}

分析实现源码,可以看出,其实就是初始化一个 ApplicationContextInitializer 应用上下文初始化器实例的集合。

接下来看下getSpringFactoriesInstances()的方法实现

private  Collection getSpringFactoriesInstances(Class type) {
    return getSpringFactoriesInstances(type, new Class[] {});
}

private  Collection getSpringFactoriesInstances(Class type,
        Class[] parameterTypes, Object... args) {
    //1.获取当前线程上下文类加载器
    ClassLoader classLoader = getClassLoader();
    //2.获取 ApplicationContextInitializer 的实例名称集合并去重
    Set names = new LinkedHashSet<>(
            //3.loadFactoryNames根据类路径下的 META-INF/spring.factories 文件解析并获取 ApplicationContextInitializer 接口的所有配置的类路径名称。
            SpringFactoriesLoader.loadFactoryNames(type, classLoader));
            //4.根据以上类路径创建初始化器实例列表
    List instances = createSpringFactoriesInstances(type, parameterTypes,
            classLoader, args, names);
    //5.初始化器实例列表排序
    AnnotationAwareOrderComparator.sort(instances);
    //6.返回初始化器实例列表
    return instances;
}

在看第6步,设置监听器,可以看到使用了ApplicationListener这个类,

package org.springframework.context;
import java.util.EventListener;
@FunctionalInterface
public interface ApplicationListener extends EventListener {
    void onApplicationEvent(E event);
}

以上代码可知,这个接口继承了 JDK 的 java.util.EventListener 接口,实现了观察者模式,它一般用来定义感兴趣的事件类型,事件类型限定于 ApplicationEvent 的子类,这同样继承了 JDK 的 java.util.EventObject 接口。

设置监听器和设置初始化器调用的方法是一样的,只是传入的类型不一样,设置监听器的接口类型为getSpringFactoriesInstances,对应的spring-boot-autoconfigure-2.1.4.RELEASE.jar!/META-INF/spring.factories 文件配置内容请见下方

# Application Listeners
org.springframework.context.ApplicationListener=\
org.springframework.boot.autoconfigure.BackgroundPreinitializer

可以看出目前只有一个 BackgroundPreinitializer 监听器。

第7步推断主入口应用类

private Class deduceMainApplicationClass() {
    try {
        StackTraceElement[] stackTrace = new RuntimeException().getStackTrace();
        for (StackTraceElement stackTraceElement : stackTrace) {
            if ("main".equals(stackTraceElement.getMethodName())) {
                return Class.forName(stackTraceElement.getClassName());
            }
        }
    }
    catch (ClassNotFoundException ex) {
        // Swallow and continue
    }
    return null;
}

这个推断入口应用类的方式有点特别,通过构造一个运行时异常,再遍历异常栈中的方法名,获取方法名为 main 的栈帧,从来得到入口类的名字再返回该类。

欢迎关注我

你可能感兴趣的:(27. 从零开始学springboot-运行原理)