springboot 启动分析一


SpringApplition 类

类简介

该类能够通过一个简单的java方法(run方法),引导和加载一个spring应用程序,默认情况下SpringApplication类将执行以下几步去引导你的应用程序

  • 根据你的classpath创建一个合适的应用上下文实例(ApplicationContext)

  • 创建一个CommandLinePropertySource实例接收并暴露命令行参数作为spring的属性(springboot应用程序引导是在主方法中,而CommandLinePropertySource实例就是接收主方法传入的各种spring参数并暴露给SpringApplication)

  • 刷新应用上下文,加载所有的单例bean

  • 最后触发任何的CommandLineRunner bean

构造方法

SpringApplication 提供了两个构造方法

/**
 * 创建一个新的SpringApplication 实例,应用上下文将会从定义的primarySources加载bean定义,
 * 可以在调用SpringApplication实例的run方法之前定制该实例
 * @param primarySources 主要的bean 资源
 */
public SpringApplication(Class<?>... primarySources) {
    this(null, primarySources);
}
  • 可以看到这里转而调用了重载的构造
/**
 * 创建一个新的SpringApplication 实例,应用上下文将会从定义的primarySources加载bean定义,
 * 可以在调用SpringApplication实例的run方法之前定制该实例
 *
 * @param resourceLoader 资源加载器
 * @param primarySources 主要的bean 资源
 */
@SuppressWarnings({"unchecked", "rawtypes"})
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
    // 初始化资源加载器,这里默认是null
    this.resourceLoader = resourceLoader;

    // 断言主资源不是null
    Assert.notNull(primarySources, "PrimarySources must not be null");

    // 将主资源放入SpringApplication 对象的primarySources 属性中
    this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));

    // 推断应用类型
    this.webApplicationType = deduceWebApplicationType();

    // 根据项目下META-INF文件夹下的spring.factories配置文件做一些应用上下文bean的初始化工作
    setInitializers((Collection) getSpringFactoriesInstances(
            ApplicationContextInitializer.class));

    // 根据项目下META-INF文件夹下的spring.factories配置文件做一些应用监听器bean的初始化工作
    setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));

    // 初始化主要的应用的Class类型,是遍历取出当前线程的堆栈信息中包含有“main”方法的class类
    this.mainApplicationClass = deduceMainApplicationClass();
}
  • 推断应用类型的方法deduceWebApplicationType()
/**
 * 根据classpath下是否存在指定的类,推断出应用的类型
 *
 * @return org.springframework.boot.WebApplicationType 应用类型枚举类
 * @date 9:32 2018/9/24
 */
private WebApplicationType deduceWebApplicationType() {
    // 判断 存在REACTIVE_WEB_ENVIRONMENT_CLASS = "org.springframework.web.reactive.DispatcherHandler";
    // 且不存在 MVC_WEB_ENVIRONMENT_CLASS = "org.springframework.web.servlet.DispatcherServlet";
    // 且不存在 JERSEY_WEB_ENVIRONMENT_CLASS = "org.glassfish.jersey.server.ResourceConfig";
    // 就是 REACTIVE 类型
    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;
    }

    // 判断只要classpath不存在二者任意之一,
    // String[] WEB_ENVIRONMENT_CLASSES = {"javax.servlet.Servlet", "org.springframework.web.context.ConfigurableWebApplicationContext"};
    // 就认为是NONE 类型的应用 即非Web型应用(Standard型)
    for (String className : WEB_ENVIRONMENT_CLASSES) {
        if (!ClassUtils.isPresent(className, null)) {
            return WebApplicationType.NONE;
        }
    }

    // 以上两者都不是,返回SERVLET 类型应用
    return WebApplicationType.SERVLET;
}
  • 获取spring工厂类实例
/**
 * 根据传入的class类型从spring.factories配置文件中获取该class类型作为key对应的class集合,
 * 再利用反射获取对应的实例
 *
 * @param type 传入的key (class类型)
 * @return java.util.Collection<T> value实例集合
 * @date 9:59 2018/9/24
 */
private <T> Collection<T> getSpringFactoriesInstances(Class<T> type) {
    return getSpringFactoriesInstances(type, new Class<?>[]{});
}


/**
 * 根据class类型的type参数,从spring.factories 配置文件中获取对应的value集合
 * 利用反射初始化value 对象返回
 *
 * @param type           spring.factories中定义的各种key
 * @param parameterTypes 参数类型
 * @param args           参数
 * @return java.util.Collection<T>
 * @date 10:05 2018/9/24
 */
private <T> Collection<T> getSpringFactoriesInstances(Class<T> type,
                                                      Class<?>[] parameterTypes, Object... args) {
    // 获取当前线程的classloader
    ClassLoader classLoader = Thread.currentThread().getContextClassLoader();

    // 使用set不可重复来收集type对应的value集合中含有的名字,避免重复实例化对象
    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(type, classLoader) 这个方法

SpringFactoriesLoader 类定义为final型,意味着不能被继承,是一个spring内部使用的通用工厂加载器,用于读取spring.factories文件,类内部定义了一个常量,指定工厂资源文件位置

/**
 * 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, @Nullable ClassLoader classLoader) {
    // 获取传入的工厂类的名字
    String factoryClassName = factoryClass.getName();
    // 加载工厂配置文件中的所有工厂类存入map中, key为工厂类型,value为所有的工厂类集合,再根据指定的工厂类名,获取指定的value集合
    return loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList());
}


// 其中用到了缓存,指定classloader只加载一次spring.factories文件中    
// 的所有配置,以key,value的形式存入一个map中,并缓存起来
// 缓存的方式是以classloader为key,整个map为value
// 缓存定义 private static final Map<ClassLoader, MultiValueMap<String,String>> 
// cache = new ConcurrentReferenceHashMap<>();

private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
    //首先从缓存中查询,该classloader是否已有缓存
    MultiValueMap<String, String> result = cache.get(classLoader);
    //  有缓存,直接返回
    if (result != null) {
        return result;
    }
    // 没有缓存,执行加载配置流程
    try {
        // 加载整个应用程序中的所有spring.factories文件,
        // 将每一个文件对应的url放入Enumeration<URL> urls中,因为各      
        // 个jar中都可能有spring.factories文件
        Enumeration<URL> urls = (classLoader != null ?
                classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
                ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
        result = new LinkedMultiValueMap<>();
        // 循环遍历所有的spring.factories文件
        while (urls.hasMoreElements()) {
            URL url = urls.nextElement();
            UrlResource resource = new UrlResource(url);
            // 利用Properties类加载每一个spring.factories文件中的所有属性
            Properties properties = PropertiesLoaderUtils.loadProperties(resource);
            // 遍历所有的属性,取出每一个entry中的key,value (工厂类集合名)
            for (Map.Entry<?, ?> entry : properties.entrySet()) {
                //  注意这里的commaDelimitedListToStringArray方法,将原value按照逗号切割成了一个工厂类名的String集合       
                List<String> factoryClassNames = Arrays.asList(StringUtils.commaDelimitedListToStringArray((String) entry.getValue()));
                // 按照key,List<String> factoryClassNames 存入result这个map中
                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);
    }
}
  • 接下来看 createSpringFactoriesInstances方法,根据反射创建各value中保存的所有的工厂类名对应的实例
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;
}

setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
应用程序监听器对象的初始化方法与以上的一致,只是传入的key为ApplicationListener.class,就不做细致分析了

  • 看下推断主应用Class类型的方法
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;
}
  • 这里给出一个springboot项目源码的META-INF/spring.factories文件内容
# 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.ServerPortInfoApplicationContextnitializer

# 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.BeanDefinitionOverrideFailureAnalyzer,\
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.NoSuchMethodFailureAnalyzer,\
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

文件中等号前面的就是一个个key,等号后面以逗号分隔的就是工厂类名集合


SpringApplication的构造器就分析到这里,接下来的文章会分析run方法。看看它是如何启动应用程序的

总结

Spring项目利用META-INF下的spring.factories文件做了初始化应用程序上下文类和监听器的定义。这块的知识点还是很重要的,后续实现自定义起步依赖会用到

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,716评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,558评论 1 294
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,431评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,127评论 0 209
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,511评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,692评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,915评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,664评论 0 202
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,412评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,616评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,105评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,424评论 2 254
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,098评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,096评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,869评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,748评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,641评论 2 271

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,100评论 18 139
  • Spring Boot 参考指南 介绍 转载自:https://www.gitbook.com/book/qbgb...
    毛宇鹏阅读 46,367评论 6 343
  • 入门 介绍 Spring Boot Spring Boot 使您可以轻松地创建独立的、生产级的基于 Spring ...
    Hsinwong阅读 16,705评论 2 89
  • 链接:http://www.cnblogs.com/xiaoxi/作者:平凡希 我们开发任何一个Spring Bo...
    聆世冷暖阅读 4,087评论 1 35
  • 许姨的粉店开在X小的对面,粉店的“左邻”是一所公立幼儿园,“右里”是一家包子店,白底的牌匾上,朱漆书字“许姨的粉店...
    柠檬要成精阅读 351评论 0 1