美文网首页Spring
springboot 启动分析一

springboot 启动分析一

作者: 0爱上1 | 来源:发表于2018-09-24 11:39 被阅读33次

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文件做了初始化应用程序上下文类和监听器的定义。这块的知识点还是很重要的,后续实现自定义起步依赖会用到

相关文章

网友评论

    本文标题:springboot 启动分析一

    本文链接:https://www.haomeiwen.com/subject/xwufoftx.html