本文系轉載,本身mark一下,加深記憶和理解。原文地址:http://zhaox.github.io/java/2016/03/22/spring-boot-start-flowhtml
本文基於Spring Boot版本1.3.3, 使用了spring-boot-starter-web。java
配置完成後,編寫了代碼以下:git
@SpringBootApplication public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } } @RestController public class RootController { public static final String PATH_ROOT = "/"; @RequestMapping(PATH_ROOT) public String welcome() { return "Welcome!"; } }
雖然只有幾行代碼,可是這已是一個完整的Web程序,當訪問url的path部分爲」/」時,返回字符串」Welcome!」。github
首先是一個很是普通的java程序入口,一個符合約定的靜態main方法。在這個main方法中,調用了SpringApplication的靜態run方法,並將Application類對象和main方法的參數args做爲參數傳遞了進去。web
而後是一個使用了兩個Spring註解的RootController類,咱們在main方法中,沒有直接使用這個類。spring
如下代碼摘自:org.springframework.boot.SpringApplication public 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對象,並調用該對象的run方法。數組
如下代碼摘自:org.springframework.boot.SpringApplication public 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(); }
構造函數中調用initialize方法,初始化SpringApplication對象的成員變量sources,webEnvironment,initializers,listeners,mainApplicationClass。sources的賦值比較簡單,就是咱們傳給SpringApplication.run方法的參數。剩下的幾個,咱們依次來看看是怎麼作的。服務器
如下代碼摘自:org.springframework.boot.SpringApplication private 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是一個boolean,該成員變量用來表示當前應用程序是否是一個Web應用程序。那麼怎麼決定當前應用程序是否Web應用程序呢,是經過在classpath中查看是否存在WEB_ENVIRONMENT_CLASSES這個數組中所包含的類,若是存在那麼當前程序便是一個Web應用程序,反之則否則。 在本文的例子中webEnvironment的值爲true。oracle
initializers成員變量,是一個ApplicationContextInitializer類型對象的集合。 顧名思義,ApplicationContextInitializer是一個能夠用來初始化ApplicationContext的接口。app
如下代碼摘自:org.springframework.boot.SpringApplication private 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類型對象的列表。
如下代碼摘自:org.springframework.boot.SpringApplication private <T> Collection<? extends T> getSpringFactoriesInstances(Class<T> type) { return getSpringFactoriesInstances(type, new Class<?>[] {}); } private <T> Collection<? extends T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) { ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); // Use names and ensure unique to protect against duplicates Set<String> names = new LinkedHashSet<String>( SpringFactoriesLoader.loadFactoryNames(type, classLoader)); List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names); AnnotationAwareOrderComparator.sort(instances); return instances; }
在該方法中,首先經過調用SpringFactoriesLoader.loadFactoryNames(type, classLoader)來獲取全部Spring Factories的名字,而後調用createSpringFactoriesInstances方法根據讀取到的名字建立對象。最後會將建立好的對象列表排序並返回。
如下代碼摘自:org.springframework.core.io.support.SpringFactoriesLoader public 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.SpringApplication private <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,並說明了他們的做用。至於什麼時候應用他們,且聽後面慢慢分解。
如下代碼摘自:org.springframework.boot.SpringApplication private 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,並說明了他們的做用。至於他們什麼時候會被觸發,等事件出現時,咱們再說明。
如下代碼摘自:org.springframework.boot.SpringApplication private 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對象,根據SpringApplication類的靜態run方法一節中的分析,接下來會調用SpringApplication對象的run方法。咱們接下來就分析這個對象的run方法。
如下代碼摘自:org.springframework.boot.SpringApplication public 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,算是開始進入正題了。下面按照執行順序,介紹該方法所作的工做。
如下代碼摘自:org.springframework.boot.SpringApplication private 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模式,更多信息能夠參考這裏。
如下代碼摘自:org.springframework.boot.SpringApplication public 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.EventPublishingRunListener public EventPublishingRunListener(SpringApplication application, String[] args) { this.application = application; this.args = args; this.multicaster = new SimpleApplicationEventMulticaster(); for (ApplicationListener<?> listener : application.getListeners()) { this.multicaster.addApplicationListener(listener); } } @Override public void started() { publishEvent(new ApplicationStartedEvent(this.application, this.args)); } @Override public void environmentPrepared(ConfigurableEnvironment environment) { publishEvent(new ApplicationEnvironmentPreparedEvent(this.application, this.args, environment)); } @Override public void contextPrepared(ConfigurableApplicationContext context) { registerApplicationEventMulticaster(context); } @Override public 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)); } @Override public 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時,咱們已經加載的事件監聽器都作了什麼操做。至於其它事件的發佈,咱們按照代碼執行的順序在後面的章節在介紹。
如下代碼摘自:org.springframework.boot.liquibase.LiquibaseServiceLocatorApplicationListener @Override public void onApplicationEvent(ApplicationStartedEvent event) { if (ClassUtils.isPresent("liquibase.servicelocator.ServiceLocator", null)) { new LiquibasePresent().replaceServiceLocator(); } }
咱們的例子中,classpath中不存在liquibase,因此不執行任何操做。
@Override public 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的輸出。
@Override public 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事件的處理這樣就結束了。之後在介紹事件處理的時候,咱們只介紹監聽該事件的監聽器的操做,而不監聽的,就再也不說明了。
如下代碼摘自:org.springframework.boot.SpringApplication public 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方法在刷新以後作一些操做。
如下代碼摘自:org.springframework.boot.DefaultApplicationArguments DefaultApplicationArguments(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.SimpleCommandLinePropertySource public SimpleCommandLinePropertySource(String... args) { super(new SimpleCommandLineArgsParser().parse(args)); }
能夠看到是把main函數的args參數當作一個PropertySource來解析。咱們的例子中,args的長度爲0,因此這裏建立的DefaultApplicationArguments也沒有實際的內容。
如下代碼摘自:org.springframework.boot.SpringApplication private 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.SpringApplication private 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 @Override public void environmentPrepared(ConfigurableEnvironment environment) { publishEvent(new ApplicationEnvironmentPreparedEvent(this.application, this.args, environment)); }
好,如今來看一看咱們加載的ApplicationListener對象都有哪些響應了這個事件,作了什麼操做:
如下代碼摘自:org.springframework.boot.context.FileEncodingApplicationListener @Override public 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 'file.encoding' is currently '" + encoding + "'. It should be '" + desired + "' (as defined in 'spring.mandatoryFileEncoding')."); logger.error("Environment variable LANG is '" + System.getenv("LANG") + "'. You could use a locale setting that matches encoding='" + desired + "'."); logger.error("Environment variable LC_ALL is '" + System.getenv("LC_ALL") + "'. You could use a locale setting that matches encoding='" + desired + "'."); throw new IllegalStateException( "The Java Virtual Machine has not been configured to use the " + "desired default character encoding (" + desired + ")."); } } }
在咱們的例子中,由於沒有spring.mandatory_file_encoding的配置,因此這個響應方法什麼都不作。
如下代碼摘自:org.springframework.boot.context.config.AnsiOutputApplicationListener @Override public 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)); } }
咱們的例子中,這兩項配置都是空的,因此這個響應方法什麼都不作。
如下代碼摘自:org.springframework.boot.context.config.ConfigFileApplicationListener @Override public 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中來。
如下內容摘自:org.springframework.boot.context.config.DelegatingApplicationListener private static final String PROPERTY_NAME = "context.listener.classes"; private SimpleApplicationEventMulticaster multicaster; @Override public 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到該監聽器中。
如下代碼摘自:org.springframework.boot.logging.LoggingApplicationListener @Override public 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作一些初始化工做。關於日誌系統更詳細的討論,值得再寫一篇文章,就不在這裏展開討論了。
如下代碼摘自:org.springframework.boot.SpringApplication private 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.SpringBootBanner 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 + ")"); 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的輸出: . ____ _ __ _ _ /\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \ ( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \ \\/ ___)| |_)| | | | | || (_| | ) ) ) ) ' |____| .__|_| |_|_| |_\__, | / / / / =========|_|==============|___/=/_/_/_/ :: Spring Boot :: (v1.3.3.RELEASE)
個人天。分析啓動流程這麼久,終於在屏幕有一行輸出了,不容易。
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.BeanUtils public 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.AnnotationConfigEmbeddedWebApplicationContext public AnnotationConfigEmbeddedWebApplicationContext() { this.reader = new AnnotatedBeanDefinitionReader(this); this.scanner = new ClassPathBeanDefinitionScanner(this); } @Override public 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); }