zoukankan      html  css  js  c++  java
  • 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如下:

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

    (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

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

    再进行 获取工厂实例 操作,步骤很简单,就是用构造器和类名生成指定的 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

    总结

    (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不一样系列之源码篇

  • 相关阅读:
    LeetCode 1275. 找出井字棋的获胜者 Find Winner on a Tic Tac Toe Game
    LeetCode 307. 区域和检索
    LeetCode 1271 十六进制魔术数字 Hexspeak
    秋实大哥与花 线段树模板
    AcWing 835. Trie字符串统计
    Leetcode 216. 组合总和 III
    Mybatis 示例之 复杂(complex)属性(property)
    Mybatis 示例之 复杂(complex)属性(property)
    Mybatis 高级结果映射 ResultMap Association Collection
    Mybatis 高级结果映射 ResultMap Association Collection
  • 原文地址:https://www.cnblogs.com/disandafeier/p/12081275.html
Copyright © 2011-2022 走看看