Spring Boot源码(二) - SpringApplication的run方法

 

目录

一、构造器初始化和run之前可以调整的配置

1、确定当前ApplicationContext类型

2、使用类加载器加载ApplicationContextInitializer和ApplicationListener

1)、获取类加载器(默认不会传入)

2)、通过类加载器和名称获取类全限定名

3)、根据类加载器、环境参数等信息反射生成类

4)、排序(按照@Order等)

二、SpringApplication的run方法(主要流程节点)


一、构造器初始化和run之前可以调整的配置

    Spring Boot在执行SpringApplication的run方法之前,其实在构造函数中已经完成了很多初始化的准备操作,并且在run方法之前我们可以配置很多信息(上一篇博客中分析了很多的字段都是有Setter的)。比如我们可以将代码写作如下:

public static void main(String[] args) {
    SpringApplication springApplication = new SpringApplication(KevinToolApplication.class);
    springApplication.setWebApplicationType(WebApplicationType.SERVLET);
    springApplication.run(args);
}

    先看看全参构造器本身都初始化了哪些组件或者配置:

public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
    this.resourceLoader = resourceLoader;
    Assert.notNull(primarySources, "PrimarySources must not be null");
    this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
    // 确定当前的ApplicationContext类型
    this.webApplicationType = WebApplicationType.deduceFromClasspath();
    // 设置ApplicationContextInitializer
    setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
    // 设置ApplicationListener
    setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
    // 确定main方法所在的类
    this.mainApplicationClass = deduceMainApplicationClass();
}

一般情况下类加载器为null,main方法所在的类则会传入(比如当前为KevinToolApplication.class)。

 

1、确定当前ApplicationContext类型

static WebApplicationType deduceFromClasspath() {
    if (ClassUtils.isPresent(WEBFLUX_INDICATOR_CLASS, null) && !ClassUtils.isPresent(WEBMVC_INDICATOR_CLASS, null)
            && !ClassUtils.isPresent(JERSEY_INDICATOR_CLASS, null)) {
        return WebApplicationType.REACTIVE;
    }
    for (String className : SERVLET_INDICATOR_CLASSES) {
        if (!ClassUtils.isPresent(className, null)) {
            return WebApplicationType.NONE;
        }
    }
    return WebApplicationType.SERVLET;
}

    现在是几个基础判断,后续可以通过Settter进行修改,最好根据createApplicationContext方法映射ApplicationContext类型。

1)、如果有DispatcherHandler,没有DispatcherServlet和ServletContainer则创建REACTIVE类型。

2)、如果没有ConfigurableWebApplicationContext类型,则创建NONE(非Web类型)。

3)、否则就创建SERVLET普通Web类型。

 

2、使用类加载器加载ApplicationContextInitializerApplicationListener

    都会使用统一的加载方法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 = 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;
}

1)、获取类加载器(默认不会传入)

    这里可以思考,同一个class文件,不同的类加载器加载完成后会得到两个类。所以每有特殊情况的话最好不要传入自己的类加载器,只是Spring Boot在这里做了扩展点。

public ClassLoader getClassLoader() {
    if (this.resourceLoader != null) {
        return this.resourceLoader.getClassLoader();
    }
    return ClassUtils.getDefaultClassLoader();
}
public static ClassLoader getDefaultClassLoader() {
    ClassLoader cl = null;
    try {
        cl = Thread.currentThread().getContextClassLoader();
    }   // 省略部分代码
    return cl;
}

      比较情况的看到,或当前线程的类加载器(AppClassLoader类型),保证整个系统中使用同一个类加载器。

 

2)、通过类加载器和名称获取类全限定名

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 ?
                // FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories"
                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;
    } // 省略部分代码
}

    首先在"META-INF/spring.factories"中获取对于的配置信息,并且按照类加载器进行缓存。缓存对象cache的结构为:Map<ClassLoader, MultiValueMap<String, String>> cache = new ConcurrentReferenceHashMap<>();

3)、根据类加载器、环境参数等信息反射生成类

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

    直接通过反射创建对象。

4)、排序(按照@Order等)

    所有使用该公共方法获取的类型都可以添加@Order或@Priority或Ordered,最后都会使用java.util.Comparator的子类AnnotationAwareOrderComparator进行排序处理。

 

二、SpringApplication的run方法(主要流程节点)

public ConfigurableApplicationContext run(String... args) {
    // 使用Spring自己的定时器工具
    StopWatch stopWatch = new StopWatch();
    stopWatch.start();

    ConfigurableApplicationContext context = null;
    Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
    // 设置Headless
    configureHeadlessProperty();
    // 获取SpringApplicationRunListener类型
    SpringApplicationRunListeners listeners = getRunListeners(args);
    // SpringApplicationRunListener生命周期的 starting
    listeners.starting();
    try {
        // 将main方法的args参数进行封装,为后续的CommandLineRunner回调做准备
        ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
        // 准备Environment
        ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
        configureIgnoreBeanInfo(environment);
        // 打印Banner图,就使用默认的SpringBootBanner即可
        Banner printedBanner = printBanner(environment);
        // 根据SpringApplication构造器出事化的WebApplicationType类型,反射创建ApplicationContext对象
        context = createApplicationContext();
        // 获取SpringBootExceptionReporter类型
        exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
                new Class[] { ConfigurableApplicationContext.class }, context);
        // Spring的ApplicationContext的refresh前的准备工作(ApplicationContextInitializer完成初始回调,
        // SpringApplicationRunListener生命周期的 contextLoaded)
        prepareContext(context, environment, listeners, applicationArguments, printedBanner);
        // Spring的ApplicationContext的refresh,ServletWebServerApplicationContext类型则会在onRefresh阶段执行
        // createWebServer完成内嵌Servlet容器的创建
        refreshContext(context);
        // refresh完成后的动作,默认为空留给子类实现
        afterRefresh(context, applicationArguments);
        stopWatch.stop();
        if (this.logStartupInfo) {
            // 如果需要打印日志,则将StopWatch中的启动信息进行打印
            new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
        }
        // SpringApplicationRunListener生命周期的 started
        listeners.started(context);
        // 完成ApplicationRunner和CommandLineRunner的回调(主要用于与args相关的回调)
        callRunners(context, applicationArguments);
    }
    catch (Throwable ex) {
        // 异常处理
        handleRunFailure(context, ex, exceptionReporters, listeners);
        throw new IllegalStateException(ex);
    }
    
    try {
        // SpringApplicationRunListener生命周期的running
        listeners.running(context);
    }
    catch (Throwable ex) {
        // 异常处理
        handleRunFailure(context, ex, exceptionReporters, null);
        throw new IllegalStateException(ex);
    }
    return context;
}

    之前说过AbstractApplicationContext的refresh分为模板方法,那当前的run方法也为模板方法,定义了执行流程。那么,当前主要关注的流程节点有:

1)、SpringApplicationRunListener对应的生命周期,一直贯穿着整个的run方法

2)、prepareEnvironment(准备环境)

    初始化ConfigurableEnvironment类型,并且赋值给ApplicationContext。可以参见:SpringIoc源码(六)- ApplicationContext(二)- refresh(obtainFreshBeanFactory和StandardEnvironment)

3)、printBanner(打印Banner图)

4)、createApplicationContext(反射创建ApplicationContext对象)

5)、SpringBootExceptionReporter(异常处理器的获取)

    为了以后执行异常时,调用SpringBootExceptionReporter列表的回调方法。

6)、prepareContext(ApplicationContext的refresh前的准备工作)

    ApplicationContext的refresh前的准备工作(ApplicationContextInitializer完成初始回调,SpringApplicationRunListener生命周期的 contextLoaded)

7)、refreshContext(ApplicationContext的refresh执行)

    ApplicationContext的refresh,ServletWebServerApplicationContext类型则会在onRefresh阶段执行。createWebServer完成内嵌Servlet服务器(如:Tomcat、Jetty)的创建。

8)、afterRefresh(模板空方法)

    refresh完成后的动作,默认为空留给子类实现

9)、如果需要打印日志,则将StopWatch中的启动信息进行打印

10)、callRunners(main方法args相关回调)

    完成ApplicationRunner和CommandLineRunner的回调(主要用于与args相关的回调)

11)、handleRunFailure(中间有异常出现,异常逻辑回调)

 

详细主要节点,后续进行分析

 

©️2020 CSDN 皮肤主题: 技术黑板 设计师: CSDN官方博客 返回首页
实付0元
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值