Springboot源码解析:一、SpringApplication的实例化

Springboot源码解析:SpringApplication的实例化

打个广告

个人想写《springboot源码解析》这一系列很久了,但是一直角儿心底的知识积累不足,所以一直没有动笔。
所以想找一些小伙伴一起写这一系列,互相纠错交流学习。

如果有小伙伴有兴趣一起把这一系列的讲解写完的话,加下我微信:13670426148,我们一起完成,当交流学习。

后期还想写一系列介绍rpc框架的,不过要再过一阵子了,先把springboot的写完

前言

这系列的教程从 Springboot项目的入口开始,即 SpringApplication.run(Application.class, args) 开始进行讲解。

启动入口

先贴一下入口类的代码:

@SpringBootApplication
//@EnableTransactionManagement
@EnableAsync
@EnableAutoConfiguration(exclude = {DataSourceAutoConfiguration.class})
@EnableScheduling
@EnableRetry
@ComponentScan(basePackages = {"*** ", "***"})
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

其中,入口类的类名是 Application, 这个类的类型将作为参数,传递给 SpringApplication的 run() 方法,还有一些初始化参数,这些都在run()方法的时候会进行处理,可以先记住他们。

现在可以记住 @EnableAutoConfiguration@EnableScheduling@ComponentScan 等注解,记住这些注解,后面将介绍其运行过程。

SpringApplication 实例化过程

Application 这个类没有继承所有任何类,他真的就是一个 启动类,就相当与写算法题时候的那个main函数,而你的计算流程就写在其他类或者方法里面。

SpringApplication用于从java main方法引导和启动Spring应用程序,默认情况下,将执行以下步骤来引导我们的应用程序:

  • 创建一个恰当的ApplicationContext实例(取决于类路径)
  • 注册CommandLinePropertySource,将命令行参数公开为Spring属性
  • 刷新应用程序上下文,加载所有单例bean
  • 触发全部CommandLineRunner bean

 大多数情况下,像SpringApplication.run(ShiroApplication.class, args);这样启动我们的应用,也可以在运行之前创建和自定义SpringApplication实例,具体可以参考注释中示例。

 SpringApplication可以从各种不同的源读取bean。 通常建议使用单个@Configuration类来引导,但是我们也可以通过以下方式来设置资源:

  • 通过AnnotatedBeanDefinitionReader加载完全限定类名
  • 通过XmlBeanDefinitionReader加载XML资源位置,或者是通过GroovyBeanDefinitionReader加载groovy脚本位置
  • 通过ClassPathBeanDefinitionScanner扫描包名称
  • 也就是说SpringApplication还是做了不少事的,具体实现后续会慢慢讲来,今天的主角只是SpringApplication构造方法。
public class SpringApplication{
    
     public SpringApplication(ResourceLoader resourceLoader, Object... sources) {
        this.bannerMode = Mode.CONSOLE;
        this.logStartupInfo = true;
        this.addCommandLineProperties = true;
        this.headless = true;
        this.registerShutdownHook = true;
 
        //todo -------------------------------------------------------
        this.additionalProfiles = new HashSet();
        //上面的信息都不是主要的,主要的信息在这里,在这里进行
        //(1)运行环境 (2) 实例化器 (3)监听器等的初始化过程,下面将详细解析
        this.initialize(sources);
    }
    
    public ConfigurableApplicationContext run(String... args) {
        *******
    }
}

这个 this.initialize(sources) 方法还是在 SpringApplication里面的,所以这个SpringApplication真的是贯穿springboot整个启动过程的一个类,后面还有一个run() 方法。

我们来看 initialize(Object[] sources) 方法的内容

private void initialize(Object[] sources) {
        //sources里面就是我们的入口类: Application.class
        if (sources != null && sources.length > 0) {
            this.sources.addAll(Arrays.asList(sources));
        }
        //这行代码设置SpringApplication的属性webEnvironment,deduceWebEnvironment方法是推断是否是web应用的核心方法
        this.webEnvironment = this.deduceWebEnvironment();
       //获取所有的实例化器Initializer.class this.setInitializers(this.getSpringFactoriesInstances(ApplicationContextInitializer.class));
        //获取所有的监听器
        this.setListeners(this.getSpringFactoriesInstances(ApplicationListener.class));
        //这个不解释了,就是我们的Application.class ,我们写的入口类,过程就是从当前的堆栈中找到我们写main方法额类,就是获取我们的入口类了
        this.mainApplicationClass = this.deduceMainApplicationClass();
}

下面就解释3个部分的具体实现:

(1) 推测运行环境

(2)获取所有的实例化器Initializer.class

? 又展示了其获取过程

(3)获取所有的监听器Initializer.class

推测运行环境

推测运行环境,并赋予个 this.webEnvironment 这个属性, deduceWebEnvironment方法是推断是否是web应用的核心方法。
在后面SpringApplication 的run()方法中创建 ApplicationContext 的时候就是根据webEnvironment这个属性来判断是 AnnotationConfigEmbeddedWebApplicationContext 还是 AnnotationConfigApplicationContext

代码如下:

private boolean deduceWebEnvironment() {
        String[] var1 = WEB_ENVIRONMENT_CLASSES;
        int var2 = var1.length;

        for(int var3 = 0; var3 < var2; ++var3) {
            String className = var1[var3];
            if (!ClassUtils.isPresent(className, (ClassLoader)null)) {
                return false;
            }
        }

        return true;
    }

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

推断过程很简单,不过我不理解为什么这么写,因为我这个是web项目,所以 this.webEnvironment 的值为true

ClassUtils.isPresent()的过程其实很简单,就是判断 WEB_ENVIRONMENT_CLASSES 里面的两个类能不能被加载,如果能被加载到,则说明是web项目,其中有一个不能被加载到,说明不是。

获取所有的实例化器Initializer.class

//看完这个方法真觉得很棒,获取工厂实例
this.setInitializers(this.getSpringFactoriesInstances(ApplicationContextInitializer.class));

private <T> Collection<? extends T> getSpringFactoriesInstances(Class<T> type) {
    return this.getSpringFactoriesInstances(type, new Class[0]);
}
//记住 ty
private <T> Collection<? extends T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) {
        //这个是获取类加载器
        ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
        //type是ApplicationContextInitializer.class,获取类型工厂的名字
        Set<String> names = new LinkedHashSet(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
        //获取工厂实例
        List<T> instances = this.createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
        //排序,按照@Order的顺序进行排序,没有@Order的话,就按照原本的顺序进行排序,不管他问题不大
        AnnotationAwareOrderComparator.sort(instances);
        return instances;
    }

获取指定类型工厂的名字

public static List<String> loadFactoryNames(Class<?> factoryClass, ClassLoader classLoader) {
        String factoryClassName = factoryClass.getName();

        try {
            //获取所有 jar包下面的 META-INF/spring.factories 的urls
            Enumeration<URL> urls = classLoader != null ? classLoader.getResources("META-INF/spring.factories") : ClassLoader.getSystemResources("META-INF/spring.factories");
            ArrayList result = new ArrayList();

            while(urls.hasMoreElements()) {
                URL url = (URL)urls.nextElement();
                //每个spring.factories里的下的内容装载成Properties信息
                Properties properties = PropertiesLoaderUtils.loadProperties(new UrlResource(url));
                //下面内容会继续解析
                String factoryClassNames = properties.getProperty(factoryClassName);
                result.addAll(Arrays.asList(StringUtils.commaDelimitedListToStringArray(factoryClassNames)));
            }

            return result;
        } catch (IOException var8) {
            throw new IllegalArgumentException("Unable to load [" + factoryClass.getName() + "] factories from location [" + "META-INF/spring.factories" + "]", var8);
        }
    }

图1.1如下:

Springboot源码解析:一、SpringApplication的实例化

Springboot源码解析:一、SpringApplication的实例化

还有很多,就不一一列举出来了

(1)就是找到所有的 /MEIT-INF下面的spring.factory

(2)转换成 properties,

(3)properties.getProperty(factoryClassName)

关于 /MEIT-INF/spring.factory,不知道大家有没有写过 starter,如果不知道是什么,很多依赖比如mybatis-plus 、springboot的包里面都有很多依赖,打成starter的形式,被我们springboot项目依赖。

可以查一查springboot自定义starter,看一下,大概就知道一个spring.factory的作用了。。

? 此时 factoryClassName 相当于是一个key获取对应的properties里面是否有 "org.springframework.context.ApplicationContextInitializer"对应的值,有的话,添加到result中

到最后可以得到的有,即图1.2

Springboot源码解析:一、SpringApplication的实例化

获取工厂实例(根据类名)

再进行 获取工厂实例 操作,步骤很简单,就是用构造器和类名生成指定的 Inializer, 到现在的过程都很简单。

贴个代码,不进行解释了

private <T> List<T> createSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, ClassLoader classLoader, Object[] args, Set<String> names) {
        List<T> instances = new ArrayList(names.size());
        Iterator var7 = names.iterator();

        while(var7.hasNext()) {
            String name = (String)var7.next();

            try {
                Class<?> instanceClass = ClassUtils.forName(name, classLoader);
                Assert.isAssignable(type, instanceClass);
                Constructor<?> constructor = instanceClass.getDeclaredConstructor(parameterTypes);
                T instance = BeanUtils.instantiateClass(constructor, args);
                instances.add(instance);
            } catch (Throwable var12) {
                throw new IllegalArgumentException("Cannot instantiate " + type + " : " + name, var12);
            }
        }

        return instances;
    }


获取所有的监听器Initializer.class

类似的上面的步骤,监听器的获得结果如下: 图1.3

Springboot源码解析:一、SpringApplication的实例化

总结

(1)还记得SpringApplication.class有那些属性吗

public class SpringApplication{
    private List<ApplicationContextInitializer<?>> initializers; //如图1.2这个是拿6个Initializer
    private WebApplicationType webApplicationType;   //这个是true
    private List<ApplicationListener<?>> listeners;   //这和是图1.3的10个Listener
    private Class<?> mainApplicationClass;  //结果就是DemoApplication
    //另外还有构造方法设置的值
    public SpringApplication(ResourceLoader resourceLoader, Object... sources) {
        this.bannerMode = Mode.CONSOLE;
        this.logStartupInfo = true;
        this.addCommandLineProperties = true;
        this.headless = true;
        this.registerShutdownHook = true;
 
        //todo -------------------------------------------------------
        this.additionalProfiles = new HashSet();
        //上面的信息都不是主要的,主要的信息在这里,在这里进行
        //(1)运行环境 (2) 实例化器 (3)监听器等的初始化过程,下面将详细解析
        this.initialize(sources);
    }
}

(2) SpringApplication.class 就是一个操作启动过程的类

? 实例化过程就是加载一些最初始的参数和信息,比如监听器,实例化器,bannerMode,additionalProfiles等信息。其中最主要的还是监听器和实例化器, 关于监听器,是springboot启动过程最重要的一部分,其启动过程的机制大概就是, 用一个广播,他广播一些event事件,然后这些监听器(10个),就会根据这些事件,做不同的反应。 监听器模式大家可以先了解一下。

下期预告

下面会讲 SpringApplication.run() 里面的内容了,主要run() 的 “ 广播-事件-监听器” 的执行过程, 争取先吃透再解析。

参考链接:
spring-boot-2.0.3不一样系列之源码篇

Springboot源码解析:一、SpringApplication的实例化

上一篇:maven项目使用逆向工程生成实体类和mapper相关简单配置


下一篇:vue中axios的post和get请求示例