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

  • 相关阅读:
    浓缩版java8新特性
    restful的认识和用法
    常用业务返回对象类ResponseJson
    微信小程序使用websocket通讯的demo,含前后端代码,亲测可用
    完整且易读的最新版小程序登录态和检验注册过没的app.js写法
    完整且易读的微信小程序的注册页面(包含倒计时验证码、获取用户信息)
    BCD工具类(8421)
    IDEA下使用protobuf2(java)
    chrome 调试技巧
    encodeURI和encodeURIComponent的区别?
  • 原文地址:https://www.cnblogs.com/disandafeier/p/12081275.html
Copyright © 2020-2023  润新知