springboot-源码解析

简介

springboot源码介绍。

启动流程相关的源码

springboot只是提供了一种快速使用spring的方式,它并没有增加spring的功能,这里介绍springboot是如何启动spring的。

springboot在spring容器的启动之外,额外做了一些工作,方便用户使用spring:

  • springboot的启动过程中,会实例化初始化器、监听器,它们是springboot特有的,用于集成第三方框架,基于SPI机制。
  • springboot会默认开启自动装配,自动装配相关的bean会和特定的条件相关联,如果条件满足,就把bean注入到spring容器中。
  • springboot会把web应用部署在内嵌的服务器上,通常是tomcat,所以springboot项目可以独立部署、独立运行。

这三个点应该是springboot启动过程中最重要的事情,具体流程随后讲解。

相关组件

@SpringBootApplication

被@SpringBootApplication注解标注的类是springboot项目的引导类,通过引导类来启动springboot项目

@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 {
}

SpringApplication

启动类,存储了springboot的启动逻辑

public class SpringApplication {

    // 主类,main方法所在的类,也就是引导类
	private Class<?> mainApplicationClass;

    // 应用类型,可选值有 普通web应用、reactor类型的web应用
	private WebApplicationType webApplicationType;
 
    // 初始化器的集合
	private List<ApplicationContextInitializer<?>> initializers;

    // 监听器的集合
	private List<ApplicationListener<?>> listeners;
}

springboot使用的spring容器

AnnotationConfigServletWebServerApplicationContext,如果当前springboot应用是一个普通的web应用的话,就使用这个spring容器。

AnnotationConfigServletWebServerApplicationContext的继承体系:

ServletWebServerApplicationContext:继承了GenericWebApplicationContext,它提供了运行在web环境下的spring容器的基本实现,当前类做出了扩展,支持内嵌的web服务器

public class ServletWebServerApplicationContext extends GenericWebApplicationContext
       implements ConfigurableWebServerApplicationContext {

    // 内置的web服务器
    private volatile WebServer webServer;

    private ServletConfig servletConfig;

    private String serverNamespace;
}

AnnotationConfigServletWebServerApplicationContext:springboot使用的spring容器

public class AnnotationConfigServletWebServerApplicationContext extends ServletWebServerApplicationContext
		implements AnnotationConfigRegistry {

    // 读取注解信息
	private final AnnotatedBeanDefinitionReader reader;

    // 扫描注解信息
	private final ClassPathBeanDefinitionScanner scanner;

	private final Set<Class<?>> annotatedClasses = new LinkedHashSet<>();

	private String[] basePackages;
}

基本流程

用户通过编写引导类来启动springboot。引导类的案例:

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

从引导类中可以看出,springboot的启动流程的逻辑全部都在run方法中

run方法:

public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {
    // 调用重载的run方法
    return run(new Class<?>[] { primarySource }, args);
}

// 重载的run方法
public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
    // 在重载的run方法中,先调用SpringApplication的构造方法,创建SpringApplication
    // 的实例,然后通过SpringApplication的实例,调用成员run方法
    return new SpringApplication(primarySources).run(args);
}

springboot的启动流程,代码上可以分为两步,SpringApplication的构造方法、SpringApplication的成员run方法。

SpringApplication的构造方法

整体流程:

public SpringApplication(Class<?>... primarySources) {
    // 调用重载的构造方法
    this(null, primarySources);
}

// 重载的构造方法,参数primarySources是用户编写的引导类
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
    this.resourceLoader = resourceLoader;
    
    // 校验主类不为null
    Assert.notNull(primarySources, "PrimarySources must not be null");
    this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
    
    // 推断当前应用程序的类型
    this.webApplicationType = WebApplicationType.deduceFromClasspath();
    
    // 加载初始化器
    setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
    
    // 加载监听器
    setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
    
    // 设置主类
    this.mainApplicationClass = deduceMainApplicationClass();
}

从源码中可以看出,SpringApplication的构造方法主要做了三件事情:

  • 推断当前应用程序的类型
  • 加载初始化器和监听器
  • 设置主类
推断当前应用程序的类型

调用WebApplicationType中的代码来推断当前程序的类型:

public enum WebApplicationType {

    // none类型,表示应用程序不应该运行在一个web容器中
    NONE,

    // servlet类型,表示应用程序应该运行在一个web容器中
    SERVLET,

    // reactive类型,表示应用程序是一个反应式的web应用,并且应用运行在一个web容器中
    REACTIVE;

    // 推断程序类型
    static WebApplicationType deduceFromClasspath() {
        // 如果当前类路径下可以加载到DispatcherHandler,并且无法加载到DispatcherServlet
        // 和ServletContainer,证明当前应用是reactive类型
        if (ClassUtils.isPresent(WEBFLUX_INDICATOR_CLASS, null) 
                && !ClassUtils.isPresent(WEBMVC_INDICATOR_CLASS, null)
                && !ClassUtils.isPresent(JERSEY_INDICATOR_CLASS, null)) {
            return WebApplicationType.REACTIVE;
        }
        // 如果当前应用不是reactive类型,并且类路径下加载不到Servlet
        // 和ConfigurableWebApplicationContext,证明当前应用是none类型
        for (String className : SERVLET_INDICATOR_CLASSES) {
            if (!ClassUtils.isPresent(className, null)) {
                return WebApplicationType.NONE;
            }
        }
        // 如果当前应用不是reactive类型并且不是none类型,那么就是servlet类型
        return WebApplicationType.SERVLET;
    }
    // 省略代码
}

在类路径下加载一个类的方法:

public static boolean isPresent(String className, @Nullable ClassLoader classLoader) {
    try {
        // 使用类加载器来加载类,如果可以加载到,返回true
        forName(className, classLoader);
        return true;
    }
    catch (IllegalAccessError err) {
        throw new IllegalStateException("Readability mismatch in inheritance hierarchy of class [" +
                className + "]: " + err.getMessage(), err);
    }
    catch (Throwable ex) {
        // Typically ClassNotFoundException or NoClassDefFoundError...
        return false;
    }
}
加载初始化器和监听器

初始化器和监听器被配置在META-INF/spring.factories文件中,这个文件中存储了一系列的键值对,键是接口名,值是接口实现类的名称。

初始化器的接口是ApplicationContextInitializer,监听器的接口是ApplicationListener

META-INF/spring.factories文件的案例:这是spring-boot的jar包中的文件:

# 初始化器
# Application Context Initializers
org.springframework.context.ApplicationContextInitializer=\
org.springframework.boot.context.ConfigurationWarningsApplicationContextInitializer,\
org.springframework.boot.context.ContextIdApplicationContextInitializer,\
org.springframework.boot.context.config.DelegatingApplicationContextInitializer,\
org.springframework.boot.web.context.ServerPortInfoApplicationContextInitializer

# 监听器
# Application Listeners
org.springframework.context.ApplicationListener=\
org.springframework.boot.ClearCachesApplicationListener,\
org.springframework.boot.builder.ParentContextCloserApplicationListener,\
org.springframework.boot.context.FileEncodingApplicationListener,\
org.springframework.boot.context.config.AnsiOutputApplicationListener,\
org.springframework.boot.context.config.ConfigFileApplicationListener,\
org.springframework.boot.context.config.DelegatingApplicationListener,\
org.springframework.boot.context.logging.ClasspathLoggingApplicationListener,\
org.springframework.boot.context.logging.LoggingApplicationListener,\
org.springframework.boot.liquibase.LiquibaseServiceLocatorApplicationListener

加载spring.factories文件:

private static Map<String, List<String>> loadSpringFactories(
    @Nullable ClassLoader classLoader) {
    // 从缓存中获取数据
    MultiValueMap<String, String> result = cache.get(classLoader);
    if (result != null) {
        return result;
    }

    try {
        // 加载类路径下所有的META-INF/spring.factories文件
        Enumeration<URL> urls = (classLoader != null ?
                classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
                ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
        result = new LinkedMultiValueMap<>();
        while (urls.hasMoreElements()) {
            URL url = urls.nextElement();
            UrlResource resource = new UrlResource(url);
            // 将文件中的内容解析到properties实例中
            Properties properties = PropertiesLoaderUtils.loadProperties(resource);
            for (Map.Entry<?, ?> entry : properties.entrySet()) {
                String factoryClassName = ((String) entry.getKey()).trim();
                for (String factoryName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
                    // 将文件内容存储到集合中
                    result.add(factoryClassName, factoryName.trim());
                }
            }
        }
        // 将结果放入缓存
        cache.put(classLoader, result);
        return result;
    }
    catch (IOException ex) {
        throw new IllegalArgumentException("Unable to load factories from location [" +
                FACTORIES_RESOURCE_LOCATION + "]", ex);
    }
}

加载初始化器和监听器的方法:

// 参数type表示获取什么类型的实例,例如,初始化器都实现类ApplicationContextInitializer
// 接口,想要获取初始化器的实例,传入ApplicationContextInitializer的类对象即可,监听器也
// 类似,传入ApplicationListener接口的类对象即可
private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, 
                                                      Class<?>[] parameterTypes, 
                                                      Object... args) {
    // 获取类加载器
    ClassLoader classLoader = getClassLoader();
    
    // 根据类对象,从spring.factories中获取配置信息,获取类的全限定名
    // Use names and ensure unique to protect against duplicates
    Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
    
    // 实例化类,排序
    List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
    AnnotationAwareOrderComparator.sort(instances);
    return instances;
}
设置主类

从堆栈信息中,获取main方法所在的类,把它设置为主类

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

StackTraceElement类:封装类堆栈信息

public final class StackTraceElement implements java.io.Serializable {
    // 类名
    // Normally initialized by VM (public constructor added in 1.5)
    private String declaringClass;
    // 方法名
    private String methodName;
    // 文件名
    private String fileName;
    // 代码行号
    private int    lineNumber;

}
总结

构造方法中做的事情:

  • 推断当前应用程序的类型,默认是servlet类型,一个普通的web应用
  • 加载初始化器和监听器,这是配置在spring.factories文件中的,这里是SPI机制的实现
  • 设置主类,从堆栈信息中找到main方法所在的类,把它作为主类

SpringApplication的成员run方法

成员run方法:

public ConfigurableApplicationContext run(String... args) {
    // 计时器,记录程序启动时花费的时间
    StopWatch stopWatch = new StopWatch();
    stopWatch.start();
    ConfigurableApplicationContext context = null;
    Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
    
    // 配置一个特殊的属性:java.awt.headless,因为有时候在服务器环境下可能没有图形界面
    configureHeadlessProperty();
    
    // 获取spring运行时监听器(SpringApplicationRunListener),它用于处理当前run方法执行过程中发生的事件
    SpringApplicationRunListeners listeners = getRunListeners(args);
    
    // 发布事件:run方法执行事件,可以做一些较早的初始化逻辑
    listeners.starting();
    try {
        // 解析用户从命令行传入的参数
        ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
        
        // 准备environment实例,在这一步,会解析application.properties和用户传入的参数,
        ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
        
        // 配置要忽略的bean,这是主要是设置一个属性
        configureIgnoreBeanInfo(environment);
        
        // 打印spring图标
        Banner printedBanner = printBanner(environment);
        
        // 创建spring容器的实例
        context = createApplicationContext();
        
        // 根据spring.factories中的配置,获取异常报告器 SpringBootExceptionReporter
        exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
                new Class[] { ConfigurableApplicationContext.class }, context);
        
        // 准备spring容器,包括设置environment组件、执行初始化器、注册引导类的bean信息
        prepareContext(context, environment, listeners, applicationArguments, printedBanner);
        
        // 刷新spring容器,在这里执行refresh方法,启动spring容器和web容器
        refreshContext(context);
        
        // 扩展点
        afterRefresh(context, applicationArguments);
        
        // 停止计时
        stopWatch.stop();
        if (this.logStartupInfo) {
            new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
        }
        
        // 发布应用启动完成事件
        listeners.started(context);
        
        // 调用ApplicationRunner和CommandLineRunner
        callRunners(context, applicationArguments);
    }
    catch (Throwable ex) {
        handleRunFailure(context, ex, exceptionReporters, listeners);
        throw new IllegalStateException(ex);
    }

    try {
        listeners.running(context);
    }
    catch (Throwable ex) {
        handleRunFailure(context, ex, exceptionReporters, null);
        throw new IllegalStateException(ex);
    }
    return context;
}

run方法中做的事情:

  • 解析用户传入的参数和application.properties
  • 打印spring图标
  • 调用初始化器
  • 启动spring容器
  • 调用ApplicationRunner和CommandLineRunner
解析用户传入的配置信息
private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,
        ApplicationArguments applicationArguments) {
    
    // 创建environment实例
    // Create and configure the environment
    ConfigurableEnvironment environment = getOrCreateEnvironment();
    
    // 配置environment实例,这里主要是把用户从命令行传入的参数添加到environment中
    configureEnvironment(environment, applicationArguments.getSourceArgs());
    ConfigurationPropertySources.attach(environment);
    // 发布环境准备就绪事件,在事件监听器中加载自定义配置文件 application.properties
    listeners.environmentPrepared(environment);
    bindToSpringApplication(environment);
    if (!this.isCustomEnvironment) {
        environment = new EnvironmentConverter(getClassLoader()).convertEnvironmentIfNecessary(environment,
                deduceEnvironmentClass());
    }
    ConfigurationPropertySources.attach(environment);
    return environment;
}

根据应用类型创建environment实例

private ConfigurableEnvironment getOrCreateEnvironment() {
    if (this.environment != null) {
        return this.environment;
    }
    // servlet类型的容器,创建StandardServletEnvironment类型的实例
    switch (this.webApplicationType) {
    case SERVLET:
        return new StandardServletEnvironment();
    case REACTIVE:
        return new StandardReactiveWebEnvironment();
    default:
        return new StandardEnvironment();
    }
}

解析参数的逻辑:environment实例中会存储用户传入的参数,这里简单提一下参数是如何解析的。

public CommandLineArgs parse(String... args) {
    CommandLineArgs commandLineArgs = new CommandLineArgs();
    for (String arg : args) {
        if (arg.startsWith("--")) {
            // 参数是 --key=value 的形式
            String optionText = arg.substring(2, arg.length());
            String optionName;
            String optionValue = null;
            if (optionText.contains("=")) {
                optionName = optionText.substring(0, optionText.indexOf('='));
                optionValue = optionText.substring(optionText.indexOf('=')+1, optionText.length());
            }
            else {
                optionName = optionText;
            }
            if (optionName.isEmpty() || (optionValue != null && optionValue.isEmpty())) {
                throw new IllegalArgumentException("Invalid argument syntax: " + arg);
            }
            commandLineArgs.addOptionArg(optionName, optionValue);
        }
        else {
            // 普通参数
            commandLineArgs.addNonOptionArg(arg);
        }
    }
    return commandLineArgs;
}

从这里看springboot应该不支持处理单横杆开头的参数

打印spring图标

图标所在位置:

class SpringBootBanner implements Banner {

    private static final String[] BANNER = { 
            "", 
            "  .   ____          _            __ _ _",
            " /\\\\ / ___'_ __ _ _(_)_ __  __ _ \\ \\ \\ \\", 
            "( ( )\\___ | '_ | '_| | '_ \\/ _` | \\ \\ \\ \\",
            " \\\\/  ___)| |_)| | | | | || (_| |  ) ) ) )", "  
            '  |____| .__|_| |_|_| |_\\__, | / / / /",
            " =========|_|==============|___/=/_/_/_/" };

    // 省略代码

}
实例化spring容器
protected ConfigurableApplicationContext createApplicationContext() {

    // 根据应用类型,创建spring容器,servlet类型的应用,默认创建
    // AnnotationConfigServletWebServerApplicationContext类型的spring容器
    Class<?> contextClass = this.applicationContextClass;
    if (contextClass == null) {
        try {
            switch (this.webApplicationType) {
            case SERVLET:
                contextClass = Class.forName(DEFAULT_SERVLET_WEB_CONTEXT_CLASS);
                break;
            case REACTIVE:
                contextClass = Class.forName(DEFAULT_REACTIVE_WEB_CONTEXT_CLASS);
                break;
            default:
                contextClass = Class.forName(DEFAULT_CONTEXT_CLASS);
            }
        }
        catch (ClassNotFoundException ex) {
            throw new IllegalStateException(
                    "Unable create a default ApplicationContext, " + "please specify an ApplicationContextClass",
                    ex);
        }
    }
    return (ConfigurableApplicationContext) BeanUtils.instantiateClass(contextClass);
}

准备spring容器
private void prepareContext(ConfigurableApplicationContext context, 
        ConfigurableEnvironment environment,
        SpringApplicationRunListeners listeners, 
        ApplicationArguments applicationArguments, 
        Banner printedBanner) {

    context.setEnvironment(environment);
    postProcessApplicationContext(context);
    // 应用初始化器,初始化器最经常做的事情,就是向spring容器中添加后置处理器和监听器
    applyInitializers(context);
    listeners.contextPrepared(context);
    if (this.logStartupInfo) {
        logStartupInfo(context.getParent() == null);
        logStartupProfileInfo(context);
    }
    // Add boot specific singleton beans
    ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
    beanFactory.registerSingleton("springApplicationArguments", applicationArguments);
    if (printedBanner != null) {
        beanFactory.registerSingleton("springBootBanner", printedBanner);
    }
    if (beanFactory instanceof DefaultListableBeanFactory) {
        ((DefaultListableBeanFactory) beanFactory)
                .setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
    }
    // Load the sources
    Set<Object> sources = getAllSources();
    Assert.notEmpty(sources, "Sources must not be empty");
    load(context, sources.toArray(new Object[0]));
    listeners.contextLoaded(context);
}

准备spring容器的过程中做的最重要的事情:应用初始化器,它可以在容器启动前执行自定义的初始化逻辑。

刷新spring容器
private void refreshContext(ConfigurableApplicationContext context) {
    // 调用父类的refresh方法
    refresh(context);
    // 注册关闭jvm结束时需要执行的回调函数
    if (this.registerShutdownHook) {
        try {
            context.registerShutdownHook();
        }
        catch (AccessControlException ex) {
            // Not allowed in some environments.
        }
    }
}

这里就是调用spring中的refresh方法,来刷新spring容器,基本流程在spring中都学过了,只有几个扩展点不一样

扩展点1:onRefresh()方法,创建web容器

// ServletWebServerApplicationContext类
@Override
protected void onRefresh() {
    super.onRefresh();
    try {
        // 创建web容器
        createWebServer();
    }
    catch (Throwable ex) {
        throw new ApplicationContextException("Unable to start web server", ex);
    }
}

// 这里以tomcat为例,创建一个内嵌进应用中的tomcat实例
public WebServer getWebServer(ServletContextInitializer... initializers) {
    Tomcat tomcat = new Tomcat();
    File baseDir = (this.baseDirectory != null) ? this.baseDirectory : createTempDir("tomcat");
    tomcat.setBaseDir(baseDir.getAbsolutePath());
    Connector connector = new Connector(this.protocol);
    tomcat.getService().addConnector(connector);
    customizeConnector(connector);
    tomcat.setConnector(connector);
    tomcat.getHost().setAutoDeploy(false);
    configureEngine(tomcat.getEngine());
    for (Connector additionalConnector : this.additionalTomcatConnectors) {
        tomcat.getService().addConnector(additionalConnector);
    }
    prepareContext(tomcat.getHost(), initializers);
    return getTomcatWebServer(tomcat);
}
运行ApplicationRunner和CommandLineRunner

它们是springboot启动后就会立刻调用的bean,用于在springboot启动后第一时间执行某些任务。

private void callRunners(ApplicationContext context, ApplicationArguments args) {
    List<Object> runners = new ArrayList<>();
    // 从spring容器中获取ApplicationRunner和CommandLineRunner
    runners.addAll(context.getBeansOfType(ApplicationRunner.class).values());
    runners.addAll(context.getBeansOfType(CommandLineRunner.class).values());
    AnnotationAwareOrderComparator.sort(runners);
    // 执行
    for (Object runner : new LinkedHashSet<>(runners)) {
        if (runner instanceof ApplicationRunner) {
            callRunner((ApplicationRunner) runner, args);
        }
        if (runner instanceof CommandLineRunner) {
            callRunner((CommandLineRunner) runner, args);
        }
    }
}
总结

springboot启动过程中,最重要的事情,是自动装配,它默认向spring容器中注入了很多bean,同时,它会启动一个内嵌的web容器。

springboot内置的tomcat

实现原理:Tomcat本身提供的外部接口,使其它应用程序能够非常方便的将Tomca嵌入到自身的应用,通过调用Tomcat提供的外部类org.apache.catalina.startup.Tomcat

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值