springboot 开篇

学习随记

使用SpringBoot有几年了,但是一直没有机会了解底层流程,最近想把自己的学习过程记录下来,就借这个机会好好的读一下SpringBoot源码吧。

最近两天一有时间就看了下SpringBoot源码,本来打算下载Spring源码的,用gradle编译好像不太好使,再说Spring的话需要用一些配置,目前公司用配置这块用得比较少了,基本都是注解。(可能我没有接触过一些二次开发spring的公司)

所以不下源码也不影响整体的流程。(自我安慰)

言归正传,这篇文章是个开头,可能废话比较多,主要想把看源码的思路理清楚;

首先先建立好一个SpringBoot项目,然后创建一个启动类即可。

如下:

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

代码及其简单,最主要的方法当然是run方法了(废话,只有一个);

那就进入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);
}

这个就清楚一点了,一共两个点:

  1. new SpringApplication(primarySources);
  2. run(args);

new SpringApplication(primarySources);


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();      	   setInitializers((Collection)getSpringFactoriesInstances(ApplicationContextInitializer.class));
       setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
       this.mainApplicationClass = deduceMainApplicationClass();
   }



一般第一个参数为null, 第二个参数就是你第一步传入的对象,就是当前的执行类。

从整体来看,这个方法执行的主要逻辑就是初始化,初始化集合,运行的类型,监听器等。

主要的方法在getSpringFactoriesInstances();这个方法上面,那就看一下这个方法做了什么事情。

调用后会执行到这个方法里面来


private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) {
   ClassLoader classLoader = getClassLoader();
   // 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. SpringFactoriesLoader.loadFactoryNames()
  2. createSpringFactoriesInstances

先进loadFactoryNames();


public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
   String factoryTypeName = factoryType.getName();
   return loadSpringFactories(classLoader).getOrDefault(factoryTypeName, 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()) {
     String factoryTypeName = ((String) entry.getKey()).trim();
     for (String factoryImplementationName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
      result.add(factoryTypeName, factoryImplementationName.trim());
     }
    }
   }
   cache.put(classLoader, result);
   return result;
  }
  catch (IOException ex) {
   throw new IllegalArgumentException("Unable to load factories from location [" +
     FACTORIES_RESOURCE_LOCATION + "]", ex);
  }
 }

主要的逻辑集中在loadSpringFactories();

这个方法做了什么事情呢?简单扫一下

第一步: 从缓存中获取值,直接返回,这说明这个方法肯定有很多地方调用,这也保证了后面通过加载器可以直接获取到对象的缓存。缓存为两个Sting 不难猜测第二个就是对应的类的全类名。(不然能存啥呢?)

第二步:通过classLoader.getResources(path)来获取对应路径的文件,并且返回一组Enumeration对象,其实这里就可以猜测到,肯定是读取"META-INF/spring.factories"的数据后,遍历URL;这个for循环没有break就表示这个直接做的读取操作。最后存在cache中,返回一个String的集合。

最后通过getOrDefault(factoryTypeName, Collections.emptyList())来过滤。

也就是ApplicationContextInitializer.class相关的类

第二个分支:createSpringFactoriesInstances()


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

这个类相较于第一个类的话看起来就更简单,通过类的全类名,反射来生成对象,最终返回出去;

所以getSpringFactoriesInstances()这个类就是扫描"META-INF/spring.factories"这个文件生成全类名,通过反射生成实例对象。当然记住扫描肯定不是只扫一个spring.factories文件,是扫描整个项目的spring.factories文件。

当然 后面还有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;
}

这个方法看起来就很简单了,是不是有点似曾相识的感觉,没错,就是Debug时候的调用栈信息。

通过这个调用栈信息找到执行main的类,返回出去,记录下来,后面有大用。

好了,到这里的话,new SpringApplication()这个就执行完成了。

最后总结一下:
new SpringApplication() 主要是初始话一些集合,并且存放ApplicationContextInitializer.class 和 ApplicationListener.class 的相关类。这个相关类记录在项目下使用的resource/META-INF/spring.factories文件夹下面,并且实例化这些类(调用了newInstance()方法), 最后,把执行main的类记录下来,通过调用栈记录的。

这个地方我想一下,如果其他类执行了main方法调用了被@SpringApplication注解的类,那么,自动扫描时候以哪个包为主。会不会造成扫描的类遗漏?

有兴趣的小伙伴可以去试下。

好了,springBoot第一篇先写到这里吧。

第一次写博客,有些地方可能写错了,欢迎指正。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值