SpringBoot源碼深度解析(一)深入理解SpringApplication

對於每一個使用過SpringBoot/SpringCloud的程序員而言,下面這段代碼可能大家都不陌生:

public class TestApplication {

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

}

SpringApplication是springboot驅動spring應用上下文的引導類,用於啓動和引導springBoot應用程序。

從SpringApplication.run(TestApplication.class, args)開始追蹤,第一步我們會來到這個地方:

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

然後下一步跳轉到這個地方:

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

按照註釋來說,這個方法接收的兩個參數分別是:

第一個參數 primarySource:加載的主要資源類

第二個參數 args:傳遞給應用的應用參數

繼續向下,我們可以看到,這一步返回了一個 new SpringApplication(primarySources).run(args)

所以說,我們先來看下SpringApplication實例化的過程:

    /**
	 * Create a new {@link SpringApplication} instance. The application context will load
	 * beans from the specified primary sources (see {@link SpringApplication class-level}
	 * documentation for details. The instance can be customized before calling
	 * {@link #run(String...)}.
	 * @param primarySources the primary bean sources
	 * @see #run(Class, String[])
	 * @see #SpringApplication(ResourceLoader, Class...)
	 * @see #setSources(Set)
	 */
	public SpringApplication(Class<?>... primarySources) {
		this(null, primarySources);
	}

這一步需要注意的是,這個地方傳遞了一個 null

    /**
	 * Create a new {@link SpringApplication} instance. The application context will load
	 * beans from the specified primary sources (see {@link SpringApplication class-level}
	 * documentation for details. The instance can be customized before calling
	 * {@link #run(String...)}.
	 * @param resourceLoader the resource loader to use
	 * @param primarySources the primary bean sources
	 * @see #run(Class, String[])
	 * @see #setSources(Set)
	 */
	@SuppressWarnings({ "unchecked", "rawtypes" })
	public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
		this.resourceLoader = resourceLoader;
		Assert.notNull(primarySources, "PrimarySources must not be null");
		this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
		this.webApplicationType = deduceWebApplicationType();
		setInitializers((Collection) getSpringFactoriesInstances(
				ApplicationContextInitializer.class));
		setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
		this.mainApplicationClass = deduceMainApplicationClass();
	}

然後便到了這個構造器。可以看出,這個構造器大概分爲了七步完成初始化:

//第一步,初始化資源加載器爲 null
this.resourceLoader = resourceLoader;
//第二步,使用斷言,確保資源類不能爲null
Assert.notNull(primarySources, "PrimarySources must not be null");
//第三步,構建一個主要資源類的LinkedHashSet
this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
//第四步,判斷當前web應用的基本類型,判斷具體環境
this.webApplicationType = deduceWebApplicationType();

我們來詳細看下deduceWebApplicationType()這個方法:

private static final String REACTIVE_WEB_ENVIRONMENT_CLASS = "org.springframework.web.reactive.DispatcherHandler";
private static final String MVC_WEB_ENVIRONMENT_CLASS = "org.springframework.web.servlet.DispatcherServlet";
private static final String[] WEB_ENVIRONMENT_CLASSES = { "javax.servlet.Servlet",
            "org.springframework.web.context.ConfigurableWebApplicationContext" };


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

這段代碼,就是推斷應用的類型 ,創建的是一個 SERVLET 應用還是 REACTIVE應用或者是 NONE。

這裏簡單看一下ClassUtils.isPresent這個方法

/**
 * Determine whether the {@link Class} identified by the supplied name is present
 * and can be loaded. Will return {@code false} if either the class or
 * one of its dependencies is not present or cannot be loaded.
 * @param className the name of the class to check
 * @param classLoader the class loader to use
 * (may be {@code null} which indicates the default class loader)
 * @return whether the specified class is present
 */
public static boolean isPresent(String className, @Nullable ClassLoader classLoader){
		try {
			forName(className, classLoader);
			return true;
		}
		catch (Throwable ex) {
			// Class or one of its dependencies is not present...
			return false;
		}
	}


/**
 * Replacement for {@code Class.forName()} that also returns Class instances
 * for primitives (e.g. "int") and array class names (e.g. "String[]").
 * Furthermore, it is also capable of resolving inner class names in Java source
 * style (e.g. "java.lang.Thread.State" instead of "java.lang.Thread$State").
 * @param name the name of the Class
 * @param classLoader the class loader to use
 * (may be {@code null}, which indicates the default class loader)
 * @return a class instance for the supplied name
 * @throws ClassNotFoundException if the class was not found
 * @throws LinkageError if the class file could not be loaded
 * @see Class#forName(String, boolean, ClassLoader)
 */
public static Class<?> forName(String name, @Nullable ClassLoader classLoader)
			throws ClassNotFoundException, LinkageError {
    //採用斷言確定類名不爲空
	Assert.notNull(name, "Name must not be null");

	Class<?> clazz = resolvePrimitiveClassName(name);
	if (clazz == null) {
		clazz = commonClassCache.get(name);
	}
	if (clazz != null) {
		return clazz;
	}
	// "java.lang.String[]" style arrays
	if (name.endsWith(ARRAY_SUFFIX)) {
		String elementClassName = name.substring(0, name.length() - ARRAY_SUFFIX.length());
		Class<?> elementClass = forName(elementClassName, classLoader);
		return Array.newInstance(elementClass, 0).getClass();
	}

	// "[Ljava.lang.String;" style arrays
	if (name.startsWith(NON_PRIMITIVE_ARRAY_PREFIX) && name.endsWith(";")) {
		String elementName = name.substring(NON_PRIMITIVE_ARRAY_PREFIX.length(), name.length() - 1);
		Class<?> elementClass = forName(elementName, classLoader);
		return Array.newInstance(elementClass, 0).getClass();
	}

	// "[[I" or "[[Ljava.lang.String;" style arrays
	if (name.startsWith(INTERNAL_ARRAY_PREFIX)) {
		String elementName = name.substring(INTERNAL_ARRAY_PREFIX.length());
		Class<?> elementClass = forName(elementName, classLoader);
		return Array.newInstance(elementClass, 0).getClass();
	}
	ClassLoader clToUse = classLoader;
	if (clToUse == null) {
		clToUse = getDefaultClassLoader();
	}
	try {
		return (clToUse != null ? clToUse.loadClass(name) : Class.forName(name));
	}
	catch (ClassNotFoundException ex) {
		int lastDotIndex = name.lastIndexOf(PACKAGE_SEPARATOR);
		if (lastDotIndex != -1) {
			String innerClassName =
					name.substring(0, lastDotIndex) + INNER_CLASS_SEPARATOR + name.substring(lastDotIndex + 1);
			try {
				return (clToUse != null ? clToUse.loadClass(innerClassName) : Class.forName(innerClassName));
			}
			catch (ClassNotFoundException ex2) {
				// Swallow - let original exception get through
			}
		}
		throw ex;
	}
}

這邊簡單談一下java的類加載機制:

java類加載過程可以分爲三個步驟:

  • 加載 
  • 鏈接 
  • 初始化 

類加載器就是來實現類加載的功能的。java爲我們提供了三種不同類型的類加載器,BootstrapClassLoader、ExtensionClassLoader和AppClassLoader,三種類加載器分別針對核心類、擴展類和classPath下以及我們自定義的jar進行加載。類加載器根據雙親委派模型進行工作,即當一個類被加載時,先交由父加載器進行,如果父加載器無法找到這個類,再交給子加載器進行加載。當然,我們可以自定義子加載器進行個性化加載。

————https://blog.csdn.net/qq_21399231/article/details/80751489

這邊再簡單說個概念:

對於類中靜態代碼塊的初始化工作,是在鏈接階段中的第二個小階段準備階段執行的。

假設你定義了一個靜態變量a:

private static int a =  10;

在鏈接階段,JVM在準備階段便將a設置爲他的初始值,也就是 0;

然後,在鏈接階段執行完成之後,在初始化階段,a才被設置爲 10;

關於具體的類加載機制,可以參見博客內其他文章。 

上文 forName 方法中,我們可以看到這麼一塊:

ClassLoader clToUse = classLoader;
if (clToUse == null) {
	clToUse = getDefaultClassLoader();
}


/**
 * Return the default ClassLoader to use: typically the thread context
 * ClassLoader, if available; the ClassLoader that loaded the ClassUtils
 * class will be used as fallback.
 * <p>Call this method if you intend to use the thread context ClassLoader
 * in a scenario where you clearly prefer a non-null ClassLoader reference:
 * for example, for class path resource loading (but not necessarily for
 * {@code Class.forName}, which accepts a {@code null} ClassLoader
 * reference as well).
 * @return the default ClassLoader (only {@code null} if even the system
 * ClassLoader isn't accessible)
 * @see Thread#getContextClassLoader()
 * @see ClassLoader#getSystemClassLoader()
 */
@Nullable
public static ClassLoader getDefaultClassLoader() {
	ClassLoader cl = null;
	try {
		cl = Thread.currentThread().getContextClassLoader();
	}
	catch (Throwable ex) {
		// Cannot access thread context ClassLoader - falling back...
	}
	if (cl == null) {
		// No thread context class loader -> use class loader of this class.
		cl = ClassUtils.class.getClassLoader();
		if (cl == null) {
			// getClassLoader() returning null indicates the bootstrap ClassLoader
			try {
				cl = ClassLoader.getSystemClassLoader();
			}
			catch (Throwable ex) {
				// Cannot access system ClassLoader - oh well, maybe the caller can live with null...
			}
		}
	}
	return cl;
}

 這就很明瞭了,如果輸入的類加載器爲空,那麼系統便從當前線程上下文中獲取默認的類加載器,如果不進行設置,那麼返回的便是默認的類加載器app ClassLoader()。

//第五步,初始化應用上下文
setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));

 先來看下這個:

	private List<ApplicationContextInitializer<?>> initializers;

/**
	 * Sets the {@link ApplicationContextInitializer} that will be applied to the Spring
	 * {@link ApplicationContext}.
	 * @param initializers the initializers to set
	 */
	public void setInitializers(
			Collection<? extends ApplicationContextInitializer<?>> initializers) {
		this.initializers = new ArrayList<>();
		this.initializers.addAll(initializers);
	}

 這裏面實例化了一個數組,用於存放 initializers 

這邊便涉及到了一個 ApplicationContextInitializer 

ApplicationContextInitializer 是一個接口,用於ConfigurableApplicationContext通過調用refresh函數來初始化Spring容器之前的回調函數,通常在web應用中,設計在初始化Spring容器之前調用。例如依賴於容器ConfigurableApplicationContext中的Enviroment來記錄一些配置信息或者使一些配置文件生效;

這裏邊涉及到了兩個東西:Spring Factories 和 SPI 原則 。

拋開這兩個東西先不管,我們繼續看 getSpringFactoriesInstances(ApplicationContextInitializer.class) 這一塊代碼。

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

private <T> Collection<T> getSpringFactoriesInstances(Class<T> type,Class<?>[],parameterTypes, Object... args) {
    //調用當前線程默認的classloader 
    ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
    
    //使用當前classloader 從類路徑下找到META-INF/spring.factories配置的所有ApplicationContextInitializer集合
    Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));

    //創建相關實例
    List<T> instances = createSpringFactoriesInstances(type, parameterTypes,classLoader, args, names);
    AnnotationAwareOrderComparator.sort(instances);
    return instances;
}

@SuppressWarnings("unchecked")
private <T> List<T> createSpringFactoriesInstances(Class<T> type,Class<?>[] parameterTypes, ClassLoader classLoader, Object[] args,Set<String> names) {
	List<T> instances = new ArrayList<>(names.size());
	//循環創建實例
    for (String name : names) {
		try {
                        //使用ClassUtils.forName  指定classLoader獲取對應class對象
			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工廠加載機制是Spring內部提供的一個約定俗成的加載方式,與java spi類似,只需要在模塊的META-INF/spring.factories文件,這個Properties格式的文件中的key是接口、註解、或抽象類的全名,value是以逗號 “ , “ 分隔的實現類,使用SpringFactoriesLoader來實現相應的實現類注入Spirng容器中。

這裏簡單說下Class.forName(className)和 ClassLoader.loadClass(className) 的區別:

Class.forName(className)方法,內部實際調用的方法是  Class.forName(className,true,classloader);

第2個boolean參數表示類是否需要初始化,  Class.forName(className)默認是需要初始化。

一旦初始化,就會觸發目標對象的 static塊代碼執行,static參數也也會被再次初始化。

    

ClassLoader.loadClass(className)方法,內部實際調用的方法是  ClassLoader.loadClass(className,false);

第2個 boolean參數,表示目標對象是否進行鏈接,false表示不進行鏈接,由上面介紹可以,

不進行鏈接意味着不進行包括初始化等一些列步驟,那麼靜態塊和靜態對象就不會得到執行
//第六步,設置監聽器
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));

 先看下這一段

private List<ApplicationListener<?>> listeners;

/**
 * Sets the {@link ApplicationListener}s that will be applied to the SpringApplication
 * and registered with the {@link ApplicationContext}.
 * @param listeners the listeners to set
 */
public void setListeners(Collection<? extends ApplicationListener<?>> listeners) {
	this.listeners = new ArrayList<>();
	this.listeners.addAll(listeners);
}

這一段代碼的意思就是構建一個 List<ApplicationListener<?>> listeners,這麼個東西。

@FunctionalInterface
public interface ApplicationListener<E extends ApplicationEvent> extends EventListener {

	/**
	 * Handle an application event.
	 * @param event the event to respond to
	 */
	void onApplicationEvent(E event);

}

/**
 * Class to be extended by all application events. Abstract as it
 * doesn't make sense for generic events to be published directly.
 *
 * @author Rod Johnson
 * @author Juergen Hoeller
 */
public abstract class ApplicationEvent extends EventObject {

	/** use serialVersionUID from Spring 1.2 for interoperability */
	private static final long serialVersionUID = 7099057708183571937L;

	/** System time when the event happened */
	private final long timestamp;


	/**
	 * Create a new ApplicationEvent.
	 * @param source the object on which the event initially occurred (never {@code null})
	 */
	public ApplicationEvent(Object source) {
		super(source);
		this.timestamp = System.currentTimeMillis();
	}


	/**
	 * Return the system time in milliseconds when the event happened.
	 */
	public final long getTimestamp() {
		return this.timestamp;
	}

}

ApplicationContext事件機制是觀察者設計模式的實現,通過ApplicationEvent類和ApplicationListener接口,可以實現ApplicationContext事件處理。

參見:https://www.jianshu.com/p/0269a8390d5f

//第七步,推斷主入口應用類
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對象的構建和初始化之後,就開始執行run方法的邏輯了。具體源代碼如下文所示:

/**
 * Run the Spring application, creating and refreshing a new
 * {@link ApplicationContext}.
 * @param args the application arguments (usually passed from a Java main method)
 * @return a running {@link ApplicationContext}
 */
public ConfigurableApplicationContext run(String... args) {
        //統計應用啓動時間
	StopWatch stopWatch = new StopWatch();
	stopWatch.start();
        //初始化spring跟容器
	ConfigurableApplicationContext context = null;
        //自定義SpringApplication啓動錯誤的回調接口
	Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
        //設置系統屬性
        configureHeadlessProperty();
        //創建所有 Spring 運行監聽器併發布應用啓動事件
	SpringApplicationRunListeners listeners = getRunListeners(args);
	listeners.starting();
	try {
            //裝配參數和環境
            ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
            //發佈ApplicationEnvironmentPreparedEvent
            ConfigurableEnvironment environment = this.prepareEnvironment(listeners, applicationArguments);
            this.configureIgnoreBeanInfo(environment);
            Banner printedBanner = this.printBanner(environment);
            //創建ApplicationContext,並裝配
            context = this.createApplicationContext();
            this.getSpringFactoriesInstances(SpringBootExceptionReporter.class, new Class[]{ConfigurableApplicationContext.class}, context);
            //發佈ApplicationPreparedEvent
            this.prepareContext(context, environment, listeners, applicationArguments, printedBanner);
            this.refreshContext(context);
            this.afterRefresh(context, applicationArguments);
            stopWatch.stop();
            if (this.logStartupInfo) {
                (new StartupInfoLogger(this.mainApplicationClass)).logStarted(this.getApplicationLog(), stopWatch);
            }
            
            //發佈ApplicationStartedEvent
            listeners.started(context);
            //執行Spring中@Bean下的一些操作,如靜態方法等
            this.callRunners(context, applicationArguments);
        } catch (Throwable var9) {
            this.handleRunFailure(context, listeners, exceptionReporters, var9);
            throw new IllegalStateException(var9);
        }
 
        //發佈ApplicationReadyEvent
        listeners.running(context);
        return context;
}

參考文章:https://blog.csdn.net/hzhahsz/article/details/83960139

先看下configureHeadlessProperty

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

private boolean headless = true;

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

 這個方法的作用是配置了一個名爲java.awt.headless的系統屬性,具體功能就是讓應用程序沒有顯示器也能啓動。

SpringApplicationRunListeners listeners = getRunListeners(args);

 這段代碼的作用是獲取監聽器,具體源代碼如下:

private static final Log logger = LogFactory.getLog(SpringApplication.class);

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

}

private <T> Collection<T> getSpringFactoriesInstances(Class<T> type,Class<?>[] parameterTypes, Object... args) {
	ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
	// Use names and ensure unique to protect against duplicates
	Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
	List<T> instances = createSpringFactoriesInstances(type, parameterTypes,classLoader, args, names);
	AnnotationAwareOrderComparator.sort(instances);
	return instances;
}

 可以看到,加載監聽器的基本步驟和上文獲取ApplicationListener的方法基本一樣,都是通過springFactoryies從META-INF/spring.factories 中獲取。

接下來便是listeners.starting了

private final List<SpringApplicationRunListener> listeners;

listeners.starting();

public void starting() {
	for (SpringApplicationRunListener listener : this.listeners) {
		listener.starting();
	}
}



private final SpringApplication application;

private final String[] args;

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


@Override
public void multicastEvent(ApplicationEvent event) {
	multicastEvent(event, resolveDefaultEventType(event));
}


@Override
public void multicastEvent(final ApplicationEvent event, @Nullable ResolvableTypeeventType) {
	ResolvableType type = (eventType != null ? eventType :resolveDefaultEventType(event));
	for (final ApplicationListener<?> listener : getApplicationListeners(event, type)) {
		Executor executor = getTaskExecutor();
		if (executor != null) {
			executor.execute(() -> invokeListener(listener, event));
		}
		else {
			invokeListener(listener, event);
		}
	}
}

詳情參見:http://www.mamicode.com/info-detail-2644218.html

 

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