SpringBoot源码解析-启动流程(一)

我们都知道启动一个SpringBoot项目,只需要运行主类的main方法,这背后启动机制是什么样的,我们来追踪源码具体分析一下:
我们的启动类是这样的:

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

进入到SpringApplication.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);
    }

我们看到最终启动分成两步:

  • 创建SpringApplication对象
  • 调用run方法

这一篇我们主要分析一下第一步:创建SpringApplication。

创建SpringApplication

创建SpringApplication对象时会调用其构造方法,我们进入其中,这边只保留了部分核心代码,每步注释如下:

      public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
	    ...//省略一些代码
        // 将传入的DemoSpringbootApplication启动类放入primarySources中,这样应用就知道主启动类是什么了
        this.primarySources = new LinkedHashSet(Arrays.asList(primarySources));
        //判断当前应用环境,NONE,SERVLET,REACTIVE三个其中的一种
        this.webApplicationType = WebApplicationType.deduceFromClasspath();
        //设置初始化器
        this.setInitializers(this.getSpringFactoriesInstances(ApplicationContextInitializer.class));
        //设置监听器
        this.setListeners(this.getSpringFactoriesInstances(ApplicationListener.class));
        //确定主配置类
        this.mainApplicationClass = this.deduceMainApplicationClass();
    }

我们分析一下其中几个步骤:

  • 判断当前应用环境
    WebApplicationType.deduceFromClasspath()方法从classpath下判断当前SpringBoot应用应该使用哪种环境启动,代码如下:

        private static final String[] SERVLET_INDICATOR_CLASSES = new String[]{"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";
        private static final String SERVLET_APPLICATION_CONTEXT_CLASS = "org.springframework.web.context.WebApplicationContext";
        private static final String REACTIVE_APPLICATION_CONTEXT_CLASS = "org.springframework.boot.web.reactive.context.ReactiveWebApplicationContext";
        static WebApplicationType deduceFromClasspath() {
            if (ClassUtils.isPresent("org.springframework.web.reactive.DispatcherHandler", (ClassLoader)null) && !ClassUtils.isPresent("org.springframework.web.servlet.DispatcherServlet", (ClassLoader)null) && !ClassUtils.isPresent("org.glassfish.jersey.servlet.ServletContainer", (ClassLoader)null)) {
                return REACTIVE;// 如果类路径下有reactive相关类,返回REACTIVE环境
            } else {
                String[] var0 = SERVLET_INDICATOR_CLASSES;
                int var1 = var0.length;
    
                for(int var2 = 0; var2 < var1; ++var2) {
                    String className = var0[var2];
                    if (!ClassUtils.isPresent(className, (ClassLoader)null)) {
                        return NONE;// 如果类路径下没有servlet相关类,返回NONE环境
                    }
                }
    
                return SERVLET;// 以上都不是,则返回SERVLET环境
            }
        }
    
  • 设置初始化器
    setInitializers方法会将一组类型为 ApplicationContextInitializer 的初始化器放入 SpringApplication 中。这组ApplicationContextInitializer是通过getSpringFactoriesInstances方法获取的,我们进入方法:

    private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) {
        ClassLoader classLoader = this.getClassLoader();
        //加载指定类型的所有已配置组件的全限定类名
        Set<String> names = new LinkedHashSet(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
        //创建这些组件的实例
        List<T> instances = this.createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
        AnnotationAwareOrderComparator.sort(instances);
        return instances;
    }
    

    loadFactoryNames方法主要功能就是读取类路径下所有jar包下的META-INF/spring.factories文件,然后找到指定类型(我们这边是ApplicationContextInitializer)的所有实现类。

       private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
        //先从缓存中获取
        MultiValueMap<String, String> result = (MultiValueMap)cache.get(classLoader);
        if (result != null) {
            return result;
        } else {
            try {
            	//读取类路径下所有jar包下的META-INF/spring.factories
                Enumeration<URL> urls = classLoader != null ? classLoader.getResources("META-INF/spring.factories") : ClassLoader.getSystemResources("META-INF/spring.factories");
                LinkedMultiValueMap result = new LinkedMultiValueMap();
                while(urls.hasMoreElements()) {
                    URL url = (URL)urls.nextElement();
                    UrlResource resource = new UrlResource(url);
                    //每个spring.factories配置文件读取到Properties中
                    Properties properties = PropertiesLoaderUtils.loadProperties(resource);
                    Iterator var6 = properties.entrySet().iterator();
                    while(var6.hasNext()) {
                        Entry<?, ?> entry = (Entry)var6.next();
                        String factoryTypeName = ((String)entry.getKey()).trim();
                        //每个配置项的值可能有多个
                        String[] var9 = StringUtils.commaDelimitedListToStringArray((String)entry.getValue());
                        int var10 = var9.length;
                        for(int var11 = 0; var11 < var10; ++var11) {
                            String factoryImplementationName = var9[var11];
                            result.add(factoryTypeName, factoryImplementationName.trim());
                        }
                    }
                }
                //放入缓存中,以便下次读取直接从缓存获取
                cache.put(classLoader, result);
                return result;
            } catch (IOException var13) {
                throw new IllegalArgumentException("Unable to load factories from location [META-INF/spring.factories]", var13);
            }
        }
    }
    

    我们debug看一下,项目初始化了如下ApplicationContextInitializer:
    在这里插入图片描述
    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());
        Iterator var7 = names.iterator();
    
        while(var7.hasNext()) {
            String name = (String)var7.next();
    
            try {
                Class<?> instanceClass = ClassUtils.forName(name, classLoader);
                Assert.isAssignable(type, instanceClass);
                Constructor<?> constructor = instanceClass.getDeclaredConstructor(parameterTypes);
                //实例化
                T instance = BeanUtils.instantiateClass(constructor, args);
                instances.add(instance);
            } catch (Throwable var12) {
                throw new IllegalArgumentException("Cannot instantiate " + type + " : " + name, var12);
            }
        }
    
        return instances;
    }
    
    
  • 设置监听器
    setListeners方法会将一组类型为 ApplicationListener的初始化器放入 SpringApplication 中,其获取方法同setInitializers,我们不再赘述。通过debug我们看到初始化了如下一些ApplicationListener:
    在这里插入图片描述

  • 确定主配置类

    	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方法所在的类DemoSpringbootApplication 。

至此,第一步创建SpringApplication的流程分析完了,下一篇我们继续分析run方法。


SpringBoot源码解析-启动流程(二)

发布了5 篇原创文章 · 获赞 3 · 访问量 1804

猜你喜欢

转载自blog.csdn.net/ym572170/article/details/104750627