spring boot 的啓動代碼很簡單,最精簡的代碼以下。html
@Configuration @SpringBootApplication public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } }
注意:spring啓動的main方法必須包含在包內java
啓動代碼很簡單,但在初始化過程當中,spring作了不少工做,打開run方法,咱們能夠看到以下代碼web
public static ConfigurableApplicationContext run(Object[] sources, String[] args) { return new SpringApplication(sources).run(args); }
大體分爲兩部分。spring
初始化的主要工做都在這個方法中完成。緩存
private void initialize(Object[] 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(); }
經過判斷是否包含WEB_ENVIRONMENT_CLASSES中定義的類來決定是不是web項目。tomcat
咱們應用的是compile("org.springframework.boot:spring-boot-starter-web"), 因此間接引用了org.springframework.web.context.ConfigurableWebApplicationContext,推論爲web環境。mvc
若是隻引用org.springframework.boot:spring-boot-starter,就不是web項目,不會啓動內置的tomcat。oracle
private static final String[] WEB_ENVIRONMENT_CLASSES = { "javax.servlet.Servlet", "org.springframework.web.context.ConfigurableWebApplicationContext" }; private boolean deduceWebEnvironment() { for (String className : WEB_ENVIRONMENT_CLASSES) { if (!ClassUtils.isPresent(className, null)) { return false; } } return true; }
下面這段代碼比較長,目的是經過META-INF/spring.factories中的org.springframework.context.ApplicationContextInitializer 讀取配置並初始化,排序。app
public void setInitializers( Collection<? extends ApplicationContextInitializer<?>> initializers) { this.initializers = new ArrayList<ApplicationContextInitializer<?>>(); this.initializers.addAll(initializers); } 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; } 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 .getDeclaredConstructor(parameterTypes); T instance = (T) BeanUtils.instantiateClass(constructor, args); instances.add(instance); } catch (Throwable ex) { throw new IllegalArgumentException( "Cannot instantiate " + type + " : " + name, ex); } } return instances; } //SpringFactoriesLoader /** * The location to look for factories. * <p>Can be present in multiple JAR files. */ 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); } }
初始化ApplicationListener的過程和ApplicationContextInitializer同樣,就不在贅述了。less
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
經過調用棧獲取啓動main函數
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; }
上面初始化的配置信息在啓動的時候會在相應的位置起做用,下面咱們分解啓動。
通過上面的初始化過程,咱們已經有了一個SpringApplication對象,根據SpringApplication類的靜態run方法分析, 接下來會調用SpringApplication對象的run方法。咱們接下來就分析這個對象的run方法。
public ConfigurableApplicationContext run(String... args) { // 啓動計時器 StopWatch stopWatch = new StopWatch(); stopWatch.start(); ConfigurableApplicationContext context = null; FailureAnalyzers analyzers = null; configureHeadlessProperty(); // SpringApplicationRunListeners listeners = getRunListeners(args); listeners.started(); try { //建立並刷新ApplicationContext ApplicationArguments applicationArguments = new DefaultApplicationArguments( args); ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments); // print spring boot banner and version Banner printedBanner = printBanner(environment); context = createApplicationContext(); analyzers = new FailureAnalyzers(context); prepareContext(context, environment, listeners, applicationArguments, printedBanner); refreshContext(context); 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, analyzers, ex); throw new IllegalStateException(ex); } }
上面的方法經過名字很明確它實現的功能。
有關java.awt.headless的內容參考: Using Headless Mode in the Java SE Platform
咱們來詳細的觀察一下第一個事件,啓動事件。
listeners.started(); //EventPublishingRunListener.java public void started() { this.initialMulticaster .multicastEvent(new ApplicationStartedEvent(this.application, this.args)); } public void multicastEvent(ApplicationEvent event) { multicastEvent(event, resolveDefaultEventType(event)); } public void multicastEvent(final ApplicationEvent event, ResolvableType eventType) { ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event)); for (final ApplicationListener<?> listener : getApplicationListeners(event, type)) { Executor executor = getTaskExecutor(); if (executor != null) { executor.execute(new Runnable() { @Override public void run() { invokeListener(listener, event); } }); } else { invokeListener(listener, event); } } } protected Collection<ApplicationListener<?>> getApplicationListeners( ApplicationEvent event, ResolvableType eventType) { Object source = event.getSource(); Class<?> sourceType = (source != null ? source.getClass() : null); ListenerCacheKey cacheKey = new ListenerCacheKey(eventType, sourceType); // Quick check for existing entry on ConcurrentHashMap... ListenerRetriever retriever = this.retrieverCache.get(cacheKey); if (retriever != null) { return retriever.getApplicationListeners(); } if (this.beanClassLoader == null || (ClassUtils.isCacheSafe(event.getClass(), this.beanClassLoader) && (sourceType == null || ClassUtils.isCacheSafe(sourceType, this.beanClassLoader)))) { // Fully synchronized building and caching of a ListenerRetriever synchronized (this.retrievalMutex) { retriever = this.retrieverCache.get(cacheKey); if (retriever != null) { return retriever.getApplicationListeners(); } retriever = new ListenerRetriever(true); Collection<ApplicationListener<?>> listeners = retrieveApplicationListeners(eventType, sourceType, retriever); this.retrieverCache.put(cacheKey, retriever); return listeners; } } else { // No ListenerRetriever caching -> no synchronization necessary return retrieveApplicationListeners(eventType, sourceType, null); } } private Collection<ApplicationListener<?>> retrieveApplicationListeners( ResolvableType eventType, Class<?> sourceType, ListenerRetriever retriever) { LinkedList<ApplicationListener<?>> allListeners = new LinkedList<ApplicationListener<?>>(); Set<ApplicationListener<?>> listeners; Set<String> listenerBeans; synchronized (this.retrievalMutex) { listeners = new LinkedHashSet<ApplicationListener<?>>(this.defaultRetriever.applicationListeners); listenerBeans = new LinkedHashSet<String>(this.defaultRetriever.applicationListenerBeans); } for (ApplicationListener<?> listener : listeners) { if (supportsEvent(listener, eventType, sourceType)) { if (retriever != null) { retriever.applicationListeners.add(listener); } allListeners.add(listener); } } if (!listenerBeans.isEmpty()) { BeanFactory beanFactory = getBeanFactory(); for (String listenerBeanName : listenerBeans) { try { Class<?> listenerType = beanFactory.getType(listenerBeanName); if (listenerType == null || supportsEvent(listenerType, eventType)) { ApplicationListener<?> listener = beanFactory.getBean(listenerBeanName, ApplicationListener.class); if (!allListeners.contains(listener) && supportsEvent(listener, eventType, sourceType)) { if (retriever != null) { retriever.applicationListenerBeans.add(listenerBeanName); } allListeners.add(listener); } } } catch (NoSuchBeanDefinitionException ex) { // Singleton listener instance (without backing bean definition) disappeared - // probably in the middle of the destruction phase } } } AnnotationAwareOrderComparator.sort(allListeners); return allListeners; } protected boolean supportsEvent(ApplicationListener<?> listener, ResolvableType eventType, Class<?> sourceType) { GenericApplicationListener smartListener = (listener instanceof GenericApplicationListener ? (GenericApplicationListener) listener : new GenericApplicationListenerAdapter(listener)); return (smartListener.supportsEventType(eventType) && smartListener.supportsSourceType(sourceType)); }
方法比較多,咱們一點一點的分析。
首先咱們注意到事件被封裝成了 ResolvableType 類,在經過 getApplicationListeners 方法過濾支持此事件的listener(初始化時加載的), 當中還增長了緩存處理。
在繼續往下跟蹤,發現最後是經過 supportsEvent 方法判斷是否支持事件,繼承關係以下。
public interface GenericApplicationListener extends ApplicationListener<ApplicationEvent>, Ordered ApplicationListener<E extends ApplicationEvent>
繼承自 GenericApplicationListener 的 GenericApplicationListenerAdapter是一個適配器, 他代理真正的 Listener,並實現了擴展方法來判斷是否支持此事件。以下圖:
這裏的判斷是個亮點! 首先在此看 ResolvableType ,他是4.0新加入的類。從doc上能夠看出,它是一個工具類,可以匹配父類,接口,甚至泛型,功能仍是至關強大的。 事件的判斷全靠它來完成關鍵的判斷。咱們看兩個初始化時加載的 Listener:
從類聲明能夠看出Event泛型是不同的,這就決定了該Listener支持什麼事件。 啓動時的事件是 ApplicationStartedEvent ,根據 Listener 能夠很清楚 ApplicationStartedEvent,ApplicationEvent 會被調用。 其餘的的事件也是相同原理。
接下來是配置,由下面代碼方法實現。
ApplicationArguments applicationArguments = new DefaultApplicationArguments( args); ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments); private ConfigurableEnvironment prepareEnvironment( SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments) { // Create and configure the environment ConfigurableEnvironment environment = getOrCreateEnvironment(); configureEnvironment(environment, applicationArguments.getSourceArgs()); listeners.environmentPrepared(environment); if (isWebEnvironment(environment) && !this.webEnvironment) { environment = convertToStandardEnvironment(environment); } return environment; } // configureEnvironment 方法先是調用configurePropertySources來配置properties, // 而後調用 configureProfiles 來配置profiles 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()])); }
listeners.environmentPrepared(environment); 廣播 ApplicationEnvironmentPreparedEvent 事件。
打印的內容是spring圖形和spring boot的版本。就不詳細說明了,有興趣的能夠自行瀏覽。
根據 webEnvironment 建立 DEFAULT_WEB_CONTEXT_CLASS 或 DEFAULT_CONTEXT_CLASS。
/** * The class name of application context that will be used by default for non-web * environments. */ public static final String DEFAULT_CONTEXT_CLASS = "org.springframework.context." + "annotation.AnnotationConfigApplicationContext"; /** * The class name of application context that will be used by default for web * environments. */ public static final String DEFAULT_WEB_CONTEXT_CLASS = "org.springframework." + "boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext";
真正的啓動如今纔開始。咱們看到最終調用的是refresh方法。
在查看代碼以前,先說明一下當前 Context 對象的繼承關係,請看下圖:
上面的分析已經得知當前啓動的是一個web項目, 因此在createApplicationContext()方法中建立的ApplicationContext是DEFAULT_WEB_CONTEXT_CLASS,也就是AnnotationConfigEmbeddedWebApplicationContext。 從圖中咱們能夠得知 AnnotationConfigEmbeddedWebApplicationContext 的繼承關係。因爲繼承自 EmbeddedWebApplicationContext , 賦予了它內嵌 servlet container 的能力。啓動容器的代碼以下:
@Override protected void onRefresh() { super.onRefresh(); try { createEmbeddedServletContainer(); } catch (Throwable ex) { throw new ApplicationContextException("Unable to start embedded container", ex); } }
啓動過程來自模板類 AbstractApplicationContext,和spring mvc 是同樣的,這裏就不過多解釋了。
@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. // 初始化並啓動 Servlet Container onRefresh(); // Check for listener beans and register them. registerListeners(); // Instantiate all remaining (non-lazy-init) singletons. finishBeanFactoryInitialization(beanFactory); // Last step: publish corresponding event. // start embed servlet container 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(); } } }
啓動完成後,會廣播事件,中止計時,整個啓動過程結束。
afterRefresh(context, applicationArguments); listeners.finished(context, null); stopWatch.stop();