美文网首页
SpringBoot启动类SpringApplication源码

SpringBoot启动类SpringApplication源码

作者: 每天进步一丢儿丢儿 | 来源:发表于2020-05-25 11:59 被阅读0次
    创建一个SpringBoot应用启动类的两个关键点
      1. 在main方法中调用 SpringApplication.run(Class<?> primarySource, String... args)
      1. 在启动类上添加@SpringBootApplication注解

    下面针对以上两点对SpringApplication源码进行解读,首先来看SpringApplication的静态run方法

    /**
         * Static helper that can be used to run a {@link SpringApplication} from the
         * specified source using default settings.
         * @param primarySource the primary source to load
         * @param args the application arguments (usually passed from a Java main method)
         * @return the running {@link ApplicationContext}
         */
        public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {
            return run(new Class<?>[] { primarySource }, args);
        }
    
        /**
         * Static helper that can be used to run a {@link SpringApplication} from the
         * specified sources using default settings and user supplied arguments.
         * @param primarySources the primary sources to load
         * @param args the application arguments (usually passed from a Java main method)
         * @return the running {@link ApplicationContext}
         */
        public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
            return new SpringApplication(primarySources).run(args);
        }
    

    可以看出第一个静态run方法是对第二个静态run方法的调用,来看看第二个静态run方法,这个方法通过SpringApplication对象的构造方法创建了一个SpringApplication的对象实例,并调用了SpringApplication的实例方法run(args[])。那么SpringApplication构造做了那些工作呢?

    /**
         * Create a new {@link SpringApplication} instance. The application context will load
         * beans from the specified primary sources (see {@link SpringApplication class-level}
         * documentation for details. The instance can be customized before calling
         * {@link #run(String...)}.
         * @param primarySources the primary bean sources
         * @see #run(Class, String[])
         * @see #SpringApplication(ResourceLoader, Class...)
         * @see #setSources(Set)
         */
        public SpringApplication(Class<?>... primarySources) {
            this(null, primarySources);
        }
    
        /**
         * Create a new {@link SpringApplication} instance. The application context will load
         * beans from the specified primary sources (see {@link SpringApplication class-level}
         * documentation for details. The instance can be customized before calling
         * {@link #run(String...)}.
         * @param resourceLoader the resource loader to use
         * @param primarySources the primary bean sources
         * @see #run(Class, String[])
         * @see #setSources(Set)
         */
        @SuppressWarnings({ "unchecked", "rawtypes" })
        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();
        }
    

    通过上面的代码可以看出SpringApplication主要做了一下几件事情:

    • 确定该应用的源。
    • 根据类路径中引入的web组件库推断出当前web环境的类型,具体推断过程参考WebApplicationType枚举类的静态deduceFromClasspath()方法。
    • 将所有实现了ApplicationContextInitializer接口的实现类进行实例化。
    • 将所有实现了ApplicationListener接口的实现类进行实例化。
    • 通过当前线程的堆栈信息推断出mainApplicationClass。

    下面重点关注ApplicationContextInitializer、ApplicationListener接口实现类的实例化过程。即对SpringApplicationget的SpringFactoriesInstances方法的调用。

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

    实例化的过程利用了java的反射机制,具体实现参考下面的代码。

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

    这段代码就是java反射的具体使用,没有特别需要说明的地方,下面主要分析一下getSpringFactoriesInstances()方法中的SpringFactoriesLoader.loadFactoryNames(type, classLoader)这句代码。

    public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
            String factoryTypeName = factoryType.getName();
            return loadSpringFactories(classLoader).getOrDefault(factoryTypeName, 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()) {
                        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);
            }
        }
    

    上面这段源码中Enumeration<URL> urls = (classLoader != null ?classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
    ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION))这句代码是一个从spring.factories中加载资源,并将指定类型的资源从Map中取出,这段代码的目的就是取出spring.factories中配置的ApplicationListener接口的实现了类和ApplicationContextInitializer接口实现类的集合,并将其实例化。

    构造之后的run()方法解析

    public ConfigurableApplicationContext run(String... args) {
                    StopWatch stopWatch = new StopWatch();
                    stopWatch.start();
                    ConfigurableApplicationContext context = null;
                    // 创建一个SpringBoot异常报告集合
                    Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
                    configureHeadlessProperty();
                    SpringApplicationRunListeners listeners = getRunListeners(args);
                    // 1. 发布一个应用启动事件
                    listeners.starting();
                    try {
                        ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
                        // 2. 根据当前ClassPath的类型创建一个应用启动环境,并发布一个环境准备事件 listeners.environmentPrepared(environment);
                        ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
                        // 配置启动环境的Bean信息忽略参数
                        configureIgnoreBeanInfo(environment);
                        // 配置应用启动环境的Banner,Banner打印机制是在环境准备完成之后进行的
                        Banner printedBanner = printBanner(environment);
                        // 3. 根据当前ClassPath环境创建ApplicationContext
                        context = createApplicationContext();
                        // 实例化异常报告类
                        exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
                                new Class[] { ConfigurableApplicationContext.class }, context);
                        // 4. 准备ApplicationContext,同时发布上下文准备事件和上下文载入事件,这一步同时准备和载入上下文
                        prepareContext(context, environment, listeners, applicationArguments, printedBanner);
                        // 刷新应用上下文
                        refreshContext(context);
                        afterRefresh(context, applicationArguments);
                        stopWatch.stop();
                        if (this.logStartupInfo) {
                            new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
                       // 5. 发布应用启动成功事件              }
                        listeners.started(context);
                        // 调用所有实现了ApplicationRunner接口和CommandRunner接口的实例
                        callRunners(context, applicationArguments);*        }
                    catch (Throwable ex) {
                        // 6. 如果整个过程包错,则会发布启动异常的错误报告,并发布启动失败事件
                        handleRunFailure(context, ex, exceptionReporters, listeners);
                        throw new IllegalStateException(ex);
                    }
     
                    try {
                        // 7.发布应用程序运行中事件
                        listeners.running(context);
                    }
                    catch (Throwable ex) {
                        handleRunFailure(context, ex, exceptionReporters, null);
                        throw new IllegalStateException(ex);
                    }
                    return context;
                }
    

    run方法做了一下几件事情

    • 开启了应用停止监控
    • 配置了headless系统环境
    • 实例化了{@link org.springframework.boot.SpringApplicationRunListener}监听器,并在SpringApplication启动过程中发布相应的事件
    • 创建并准备了应用启动的环境,如果是Servlet环境则创建StandardServletEnvironment实例,如果是Reactive
      环境则创建StandardReactiveWebEnvironment环境,默认则创建StandardEnvironment环境
    • 实例化了{@link org.springframework.boot.SpringBootExceptionReporter}
    • 创建并准备了应用上下文环境,并在期间执行了所有{@link ApplicationContextInitializer}的接口实现的调用。
    • 刷新了应用上下文环境
    • 调用了{@link org.springframework.boot.ApplicationRunner}和{@link org.springframework.boot.CommandLineRunner}实现类的run方法
    • 当启动报错时,处理了错误报告

    SpringBoot应用上下文的实例化过程,代码如下:

    protected ConfigurableApplicationContext createApplicationContext() {
            Class<?> contextClass = this.applicationContextClass;
            if (contextClass == null) {
                try {
                    switch (this.webApplicationType) {
                    case SERVLET:
                        contextClass = Class.forName(DEFAULT_SERVLET_WEB_CONTEXT_CLASS);
                        break;
                    case REACTIVE:
                        contextClass = Class.forName(DEFAULT_REACTIVE_WEB_CONTEXT_CLASS);
                        break;
                    default:
                        contextClass = Class.forName(DEFAULT_CONTEXT_CLASS);
                    }
                }
                catch (ClassNotFoundException ex) {
                    throw new IllegalStateException(
                            "Unable create a default ApplicationContext, please specify an ApplicationContextClass", ex);
                }
            }
            return (ConfigurableApplicationContext) BeanUtils.instantiateClass(contextClass);
        }
    
    

    这段代码通过反射的方式实例化了相应的上下文,本例中我们使用的是webServlet环境所以实例化的是{@link org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext}容器。到此SpringBoot的启动流程差不多就完成了。

    相关文章

      网友评论

          本文标题:SpringBoot启动类SpringApplication源码

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