美文网首页
springboot的启动流程(一):入口和初始化

springboot的启动流程(一):入口和初始化

作者: 初阳拾叁 | 来源:发表于2018-07-11 21:48 被阅读0次

    1. 入口

    public static void main(String[] args) {
        #1.初始化SpringApplication类,之后执行run方法
        new SpringApplication(ApringTestApplication.class).run(args);
        #2.直接调用SpringApplication类中的run的静态方法
        SpringApplication.run(ApringTestApplication.class, args);
        }
    

    一般来说,由以上两种的启动springboot的方法,但是两种方法草你个本质上来说是一样的。接下来咱们来看一下第二种方法中run静态方法中的执行:

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

    进入两一个run方法,进入之后的方法如下:

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

    到这里之后大家可以看出来,第二种执行的静态方法最终也是初始化类,之后调用类的run方法。

    2.初始化

    进入类的初始化可以看到如下代码:

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

    接下来咱们对代码进行分析:

    this.resourceLoader = resourceLoader;
    Assert.notNull(primarySources, "PrimarySources must not be null");
    this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
    

    前三行对传入的参数进行进行了判断和赋值,从上面的参数可以看出resourceLoader为null,primarySources为我创建的启动类的名字ApringTestApplication。

    this.webApplicationType = deduceWebApplicationType();
    
        private static final String REACTIVE_WEB_ENVIRONMENT_CLASS = "org.springframework."
                + "web.reactive.DispatcherHandler";
    
        private static final String MVC_WEB_ENVIRONMENT_CLASS = "org.springframework."
                + "web.servlet.DispatcherServlet";
    
        private static final String[] WEB_ENVIRONMENT_CLASSES = { "javax.servlet.Servlet",
                "org.springframework.web.context.ConfigurableWebApplicationContext" };
    
    private WebApplicationType deduceWebApplicationType() {
            if (ClassUtils.isPresent(REACTIVE_WEB_ENVIRONMENT_CLASS, null)
                    && !ClassUtils.isPresent(MVC_WEB_ENVIRONMENT_CLASS, null)) {
                return WebApplicationType.REACTIVE;
            }
            for (String className : WEB_ENVIRONMENT_CLASSES) {
                if (!ClassUtils.isPresent(className, null)) {
                    return WebApplicationType.NONE;
                }
            }
            return WebApplicationType.SERVLET;
        }
    

    这里对当前的环境进行了判断,如果classpath中包含REACTIVE_WEB_ENVIRONMENT_CLASS并且不包含MVC_WEB_ENVIRONMENT_CLASS,则环境为REACTIVE。如果classpath中WEB_ENVIRONMENT_CLASSES有任意一个不包含,则环境为NONE。以上两者都不是就是SERVLET。

    setInitializers((Collection) getSpringFactoriesInstances(
                    ApplicationContextInitializer.class));
    setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
    

    这两行代码是设置initializers和listeners,在启动的时候会用上。其中需要说明的是getSpringFactoriesInstances方法之后会多次出现,需要重点讲解一下:

    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) {
            ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
            // 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;
        }
    

    这个过程中是首先获取当前线程的ClassLoader,之后根据SpringFactoriesLoader.loadFactoryNames获取需要的类的名字,最后获取类。下面是SpringFactoriesLoader.loadFactoryNames的方法实现:

    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();
            return loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList());
        }
    
    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()) {
                        List<String> factoryClassNames = Arrays.asList(
                                StringUtils.commaDelimitedListToStringArray((String) entry.getValue()));
                        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);
            }
        }
    
    

    获取缓存中是否有传入的classloader的名字的缓存,如果有就直接返回,没有继续深入。当传入的classloader部位null时,从传入的classloader中获取”META-INF/spring.factories”中的数据,并且整理成键值对的形式放入缓存,之后返回。从放回的键值对中获取需要的key对应的value。比如说这个例子中传入的ApplicationContextInitializer和ApplicationListener。对应的”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.ServerPortInfoApplicationContextInitializer
    
    # 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.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.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
    
    

    包含需要的ApplicationContextInitializer:

    # 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.ServerPortInfoApplicationContextInitializer
    

    和ApplicationListener

    # 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
    

    最后初始化的代码如下:

    this.mainApplicationClass = deduceMainApplicationClass();
    

    这里主要的是获取当前类的main方法,代码如下:

        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的启动流程(一):入口和初始化

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