Spring源代码分析-springboot启动

一. 入口

对于Springboot工程,我们只需要一行代码调用就可以运行起来,如下:

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

那接下来我们就要从这行代码入手,分析其内部实现过程。

二. 源代码分析

代码位置:org.springframework.boot.SpringApplication

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

    /**
     * Static helper that can be used to run a {@link SpringApplication} from the
     * specified sources using default settings and user supplied arguments.
     * @param primarySources the primary sources to load
     * @param args the application arguments (usually passed from a Java main method)
     * @return the running {@link ApplicationContext}
     */
    public static ConfigurableApplicationContext run(Class<?>[] primarySources,
            String[] args) {
        return new SpringApplication(primarySources).run(args);
    }

上面代码我们可以发现几点:

  1. run方法其实是可以接受一个Class数组的
  2. 在run方法里面,实例化了一个SpringApplication对象,然后调用它的run方法。
    那接下来,我们就要分析这两点,初始化SpringApplication对象,以及它的run方法实现。

2.1 实例化SpringApplication对象

@SuppressWarnings({ "unchecked", "rawtypes" })
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
          this.resourceLoader = resourceLoader;
          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();
    }

这里比较重要的一个是getSpringFactoriesInstances,这个方法是根据类型去读取spring.factories配置文件里面对应的配置项。
比如setInitializers里面,传入的类型是ApplicationContextInitializer.class。
那接下来,我们来分析这个读取流程。

spring.factories是Spring提供给厂商自定义配置的文件,我们也可以定义我们自己的spring.factories文件,然后就会运行里面的配置项。
比如在我们自己的项目里面,添加一个spring.factories文件,里面配置SpringApplicationRunListener除了系统的之外,我还加了MyEventPublishListener

Spring源代码分析-springboot启动

在MyEventPublishListener里面,只是打印了一下运行日志

Spring源代码分析-springboot启动

Spring源代码分析-springboot启动

那我们的服务跑起来的时候,有如下日志:

Spring源代码分析-springboot启动

也就是说我们自定义的spring.factories配置是生效了的,我们在项目启动的时候,做一些自定义配置,比如初始化资源。

2.1.1 获取spring.factories配置

private <T> Collection<T> getSpringFactoriesInstances(Class<T> type) {
        return getSpringFactoriesInstances(type, new Class<?>[] {});
    }

    private <T> Collection<T> getSpringFactoriesInstances(Class<T> type,
            Class<?>[] parameterTypes, Object... args) {
        ClassLoader classLoader = getClassLoader();
        // Use names and ensure unique to protect against duplicates
        // 1. 获取配置项
        Set<String> names = new LinkedHashSet<>(
                SpringFactoriesLoader.loadFactoryNames(type, classLoader));
        // 2. 初始化配置项        
        List<T> instances = createSpringFactoriesInstances(type, parameterTypes,
                classLoader, args, names);
     // 3. 排序,值越小,优先级越高
        AnnotationAwareOrderComparator.sort(instances);
        return instances;
    }

继续往里面看获取配置项SpringFactoriesLoader.loadFactoryNames方法。

2.1.2 SpringFactoriesLoader.loadFactoryNames

SpringFactoriesLoader类定义是在Spring-core里面,我们上一篇讲过,Spring有3个核心组件,Spring-core, Spring-context, Spring-beans。他们的代码在Spring-framework里面。
于是,我们打开Spring-framework,找到其代码,如下图。
Spring源代码分析-springboot启动

代码如下;

public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";

public static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader) {
        String factoryClassName = factoryClass.getName();
        return loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList());
    }

    private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
        MultiValueMap<String, String> result = cache.get(classLoader);
        if (result != null) {
            return result;
        }

        try {
            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 = 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);
        }
    }

这个方法还是简单明了的,从META-INF/spring.factories里面去读取factoryClass.getName()的配置,就像springboot里面提供的配置一样,ApplicationContextInitializer类型就是下面的配置,有4个配置项。

# 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

所以上面setInitializers的时候,就是读取这4个类型

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

然后实例化,存入SpringApplication里面的成员列表里面

private List<ApplicationContextInitializer<?>> initializers;

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

接下来还要设置listeners
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));

2.1.3 获取ApplicationListener

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

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

getSpringFactoriesInstance的实现上面已经解释过了,在springboot的默认配置是这样的。

# Run Listeners
org.springframework.boot.SpringApplicationRunListener=\
org.springframework.boot.context.event.EventPublishingRunListener

所以listeners里面存储的是EventPublishingRunListener对象。

/**
 * {@link SpringApplicationRunListener} to publish {@link SpringApplicationEvent}s.
 * <p>
 * Uses an internal {@link ApplicationEventMulticaster} for the events that are fired
 * before the context is actually refreshed.
 *
 * @author Phillip Webb
 * @author Stephane Nicoll
 * @author Andy Wilkinson
 * @author Artsiom Yudovin
 */
public class EventPublishingRunListener implements SpringApplicationRunListener, Ordered {

}

EventPublishingRunListener从注释上面来说,是使用ApplicationEventMulticaster来发布SpringApplicationEvent事件。其实EventPublishingRunListener其实就是个事件中转站,它利用ApplicationEventMulticater来转发事件。

ApplicationEventMulticaster是Spring-context里面提供的一种机制,它可以根据传入的事件类型找到相应的监听者,这个和安卓的EventBus效果类似。
这个事件分发后面篇幅我们再说。

回到SpringApplication实例化的构造方法,最后一个行,获取mainApplicationClass

2.1.4 获取mainApplicationClass

this.mainApplicationClass = deduceMainApplicationClass();

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方法所在类,找到那个类。

那到这里,实例化SpringApplication实例化就完成了,接下来就去看看run方法。

2.1.5 自定义项目的spring.factories文件

上面说了那么多spring.factories文件的东西,那么如果我们自己项目中想使用spring.factories文件,是定义到哪个文件夹位置呢?
其实需要是放在src/main/resources/META-INF目录下面,如果没有这个目录,请自己创建文件夹,如下图所示:

Spring源代码分析-springboot启动

2.2 SpringApplication.run

根据注释,它是运行一个Spring application,创建和刷新一个新的ApplicationContext。代码有点长,慢慢看。

/**
     * Run the Spring application, creating and refreshing a new
     * {@link ApplicationContext}.
     * @param args the application arguments (usually passed from a Java main method)
     * @return a running {@link ApplicationContext}
     */
    public ConfigurableApplicationContext run(String... args) {
      //1. 启动计时器
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();
        ConfigurableApplicationContext context = null;
        Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
        //2. 设置java.awt.headless系统属性为true,没有图形界面,适合于服务器程序开发
        configureHeadlessProperty();
        //3. 获取spring.factories配置文件中的运行listener,一般是EventPublishingRunListener,作为事件中转
        SpringApplicationRunListeners listeners = getRunListeners(args);
        //4. 通知开始starting,发出starting事件给观察者.
        listeners.starting();
        try {
            ApplicationArguments applicationArguments = new DefaultApplicationArguments(
                    args);
            ConfigurableEnvironment environment = prepareEnvironment(listeners,
                    applicationArguments);
            configureIgnoreBeanInfo(environment);
            Banner printedBanner = printBanner(environment);
            context = createApplicationContext();
            exceptionReporters = getSpringFactoriesInstances(
                    SpringBootExceptionReporter.class,
                    new Class[] { ConfigurableApplicationContext.class }, context);
            prepareContext(context, environment, listeners, applicationArguments,
                    printedBanner);
            refreshContext(context);
            afterRefresh(context, applicationArguments);
            stopWatch.stop();
            if (this.logStartupInfo) {
                new StartupInfoLogger(this.mainApplicationClass)
                        .logStarted(getApplicationLog(), stopWatch);
            }
            listeners.started(context);
            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;
    }

2.2.1 启动计时器

StopWatch stopWatch = new StopWatch();
stopWatch.start();

StopWatch是Spring-core里面提供的功能,可以用来统计总共运行时间

/**
 * Simple stop watch, allowing for timing of a number of tasks,
 * exposing total running time and running time for each named task.
 *
 * <p>Conceals use of {@code System.currentTimeMillis()}, improving the
 * readability of application code and reducing the likelihood of calculation errors.
 *
 * <p>Note that this object is not designed to be thread-safe and does not
 * use synchronization.
 *
 * <p>This class is normally used to verify performance during proof-of-concepts
 * and in development, rather than as part of production applications.
 *
 * @author Rod Johnson
 * @author Juergen Hoeller
 * @author Sam Brannen
 * @since May 2, 2001
 */
public class StopWatch {
    ...
    public void start(String taskName) throws IllegalStateException {
        if(this.running) {
            throw new IllegalStateException("Can't start StopWatch: it's already running");
        } else {
            this.running = true;
            this.currentTaskName = taskName;
            this.startTimeMillis = System.currentTimeMillis();
        }
    }

        ....
}

记录开始时间 this.startTimeMillis = System.currentTimeMillis(); 这个startTimeMillis值待会在stopWatch.stop()时会使用。

2.2.2 设置成服务器模式

private static final String SYSTEM_PROPERTY_JAVA_AWT_HEADLESS = "java.awt.headless";

configureHeadlessProperty();

private void configureHeadlessProperty() {
        System.setProperty(SYSTEM_PROPERTY_JAVA_AWT_HEADLESS, System.getProperty(
                SYSTEM_PROPERTY_JAVA_AWT_HEADLESS, Boolean.toString(this.headless)));
}

一般是服务器模式下设置java.awt.headless=true,表示可能缺少键盘等支持。

2.2.3 获取Run Listener

SpringApplicationRunListeners listeners = getRunListeners(args);

private SpringApplicationRunListeners getRunListeners(String[] args) {
        Class<?>[] types = new Class<?>[] { SpringApplication.class, String[].class };
        return new SpringApplicationRunListeners(logger, getSpringFactoriesInstances(
                SpringApplicationRunListener.class, types, this, args));
}

getSpringFactoriesInstances在上面已经分析过了,可以根据传入的class type获取到spring.factories配置的类。而对于SpringApplicationRunListeners,一般配置是这样的

org.springframework.boot.SpringApplicationRunListener=\
org.springframework.boot.context.event.EventPublishingRunListener,\

所以实例化后就是EventPublishingRunListener对象,EventPublishingRunListener是一个事件中间转发类,利用一个SimpleApplicationEventMulticaster转发各种事件。

大概如下:

/**
 * {@link SpringApplicationRunListener} to publish {@link SpringApplicationEvent}s.
 * <p>
 * Uses an internal {@link ApplicationEventMulticaster} for the events that are fired
 * before the context is actually refreshed.
 *
 * @author Phillip Webb
 * @author Stephane Nicoll
 * @author Andy Wilkinson
 * @author Artsiom Yudovin
 */
public class EventPublishingRunListener implements SpringApplicationRunListener, Ordered {

    private final SpringApplication application;

    private final String[] args;

    private final SimpleApplicationEventMulticaster initialMulticaster;

    public EventPublishingRunListener(SpringApplication application, String[] args) {
        this.application = application;
        this.args = args;
        this.initialMulticaster = new SimpleApplicationEventMulticaster();
        for (ApplicationListener<?> listener : application.getListeners()) {
            this.initialMulticaster.addApplicationListener(listener);
        }
    }

    @Override
    public int getOrder() {
        return 0;
    }

    @Override
    public void starting() {
        this.initialMulticaster.multicastEvent(
                new ApplicationStartingEvent(this.application, this.args));
    }

    @Override
    public void environmentPrepared(ConfigurableEnvironment environment) {
        this.initialMulticaster.multicastEvent(new ApplicationEnvironmentPreparedEvent(
                this.application, this.args, environment));
    }

    @Override
    public void contextPrepared(ConfigurableApplicationContext context) {
        this.initialMulticaster.multicastEvent(new ApplicationContextInitializedEvent(
                this.application, this.args, context));
    }

    ...
    }   

SimpleApplicationEventMulticaster是Spring-context提供的一种消息传递机制,这个具体可以再分析。

2.2.4 发送starting消息

listeners.starting();

@Override
    public void starting() {
        this.initialMulticaster.multicastEvent(
                new ApplicationStartingEvent(this.application, this.args));
}

listeners 的值如下

Spring源代码分析-springboot启动

其中MyEventPublishListener是我们在spring.factories配置的
Spring源代码分析-springboot启动

而EventPublishingRunListener是SpringBoot里面spring.factories配置的。
Spring源代码分析-springboot启动

所以会执行MyEventPublishListener和EventPublishingRunListener的starting方法。

2.2.5 准备环境

ApplicationArguments applicationArguments = new DefaultApplicationArguments(
                    args);
ConfigurableEnvironment environment = prepareEnvironment(listeners,
                    applicationArguments);
configureIgnoreBeanInfo(environment);
private ConfigurableEnvironment prepareEnvironment(
            SpringApplicationRunListeners listeners,
            ApplicationArguments applicationArguments) {
        // Create and configure the environment
        ConfigurableEnvironment environment = getOrCreateEnvironment();
        configureEnvironment(environment, applicationArguments.getSourceArgs());
        listeners.environmentPrepared(environment);
        bindToSpringApplication(environment);
        if (!this.isCustomEnvironment) {
            environment = new EnvironmentConverter(getClassLoader())
                    .convertEnvironmentIfNecessary(environment, deduceEnvironmentClass());
        }
        ConfigurationPropertySources.attach(environment);
        return environment;
    }
private ConfigurableEnvironment getOrCreateEnvironment() {
        if (this.environment != null) {
            return this.environment;
        }
        switch (this.webApplicationType) {
            case SERVLET:
                return new StandardServletEnvironment();
            case REACTIVE:
                return new StandardReactiveWebEnvironment();
            default:
                return new StandardEnvironment();
        }
    }

可以根据不同的项目类型创建不同的Environment,关于Environment具体可以参考下面链接
https://www.jb51.net/article/145192.htm

2.2.6 打印Banner

Banner printedBanner = printBanner(environment);

相信大家对Springboot启动的图形不会陌生,类似于下图:
Spring源代码分析-springboot启动

那么这个图形就是从这里打印出来的。

private Banner printBanner(ConfigurableEnvironment environment) {
        if (this.bannerMode == Banner.Mode.OFF) {
            return null;
        }
        ResourceLoader resourceLoader = (this.resourceLoader != null)
                ? this.resourceLoader : new DefaultResourceLoader(getClassLoader());
        SpringApplicationBannerPrinter bannerPrinter = new SpringApplicationBannerPrinter(
                resourceLoader, this.banner);
        if (this.bannerMode == Mode.LOG) {
            return bannerPrinter.print(environment, this.mainApplicationClass, logger);
        }
        return bannerPrinter.print(environment, this.mainApplicationClass, System.out);
    }

printBanner会构造一个SpringApplicationBannerPrinter对象,然后根据不同的模式(this.bannerMode == Mode.LOG),通过logger或者System.out打印。
所以主要看SpringApplicationBannerPrinter的print做了什么。

public Banner print(Environment environment, Class<?> sourceClass, PrintStream out) {
        Banner banner = getBanner(environment);
        banner.printBanner(environment, sourceClass, out);
        return new PrintedBanner(banner, sourceClass);
    }

主要看getBanner方法

private Banner getBanner(Environment environment) {
        Banners banners = new Banners();
        banners.addIfNotNull(getImageBanner(environment));
        banners.addIfNotNull(getTextBanner(environment));
        if (banners.hasAtLeastOneBanner()) {
            return banners;
        }
        if (this.fallbackBanner != null) {
            return this.fallbackBanner;
        }
        return DEFAULT_BANNER;
    }

这里springboot支持自定义的启动图形,getImageBanner和getTextBanner。 如果没有定义,那么就尝试寻找fallbackBanner,这个fallbackBanner可以通过SpringApplication设置, 一般我们都不会设置。
所以默认都会返回DEFAULT_BANNER,DEFAULT_BANNER的定义如下:

private static final Banner DEFAULT_BANNER = new SpringBootBanner();
class SpringBootBanner implements Banner {

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

    private static final String SPRING_BOOT = " :: Spring Boot :: ";

    private static final int STRAP_LINE_SIZE = 42;

    @Override
    public void printBanner(Environment environment, Class<?> sourceClass,
            PrintStream printStream) {
        for (String line : BANNER) {
            printStream.println(line);
        }
        String version = SpringBootVersion.getVersion();
        version = (version != null) ? " (v" + version + ")" : "";
        StringBuilder padding = new StringBuilder();
        while (padding.length() < STRAP_LINE_SIZE
                - (version.length() + SPRING_BOOT.length())) {
            padding.append(" ");
        }

        printStream.println(AnsiOutput.toString(AnsiColor.GREEN, SPRING_BOOT,
                AnsiColor.DEFAULT, padding.toString(), AnsiStyle.FAINT, version));
        printStream.println();
    }

}

上面String[] BANNER对象是不是很熟悉呢?嗯,就是我们命令行里面打印输出的那个了。

得到SpringBootBanner对象后,通过调用printBanner方法打印出来,所以我们就看到命令行的输出

banner.printBanner(environment, sourceClass, out);

2.2.7 StopWatch.stop()

public ConfigurableApplicationContext run(String... args) {
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();
        ...
        try {
            ....
            stopWatch.stop();
            if (this.logStartupInfo) {
                new StartupInfoLogger(this.mainApplicationClass)
                        .logStarted(getApplicationLog(), stopWatch);
            }
            return context;
        }
        catch (Throwable ex) {
            handleRunFailure(context, listeners, analyzers, ex);
            throw new IllegalStateException(ex);
        }
    }

之前分析stopWatch.start()里面是记录了启动开始时间,那么stopWatch.stop()是做什么呢?代码如下:

public void stop() throws IllegalStateException {
        if(!this.running) {
            throw new IllegalStateException("Can't stop StopWatch: it's not running");
        } else {
            long lastTime = System.currentTimeMillis() - this.startTimeMillis;
            this.totalTimeMillis += lastTime;
            this.lastTaskInfo = new StopWatch.TaskInfo(this.currentTaskName, lastTime);
            if(this.keepTaskList) {
                this.taskList.add(this.lastTaskInfo);
            }

            ++this.taskCount;
            this.running = false;
            this.currentTaskName = null;
        }
    }

这里面记录了程序启动时间,也就是从stopWatch.start()到stop的时间-totalTimeMillis

接下来判断是否需要打印启动时间

if (this.logStartupInfo) {
                new StartupInfoLogger(this.mainApplicationClass)
                        .logStarted(getApplicationLog(), stopWatch);
            }

如果需要打印,那么调用logStarted打印信息,如下:

public void logStarted(Log log, StopWatch stopWatch) {
        if (log.isInfoEnabled()) {
            log.info(getStartedMessage(stopWatch));
        }
    }
private StringBuilder getStartedMessage(StopWatch stopWatch) {
        StringBuilder message = new StringBuilder();
        message.append("Started ");
        message.append(getApplicationName());
        message.append(" in ");
        message.append(stopWatch.getTotalTimeSeconds());
        try {
            double uptime = ManagementFactory.getRuntimeMXBean().getUptime() / 1000.0;
            message.append(" seconds (JVM running for " + uptime + ")");
        }
        catch (Throwable ex) {
            // No JVM time available
        }
        return message;
    }

获取的打印信息getStartedMessage,也就是如下图所示:

Spring源代码分析-springboot启动

三. 参考链接

Spring Environment介绍

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章