首页 > 编程语言 >【java】(一)SpringBoot 源码解析——SpringApplication 初始化

【java】(一)SpringBoot 源码解析——SpringApplication 初始化

时间:2022-12-12 11:22:50浏览次数:46  
标签:java String primarySources return 源码 new 加载 Class SpringBoot

1.前言

深入学习springboot笔记系列,可能会有错误还请指正,互相勉励,互相学习。

SpringBoot 项目启动只需启动 主类的 main 函数即可启动java服务,相比于以往的部署java服务简化方便了很多,接下我们从主函数入手一步一步剖析源码是如何通过main函数启动服务的。

2.SpringBoot 项目程序入口

主函数通过一个静态 run 方法完成整个服务的构建。

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

接下来看看静态的 run 方法的内部实现。

2.1.run 方法构造

public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {
return run(new Class<?>[] { primarySource }, args);
}
public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
return new SpringApplication(primarySources).run(args);
}

以上通过 new SpringApplication(primarySources)  执行了初始化的一些相关操作。

3.SpringApplication 初始化

public SpringApplication(Class<?>... primarySources) {
  this(null, primarySources);
}

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 = WebApplicationType.deduceFromClasspath();
  // 初始化 META-INF/spring.factories 中 所有的 BootstrapRegistryInitializer 类
  this.bootstrapRegistryInitializers = new ArrayList<>(getSpringFactoriesInstances(BootstrapRegistryInitializer.class));
  // 初始化 META-INF/spring.factories 中 所有的 ApplicationContextInitializer 类
  setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
  // 初始化 META-INF/spring.factories 中 所有的 ApplicationListener 类
  setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
  // 推断主类
  this.mainApplicationClass = deduceMainApplicationClass();
}

SpringApplication 初始化主要初始化了 resourceLoader、primarySources、webApplicationType 、bootstrapRegistryInitializers、initializers、listeners、mainApplicationClass 这几个对象。

其中resourceLoader 默认为null, primarySources 则为主类的有序去重集合。

3.1.webApplicationType 推断当前项目启动类型

private static final String[] SERVLET_INDICATOR_CLASSES = { "javax.servlet.Servlet",
            "org.springframework.web.context.ConfigurableWebApplicationContext" };

private static final String WEBMVC_INDICATOR_CLASS = "org.springframework.web.servlet.DispatcherServlet";

private static final String WEBFLUX_INDICATOR_CLASS = "org.springframework.web.reactive.DispatcherHandler";

private static final String JERSEY_INDICATOR_CLASS = "org.glassfish.jersey.servlet.ServletContainer";


static WebApplicationType deduceFromClasspath() {
//webflux 响应式 if (ClassUtils.isPresent(WEBFLUX_INDICATOR_CLASS, null) && !ClassUtils.isPresent(WEBMVC_INDICATOR_CLASS, null) && !ClassUtils.isPresent(JERSEY_INDICATOR_CLASS, null)) { return WebApplicationType.REACTIVE; }
// 是否web 项目 for (String className : SERVLET_INDICATOR_CLASSES) { if (!ClassUtils.isPresent(className, null)) { return WebApplicationType.NONE; } } return WebApplicationType.SERVLET; }

此处使用 ClassUtils.isPresent(String className, @Nullable ClassLoader classLoader) 方法为内部调用类加载加载响应的类,如果未找到则抛出ClassNotFoundException异常。类加载 WEBMVC_INDICATOR_CLASS、WEBFLUX_INDICATOR_CLASS 、JERSEY_INDICATOR_CLASS 几种不同的 Servlet,如果未加载到则为false,从而推断项目启动类型。此处我们是web项目,因此最后的返回值是WebApplicationType.SERVLET。

3.2.bootstrapRegistryInitializers 初始化


  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) {
//获取默认类加载器AppClassLoader ClassLoader classLoader = getClassLoader(); //使用默认类加载器加载META-INFO/spring.factories 中配置的类 Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
//使用反射将上一步 类加载完成的 names中的类实例化 List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
// 将实例化的类排序 排序规则:继承了 PriorityOrdered 接口的 优先级最高, 其次实现Ordered 接口 或者添加@Order 注解 通过比较order值的大小来排序,值越小优先级越高。 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 { 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; } public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) { ClassLoader classLoaderToUse = classLoader; if (classLoaderToUse == null) { classLoaderToUse = SpringFactoriesLoader.class.getClassLoader(); } String factoryTypeName = factoryType.getName(); return loadSpringFactories(classLoaderToUse).getOrDefault(factoryTypeName, Collections.emptyList()); }
private static Map<String, List<String>> loadSpringFactories(ClassLoader classLoader) {
// 缓存cache 中查找 Map<String, List<String>> result = cache.get(classLoader); if (result != null) { return result; } result = new HashMap<>(); try {
// 此处使用类加载器读取所有依赖的包中 MEAT-INFO/spring.factories 中配置的类,并添加在缓存cache中 Enumeration<URL> urls = classLoader.getResources(FACTORIES_RESOURCE_LOCATION); while (urls.hasMoreElements()) { URL url = urls.nextElement(); UrlResource resource = new UrlResource(url); Properties properties = PropertiesLoaderUtils.loadProperties(resource); for (Map.Entry<?, ?> entry : properties.entrySet()) { String factoryTypeName = ((String) entry.getKey()).trim(); String[] factoryImplementationNames = StringUtils.commaDelimitedListToStringArray((String) entry.getValue()); for (String factoryImplementationName : factoryImplementationNames) { result.computeIfAbsent(factoryTypeName, key -> new ArrayList<>()) .add(factoryImplementationName.trim()); } } } // Replace all lists with unmodifiable lists containing unique elements result.replaceAll((factoryType, implementations) -> implementations.stream().distinct() .collect(Collectors.collectingAndThen(Collectors.toList(), Collections::unmodifiableList))); cache.put(classLoader, result); } catch (IOException ex) { throw new IllegalArgumentException("Unable to load factories from location [" + FACTORIES_RESOURCE_LOCATION + "]", ex); } return result; }

以上方法通过默认的类加载AppClassLoader 加载依赖包中 META-INFO/spring.factories 路径中所有配置的类并缓存在cache中,然后由class 类型找出缓存cache 中需要相应加载的类并通过反射将类实例化并排序返回。

3.3.initializers 、listeners 初始化

同 bootstrapRegistryInitializers 加载流程一致,通过cache 缓存提高加载速度。

3.4.mainApplicationClass 初始化

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的构造器primarySources 是数组类型,因此无法直接通过primarySource 来判断main方法属于哪个class。

至此 SpringApplication类初始化加载完成。

总结

1.通过类加载器加载依赖的servlet判断项目类型;

2.通过类加载器加载指定路径文件 MEAT-INFO/spring.factories 读取指定配置文件并使用cache 缓存提高加载速度;

3.通过java反射的方式将指定的 BootstrapRegistryInitializer.class、ApplicationContextInitializer.class、ApplicationListener.class 类的实现类实例化;

4.通过运行异常的堆栈信息推断main方法所在的类为主类。

 

后续

下一章 将继续 讲解 SpringBoot 后续启动流程,谢谢观看。

标签:java,String,primarySources,return,源码,new,加载,Class,SpringBoot
From: https://www.cnblogs.com/anlizhaomi/p/16956073.html

相关文章

  • vue源码分析-diff算法核心原理
    这一节,依然是深入剖析Vue源码系列,上几节内容介绍了VirtualDOM是Vue在渲染机制上做的优化,而渲染的核心在于数据变化时,如何高效的更新节点,这就是diff算法。由于源码中关于d......
  • SpringBoot+Vue+kkFielView实现文件预览时提示:Illegal base64 character 3a以及Vue中
    场景kkFileViewhttps://kkfileview.keking.cn/zh-cn/index.htmlkkFileView为文件文档在线预览解决方案,该项目使用流行的springboot搭建,易上手和部署,基本支持主流办公......
  • SpringBoot+Vue+kkFileView实现文档管理(文档上传、下载、在线预览)
    场景SpringBoot+Vue+OpenOffice实现文档管理(文档上传、下载、在线预览):https://blog.csdn.net/BADAO_LIUMANG_QIZHI/article/details/121363504上面在使用OpenOffice实......
  • SpringBoot整合邮件服务
    SpringBoot整合邮件服务配置登录到QQ邮箱:​​https://mail.qq.com/​​选择账户点击开启SMTP服务:发送短信:发送完,点击我已发送,然后得到密码:POM依赖:<dependency><groupId>o......
  • 如何将springboot转称外置tomcat启动
    正常情况下,我们开发SpringBoot项目,由于内置了Tomcat,所以项目可以直接启动,部署到服务器的时候,直接打成jar包,就可以运行了(使用内置Tomcat的话,可以在application.yml......
  • java 实现Excel导入导出功能
    本文记录首先需要准备一个导入模板的实体类importcn.iocoder.yudao.framework.excel.core.annotations.DictFormat;importcn.iocoder.yudao.framework.excel.core.co......
  • java可以开发电脑桌面应用吗?java开发用什么软件?
    java开发PC桌面程序 Java是一门面向对象编程语言,作为静态面向对象编程语言的代表,极好地实现了面向对象理论,允许程序员以优雅的思维方式进行复杂的编程,那java可以开发电......
  • JAVA8 lambda之groupby三种用法
    一,有一个参数的groupby第一个参数:按照该参数规则进行分组。结果返回一个Map集合,Map的key是按照该规则执行后返回的每个结果,Map的value是一个List集合,该集合中的值是能满足......
  • 0停机迁移Nacos?Java字节码技术来帮忙
    摘要:本文介绍如何将SpringCloud应用从开源Consul无缝迁移至华为云Nacos。本文分享自华为云社区《0停机迁移Nacos?Java字节码技术来帮忙》,作者:华为云PaaS服务小智。1.市场......
  • 你不知道的开源分布式存储系统 Alluxio 源码完整解析(上篇)
    一、前言目前数据湖已成为大数据领域的最新热门话题之一,而什么是数据湖,每家数据平台和云厂商都有自己的解读。整体来看,数据湖主要的能力优势是:集中式存储原始的、海量的、多......