SpringBoot 启动过程源码分析

作者: jwfy | 来源:发表于2018-10-20 12:22 被阅读4次

    主要了解和学习下SpringBoot启动的大致原理是如何,以及知道几个注解的真正含义和用途是什么,SpringBoot就可以以SpringApplication.run(Bootstrap.class);这样的一句代码作为启动的入口

    1、SpringApplication 对象实例化

    SpringApplication 文件

    public static ConfigurableApplicationContext run(Object[] sources, String[] args) {
          // 传递的source其实就是类Bootstrap
        return new SpringApplication(sources).run(args);
        // 实例化一个SpringApplication对象执行run方法
    }
    

    实例化的时候又会执行initialize 方法

    private void initialize(Object[] sources) {
          // 这个source依旧是上文说的Bootstrap.class 类
        if (sources != null && sources.length > 0) {
            this.sources.addAll(Arrays.asList(sources));
            // 添加到source资源列表里面去
        }
        this.webEnvironment = deduceWebEnvironment();
        // 设置其是否为web环境
        setInitializers((Collection) getSpringFactoriesInstances(
                ApplicationContextInitializer.class));
        setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
        // 拆分为两步,一步是getSpringFactoriesInstances,再者就是set操作
        // set操作很简单,就是设置当前对象的初始化对象以及监听器
        this.mainApplicationClass = deduceMainApplicationClass();
        // 通过堆栈信息,推断 main方法的类对象为当前的主程序类
    }
    
    private static final String[] WEB_ENVIRONMENT_CLASSES = { "javax.servlet.Servlet",
                "org.springframework.web.context.ConfigurableWebApplicationContext" };
    
    private boolean deduceWebEnvironment() {
        for (String className : WEB_ENVIRONMENT_CLASSES) {
               // 遍历包含上述两个类名称的数组
            if (!ClassUtils.isPresent(className, null)) {
                   // 一旦发现不存在该类,就立即返回 deduce 推断不是web环境
                return false;
            }
        }
        // 必须同时包含两个类,才推断出为web环境
        return true;
    }
    

    getSpringFactoriesInstances 方法操作

    private <T> Collection<? extends T> getSpringFactoriesInstances(Class<T> type,
            Class<?>[] parameterTypes, Object... args) {
            // 传递的type就是上面说的ApplicationContextInitializer.class以及ApplicationListener.class类
            // 类型以及参数目前都没有具体指
        ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
        // Use names and ensure unique to protect against duplicates
        Set<String> names = new LinkedHashSet<String>(
                SpringFactoriesLoader.loadFactoryNames(type, classLoader));
                // 通过SpringFactoriesLoader 获取对应的名称,具体详情可以看下面的代码块
                // 这点需要重点关注下!!!
                // 结果就是返回一个set集合
        List<T> instances = createSpringFactoriesInstances(type, parameterTypes,
                classLoader, args, names);
                // 看样子就是创建一个实例的集合
        AnnotationAwareOrderComparator.sort(instances);
        // 然后通过AnnotationAwareOrderComparator 的排序规则跪实例集合进行排序
        // 排序就是看是否存在Order或者Priority注解,然后取得注解的值,排在集合前面
        return instances;
    }
    
    private <T> List<T> createSpringFactoriesInstances(Class<T> type,
            Class<?>[] parameterTypes, ClassLoader classLoader, Object[] args,
            Set<String> names) {
        List<T> instances = new ArrayList<T>(names.size());
        for (String name : names) {
               // 遍历上面取到的name 集合
            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);
            }
        }
        // 最后生成name映射的实例集合
        return instances;
    }
    

    SpringFactoriesLoader.loadFactoryNames 方法

    public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
    
    public static List<String> loadFactoryNames(Class<?> factoryClass, ClassLoader classLoader) {
        // 传递的factoryClass 就是上面的ApplicationContextInitializer、ApplicationListener.等
        String factoryClassName = factoryClass.getName();
        // 获取类的全名称
        try {
            Enumeration<URL> urls = (classLoader != null ? classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
                    ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
            // 如果类加载器为null,则使用系统默认的方法,否则使用当前传递的类加载器读取
            // 当前类加载器可以获取到的所有文件路径为“META-INF/spring.factories” 的地址
            
            List<String> result = new ArrayList<String>();
            while (urls.hasMoreElements()) {
                  // 迭代遍历url
                URL url = urls.nextElement();
                Properties properties = PropertiesLoaderUtils.loadProperties(new UrlResource(url));
                // 读取映射的spring.factories 文件的KV键值对,存放到properties对象中
                String factoryClassNames = properties.getProperty(factoryClassName);
                // 类似于map一般,获取对应的值
                result.addAll(Arrays.asList(StringUtils.commaDelimitedListToStringArray(factoryClassNames)));
                // 对值使用逗号分隔,生成list,然后去重添加到result
            }
            
            // 总结下来就是遍历当前类环境中的所有路径为“META-INF/spring.factories”的文件
            // 读取文件,然后获取k为当前类名称的所有值,然后存储到set中返回
            return result;
        }
        catch (IOException ex) {
            throw new IllegalArgumentException("Unable to load [" + factoryClass.getName() +
                    "] factories from location [" + FACTORIES_RESOURCE_LOCATION + "]", ex);
        }
    }
    

    到这里整个的initialize操作就已经清楚了,通过类加载器可获取的所有为“META-INF/spring.factories” 的地址的文件内容,然后获取key为ApplicationContextInitializer.class以及ApplicationListener.class的类名称的值集合
    然后依次就行实例化,最后排序返回,最后保存到当前对象的初始化集合以及监听器集合中,便于后续操作

    需要注意到SpringFactoriesLoader.loadFactoryNames 后面很多地方都需要使用该方法去获取相关内容

    当然现在只是完成了SpringApplication构造器里面的方法,还剩下后面的run(args)方法执行

    如下代码块就是SpringBoot的执行过程(最后的套路依旧是Spring Framework的执行策略)

    2、SpringApplication的run方法启动

    public ConfigurableApplicationContext run(String... args) {
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();
        // 记录当前服务开始启动
        ConfigurableApplicationContext context = null;
        // 上下文context,非常关键
        FailureAnalyzers analyzers = null;
        configureHeadlessProperty();
        // 给系统设置headless属性值
        SpringApplicationRunListeners listeners = getRunListeners(args);
        // 就是通过SpringFactoriesLoader 获取到所有SpringApplicationRunListener.class的对象
        // 其中args是用来进行实例化SpringApplicationRunListener对应的对象的构造器参数
        // 最后返回listener是整个系统的监听器
        listeners.starting();
        // 监听器开始执行
        try {
            ApplicationArguments applicationArguments = new DefaultApplicationArguments(
                    args);
            // 默认程序参数
            ConfigurableEnvironment environment = prepareEnvironment(listeners,
                    applicationArguments);
            // 准备运行的环境上下文
            Banner printedBanner = printBanner(environment);
            // 打印banner,默认输出当前springboot版本等内容,可以自定义设置文本或者图片
            // 具体看下面的方法详解
            context = createApplicationContext();
            // 创建SpringBoot最重要的上下文容器
            analyzers = new FailureAnalyzers(context);
            // 分析上下文出现问题的点,便于使用者可以直观的发现问题出现在哪里
            // 其实套路类似,就是使用SpringFactoriesLoader获取所有的FailureAnalyzer实例对象,然后设置其bean工厂为context的bean工厂上下文
            prepareContext(context, environment, listeners, applicationArguments,
                    printedBanner);
            // 看名称就是对context的前置准备工作,细节在后面说
            refreshContext(context);
            // 切入到spring framework的方式去完成context内容的装载
            // 如果需要注册终止钩子,则注册一个
            afterRefresh(context, applicationArguments);
            // 基本上认为springboot所需的服务都加载完成,进行最后的处理操作
            // 里面常用的就是CommandLineRunner
            listeners.finished(context, null);
            // 监听器的启动结束事件,
            stopWatch.stop();
            // 表示SpringBoot服务启动步骤完成,统计下启动时间等操作
            if (this.logStartupInfo) {
                new StartupInfoLogger(this.mainApplicationClass)
                        .logStarted(getApplicationLog(), stopWatch);
                  // 打印SpringBoot启动成功的消息,例如 Started xxx in 12.4 seconds 等信息
            }
            return context;
        }
        catch (Throwable ex) {
            handleRunFailure(context, listeners, analyzers, ex);
            // 启动失败了就会输出Application startup failed 日志
            // 并且会输出具体的错误内容信息
            throw new IllegalStateException(ex);
        }
    }
    
    private ConfigurableEnvironment prepareEnvironment(
            SpringApplicationRunListeners listeners,
            ApplicationArguments applicationArguments) {
        // Create and configure the environment
        ConfigurableEnvironment environment = getOrCreateEnvironment();
        // 如果当前环境值不为null,直接返回
        // 否则根据上文推断出的webEnvironment boolean 值 生成对象的环境对象
        // 当为true的时候,生成StandardServletEnvironment
        // 否则生成的是StandardEnvironment
        configureEnvironment(environment, applicationArguments.getSourceArgs());
        listeners.environmentPrepared(environment);
        if (!this.webEnvironment) {
              // 如果不是web的环境,再对当前的环境进行包装,生成一个新的运行环境对象
            environment = new EnvironmentConverter(getClassLoader())
                    .convertToStandardEnvironmentIfNecessary(environment);
        }
        return environment;
    }
    
    private Banner printBanner(ConfigurableEnvironment environment) {
         // 参数environment就是上面生成的环境对象
        if (this.bannerMode == Banner.Mode.OFF) {
              // 如果设置了banner关闭模式,则不进行打印输出操作
            return null;
        }
        ResourceLoader resourceLoader = this.resourceLoader != null ? this.resourceLoader
                : new DefaultResourceLoader(getClassLoader());
        // 资源加载器生成
        SpringApplicationBannerPrinter bannerPrinter = new SpringApplicationBannerPrinter(
                resourceLoader, this.banner);
        // 后续使用SpringApplicationBannerPrinter 类的print进行输出操作
        if (this.bannerMode == Mode.LOG) {
              // 打印模式,如果是log则输出到log中,否则输出到终端中
            return bannerPrinter.print(environment, this.mainApplicationClass, logger);
        }
        return bannerPrinter.print(environment, this.mainApplicationClass, System.out);
        // 大致操作就是先看是否存在自定义的图片类型或者文字类型 banner,如果有就优先确定banner对象
        // 否则就默认使用SpringBootBanner的banner(这个里面就包含了常规的springboot输出内容)
        // 然后解析banner的资源,得出将要输出的字符串内容(利用日志直接输出),存储到PrintedBanner
    }
    
    public static final String DEFAULT_WEB_CONTEXT_CLASS = "org.springframework."
            + "boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext";
    
    public static final String DEFAULT_CONTEXT_CLASS = "org.springframework.context."
            + "annotation.AnnotationConfigApplicationContext";
    
    protected ConfigurableApplicationContext createApplicationContext() {
        Class<?> contextClass = this.applicationContextClass;
        if (contextClass == null) {
            try {
                contextClass = Class.forName(this.webEnvironment
                        ? DEFAULT_WEB_CONTEXT_CLASS : DEFAULT_CONTEXT_CLASS);
                // 如果是web环境,则使用AnnotationConfigEmbeddedWebApplicationContext
                // 否则就使用AnnotationConfigApplicationContext
            }
            catch (ClassNotFoundException ex) {
                throw new IllegalStateException(
                        "Unable create a default ApplicationContext, "
                                + "please specify an ApplicationContextClass",
                        ex);
            }
        }
        return (ConfigurableApplicationContext) BeanUtils.instantiate(contextClass);
        // 直接通过类,反射生成无构造参数的对象,一般情况就是AnnotationConfigEmbeddedWebApplicationContext对象了
    }
    
    private void prepareContext(ConfigurableApplicationContext context,
            ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,
            ApplicationArguments applicationArguments, Banner printedBanner) {
        // 传递上下文、环境、上下文参数等数据
        context.setEnvironment(environment);
        postProcessApplicationContext(context);
        // 前置处理context上下文,包含了beanNameGenerator和resourceLoader
        // 其中beanNameGenerator 可以自定义规则约定bean的名称功能
        applyInitializers(context);
        // 应用ApplicationContextInitializer去初始化完成对context的操作
        // 具体的ApplicationContextInitializer对象就是在SpringApplication对象的构造方法中实例化创建的
        // 可以给context添加额外的操作,同时也可以很方便的自定义完成自己需要的功能
        listeners.contextPrepared(context);
        // 执行contextPrepared 上下文准备工作的事件
        if (this.logStartupInfo) {
               // 日志启动标志位,默认为true
            logStartupInfo(context.getParent() == null);
            logStartupProfileInfo(context);
            // 明确当前执行的主函数log,输出SpringBoot的开始启动信息
        }
    
        // 注册springApplicationArguments 这个bean到context中去
        context.getBeanFactory().registerSingleton("springApplicationArguments",
                applicationArguments);
        if (printedBanner != null) {
            context.getBeanFactory().registerSingleton("springBootBanner", printedBanner);
            // 同样是注册,打印早就完成了
        }
    
        // Load the sources
        Set<Object> sources = getSources();
        // 一般情况下这个source就是SpringBoot 启动的主类Class,注意不是实例对象
        Assert.notEmpty(sources, "Sources must not be empty");
        load(context, sources.toArray(new Object[sources.size()]));
        // 把source也就是主类当做bean,加载到spring的容器中
        listeners.contextLoaded(context);
        // 监听器的上下文导入完成事件 执行
    }
    
    private void callRunners(ApplicationContext context, ApplicationArguments args) {
        List<Object> runners = new ArrayList<Object>();
        runners.addAll(context.getBeansOfType(ApplicationRunner.class).values());
        runners.addAll(context.getBeansOfType(CommandLineRunner.class).values());
        AnnotationAwareOrderComparator.sort(runners);
        // 从context获取ApplicationRunner和CommandLineRunner 对象
        // 然后按照对应的规则进行排序
        for (Object runner : new LinkedHashSet<Object>(runners)) {
            if (runner instanceof ApplicationRunner) {
                callRunner((ApplicationRunner) runner, args);
            }
            if (runner instanceof CommandLineRunner) {
                callRunner((CommandLineRunner) runner, args);
            }
            // 分别执行各自的run方法
        }
        // 一般情况,我们如果需要在SpringBoot加载完成后需要完成一些自定义操作就是注册
        // ApplicationRunner或者CommandLineRunner 的bean对象,然后自定义实现run方法即可
    }
    

    3、总结

    就SpringBoot的启动整个过程而已,还是很清晰的,SpringBoot的套用SpringFramework的机制,为我们自定义实现功能提供了很好的便利,整个的SpringBoot就是重新包装了一个SpringFramework。

    里面有一个点是SpringFactoriesLoader.loadFactoryNames,从Spring3.2加入的功能,可以读取META-INF/spring.factories文件需要的内容数据,例如SpringBoot中EnableAutoConfiguration也是充分使用了该功能实现的,后续也会针对该功能总结一篇学习笔记

    更多关于Spring的内容可以看看Spring 源码学习

    相关文章

      网友评论

        本文标题:SpringBoot 启动过程源码分析

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