springboot 2.0.3 源碼分析(一)SpringApplication的構造函數

     對我來說,寫博客是一個自我學習,自我提升的一種方式,本文所有的內容也是我查看了好多博主的文章後,在自我探索和驗證的過程,我會盡量把所有涉及到的方法都寫在本文中,也希望您能在閱讀的同時對我提出寶貴意見。

    我們都知道springboot都有一個main方法,main裏面調用SpringApplication.run()啓動整個spring-boot程序,args就是我們執行啓動命令帶入的參數,默認以空格隔開

 從main函數說起

   

/**
 * com.bicai.channelmanager
 * {@link }
 *
 * @author lydong
 * @see
 * @since 2018/7/10 上午11:48
 */
@MapperScan("com.bicai.channelmanager.**.mapper")
@EnableFeignClients({"com.bicai.channelmanager.server", "com.bicai.common.component.feign"})
@EnableEurekaClient
@SpringBootApplication
public class ChannelManagerBootStrapApplication {
    public static void main(String[] args) {
        SpringApplication.run(ChannelManagerBootStrapApplication.class, args);
    }
}

從run方法點進去看看

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

從這裏可以看出,我們傳入的class參數被包裝成數組,可以發現springboot其實可以同時開啓多個應用。執行run方法返回SpringApplication實例。而且這裏只有一個方法run,進入看一看

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

走到這裏,發現這裏方法是通過兩步來達成的,首先是SpringApplication的構造方法,然後調用其run方法。先從構造方法看,進去,裏面是this(null,primarySources)方法,直接進去

public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
                //1、資源初始化資源加載器爲 null
		this.resourceLoader = resourceLoader;
                //2、驗證非null
		Assert.notNull(primarySources, "PrimarySources must not be null");
                //3、初始化主要加載資源類集合並用set去重
		this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
                //4、推斷當前 WEB 應用類型
		this.webApplicationType = deduceWebApplicationType();
                //5、設置應用上下文初始化器(getSpringFactoriesInstances內部通過Thread.currentThread().getContextClassLoader()獲取加載器加載)
		setInitializers((Collection) getSpringFactoriesInstances(
				ApplicationContextInitializer.class));
                //6、設置監聽器
		setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
                //7、推斷主入口應用類
		this.mainApplicationClass = deduceMainApplicationClass();
	}

如何判斷的WEB應用類型的呢

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

public static final String DEFAULT_REACTIVE_WEB_CONTEXT_CLASS = "org.springframework."
			+ "boot.web.reactive.context.AnnotationConfigReactiveWebServerApplicationContext";

	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";

public enum WebApplicationType {

	/**
	 * The application should not run as a web application and should not start an
	 * embedded web server.
	 */
	NONE,

	/**
	 * The application should run as a servlet-based web application and should start an
	 * embedded servlet web server.
	 */
	SERVLET,

	/**
	 * The application should run as a reactive web application and should start an
	 * embedded reactive web server.
	 */
	REACTIVE

}

isPresent方法裏很簡單,就是嘗試去加載這個類,如果這個類在項目的依賴裏,那麼會返回true,也就會返回響應web類型,是一個枚舉類.在往下看。

public interface ApplicationContextInitializer<C extends ConfigurableApplicationContext> {

	/**
	 * Initialize the given application context.
	 * @param applicationContext the application to configure
	 */
	void initialize(C applicationContext);

}

用來初始化指定的 Spring 應用上下文,如註冊屬性資源、激活 Profiles 等,再來看下setInitializers方法

public void setInitializers(
			Collection<? extends ApplicationContextInitializer<?>> initializers) {
		this.initializers = new ArrayList<>();
		this.initializers.addAll(initializers);
	}

就是初始化一個 ApplicationContextInitializer 應用上下文初始化器實例的集合

在來看下getSpringFactoriesInstances方法

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

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

設置應用上下文初始化器主要分以下五個步驟:

1、獲取當前線程的上下文類加載器

ClassLoader classLoader = Thread.currentThread().getContextClassLoader();

2、獲取ApplicationContextInitializer 實例名稱並去重

Set<String> names = new LinkedHashSet<>(
                SpringFactoriesLoader.loadFactoryNames(type, classLoader));

loadFactoryNames 方法相關的源碼如下:

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

public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";

private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
    MultiValueMap<String, String> result = cache.get(classLoader);
    if (result != null) {
        return result;
    }

    try {
        Enumeration<URL> urls = (classLoader != null ?
                classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
                ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
        result = new LinkedMultiValueMap<>();
        while (urls.hasMoreElements()) {
            URL url = urls.nextElement();
            UrlResource resource = new UrlResource(url);
            Properties properties = PropertiesLoaderUtils.loadProperties(resource);
            for (Map.Entry<?, ?> entry : properties.entrySet()) {
                List<String> factoryClassNames = Arrays.asList(
                        StringUtils.commaDelimitedListToStringArray((String) entry.getValue()));
                result.addAll((String) entry.getKey(), factoryClassNames);
            }
        }
        cache.put(classLoader, result);
        return result;
    }
    catch (IOException ex) {
        throw new IllegalArgumentException("Unable to load factories from location [" +
                FACTORIES_RESOURCE_LOCATION + "]", ex);
    }
}

根據類路徑下的 META-INF/spring.factories 文件解析並獲取 ApplicationContextInitializer 接口的所有配置的類路徑名稱。

在根據傳入的type匹配加載的class

 查看該文件spring-boot-autoconfigure-2.0.3.RELEASE.jar,/META-INF/spring.factories 的初始化器相關配置內容如下:

# Initializers
org.springframework.context.ApplicationContextInitializer=\
org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer,\
org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener

3、根據以上的類路徑創建初始化器實例列表

List<T> instances = createSpringFactoriesInstances(type, parameterTypes,
                classLoader, args, names);

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

4、初始化器實例列表排序

AnnotationAwareOrderComparator.sort(instances);

 設置完初始化器之後,設置監聽器,可以看下這個類ApplicationListener

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

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

}

看到這裏可知,ApplicationListener繼承了JDK的java.util.EventListener接口,實現了觀察者模式,在Spring 3.0時,ApplicationListener可以通用地聲明事件類型是它感興趣的。當用Spring ApplicationContext註冊時,事件將相應地過濾*,並調用偵聽器以匹配事件

設置監聽器和設置初始化器調用的方法是一樣的,只是傳入的類型不一樣,設置監聽器的接口類型爲:

getSpringFactoriesInstances,對應的 spring-boot-autoconfigure-2.0.3.RELEASE.jar,/META-INF/spring.factories 文件配置內容請見下方。

# Application Listeners
org.springframework.context.ApplicationListener=\
org.springframework.boot.autoconfigure.BackgroundPreinitializer

 可以看出目前只有一個 BackgroundPreinitializer 監聽器。

然後推斷主入口應用類

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

這個方式很特別,我是第一次看到,是構造一個運行時異常,在遍歷異常棧中的方法名,找到方法名匹配main方法的棧幀,然後返回該類

到這,SpringApplication的構造方法的初始化流程就執行完畢,之後執行run方法,內容很多,下一章在總結。

 

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