Spring源码分析1-RESTfulWeb启动过程

Spring框架用的很多了,但是没有深入研究过原理。因为对原理不了解,如果遇到复杂的应用情景,需要修改Spring源码;或者是奇怪的报错,需要通过源码来进行分析的时候,就只能束手无策了。所以现在开始正式阅读Spring源码,这是系列的第一篇,希望能坚持下去。

说在前边:本文是我阅读RestfulWeb启动过程写的流水账,瑕疵多多。

第一篇先从最简单的情景入手,分析RESTful Web应用的启动加载流程

RESTfulWeb 应用

什么是REST

REST(Representational State Transfer 表述性状态传输)是2000年的时候 Roy Fielding 在他的博士论文中的定义。REST是设计分布式系统的一种架构风格,它并不是一个标准,而是一系列的约束,包括:无状态、客户端/服务端关系、统一接口等。REST跟HTTP不是强相关,但最常见的是这两者的关联。

其他文绉绉的定义请详见Understanding REST

应用实践

使用spring教程提供的样例gs-rest-service

  • 标准的spring boot application 入口
@SpringBootApplication
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}
  • 暴露的controller
@RestController
public class GreetingController {
    private static final String template = "Hello, %s!";
    private final AtomicLong counter = new AtomicLong();

    @RequestMapping("/greeting")
    public Greeting greeting(@RequestParam(value="name", defaultValue="World") String name) {
        return new Greeting(counter.incrementAndGet(),
                            String.format(template, name));
    }
}

好的,断点打好,debug启动,准备就绪。

欢迎来到Spring世界!

构造SpringApplication

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

第一行代码是一个run,从这里进去后,可以看到这是SpringApplication提供的一个静态方法,这个Helper用于使用默认配置和指定的资源(这里是Application.class)来运行一个SpringApplication。简单说,就是按照默认方式构造一个SpringApplication!入参里还有个String[]args ,这其实就是寻常的main方法入参列表!

继续往下

/**
     * Create a new {@link SpringApplication} instance. The application context will load
     * beans from the specified primary sources (see {@link SpringApplication class-level}
     * documentation for details. The instance can be customized before calling
     * {@link #run(String...)}.
     * @param resourceLoader the resource loader to use
     * @param primarySources the primary bean sources
     * @see #run(Class, String[])
     * @see #setSources(Set)
     */
    @SuppressWarnings({ "unchecked", "rawtypes" })
    public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
        //资源加载器,这里传入的是Null
        this.resourceLoader = resourceLoader;
        //主资源不能为null,其实就是Application.class
        Assert.notNull(primarySources, "PrimarySources must not be null");
        this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
        //推断web应用类型
        this.webApplicationType = deduceWebApplicationType();
        //获取初始化器实例们
        setInitializers((Collection) getSpringFactoriesInstances(
                ApplicationContextInitializer.class));
        //获取监听器实例们
        setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
        //推断应用主类
        this.mainApplicationClass = deduceMainApplicationClass();
    }

从上边的代码可以看到,构造SpringApplication的伟大使命,只要走四步!真是太容易了!请看:

推断web应用类型

private WebApplicationType deduceWebApplicationType() {
        if (ClassUtils.isPresent(REACTIVE_WEB_ENVIRONMENT_CLASS, null)
                && !ClassUtils.isPresent(MVC_WEB_ENVIRONMENT_CLASS, null)) {
            return WebApplicationType.REACTIVE;
        }
        for (String className : WEB_ENVIRONMENT_CLASSES) {
            if (!ClassUtils.isPresent(className, null)) {
                return WebApplicationType.NONE;
            }
        }
        return WebApplicationType.SERVLET;
    }

这里是根据某些特征类来确定web应用类型的。web应用类型居然有三种!

public enum WebApplicationType {

    /**
     * The application should not run as a web application and should not start an embedded web server.
     *普通应用,不作为web应用运行,不需要启动内嵌web server
     */
    NONE,

    /**
     * The application should run as a servlet-based web application and should start an embedded servlet web server.
     * 基于servlet的web应用,需要启动内嵌web server
     */
    SERVLET,

    /**
     * The application should run as a reactive web application and should start an embedded reactive web server.
     *交互式web应用,需要启动内嵌web server
     */
    REACTIVE

}

这三种有啥区别,我不知道!我也是一脸懵逼的!反正这次是SERVLET类型。他们的区别我以后肯定会知道的,先用小笔笔记下来。

获取初始化器实例

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 = Thread.currentThread().getContextClassLoader();
    // Use names and ensure unique to protect against duplicates
    //从SpringFactoriesLoader那里搞来一堆实例的类名
    Set<String> names = new LinkedHashSet<>(
            SpringFactoriesLoader.loadFactoryNames(type, classLoader));
    //然后将那堆类名,通过classloader给实例化了!
    List<T> instances = createSpringFactoriesInstances(type, parameterTypes,
            classLoader, args, names);
    AnnotationAwareOrderComparator.sort(instances);
    return instances;
}

从上边可以看到,实例化之前,先去SpringFactoriesLoader弄来一个集合的类名。我们看看怎么弄出来的,逐层深入,看到:

//这里classloader不是空,正是上文获取的当前线程的classloader
//然后从FACTORIES_RESOURCE_LOCATION这个资源文件里获取了一把url
Enumeration<URL> urls = (classLoader != null ?
        classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
        ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
result = new LinkedMultiValueMap<>();
//将拿到的urls解析,获得一大把类名
while (urls.hasMoreElements()) {
    URL url = urls.nextElement();
    UrlResource resource = new UrlResource(url);
    Properties properties = PropertiesLoaderUtils.loadProperties(resource);
    for (Map.Entry<?, ?> entry : properties.entrySet()) {
        List<String> factoryClassNames = Arrays.asList(
                StringUtils.commaDelimitedListToStringArray((String) entry.getValue()));
        result.addAll((String) entry.getKey(), factoryClassNames);
    }
}
cache.put(classLoader, result);
return result;

一看,呵!原来类名都是写死在文件FACTORIES_RESOURCE_LOCATION=”META-INF/spring.factories”里的。因为传入的classloader是当前线程ClassLoader,调用classLoader.getResources(String)的时候会把所有jar里的spring.factories都找出来。所以,其中一个spring.factories文件是在spring-boot包下的。打开瞧一瞧,里边东西还真不少:

# PropertySource Loaders
org.springframework.boot.env.PropertySourceLoader=\
org.springframework.boot.env.PropertiesPropertySourceLoader,\
org.springframework.boot.env.YamlPropertySourceLoader

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

# Error Reporters
org.springframework.boot.SpringBootExceptionReporter=\
org.springframework.boot.diagnostics.FailureAnalyzers

# 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

# Environment Post Processors
org.springframework.boot.env.EnvironmentPostProcessor=\
org.springframework.boot.cloud.CloudFoundryVcapEnvironmentPostProcessor,\
org.springframework.boot.env.SpringApplicationJsonEnvironmentPostProcessor,\
org.springframework.boot.env.SystemEnvironmentPropertySourceEnvironmentPostProcessor

# Failure Analyzers
org.springframework.boot.diagnostics.FailureAnalyzer=\
org.springframework.boot.diagnostics.analyzer.BeanCurrentlyInCreationFailureAnalyzer,\
org.springframework.boot.diagnostics.analyzer.BeanNotOfRequiredTypeFailureAnalyzer,\
org.springframework.boot.diagnostics.analyzer.BindFailureAnalyzer,\
org.springframework.boot.diagnostics.analyzer.BindValidationFailureAnalyzer,\
org.springframework.boot.diagnostics.analyzer.UnboundConfigurationPropertyFailureAnalyzer,\
org.springframework.boot.diagnostics.analyzer.ConnectorStartFailureAnalyzer,\
org.springframework.boot.diagnostics.analyzer.NoUniqueBeanDefinitionFailureAnalyzer,\
org.springframework.boot.diagnostics.analyzer.PortInUseFailureAnalyzer,\
org.springframework.boot.diagnostics.analyzer.ValidationExceptionFailureAnalyzer,\
org.springframework.boot.diagnostics.analyzer.InvalidConfigurationPropertyNameFailureAnalyzer,\
org.springframework.boot.diagnostics.analyzer.InvalidConfigurationPropertyValueFailureAnalyzer

# FailureAnalysisReporters
org.springframework.boot.diagnostics.FailureAnalysisReporter=\
org.springframework.boot.diagnostics.LoggingFailureAnalysisReporter

当然这里就提示了我们,我们可以在自己的META-INF/spring.factories,spring也可以找的到里边的listener,然后进行注册。

好了,刚说到拿到了上边那包类名了。继续 ~

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

弱水三千,我只取一瓢。根据factoryClassName(这里是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
List<T> instances = createSpringFactoriesInstances(type, parameterTypes,
                classLoader, args, names);

拿到类名了,这里就开始实例化了。

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

实例化完了,就结束了!设置到SpringApplication里以备后用!

获取监听器实例

这一步跟获取初始化器是一模一样的,只有key不一样,这里用的key是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

也设置到SpringApplication里以备后用!

推断主类

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方法,可以看到非常复杂的一个方法。浏览一遍,然后我们一步一步分析

    /**
     * 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) {
        //计时器开动
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();
        ConfigurableApplicationContext context = null;
        Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
        //写入headless模式的环境变量 java.awt.headless=true
        configureHeadlessProperty();
        //获得运行时监听器,并调用start
        SpringApplicationRunListeners listeners = getRunListeners(args);
        listeners.starting();
        try {
            ApplicationArguments applicationArguments = new DefaultApplicationArguments(
                    args);
            //准备环境容器
            ConfigurableEnvironment environment = prepareEnvironment(listeners,
                    applicationArguments);
            configureIgnoreBeanInfo(environment);
            //准备环境容器
            Banner printedBanner = printBanner(environment);
            //创建上下文
            context = createApplicationContext();
            //构建exceptionReporters
            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);
            }
            //触发started事件
            listeners.started(context);
            //调用Runner
            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;
    }

打开HeadlessMode

一开始先打开headless模式,这个玩意儿没接触过,简单了解下。就是当设置了java.awt.headless=true的时候,java应用就会默认进入headless模式。Headless模式是一个没有键盘、鼠标、屏幕的一个模式,所以当你进入headless模式,就意味着进行GUI开发,就会受限制,甚至出现异常,可以看看Button的代码:

static {
    /* ensure that the necessary native libraries are loaded */
    Toolkit.loadLibraries();
    if (!GraphicsEnvironment.isHeadless()) {
        initIDs();
    }
}
/**
     * Initialize JNI field and method IDs for fields that may be
     * accessed from C.
     */
    private static native void initIDs();

一上来先检查是不是headless模式再决定是否initIDs().

headless模式更多信息,可以看看oracle的文档

运行时监听器

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

呵!又跑去META-INF/spring.factories 去捞类名了,这次的key是org.springframework.boot.SpringApplicationRunListener,捞出来一堆listeners实例之后,塞到SpringApplicationRunListeners里封装起来备用!对了,这次捞出来的实例只有一个,是它:

org.springframework.boot.context.event.EventPublishingRunListener

有个地方要注意下,这个构造的时候做了个小动作。他把SpringApplication的监听器,一股脑全注册到他自己名下了。呵!listener !

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);
        }
    }

紧接着麻烦就通过将第一个ApplicationStartingEvent传递给这些listeners。当然不是所有的监听器都会处理这个Event,里边有复杂的判断逻辑,暂时跳过。监听器收到event之后,自然会做自己的一些准备工作。

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

可以看到,这个listener的机制,可以使得启动加载环节的可拓展性非常强,可以自己酌情增减listener。因为SpringApplication暴露了添加listener的接口:

/**
     * Add {@link ApplicationListener}s to be applied to the SpringApplication and
     * registered with the {@link ApplicationContext}.
     * @param listeners the listeners to add
     */
public void addListeners(ApplicationListener<?>... listeners) {
    this.listeners.addAll(Arrays.asList(listeners));
}

另外,也可以定义自己的META-INF/spring.factories文件,在里边注册自己的listener。

准备环境容器

private ConfigurableEnvironment prepareEnvironment(
            SpringApplicationRunListeners listeners,
            ApplicationArguments applicationArguments) {
    // Create and configure the environment
    ConfigurableEnvironment environment = getOrCreateEnvironment();
    //处理propertySource和profile
    configureEnvironment(environment, applicationArguments.getSourceArgs());
    //通知监听器
    listeners.environmentPrepared(environment);
    //将环境容器和SpringApplication绑定
    bindToSpringApplication(environment);
    if (this.webApplicationType == WebApplicationType.NONE) {
        environment = new EnvironmentConverter(getClassLoader())
                .convertToStandardEnvironmentIfNecessary(environment);
    }
    ConfigurationPropertySources.attach(environment);
    return environment;
}

打印Banner

Banner printedBanner = printBanner(environment);

这个没啥,打印banner。spring支持多种banner,可以酌情使用。默认使用SpringBootBanner,打印出来,如下样式:

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v2.0.3.RELEASE)

创建上下文

context = createApplicationContext();

这一步创建web应用的上下文,啥叫上下文,我现在也说不清楚。以后肯定会知道的。

SERVLET类型的应用在这里实例化的其实是这个类:
org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext

构建exceptionReporters

exceptionReporters = getSpringFactoriesInstances(
                    SpringBootExceptionReporter.class,
                    new Class[] { ConfigurableApplicationContext.class }, context);

又又又是那个熟悉的“捞类”方法!
这里捞出的类是这个:
org.springframework.boot.diagnostics.FailureAnalyzers

捞出来存在了临时变量exceptionReporters里,后文里看到当启动过程抛异常的时候,会使用到这个实例。有啥用途,字面看就是报告异常,以后再深入研究。

准备上下文

private void prepareContext(ConfigurableApplicationContext context,
            ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,
            ApplicationArguments applicationArguments, Banner printedBanner) {
        context.setEnvironment(environment);
        //给上下文context预设ResourceLoader、Classloader等
        postProcessApplicationContext(context);
        //应用初始化器
        applyInitializers(context);
        // 触发Spring Boot启动过程的contextPrepared事件
        listeners.contextPrepared(context);
        if (this.logStartupInfo) {
            logStartupInfo(context.getParent() == null);
            logStartupProfileInfo(context);
        }

        // Add boot specific singleton beans 
        //添加两个spring boot启动过程中的特殊的单例beans  
        context.getBeanFactory().registerSingleton("springApplicationArguments",
                applicationArguments);
        if (printedBanner != null) {
            context.getBeanFactory().registerSingleton("springBootBanner", printedBanner);
        }

        // Load the sources
        //获得需要加载的资源
        Set<Object> sources = getAllSources();
        Assert.notEmpty(sources, "Sources must not be empty");
        //创建BeanDefinitionLoader,并加载sources到应用上下文context
        load(context, sources.toArray(new Object[0]));
        // 触发Spring Boot启动过程的contextLoaded事件
        listeners.contextLoaded(context);
    }

这里边东西挺多的,可以着重看下applyInitializers(context) 和 加载bean到context的这两个步骤。

刷新上下文

@Override
    public void refresh() throws BeansException, IllegalStateException {
        synchronized (this.startupShutdownMonitor) {
            // Prepare this context for refreshing.
            prepareRefresh();

            // Tell the subclass to refresh the internal bean factory.
            ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

            // Prepare the bean factory for use in this context.
            prepareBeanFactory(beanFactory);

            try {
                // Allows post-processing of the bean factory in context subclasses.
                postProcessBeanFactory(beanFactory);

                // Invoke factory processors registered as beans in the context.
                invokeBeanFactoryPostProcessors(beanFactory);

                // Register bean processors that intercept bean creation.
                registerBeanPostProcessors(beanFactory);

                // Initialize message source for this context.
                initMessageSource();

                // Initialize event multicaster for this context.
                initApplicationEventMulticaster();

                // Initialize other special beans in specific context subclasses.
                onRefresh();

                // Check for listener beans and register them.
                registerListeners();

                // Instantiate all remaining (non-lazy-init) singletons.
                finishBeanFactoryInitialization(beanFactory);

                // Last step: publish corresponding event.
                finishRefresh();
            }

            catch (BeansException ex) {
                if (logger.isWarnEnabled()) {
                    logger.warn("Exception encountered during context initialization - " +
                            "cancelling refresh attempt: " + ex);
                }

                // Destroy already created singletons to avoid dangling resources.
                destroyBeans();

                // Reset 'active' flag.
                cancelRefresh(ex);

                // Propagate exception to caller.
                throw ex;
            }

            finally {
                // Reset common introspection caches in Spring's core, since we
                // might not ever need metadata for singleton beans anymore...
                resetCommonCaches();
            }
        }
    }

呵!好家伙,debug进去发现父类的refresh是个超级复杂的方法,刷新上下文居然做了这么多东西。本文抱着粗糙概览spring启动过程的想法,所以这里先不对这个方法展开分析。

需要注意下就是刷新完上下文之后,spring顺手注册了ShutdownHook,在doClose()做了很多资源释放以及触发ContextClosedEvent事件:

    @Override
    public void registerShutdownHook() {
        if (this.shutdownHook == null) {
            // No shutdown hook registered yet.
            this.shutdownHook = new Thread() {
                @Override
                public void run() {
                    synchronized (startupShutdownMonitor) {
                        doClose();
                    }
                }
            };
            Runtime.getRuntime().addShutdownHook(this.shutdownHook);
        }
    }

上下文后置处理

/**
     * Called after the context has been refreshed.
     * @param context the application context
     * @param args the application arguments
     */
    protected void afterRefresh(ConfigurableApplicationContext context,
            ApplicationArguments args) {
    }

略感意外的是这里居然是空的protected方法,网上有些教程说到在这里会调用callRunners,估计现在最新的代码已经把这个挪到了后边了吧。

调用Runner

private void callRunners(ApplicationContext context, ApplicationArguments args) {
        List<Object> runners = new ArrayList<>();
        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);
            }
        }
    }

可以看到会有两种Runner,分别为ApplicationRunner 和 CommandLineRunner。凡事实现了这两个接口的Bean,都会在这个地方被调用。这两种runner没特别大的区别,都是实现run方法,只有入参不一样,一个是封装好的ApplicationArguments类型,另一个是直接的String不定长数组类型。

然后就结束啦,这就是RESTful Web启动的过程概览啦!

参考资料

[1] Spring 官方教程
[2] SpringBoot 启动过程源码分析
[3] Spring Boot启动过程的定制化


转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 duval1024@gmail.com

×

喜欢就点赞,疼爱就打赏