> Java > java지도 시간 > Spring Boot의 시작 프로세스에 대한 관련 소개

Spring Boot의 시작 프로세스에 대한 관련 소개

巴扎黑
풀어 주다: 2017-08-10 14:50:50
원래의
1880명이 탐색했습니다.

환경

이 문서는 spring-boot-starter-web을 사용하는 Spring Boot 버전 1.3.3을 기반으로 합니다.

구성이 완료되면 다음과 같이 코드가 작성됩니다.

@SpringBootApplicationpublic class Application {    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}@RestControllerpublic class RootController {    public static final String PATH_ROOT = "/";    @RequestMapping(PATH_ROOT)    public String welcome() {        return "Welcome!";
    }

}
로그인 후 복사

코드가 몇 줄밖에 안 되지만 접속 URL의 경로 부분이 "/"이면 이미 완전한 웹 프로그램입니다. , "Welcome!"이라는 문자열이 반환됩니다.

우선 매우 일반적인 Java 프로그램 항목이며, 계약을 준수하는 정적 메인 메소드입니다. 이 메인 메소드에서는 SpringApplication의 static run 메소드가 호출되고, Application 클래스 객체와 메인 메소드의 매개변수 args가 매개변수로 전달된다.

그리고 두 개의 Spring 주석을 사용하는 RootController 클래스가 있습니다. 우리는 이 클래스를 기본 메서드에서 직접 사용하지 않습니다.

SpringApplication 클래스의 정적 실행 메소드

以下代码摘自:org.springframework.boot.SpringApplicationpublic static ConfigurableApplicationContext run(Object source, String... args) {    return run(new Object[] { source }, args);
}public static ConfigurableApplicationContext run(Object[] sources, String[] args) {    return new SpringApplication(sources).run(args);
}
로그인 후 복사

이 정적 메소드에서는 SpringApplication 객체를 생성하고 객체의 실행 메소드를 호출합니다.

SpringApplication 개체를 생성합니다

以下代码摘自:org.springframework.boot.SpringApplicationpublic SpringApplication(Object... sources) {    initialize(sources);
}private void initialize(Object[] sources) {    // 为成员变量sources赋值    if (sources != null && sources.length > 0) {        this.sources.addAll(Arrays.asList(sources));
    }    this.webEnvironment = deduceWebEnvironment();    setInitializers((Collection) getSpringFactoriesInstances(
            ApplicationContextInitializer.class));    setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));    this.mainApplicationClass = deduceMainApplicationClass();
}
로그인 후 복사

생성자에서 초기화 메서드를 호출하여 SpringApplication 개체의 멤버 변수 소스, webEnvironment, 초기화 프로그램, 리스너 및 mainApplicationClass를 초기화합니다. 소스 할당은 상대적으로 간단합니다. 이는 SpringApplication.run 메소드에 전달하는 매개변수입니다. 나머지 하나하나 살펴보겠습니다.

첫 번째는 webEnvironment:

以下代码摘自:org.springframework.boot.SpringApplicationprivate boolean webEnvironment; 

private static final String[] WEB_ENVIRONMENT_CLASSES = { "javax.servlet.Servlet",            "org.springframework.web.context.ConfigurableWebApplicationContext" };private void initialize(Object[] sources) {
    ...        // 为成员变量webEnvironment赋值        this.webEnvironment = deduceWebEnvironment();
    ...
}private boolean deduceWebEnvironment() {    for (String className : WEB_ENVIRONMENT_CLASSES) {        if (!ClassUtils.isPresent(className, null)) {            return false;
        }
    }    return true;
}
로그인 후 복사

webEnvironment가 부울값임을 알 수 있으며 이 멤버 변수는 현재 애플리케이션이 웹 애플리케이션인지 여부를 나타내는 데 사용됩니다. 따라서 현재 애플리케이션이 웹 애플리케이션인지 확인하는 방법은 WEB_ENVIRONMENT_CLASSES 배열에 포함된 클래스가 클래스 경로에 존재하는지 확인하는 것입니다. 존재하는 경우 현재 프로그램은 웹 애플리케이션이고 그 반대의 경우도 마찬가지입니다.
이 기사의 예에서 webEnvironment의 값은 true입니다.

그런 다음 초기화 프로그램이 있습니다.

ApplicationContextInitializer 유형 개체의 컬렉션인

initializers 멤버 변수입니다. 이름에서 알 수 있듯이 ApplicationContextInitializer는 ApplicationContext를 초기화하는 데 사용할 수 있는 인터페이스입니다.

以下代码摘自:org.springframework.boot.SpringApplicationprivate List<ApplicationContextInitializer<?>> initializers;private void initialize(Object[] sources) {
    ...    // 为成员变量initializers赋值    setInitializers((Collection) getSpringFactoriesInstances(
            ApplicationContextInitializer.class));
    ...
}public void setInitializers(
        Collection<? extends ApplicationContextInitializer<?>> initializers) {    this.initializers = new ArrayList<ApplicationContextInitializer<?>>();    this.initializers.addAll(initializers);
}
로그인 후 복사

getSpringFactoriesInstances(ApplicationContextInitializer.class)를 호출하여 ApplicationContextInitializer 유형 객체 목록을 가져오는 것이 핵심임을 알 수 있습니다.
🎜rreee🎜

在该方法中,首先通过调用SpringFactoriesLoader.loadFactoryNames(type, classLoader)来获取所有Spring Factories的名字,然后调用createSpringFactoriesInstances方法根据读取到的名字创建对象。最后会将创建好的对象列表排序并返回。

以下代码摘自:org.springframework.core.io.support.SpringFactoriesLoaderpublic static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";public static List<String> loadFactoryNames(Class<?> factoryClass, ClassLoader classLoader) {
    String factoryClassName = factoryClass.getName();    try {
        Enumeration<URL> urls = (classLoader != null ? classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
                ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
        List<String> result = new ArrayList<String>();        while (urls.hasMoreElements()) {
            URL url = urls.nextElement();
            Properties properties = PropertiesLoaderUtils.loadProperties(new UrlResource(url));
            String factoryClassNames = properties.getProperty(factoryClassName);
            result.addAll(Arrays.asList(StringUtils.commaDelimitedListToStringArray(factoryClassNames)));
        }        return result;
    }    catch (IOException ex) {        throw new IllegalArgumentException("Unable to load [" + factoryClass.getName() +                "] factories from location [" + FACTORIES_RESOURCE_LOCATION + "]", ex);
    }
}
로그인 후 복사

可以看到,是从一个名字叫spring.factories的资源文件中,读取key为org.springframework.context.ApplicationContextInitializer的value。而spring.factories的部分内容如下:

以下内容摘自spring-boot-1.3.3.RELEASE.jar中的资源文件META-INF/spring.factories# 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.context.web.ServerPortInfoApplicationContextInitializer
로그인 후 복사

可以看到,最近的得到的,是ConfigurationWarningsApplicationContextInitializer,ContextIdApplicationContextInitializer,DelegatingApplicationContextInitializer,ServerPortInfoApplicationContextInitializer这四个类的名字。

接下来会调用createSpringFactoriesInstances来创建ApplicationContextInitializer实例。

以下代码摘自:org.springframework.boot.SpringApplicationprivate <T> List<T> createSpringFactoriesInstances(Class<T> type,
        Class<?>[] parameterTypes, ClassLoader classLoader, Object[] args,
        Set<String> names) {
    List<T> instances = new ArrayList<T>(names.size());    for (String name : names) {        try {
            Class<?> instanceClass = ClassUtils.forName(name, classLoader);
            Assert.isAssignable(type, instanceClass);
            Constructor<?> constructor = instanceClass.getConstructor(parameterTypes);
            T instance = (T) constructor.newInstance(args);
            instances.add(instance);
        }        catch (Throwable ex) {            throw new IllegalArgumentException(                    "Cannot instantiate " + type + " : " + name, ex);
        }
    }    return instances;
}
로그인 후 복사

所以在我们的例子中,SpringApplication对象的成员变量initalizers就被初始化为,ConfigurationWarningsApplicationContextInitializer,ContextIdApplicationContextInitializer,DelegatingApplicationContextInitializer,ServerPortInfoApplicationContextInitializer这四个类的对象组成的list。

下图画出了加载的ApplicationContextInitializer,并说明了他们的作用。至于何时应用他们,且听后面慢慢分解。


接下来是成员变量listeners

以下代码摘自:org.springframework.boot.SpringApplicationprivate List<ApplicationListener<?>> listeners;private void initialize(Object[] sources) {
    ...    // 为成员变量listeners赋值    setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
    ...
}public void setListeners(Collection<? extends ApplicationListener<?>> listeners) {    this.listeners = new ArrayList<ApplicationListener<?>>();    this.listeners.addAll(listeners);
}
로그인 후 복사

listeners成员变量,是一个ApplicationListener类型对象的集合。可以看到获取该成员变量内容使用的是跟成员变量initializers一样的方法,只不过传入的类型从ApplicationContextInitializer.class变成了ApplicationListener.class。

看一下spring.factories中的相关内容:

以下内容摘自spring-boot-1.3.3.RELEASE.jar中的资源文件META-INF/spring.factories# Application Listeners
org.springframework.context.ApplicationListener=\
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.liquibase.LiquibaseServiceLocatorApplicationListener,\
org.springframework.boot.logging.ClasspathLoggingApplicationListener,\
org.springframework.boot.logging.LoggingApplicationListener
로그인 후 복사

也就是说,在我们的例子中,listener最终会被初始化为ParentContextCloserApplicationListener,FileEncodingApplicationListener,AnsiOutputApplicationListener,ConfigFileApplicationListener,DelegatingApplicationListener,LiquibaseServiceLocatorApplicationListener,ClasspathLoggingApplicationListener,LoggingApplicationListener这几个类的对象组成的list。

下图画出了加载的ApplicationListener,并说明了他们的作用。至于他们何时会被触发,等事件出现时,我们再说明。


最后是mainApplicationClass

以下代码摘自:org.springframework.boot.SpringApplicationprivate Class<?> mainApplicationClass;private void initialize(Object[] sources) {
    ...    // 为成员变量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;
}
로그인 후 복사

在deduceMainApplicationClass方法中,通过获取当前调用栈,找到入口方法main所在的类,并将其复制给SpringApplication对象的成员变量mainApplicationClass。在我们的例子中mainApplicationClass即是我们自己编写的Application类。

SpringApplication对象的run方法

经过上面的初始化过程,我们已经有了一个SpringApplication对象,根据SpringApplication类的静态run方法一节中的分析,接下来会调用SpringApplication对象的run方法。我们接下来就分析这个对象的run方法。

以下代码摘自:org.springframework.boot.SpringApplicationpublic ConfigurableApplicationContext run(String... args) {
    StopWatch stopWatch = new StopWatch();
    stopWatch.start();
    ConfigurableApplicationContext context = null;    configureHeadlessProperty();
    SpringApplicationRunListeners listeners = getRunListeners(args);
    listeners.started();    try {
        ApplicationArguments applicationArguments = new DefaultApplicationArguments(
                args);
        context = createAndRefreshContext(listeners, applicationArguments);        afterRefresh(context, applicationArguments);
        listeners.finished(context, null);
        stopWatch.stop();        if (this.logStartupInfo) {            new StartupInfoLogger(this.mainApplicationClass)
                    .logStarted(getApplicationLog(), stopWatch);
        }        return context;
    }    catch (Throwable ex) {        handleRunFailure(context, listeners, ex);        throw new IllegalStateException(ex);
    }
}
로그인 후 복사
  • 可变个数参数args即是我们整个应用程序的入口main方法的参数,在我们的例子中,参数个数为零。

  • StopWatch是来自org.springframework.util的工具类,可以用来方便的记录程序的运行时间。

SpringApplication对象的run方法创建并刷新ApplicationContext,算是开始进入正题了。下面按照执行顺序,介绍该方法所做的工作。

headless模式

以下代码摘自:org.springframework.boot.SpringApplicationprivate static final String SYSTEM_PROPERTY_JAVA_AWT_HEADLESS = "java.awt.headless";private boolean headless = true;public ConfigurableApplicationContext run(String... args) {
    ...    //设置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,因为我们开发的是服务器程序,一般运行在没有显示器和键盘的环境。关于java中的headless模式,更多信息可以参考这里。

SpringApplicationRunListeners

以下代码摘自:org.springframework.boot.SpringApplicationpublic ConfigurableApplicationContext run(String... args) {
    ...
    SpringApplicationRunListeners listeners = getRunListeners(args);
    listeners.started();    /**         * 创建并刷新ApplicationContext         * context = createAndRefreshContext(listeners, applicationArguments);         **/
    listeners.finished(context, null);
    ...
}private SpringApplicationRunListeners getRunListeners(String[] args) {
    Class<?>[] types = new Class<?>[] { SpringApplication.class, String[].class };    return new SpringApplicationRunListeners(logger, getSpringFactoriesInstances(
            SpringApplicationRunListener.class, types, this, args));
}
로그인 후 복사

run方法中,加载了一系列SpringApplicationRunListener对象,在创建和更新ApplicationContext方法前后分别调用了listeners对象的started方法和finished方法, 并在创建和刷新ApplicationContext时,将listeners作为参数传递到了createAndRefreshContext方法中,以便在创建和刷新ApplicationContext的不同阶段,调用listeners的相应方法以执行操作。所以,所谓的SpringApplicationRunListeners实际上就是在SpringApplication对象的run方法执行的不同阶段,去执行一些操作,并且这些操作是可配置的。

同时,可以看到,加载SpringApplicationRunListener时,使用的是跟加载ApplicationContextInitializer和ApplicationListener时一样的方法。那么加载了什么,就可以从spring.factories文件中看到了:

以下内容摘自spring-boot-1.3.3.RELEASE.jar中的资源文件META-INF/spring.factories# Run Listeners
org.springframework.boot.SpringApplicationRunListener=\
org.springframework.boot.context.event.EventPublishingRunListener
로그인 후 복사

可以看到,在我们的例子中加载的是org.springframework.boot.context.event.EventPublishingRunListener。我们看一看这个SpringApplicationRunListener究竟做了点什么工作了?

以下代码摘自:org.springframework.boot.context.event.EventPublishingRunListenerpublic EventPublishingRunListener(SpringApplication application, String[] args) {    this.application = application;    this.args = args;    this.multicaster = new SimpleApplicationEventMulticaster();    for (ApplicationListener<?> listener : application.getListeners()) {        this.multicaster.addApplicationListener(listener);
    }
}@Overridepublic void started() {    publishEvent(new ApplicationStartedEvent(this.application, this.args));
}@Overridepublic void environmentPrepared(ConfigurableEnvironment environment) {    publishEvent(new ApplicationEnvironmentPreparedEvent(this.application, this.args,
            environment));
}@Overridepublic void contextPrepared(ConfigurableApplicationContext context) {    registerApplicationEventMulticaster(context);
}@Overridepublic void contextLoaded(ConfigurableApplicationContext context) {    for (ApplicationListener<?> listener : this.application.getListeners()) {        if (listener instanceof ApplicationContextAware) {
            ((ApplicationContextAware) listener).setApplicationContext(context);
        }
        context.addApplicationListener(listener);
    }    publishEvent(new ApplicationPreparedEvent(this.application, this.args, context));
}@Overridepublic void finished(ConfigurableApplicationContext context, Throwable exception) {    publishEvent(getFinishedEvent(context, exception));
}
로그인 후 복사

EventPublishingRunListener在对象初始化时,将SpringApplication对象的成员变量listeners全都保存下来,然后在自己的public方法被调用时,发布相应的事件,或执行相应的操作。可以说这个RunListener是在SpringApplication对象的run方法执行到不同的阶段时,发布相应的event给SpringApplication对象的成员变量listeners中记录的事件监听器。

下图画出了SpringApplicationRunListeners相关的类结构,虽然我们的例子中只有一个SpringApplicationRunListener,但在这样的设计下,想要扩展是非常容易的!


接下来,我们看一下在调用listeners的started方法。在我们的例子中,也就是发布了ApplicationStartedEvent时,我们已经加载的事件监听器都做了什么操作。至于其它事件的发布,我们按照代码执行的顺序在后面的章节在介绍。

  • ParentContextCloserApplicationListener不监听ApplicationStartedEvent,没有操作;

  • FileEncodingApplicationListener不监听ApplicationStartedEvent,没有操作;

  • AnsiOutputApplicationListener不监听ApplicationStartedEvent,没有操作;

  • ConfigFileApplicationListener不监听ApplicationStartedEvent,没有操作;

  • DelegatingApplicationListener不监听ApplicationStartedEvent,没有操作;

  • LiquibaseServiceLocatorApplicationListener监听ApplicationStartedEvent,会检查classpath中是否有liquibase.servicelocator.ServiceLocator并做相应操作;

以下代码摘自:org.springframework.boot.liquibase.LiquibaseServiceLocatorApplicationListener@Overridepublic void onApplicationEvent(ApplicationStartedEvent event) {    if (ClassUtils.isPresent("liquibase.servicelocator.ServiceLocator", null)) {        new LiquibasePresent().replaceServiceLocator();
    }
}
로그인 후 복사

我们的例子中,classpath中不存在liquibase,所以不执行任何操作。

  • ClasspathLoggingApplicationListener监听ApplicationStartedEvent,会打印classpath到debug日志;

@Overridepublic void onApplicationEvent(ApplicationEvent event) {    if (event instanceof ApplicationStartedEvent) {        if (this.logger.isDebugEnabled()) {            this.logger.debug("Application started with classpath: " + getClasspath());
    }
    ...
}private String getClasspath() {
    ClassLoader classLoader = Thread.currentThread().getContextClassLoader();    if (classLoader instanceof URLClassLoader) {        return Arrays.toString(((URLClassLoader) classLoader).getURLs());
    }    return "unknown";
}
로그인 후 복사

因为是debug级别的日志,而SpringBoot的默认日志级别是info级,所以我们在控制台不会看到classpath的输出。

  • LoggingApplicationListener监听ApplicationStartedEvent,会根据classpath中的类情况创建相应的日志系统对象,并执行一些初始化之前的操作;

@Overridepublic void onApplicationEvent(ApplicationEvent event) {    if (event instanceof ApplicationStartedEvent) {        onApplicationStartedEvent((ApplicationStartedEvent) event);
    }
    ...
}private void onApplicationStartedEvent(ApplicationStartedEvent event) {    this.loggingSystem = LoggingSystem
            .get(event.getSpringApplication().getClassLoader());    this.loggingSystem.beforeInitialize();
}
로그인 후 복사

我们的例子中,创建的是org.springframework.boot.logging.logback.LogbackLoggingSystem类的对象,Logback是SpringBoot默认采用的日志系统。下图画出了SpringBoot中的日志系统体系:


好了,ApplicationStartedEvent事件的处理这样就结束了。以后在介绍事件处理的时候,我们只介绍监听该事件的监听器的操作,而不监听的,就不再说明了。

创建并刷新ApplicationContext

以下代码摘自:org.springframework.boot.SpringApplicationpublic ConfigurableApplicationContext run(String... args) {
    ...    try {
        ApplicationArguments applicationArguments = new DefaultApplicationArguments(
                args);
        context = createAndRefreshContext(listeners, applicationArguments);        afterRefresh(context, applicationArguments);
        ...
    }    catch (Throwable ex) {        handleRunFailure(context, listeners, ex);        throw new IllegalStateException(ex);
    }
}
로그인 후 복사

首先是创建一个DefaultApplicationArguments对象,之后调用createAndRefreshContext方法创建并刷新一个ApplicationContext,最后调用afterRefresh方法在刷新之后做一些操作。

先来看看DefaultApplicationArguments吧:

以下代码摘自:org.springframework.boot.DefaultApplicationArgumentsDefaultApplicationArguments(String[] args) {
    Assert.notNull(args, "Args must not be null");    this.source = new Source(args);    this.args = args;
}private static class Source extends SimpleCommandLinePropertySource {

    Source(String[] args) {        super(args);
    }
    ...
}

以下代码摘自:org.springframework.core.env.SimpleCommandLinePropertySourcepublic SimpleCommandLinePropertySource(String... args) {    super(new SimpleCommandLineArgsParser().parse(args));
}
로그인 후 복사

可以看到是把main函数的args参数当做一个PropertySource来解析。我们的例子中,args的长度为0,所以这里创建的DefaultApplicationArguments也没有实际的内容。

创建并配置ApplicationConext的Environment

以下代码摘自:org.springframework.boot.SpringApplicationprivate ConfigurableEnvironment environment;private boolean webEnvironment;private ConfigurableApplicationContext createAndRefreshContext(
        SpringApplicationRunListeners listeners,
        ApplicationArguments applicationArguments) {
    ConfigurableApplicationContext context;    // 创建并配置Environment
    ConfigurableEnvironment environment = getOrCreateEnvironment();    configureEnvironment(environment, applicationArguments.getSourceArgs());
    listeners.environmentPrepared(environment);    if (isWebEnvironment(environment) && !this.webEnvironment) {
        environment = convertToStandardEnvironment(environment);
    }

    ...    return context;
}private ConfigurableEnvironment getOrCreateEnvironment() {    if (this.environment != null) {        return this.environment;
    }    if (this.webEnvironment) {        return new StandardServletEnvironment();
    }    return new StandardEnvironment();
}
로그인 후 복사

Spring Application的Environment代表着程序运行的环境,主要包含了两种信息,一种是profiles,用来描述哪些bean definitions是可用的;一种是properties,用来描述系统的配置,其来源可能是配置文件、JVM属性文件、操作系统环境变量等等。

首先要调用getOrCreateEnvironment方法获取一个Environment对象。在我们的例子中,执行到此处时,environment成员变量为null,而webEnvironment成员变量的值为true,所以会创建一个StandardServletEnvironment对象并返回。

之后是调用configureEnvironment方法来配置上一步获取的Environment对象,代码如下:

以下代码摘自:org.springframework.boot.SpringApplicationprivate Map<String, Object> defaultProperties;private boolean addCommandLineProperties = true;private Set<String> additionalProfiles = new HashSet<String>();protected void configureEnvironment(ConfigurableEnvironment environment,
        String[] args) {    configurePropertySources(environment, args);    configureProfiles(environment, args);
}protected void configurePropertySources(ConfigurableEnvironment environment,
        String[] args) {
    MutablePropertySources sources = environment.getPropertySources();    if (this.defaultProperties != null && !this.defaultProperties.isEmpty()) {
        sources.addLast(                new MapPropertySource("defaultProperties", this.defaultProperties));
    }    if (this.addCommandLineProperties && args.length > 0) {
        String name = CommandLinePropertySource.COMMAND_LINE_PROPERTY_SOURCE_NAME;        if (sources.contains(name)) {
            PropertySource<?> source = sources.get(name);
            CompositePropertySource composite = new CompositePropertySource(name);
            composite.addPropertySource(new SimpleCommandLinePropertySource(
                    name + "-" + args.hashCode(), args));
            composite.addPropertySource(source);
            sources.replace(name, composite);
        }        else {
            sources.addFirst(new SimpleCommandLinePropertySource(args));
        }
    }
}protected void configureProfiles(ConfigurableEnvironment environment, String[] args) {
    environment.getActiveProfiles(); // ensure they are initialized    // But these ones should go first (last wins in a property key clash)
    Set<String> profiles = new LinkedHashSet<String>(this.additionalProfiles);
    profiles.addAll(Arrays.asList(environment.getActiveProfiles()));
    environment.setActiveProfiles(profiles.toArray(new String[profiles.size()]));
}
로그인 후 복사

configureEnvironment方法先是调用configurePropertySources来配置properties,然后调用configureProfiles来配置profiles。

configurePropertySources首先查看SpringApplication对象的成员变量defaultProperties,如果该变量非null且内容非空,则将其加入到Environment的PropertySource列表的最后。然后查看SpringApplication对象的成员变量addCommandLineProperties和main函数的参数args,如果设置了addCommandLineProperties=true,且args个数大于0,那么就构造一个由main函数的参数组成的PropertySource放到Environment的PropertySource列表的最前面(这就能保证,我们通过main函数的参数来做的配置是最优先的,可以覆盖其他配置)。在我们的例子中,由于没有配置defaultProperties且main函数的参数args个数为0,所以这个函数什么也不做。

configureProfiles首先会读取Properties中key为spring.profiles.active的配置项,配置到Environment,然后再将SpringApplication对象的成员变量additionalProfiles加入到Environment的active profiles配置中。在我们的例子中,配置文件里没有spring.profiles.active的配置项,而SpringApplication对象的成员变量additionalProfiles也是一个空的集合,所以这个函数没有配置任何active profile。

到现在,Environment就算是配置完成了。接下来调用SpringApplicationRunListeners类的对象listeners发布ApplicationEnvironmentPreparedEvent事件:

以下代码摘自:org.springframework.boot.context.event.EventPublishingRunListener@Overridepublic void environmentPrepared(ConfigurableEnvironment environment) {    publishEvent(new ApplicationEnvironmentPreparedEvent(this.application, this.args,
            environment));
}
로그인 후 복사

好,现在来看一看我们加载的ApplicationListener对象都有哪些响应了这个事件,做了什么操作:

  • FileEncodingApplicationListener响应该事件,检查file.encoding配置是否与spring.mandatory_file_encoding一致:

以下代码摘自:org.springframework.boot.context.FileEncodingApplicationListener@Overridepublic void onApplicationEvent(ApplicationEnvironmentPreparedEvent event) {
    RelaxedPropertyResolver resolver = new RelaxedPropertyResolver(
            event.getEnvironment(), "spring.");    if (resolver.containsProperty("mandatoryFileEncoding")) {
        String encoding = System.getProperty("file.encoding");
        String desired = resolver.getProperty("mandatoryFileEncoding");        if (encoding != null && !desired.equalsIgnoreCase(encoding)) {
            logger.error("System property &#39;file.encoding&#39; is currently &#39;" + encoding
                    + "&#39;. It should be &#39;" + desired
                    + "&#39; (as defined in &#39;spring.mandatoryFileEncoding&#39;).");
            logger.error("Environment variable LANG is &#39;" + System.getenv("LANG")
                    + "&#39;. You could use a locale setting that matches encoding=&#39;"
                    + desired + "&#39;.");
            logger.error("Environment variable LC_ALL is &#39;" + System.getenv("LC_ALL")
                    + "&#39;. You could use a locale setting that matches encoding=&#39;"
                    + desired + "&#39;.");            throw new IllegalStateException(                    "The Java Virtual Machine has not been configured to use the "
                            + "desired default character encoding (" + desired
                            + ").");
        }
    }
}
로그인 후 복사

在我们的例子中,因为没有spring.mandatory_file_encoding的配置,所以这个响应方法什么都不做。

  • AnsiOutputApplicationListener响应该事件,根据spring.output.ansi.enabled和spring.output.ansi.console-available对AnsiOutput类做相应配置:

以下代码摘自:org.springframework.boot.context.config.AnsiOutputApplicationListener@Overridepublic void onApplicationEvent(ApplicationEnvironmentPreparedEvent event) {
    RelaxedPropertyResolver resolver = new RelaxedPropertyResolver(
            event.getEnvironment(), "spring.output.ansi.");    if (resolver.containsProperty("enabled")) {
        String enabled = resolver.getProperty("enabled");
        AnsiOutput.setEnabled(Enum.valueOf(Enabled.class, enabled.toUpperCase()));
    }    if (resolver.containsProperty("console-available")) {
        AnsiOutput.setConsoleAvailable(
                resolver.getProperty("console-available", Boolean.class));
    }
}
로그인 후 복사

我们的例子中,这两项配置都是空的,所以这个响应方法什么都不做。

  • ConfigFileApplicationListener加载该事件,从一些约定的位置加载一些配置文件,而且这些位置是可配置的。

以下代码摘自:org.springframework.boot.context.config.ConfigFileApplicationListener@Overridepublic void onApplicationEvent(ApplicationEvent event) {    if (event instanceof ApplicationEnvironmentPreparedEvent) {        onApplicationEnvironmentPreparedEvent(
                (ApplicationEnvironmentPreparedEvent) event);
    }    if (event instanceof ApplicationPreparedEvent) {        onApplicationPreparedEvent(event);
    }
}private void onApplicationEnvironmentPreparedEvent(
        ApplicationEnvironmentPreparedEvent event) {
    List<EnvironmentPostProcessor> postProcessors = loadPostProcessors();
    postProcessors.add(this);
    AnnotationAwareOrderComparator.sort(postProcessors);    for (EnvironmentPostProcessor postProcessor : postProcessors) {
        postProcessor.postProcessEnvironment(event.getEnvironment(),
                event.getSpringApplication());
    }
}List<EnvironmentPostProcessor> loadPostProcessors() {    return SpringFactoriesLoader.loadFactories(EnvironmentPostProcessor.class,            getClass().getClassLoader());
}


以下内容摘自spring-boot-1.3.3.RELEASE.jar中的资源文件META-INF/spring.factories# Environment Post Processors
org.springframework.boot.env.EnvironmentPostProcessor=\
org.springframework.boot.cloud.CloudFoundryVcapEnvironmentPostProcessor,\
org.springframework.boot.env.SpringApplicationJsonEnvironmentPostProcessor
로그인 후 복사

可以看到,ConfigFileApplicationListener从META-INF/spring.factories文件中读取EnvironmentPostProcessor配置,加载相应的EnvironmentPostProcessor类的对象,并调用其postProcessEnvironment方法。在我们的例子中,会加载CloudFoundryVcapEnvironmentPostProcessor和SpringApplicationJsonEnvironmentPostProcessor并执行,由于我们的例子中没有CloudFoundry和Json的配置,所以这个响应,不会加载任何的配置文件到Environment中来。

  • DelegatingApplicationListener响应该事件,将配置文件中key为context.listener.classes的配置项,加载在成员变量multicaster中:

以下内容摘自:org.springframework.boot.context.config.DelegatingApplicationListenerprivate static final String PROPERTY_NAME = "context.listener.classes";private SimpleApplicationEventMulticaster multicaster;@Overridepublic void onApplicationEvent(ApplicationEvent event) {    if (event instanceof ApplicationEnvironmentPreparedEvent) {
        List<ApplicationListener<ApplicationEvent>> delegates = getListeners(
                ((ApplicationEnvironmentPreparedEvent) event).getEnvironment());        if (delegates.isEmpty()) {            return;
        }        this.multicaster = new SimpleApplicationEventMulticaster();        for (ApplicationListener<ApplicationEvent> listener : delegates) {            this.multicaster.addApplicationListener(listener);
        }
    }    if (this.multicaster != null) {        this.multicaster.multicastEvent(event);
    }
}@SuppressWarnings("unchecked")private List<ApplicationListener<ApplicationEvent>> getListeners(
        ConfigurableEnvironment env) {
    String classNames = env.getProperty(PROPERTY_NAME);
    List<ApplicationListener<ApplicationEvent>> listeners = new ArrayList<ApplicationListener<ApplicationEvent>>();    if (StringUtils.hasLength(classNames)) {        for (String className : StringUtils.commaDelimitedListToSet(classNames)) {            try {
                Class<?> clazz = ClassUtils.forName(className,
                        ClassUtils.getDefaultClassLoader());
                Assert.isAssignable(ApplicationListener.class, clazz, "class ["
                        + className + "] must implement ApplicationListener");
                listeners.add((ApplicationListener<ApplicationEvent>) BeanUtils
                        .instantiateClass(clazz));
            }            catch (Exception ex) {                throw new ApplicationContextException(                        "Failed to load context listener class [" + className + "]",
                        ex);
            }
        }
    }
    AnnotationAwareOrderComparator.sort(listeners);    return listeners;
}
로그인 후 복사

我们的例子中,因为没有key为context.listener.classes的Property,所以不会加载任何listener到该监听器中。

  • LoggingApplicationListener响应该事件,并对在ApplicationStarted时加载的LoggingSystem做一些初始化工作:

以下代码摘自:org.springframework.boot.logging.LoggingApplicationListener@Overridepublic void onApplicationEvent(ApplicationEvent event) {    if (event instanceof ApplicationStartedEvent) {        onApplicationStartedEvent((ApplicationStartedEvent) event);
    }    else if (event instanceof ApplicationEnvironmentPreparedEvent) {        onApplicationEnvironmentPreparedEvent(
                (ApplicationEnvironmentPreparedEvent) event);
    }    else if (event instanceof ApplicationPreparedEvent) {        onApplicationPreparedEvent((ApplicationPreparedEvent) event);
    }    else if (event instanceof ContextClosedEvent && ((ContextClosedEvent) event)
            .getApplicationContext().getParent() == null) {        onContextClosedEvent();
    }
}private void onApplicationEnvironmentPreparedEvent(
        ApplicationEnvironmentPreparedEvent event) {    if (this.loggingSystem == null) {        this.loggingSystem = LoggingSystem
                .get(event.getSpringApplication().getClassLoader());
    }    initialize(event.getEnvironment(), event.getSpringApplication().getClassLoader());
}protected void initialize(ConfigurableEnvironment environment,
        ClassLoader classLoader) {
    LogFile logFile = LogFile.get(environment);    setSystemProperties(environment, logFile);    initializeEarlyLoggingLevel(environment);    initializeSystem(environment, this.loggingSystem, logFile);    initializeFinalLoggingLevels(environment, this.loggingSystem);    registerShutdownHookIfNecessary(environment, this.loggingSystem);
}
로그인 후 복사

在我们的例子中,是对加载的LogbackLoggingSystem做一些初始化工作。关于日志系统更详细的讨论,值得再写一篇文章,就不在这里展开讨论了。

打印banner

以下代码摘自:org.springframework.boot.SpringApplicationprivate Banner banner;private Banner.Mode bannerMode = Banner.Mode.CONSOLE;public static final String BANNER_LOCATION_PROPERTY = "banner.location";public static final String BANNER_LOCATION_PROPERTY_VALUE = "banner.txt";private static final Banner DEFAULT_BANNER = new SpringBootBanner();private ConfigurableApplicationContext createAndRefreshContext(
        SpringApplicationRunListeners listeners,
        ApplicationArguments applicationArguments) {

    ...    if (this.bannerMode != Banner.Mode.OFF) {        printBanner(environment);
    }
    ...
}protected void printBanner(Environment environment) {
    Banner selectedBanner = selectBanner(environment);    if (this.bannerMode == Banner.Mode.LOG) {        try {
            logger.info(createStringFromBanner(selectedBanner, environment));
        }        catch (UnsupportedEncodingException ex) {
            logger.warn("Failed to create String for banner", ex);
        }
    }    else {
        selectedBanner.printBanner(environment, this.mainApplicationClass,
                System.out);
    }
}private Banner selectBanner(Environment environment) {
    String location = environment.getProperty(BANNER_LOCATION_PROPERTY,
            BANNER_LOCATION_PROPERTY_VALUE);
    ResourceLoader resourceLoader = this.resourceLoader != null ? this.resourceLoader
            : new DefaultResourceLoader(getClassLoader());
    Resource resource = resourceLoader.getResource(location);    if (resource.exists()) {        return new ResourceBanner(resource);
    }    if (this.banner != null) {        return this.banner;
    }    return DEFAULT_BANNER;
}private String createStringFromBanner(Banner banner, Environment environment)        throws UnsupportedEncodingException {
    ByteArrayOutputStream baos = new ByteArrayOutputStream();
    banner.printBanner(environment, this.mainApplicationClass, new PrintStream(baos));
    String charset = environment.getProperty("banner.charset", "UTF-8");    return baos.toString(charset);
}
로그인 후 복사

printBanner方法中,首先会调用selectBanner方法得到一个banner对象,然后判断bannerMode的类型,如果是Banner.Mode.LOG,那么将banner对象转换为字符串,打印一条info日志,否则的话,调用banner对象的printbanner方法,将banner打印到标准输出System.out。

在我们的例子中,bannerMode是Banner.Mode.Console,而且也不曾提供过banner.txt这样的资源文件。所以selectBanner方法中得到到便是默认的banner对象,即SpringBootBanner类的对象:

以下代码摘自:org.springframework.boot.SpringBootBannerprivate static final String[] BANNER = { "",        "  .   ____          _            __ _ _",        " /\\\\ / ___&#39;_ __ _ _(_)_ __  __ _ \\ \\ \\ \\",        "( ( )\\___ | &#39;_ | &#39;_| | &#39;_ \\/ _` | \\ \\ \\ \\",        " \\\\/  ___)| |_)| | | | | || (_| |  ) ) ) )",        "  &#39;  |____| .__|_| |_|_| |_\\__, | / / / /",        " =========|_|==============|___/=/_/_/_/" };private static final String SPRING_BOOT = " :: Spring Boot :: ";private static final int STRAP_LINE_SIZE = 42;@Overridepublic void printBanner(Environment environment, Class<?> sourceClass,
        PrintStream printStream) {    for (String line : BANNER) {
        printStream.println(line);
    }
    String version = SpringBootVersion.getVersion();
    version = (version == null ? "" : " (v" + version + ")");
    String padding = "";    while (padding.length() < STRAP_LINE_SIZE
            - (version.length() + SPRING_BOOT.length())) {
        padding += " ";
    }

    printStream.println(AnsiOutput.toString(AnsiColor.GREEN, SPRING_BOOT,
            AnsiColor.DEFAULT, padding, AnsiStyle.FAINT, version));
    printStream.println();
}
로그인 후 복사

先打印个Spring的图形,然后打印个Spring Boot的文本,再然后打印一下Spring Boot的版本。会在控制台看到如下输出:

以下内容是程序启动后在console的输出:

  .   ____          _            __ _ _
 /\\ / ___&#39;_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | &#39;_ | &#39;_| | &#39;_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )  &#39;  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v1.3.3.RELEASE)
로그인 후 복사

我的天。分析启动流程这么久,终于在屏幕有一行输出了,不容易。

创建ApplicationContext

private Class<? extends ConfigurableApplicationContext> applicationContextClass;public static final String DEFAULT_CONTEXT_CLASS = "org.springframework.context."
        + "annotation.AnnotationConfigApplicationContext";public static final String DEFAULT_WEB_CONTEXT_CLASS = "org.springframework."
        + "boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext";private ConfigurableApplicationContext createAndRefreshContext(
        SpringApplicationRunListeners listeners,
        ApplicationArguments applicationArguments) {
    ConfigurableApplicationContext context;

    ...

    context = createApplicationContext();
    context.setEnvironment(environment);    postProcessApplicationContext(context);    applyInitializers(context);
    listeners.contextPrepared(context);    if (this.logStartupInfo) {        logStartupInfo(context.getParent() == null);        logStartupProfileInfo(context);
    }

    ...    return context;
}protected ConfigurableApplicationContext createApplicationContext() {
    Class<?> contextClass = this.applicationContextClass;    if (contextClass == null) {        try {
            contextClass = Class.forName(this.webEnvironment
                    ? DEFAULT_WEB_CONTEXT_CLASS : DEFAULT_CONTEXT_CLASS);
        }        catch (ClassNotFoundException ex) {            throw new IllegalStateException(                    "Unable create a default ApplicationContext, "
                            + "please specify an ApplicationContextClass",
                    ex);
        }
    }    return (ConfigurableApplicationContext) BeanUtils.instantiate(contextClass);
}
로그인 후 복사

createAndRefreshContext中调用createApplicationContext获取创建ApplicationContext,可以看到,当检测到本次程序是一个web应用程序(成员变量webEnvironment为true)的时候,就加载类DEFAULT_WEB_CONTEXT_CLASS,否则的话加载DEFAULT_CONTEXT_CLASS。我们的例子是一个web应用程序,所以会加载DEFAULT_WEB_CONTEXT_CLASS,也就是org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext。我们先来看一看这个AnnotationConfigEmbeddedWebApplicationContext具体有什么功能。下图画出了它的继承体系。


可以看到我们加载的这个AnnotationConfigEmbeddedWebApplicationContext类,从名字就可以看出来,首先是一个WebApplicationContext实现了WebApplicationContext接口,然后是一个EmbeddedWebApplicationContext,这意味着它会自动创建并初始化一个EmbeddedServletContainer,同时还支持AnnotationConfig,会将使用注解标注的bean注册到ApplicationContext中。更详细的过程,后面在例子中再一一剖析。

可以看到在加载类对象AnnotationConfigEmbeddedWebApplicationContext之后,createApplicationContext方法中紧接着调用BeanUtils的instantiate方法来创建ApplicationContext对象,其代码如下:

以下代码摘自:org.springframework.beans.BeanUtilspublic static <T> T instantiate(Class<T> clazz) throws BeanInstantiationException {
    Assert.notNull(clazz, "Class must not be null");    if (clazz.isInterface()) {        throw new BeanInstantiationException(clazz, "Specified class is an interface");
    }    try {        return clazz.newInstance();
    }    catch (InstantiationException ex) {        throw new BeanInstantiationException(clazz, "Is it an abstract class?", ex);
    }    catch (IllegalAccessException ex) {        throw new BeanInstantiationException(clazz, "Is the constructor accessible?", ex);
    }
}
로그인 후 복사

通过调用Class对象的newInstance()方法来实例化对象,这等同于直接调用类的空的构造方法,所以我们来看AnnotationConfigEmbeddedWebApplicationContext类的构造方法:

以下代码摘自:org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContextpublic AnnotationConfigEmbeddedWebApplicationContext() {    this.reader = new AnnotatedBeanDefinitionReader(this);    this.scanner = new ClassPathBeanDefinitionScanner(this);
}@Overridepublic void setEnvironment(ConfigurableEnvironment environment) {    super.setEnvironment(environment);    this.reader.setEnvironment(environment);    this.scanner.setEnvironment(environment);
}
로그인 후 복사

构造方法中初始化了两个成员变量,类型分别为AnnotatedBeanDefinitionReader和ClassPathBeanDefinitionScanner用以加载使用注解的bean定义。

这样ApplicationContext对象就创建出来了,在createAndRefreshContext方法中创建了ApplicationContext对象之后会紧接着调用其setEnvironment将我们之前准备好的Environment对象赋值进去。之后分别调用postProcessApplicationContext和applyInitializers做一些处理和初始化的操作。

先来看看postProcessApplicationContext:

protected void postProcessApplicationContext(ConfigurableApplicationContext context) {    if (this.webEnvironment) {        if (context instanceof ConfigurableWebApplicationContext) {
            ConfigurableWebApplicationContext configurableContext = (ConfigurableWebApplicationContext) context;            if (this.beanNameGenerator != null) {
                configurableContext.getBeanFactory().registerSingleton(
                        AnnotationConfigUtils.CONFIGURATION_BEAN_NAME_GENERATOR,                        this.beanNameGenerator);
            }
        }
    }    if (this.resourceLoader != null) {        if (context instanceof GenericApplicationContext) {
            ((GenericApplicationContext) context)
                    .setResourceLoader(this.resourceLoader);
        }        if (context instanceof DefaultResourceLoader) {
            ((DefaultResourceLoader) context)
                    .setClassLoader(this.resourceLoader.getClassLoader());
        }
    }
}
로그인 후 복사

如果成员变量beanNameGenerator不为Null,那么为ApplicationContext对象注册beanNameGenerator bean。如果成员变量resourceLoader不为null,则为ApplicationContext对象设置ResourceLoader。我们的例子中,这两个成员变量都为Null,所以什么都不做。

之后是applyInitializers方法:

protected void applyInitializers(ConfigurableApplicationContext context) {    for (ApplicationContextInitializer initializer : getInitializers()) {
        Class<?> requiredType = GenericTypeResolver.resolveTypeArgument(
                initializer.getClass(), ApplicationContextInitializer.class);
        Assert.isInstanceOf(requiredType, context, "Unable to call initializer.");
        initializer.initialize(context);
    }
}public Set<ApplicationContextInitializer<?>> getInitializers() {    return asUnmodifiableOrderedSet(this.initializers);
}private static <E> Set<E> asUnmodifiableOrderedSet(Collection<E> elements) {
    List<E> list = new ArrayList<E>();
    list.addAll(elements);
    Collections.sort(list, AnnotationAwareOrderComparator.INSTANCE);    return new LinkedHashSet<E>(list);
}
로그인 후 복사

위 내용은 Spring Boot의 시작 프로세스에 대한 관련 소개의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

관련 라벨:
원천:php.cn
본 웹사이트의 성명
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.
인기 튜토리얼
더>
최신 다운로드
더>
웹 효과
웹사이트 소스 코드
웹사이트 자료
프론트엔드 템플릿