zoukankan      html  css  js  c++  java
  • springboot情操陶冶-SpringApplication(一)

    SpringApplication是所有springboot的入口类,分析此类有助于我们了解springboot的工作机制。本文以2.0.3.REALEASE版本作分析

    SpringApplication

    调用实例如下

    package com.example.demospringbootweb;
    
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    
    @SpringBootApplication
    public class DemoSpringbootWebApplication {
    
        public static void main(String[] args) {
            SpringApplication.run(DemoSpringbootWebApplication.class, args);
        }
    }
    

    调用的是SpringApplication.run()方法进行应用程序的启动。代码很简单也容易让用户上手,笔者这就进入其具体的类以探瑰宝。

    注释描述

    先看下其官方注释,有助于我们入门。由于注释过长,笔者此处只对其主要内容作下翻译总结

    1. 可以简单的通过main()函数来辅助启动一个spring应用程序。默认情况下其会按照以下步骤来辅助我们创建的应用

      • 创建一个关联的ApplicationContext实例
      • 注册CommandLinePropertySource实例暴露命令行的参数作为spring的属性
      • 刷新ApplicationContext,并加载所有的单例beans
      • 触发实现了CommandLineRunner的实例beans
    2. SpringApplications可以读取来自不同源的beans。官方建议用户使用@Configuration注解相应的启动类,当然也支持从以下方式加载相应的beans

      • AnnotatedBeanDefinitionReader加载指定的类
      • XmlBeanDefinitionReader加载XML的配置信息或者GroovyBeanDefinitionReader加载groovy脚本资源
      • ClassPathBeanDefinitionScanner扫描指定的包加载相应bean

    过于抽象,笔者继续通过源码来对上述的内容进行回顾

    构造函数

    	/**
    	 * 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));
    		// 推断是否为web环境
    		this.webApplicationType = deduceWebApplicationType();
    		// 加载ApplicationContextInitializer接口类
    		setInitializers((Collection) getSpringFactoriesInstances(
    				ApplicationContextInitializer.class));
    		// 加载ApplicationListener接口类
    		setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
    		// 推断主函数类
    		this.mainApplicationClass = deduceMainApplicationClass();
    	}
    

    对上述的注释作下简单的解释

    SpringApplication#deduceWebApplicationType()

    推断是否为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;
    	}
    

    从代码层看总共有三种应用类型,也代表了三个环境类型

    • WebApplicationType.REACTIVE reactive web应用(classpath环境下须有org.springframework.web.reactive.DispatcherHandler)
    • WebApplicationType.SERVLET servlet web应用(classpath环境下存在javax.servlet.Servlet或者org.springframework.web.context.ConfigurableWebApplicationContext)
    • WebApplicationType.NONE 简单的JAVA应用(classpath环境不存在上述的类)

    SpringApplication#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#getSpringFactoriesInstances()

    找寻相应的接口实现类,源码如下

    	private <T> Collection<T> getSpringFactoriesInstances(Class<T> type,
    			Class<?>[] parameterTypes, Object... args) {
    		// 上下文classLoader
    		ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
    		// 通过SpringFactoriesLoader来加载相应的类
    		Set<String> names = new LinkedHashSet<>(
    				SpringFactoriesLoader.loadFactoryNames(type, classLoader));
    		List<T> instances = createSpringFactoriesInstances(type, parameterTypes,
    				classLoader, args, names);
    		AnnotationAwareOrderComparator.sort(instances);
    		return instances;
    	}
    

    进而查看相应的静态方法SpringFactoriesLoader.loadFactoryNames(),源码如下

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

    关键处理类出来了,源码跟上

    	private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
    		// 缓存处理
    		MultiValueMap<String, String> result = cache.get(classLoader);
    		if (result != null) {
    			return result;
    		}
    
    		try {
    			// 找寻所有classpath下的"META-INF/spring.factories"文件
    			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集合
    					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);
    		}
    	}
    

    由此我们得出结论,classpath环境下所有含META-INF/spring.factories的文件,里面约定了默认的实现。笔者以spring-boot-2.0.3.REALEASE.jar为例

    # PropertySource Loaders
    org.springframework.boot.env.PropertySourceLoader=
    org.springframework.boot.env.PropertiesPropertySourceLoader,
    org.springframework.boot.env.YamlPropertySourceLoader
    
    # Run Listeners
    org.springframework.boot.SpringApplicationRunListener=
    org.springframework.boot.context.event.EventPublishingRunListener
    
    # Error Reporters
    org.springframework.boot.SpringBootExceptionReporter=
    org.springframework.boot.diagnostics.FailureAnalyzers
    
    # 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.web.context.ServerPortInfoApplicationContextInitializer
    
    # Application Listeners
    org.springframework.context.ApplicationListener=
    org.springframework.boot.ClearCachesApplicationListener,
    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.context.logging.ClasspathLoggingApplicationListener,
    org.springframework.boot.context.logging.LoggingApplicationListener,
    org.springframework.boot.liquibase.LiquibaseServiceLocatorApplicationListener
    
    # Environment Post Processors
    org.springframework.boot.env.EnvironmentPostProcessor=
    org.springframework.boot.cloud.CloudFoundryVcapEnvironmentPostProcessor,
    org.springframework.boot.env.SpringApplicationJsonEnvironmentPostProcessor,
    org.springframework.boot.env.SystemEnvironmentPropertySourceEnvironmentPostProcessor
    
    # Failure Analyzers
    org.springframework.boot.diagnostics.FailureAnalyzer=
    org.springframework.boot.diagnostics.analyzer.BeanCurrentlyInCreationFailureAnalyzer,
    org.springframework.boot.diagnostics.analyzer.BeanNotOfRequiredTypeFailureAnalyzer,
    org.springframework.boot.diagnostics.analyzer.BindFailureAnalyzer,
    org.springframework.boot.diagnostics.analyzer.BindValidationFailureAnalyzer,
    org.springframework.boot.diagnostics.analyzer.UnboundConfigurationPropertyFailureAnalyzer,
    org.springframework.boot.diagnostics.analyzer.ConnectorStartFailureAnalyzer,
    org.springframework.boot.diagnostics.analyzer.NoUniqueBeanDefinitionFailureAnalyzer,
    org.springframework.boot.diagnostics.analyzer.PortInUseFailureAnalyzer,
    org.springframework.boot.diagnostics.analyzer.ValidationExceptionFailureAnalyzer,
    org.springframework.boot.diagnostics.analyzer.InvalidConfigurationPropertyNameFailureAnalyzer,
    org.springframework.boot.diagnostics.analyzer.InvalidConfigurationPropertyValueFailureAnalyzer
    
    # FailureAnalysisReporters
    org.springframework.boot.diagnostics.FailureAnalysisReporter=
    org.springframework.boot.diagnostics.LoggingFailureAnalysisReporter
    
    

    因此SpringApplication构造函数中加载的ApplicationContextInitializer类有如下

    • ConfigurationWarningsApplicationContextInitializer (对ComponentScan指定的值为"org"等进行报警输出)
    • ContextIdApplicationContextInitializer (创建默认名为application的ContextId对象,也可通过spring.application.name指定)
    • DelegatingApplicationContextInitializer (对context.initializer.classes指定的class集合进行加载)
    • ServerPortInfoApplicationContextInitializer (将local.server.port设置为指定的web端口,默认为8080)


    而加载的ApplicationListener类有如下

    • ClearCachesApplicationListener (反射工具缓存清空事件)
    • ParentContextCloserApplicationListener (父ApplicationContext关闭事件)
    • FileEncodingApplicationListener (系统变量配置的file.encoding值是否与环境变量spring.mandatory-file-encoding一致事件)
    • AnsiOutputApplicationListener (控制台彩色输出事件,可通过spring.output.ansi.enabled来指定)
    • ConfigFileApplicationListener (读取spring.profile.active/spring.profile.include配置)
    • DelegatingApplicationListener (委托事件处理类)
    • ClasspathLoggingApplicationListener (打印classpath信息,级别为debug)
    • LoggingApplicationListener (日志处理事件)
    • LiquibaseServiceLocatorApplicationListener (classpath是否存在liquibase的CustomResolverServiceLocator类判断事件)

    其中ApplicationListener所绑定事件的触发顺序小结如下

    1.ApplicationStartingEvent[应用程序启动事件starting]

    2.ApplicationEnvironmentPreparedEvent[环境变量配置事件environmentPrepared]

    3.ApplicationPreparedEvent[spring上下文加载前事件contextPrepared]

    4.ApplicationStartedEvent[spring上下文加载完毕事件contextLoaded]此事件之后会统一调用ApplicationRunner/CommandLineRunner的实现类

    5.ApplicationReadyEvent[应用准备事件running]

    6.ApplicationFailedEvent/ContextClosedEvent[抛异常或者启动失败调用]

    小结

    由此SpringApplication构造函数完成了一些必要的初始化,重点在于ApplicationContextInitializerApplicationListener接口类。并且通过构造函数反射来进行实例化

    限于篇幅过长,笔者将对SpringApplication#run()方法的具体解析放于下一章节来分析

  • 相关阅读:
    R: 聚类分析
    R: 主成分分析 ~ PCA(Principal Component Analysis)
    R: 关于 ggplot2 的初探
    R: 字符串处理包:stringr
    R: 用 R 查看、管理文件(夹)
    R: 关于文件 文件夹的处理:file.show() dir.create().....
    R: 绘图 pie & hist
    R: 绘图 barplot
    R: 对向量中的每个元素,检查其是否包含某个“单词”
    R: 一页显示多张图的方法
  • 原文地址:https://www.cnblogs.com/question-sky/p/9366500.html
Copyright © 2011-2022 走看看