Spring基礎(chǔ)系列-容器啟動流程(2)


原創(chuàng)文章,轉(zhuǎn)載請標注出處:《Spring基礎(chǔ)系列-容器啟動流程(2)》


一、概述

上一篇文章講述了SSM架構(gòu)的web項目中容器的啟動流程,這一篇我們來看看SpringBoot項目中容器的啟動流程。

二、啟動流程

Springboot項目需要直接從main方法啟動。

源碼1-來自DemoApplicaiton(應(yīng)用自定義的)

@SpringBootApplication
public class DemoApplication {

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

首先調(diào)用的是SpringApplication中的run方法。

源碼2-來自:SpringApplication

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

第一個run方法會調(diào)用第二個run方法。

在第二個run方法中有兩步:

  • 第一步:創(chuàng)建SpringApplicaiton實例
  • 第二步:調(diào)用run方法

首先我們先創(chuàng)建SpringApplication實例:

源碼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));
        //判斷應(yīng)用類型
        this.webApplicationType = deduceWebApplicationType();
        //添加初始化器
        setInitializers((Collection) getSpringFactoriesInstances(
                ApplicationContextInitializer.class));
        //添加監(jiān)聽器
        setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
        //定位main方法所在類,并做記錄
        this.mainApplicationClass = deduceMainApplicationClass();
    }

SpringBoot應(yīng)用有三種類型:

源碼4-來自:WebApplicationType

public enum WebApplicationType {
    NONE,SERVLET,REACTIVE
}

解析:

  • NONE:應(yīng)用不是web應(yīng)用,啟動時不必啟動內(nèi)置的服務(wù)器程序
  • SERVLET:這是一個基于Servlet的web應(yīng)用,需要開啟一個內(nèi)置的Servlet容器(web服務(wù)器)
  • REACTIVE:這是一個反應(yīng)式web應(yīng)用,需要開啟一個內(nèi)置的反應(yīng)式web服務(wù)器

三者的判斷條件如下

源碼5-來自:SpringApplication

    private WebApplicationType deduceWebApplicationType() {
        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;
        }
        for (String className : WEB_ENVIRONMENT_CLASSES) {
            if (!ClassUtils.isPresent(className, null)) {
                return WebApplicationType.NONE;
            }
        }
        return WebApplicationType.SERVLET;
    }

上面的方法中涉及四個常量:

源碼6-來自:SpringApplication

    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 JERSEY_WEB_ENVIRONMENT_CLASS = "org.glassfish.jersey.server.ResourceConfig";

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

至于代碼很是簡單。

然后是設(shè)置初始化器,結(jié)合之前的講述在SSM架構(gòu)的web應(yīng)用中也擁有初始化器,這兩個代表的是同一個概念,不同于之前的處理方式,這里僅僅是將初始化器找到并設(shè)置到initializers屬性中。

我們來看看它是如何獲取這些初始化器的:

源碼7-來自:SpringApplication、SpringFactoriesLoader

    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));
        //創(chuàng)建實例
        List<T> instances = createSpringFactoriesInstances(type, parameterTypes,
                classLoader, args, names);
        //排序
        AnnotationAwareOrderComparator.sort(instances);
        return instances;
    }
    
   // 下面來自:SpringFactoriesLoader
    
    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 
            //從META-INF/spring.factories文件中獲取配置的內(nèi)容
            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);
                }
            }
            //添加到緩存?zhèn)溆?            cache.put(classLoader, result);
            return result;
        }
        catch (IOException ex) {
            throw new IllegalArgumentException("Unable to load factories from location [" +
                    FACTORIES_RESOURCE_LOCATION + "]", ex);
        }
    }

由此可見,初始化器是從META-INF/spring.factories文件中獲取到的,那么我們就可以自建該目錄文件進行添加,前提是需要自定義初始化器。

還有這里只有在第一次調(diào)用時需要從文件讀取,第二次就可以從緩存獲取,哪怕需要的不是初始化器的內(nèi)容,因為第一次的時候就會將所有的配置內(nèi)容全部獲取并解析保存到緩存?zhèn)溆?。這也就為下一步設(shè)置監(jiān)聽器這一步省下了不少時間。

最后一步就是將main方法所在類做個記錄。

源碼8-來自:SpringApplication

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

通過新建一個運行時異常的方式獲取方法調(diào)用棧,在棧中搜索方法名為main的方法所在的類。

如果我們想要獲取方法的調(diào)用棧也可以采用這種方式,使用異常來獲取調(diào)用棧。

完成SpringApplication實例的創(chuàng)建之后,直接調(diào)用其run方法,執(zhí)行應(yīng)用啟動。

源碼9-來自:SpringApplication

    public ConfigurableApplicationContext run(String... args) {
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();
        ConfigurableApplicationContext context = null;
        Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
        //配置系統(tǒng)屬性headless,默認為true
        configureHeadlessProperty();
        //獲取所有的運行時監(jiān)聽器
        SpringApplicationRunListeners listeners = getRunListeners(args);
        listeners.starting();//啟動監(jiān)聽器
        try {
            //封裝自定義參數(shù)
            ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
            //根據(jù)參數(shù)配置環(huán)境environment
            //如果是web項目則創(chuàng)建的是StandardServletEnvironment,否則是StandardEnvironment
            //然后配置屬性和profile
            ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
            //設(shè)置是否忽略BeanInfo,默認為true
            configureIgnoreBeanInfo(environment);
            //打印banner
            Banner printedBanner = printBanner(environment);
            //創(chuàng)建ApplicationContext
            //如果是Servlet web項目則創(chuàng)建AnnotationConfigEmbeddedWebApplicationContext,如果是Reactive web
            //項目則創(chuàng)建AnnotationConfigReactiveWebServerApplicationContext,否則AnnotationConfigApplicationContext
            context = createApplicationContext();
            //加載異常報告器
            exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class, 
                new Class[] { ConfigurableApplicationContext.class }, context);
            //籌備上下文環(huán)境,也就是初始化
            prepareContext(context, environment, listeners, applicationArguments, printedBanner);
            //刷新上下文
            refreshContext(context);
            //刷新后操作,這是兩個孔方法,是可以自定義實現(xiàn)邏輯的
            afterRefresh(context, applicationArguments);
            stopWatch.stop();
            if (this.logStartupInfo) {
                new StartupInfoLogger(this.mainApplicationClass)
                        .logStarted(getApplicationLog(), stopWatch);
            }
            listeners.started(context);//容器和應(yīng)用啟用之后,callRunner之前執(zhí)行
            //容器啟動完成之后回調(diào)runner,包括:ApplicationRunner和CommandLineRunner
            callRunners(context, applicationArguments);
        }
        catch (Throwable ex) {
            handleRunFailure(context, ex, exceptionReporters, listeners);
            throw new IllegalStateException(ex);
        }

        try {
            listeners.running(context);
        }
        catch (Throwable ex) {
            handleRunFailure(context, ex, exceptionReporters, null);
            throw new IllegalStateException(ex);
        }
        return context;
    }

這是整個容器啟動的所有邏輯入口,參照注釋即可理解,我們重點關(guān)注prepareContext方法,這個方法是在容器刷新之前的預(yù)初始化操作:

源碼10-來自:SpringApplication

    private void prepareContext(ConfigurableApplicationContext context,
            ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,
            ApplicationArguments applicationArguments, Banner printedBanner) {
        //設(shè)置environment到上下文
        context.setEnvironment(environment);
        //設(shè)置BeanName生成器
        //設(shè)置資源加載器或者類加載器
        postProcessApplicationContext(context);
        //執(zhí)行初始化器
        applyInitializers(context);
        listeners.contextPrepared(context);//執(zhí)行監(jiān)聽器的contextPrepared操作
        //配置日志信息
        if (this.logStartupInfo) {
            logStartupInfo(context.getParent() == null);
            logStartupProfileInfo(context);
        }

        // Add boot specific singleton beans
        //注冊單例(springApplicationArguments,springBootBanner)
        context.getBeanFactory().registerSingleton("springApplicationArguments",
                applicationArguments);
        if (printedBanner != null) {
            context.getBeanFactory().registerSingleton("springBootBanner", printedBanner);
        }

        // Load the sources
        Set<Object> sources = getAllSources();
        Assert.notEmpty(sources, "Sources must not be empty");
        //加載Bean到容器
        load(context, sources.toArray(new Object[0]));
        //執(zhí)行監(jiān)聽器的contextLoaded操作
        listeners.contextLoaded(context);
    }

重點關(guān)注下load方法,他的目的是加載Bean定義:

源碼11-來自:SpringApplication

    //為load操作做準備,創(chuàng)建資源讀取器掃描器
    protected void load(ApplicationContext context, Object[] sources) {
        if (logger.isDebugEnabled()) {
            logger.debug(
                    "Loading source " + StringUtils.arrayToCommaDelimitedString(sources));
        }
        //創(chuàng)建BeanDefinition讀取器,掃描器(AnnotatedBeanDefinitionReader,XmlBeanDefinitionReader,
        //GroovyBeanDefinitionReader,ClassPathBeanDefinitionScanner)
        BeanDefinitionLoader loader = createBeanDefinitionLoader(
                getBeanDefinitionRegistry(context), sources);
        //配置BeanName生成器,資源加載器,環(huán)境參數(shù)到讀取器和掃描器中
        if (this.beanNameGenerator != null) {
            loader.setBeanNameGenerator(this.beanNameGenerator);
        }
        if (this.resourceLoader != null) {
            loader.setResourceLoader(this.resourceLoader);
        }
        if (this.environment != null) {
            loader.setEnvironment(this.environment);
        }
        //執(zhí)行Bean的加載
        loader.load();
    }

    //統(tǒng)計加載Bean的數(shù)量
    public int load() {
        int count = 0;
        for (Object source : this.sources) {
            count += load(source);
        }
        return count;
    }

    //針對不同的情況加載bean資源
    private int load(Object source) {
        Assert.notNull(source, "Source must not be null");
        //
        if (source instanceof Class<?>) {
            return load((Class<?>) source);
        }
        //Resource資源可能是XML定義的Bean資源或者groovy定義的Bean資源
        if (source instanceof Resource) {
            return load((Resource) source);
        }
        //Packet資源一般是用于注解定義的Bean資源,需要掃描器掃描包
        if (source instanceof Package) {
            return load((Package) source);
        }
        //
        if (source instanceof CharSequence) {
            return load((CharSequence) source);
        }
        throw new IllegalArgumentException("Invalid source type " + source.getClass());
    }

針對不同的資源進行加載。

下面就要回到run方法中的重點操作refreshContext(context)了:

源碼12-來自:SpringApplication

    private void refreshContext(ConfigurableApplicationContext context) {
        refresh(context);
        if (this.registerShutdownHook) {
            try {
                context.registerShutdownHook();
            }
            catch (AccessControlException ex) {
                // Not allowed in some environments.
            }
        }
    }

    protected void refresh(ApplicationContext applicationContext) {
        Assert.isInstanceOf(AbstractApplicationContext.class, applicationContext);
        ((AbstractApplicationContext) applicationContext).refresh();
    }

這里也到了執(zhí)行refresh()方法的時候了,下面我們就要看看這個refresh了。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容