java复习笔记2--SpringCloud系列一:微服务启动原理探索

微服务架构的趋势

随着数据量的不断增大,大数据时代的到来,互联网技术的不断发展和变革,微服务架构和云服务平台以及大数据成为了时下最热门的话题。现在,比较流行的微服务框架也有很多, 比如阿里的Dubbo,基于soringBoot的SpringCloud,Apache的thrift,google的Grpc,这些都提供了很多高并发以及负载均衡等的一系列的解决方案。而我们今天的主角,就是热度最高的SpringCloud,目前只支持java。

什么是SpringCloud

springCloud是基于SpringBoot的一整套实现微服务的框架。他提供了微服务开发所需的配置管理、服务发现、断路器、智能路由、微代理、控制总线、全局锁、决策竞选、分布式会话和集群状态管理等组件。 而SpringCloud之所以又这么完善的解决方案和体系,得益于他兼容了很多优秀的开源的框架,这个之后会具体的一个个温习。今天,我们就先从SpringCloud的启动开始看,学习一下他的启动和加载过程。

SpringCloud的启动方式

相比大家对于SpringBoot或者SpringCloud都不陌生,他的启动方式很简单:

@SpringBootApplication
@EnableFeignClients
@EnableDiscoveryClient
public class EurekaClientApplication {

	public static void main(String[] args) {
		SpringApplication.run(EurekaClientApplication.class, args);
	}
}

当然,也可以分开写,像下面这样:

@SpringBootApplication
@EnableFeignClients
@EnableDiscoveryClient
public class EurekaClientApplication {

	public static void main(String[] args) {
		SpringApplication application = new SpringApplication(EurekaClientApplication.class);
		application.run(args);
	}
}

其实二者的本质是完全一样的。@SpringBootApplication注解就是备注这个服务为SpringBoot服务,关于注解这里就先不详细讲了,之后会单独花时间去详细说明,结合反射一起。今天,先重点弄清楚启动流程以及都做了什么。我们就按照第二种模式,分两个方法进行探索。

初始化SpringApplication

首先看第一行代码,SpringApplication application = new SpringApplication(EurekaClientApplication.class);这个是一个简单的构造方法,大概能够猜测是实例化一个SpringApplication。接下来上代码:

    public SpringApplication(ResourceLoader resourceLoader, Class... primarySources) {
        this.sources = new LinkedHashSet();
        this.bannerMode = Mode.CONSOLE;
        this.logStartupInfo = true;
        this.addCommandLineProperties = true;
        this.headless = true;
        this.registerShutdownHook = true;
        this.additionalProfiles = new HashSet();
        this.resourceLoader = resourceLoader;
        Assert.notNull(primarySources, "PrimarySources must not be null");
        this.primarySources = new LinkedHashSet(Arrays.asList(primarySources));
        this.webApplicationType = this.deduceWebApplicationType();
        this.setInitializers(this.getSpringFactoriesInstances(ApplicationContextInitializer.class));
        this.setListeners(this.getSpringFactoriesInstances(ApplicationListener.class));
        this.mainApplicationClass = this.deduceMainApplicationClass();
    }

重点来了,这里主要做了三件事。

  • 1.属性赋值,这个很好理解
  • 2.检查服务的类型,web项目还是普通项目,也就是this.webApplicationType = this.deduceWebApplicationType();这行代码。推断当前环境是哪种Web环境(Servlet、Reactive),或者不是Web环境,判断逻辑是Classpath是否有相应环境需要的类,比如DispatcherHandler,.servlet.DispatcherServlet,代码思路比较清晰,源码就不贴了,大家有时间可以去看一下。
  • 3.初始化initializers,它是一个用来存放SpringApplication所需的ApplicationContextInitializer实例的ArrayList,这些实例是用来初始化应用程序的上下文环境的。走读代码发现,这里会加载所有继承 ApplicationContextInitializer的实现类。在Spring中也有一种类似于Java SPI(service provider interface 不了解的可以百度了解一下)的加载机制它在META-INF/spring.factories文件中配置接口的实现类名称,然后在程序中读取这些配置文件并实例化。这种自定义的SPI机制是Spring Boot Starter实现的基础。
Set<String> names = new LinkedHashSet(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
List<T> instances = this.createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);

这两行代码就是具体实现,他会加载所有spring包下的META-INF/spring.factories,里面会有类实现接口的描述,这样,通过接口,可以找到所有的实现类,进而初始化这些类的实例对象。
在这里插入图片描述
上面有具体的路径,大家可以点进去看一下,具体的接口和实现类的描述和排版,类似于一个键值对的关系。

  • 4.初始化listeners,方法和上面雷同,导入所有的ApplicationListener的实现类并创建实例对象。
  • 5.deduceMainApplicationClass()推断当前的应用程序的入口类,即我们的EurekaClientApplication.class。因为这里入参可以是多个,维护的是一个set,所以会执行这个操作,对于我们这个来说,mainApplicationClass = EurekaClientApplication.class。

启动 Spring 应用程序

这里,就是我们之前启动SpringCloud的第二行代码,调用SpringApplicaiton的run方法。这里代码模块较复杂,建议debug模式下一步步细看,首先,我们贴出源码:

public ConfigurableApplicationContext run(String... args) {
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();
        ConfigurableApplicationContext context = null;
        Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList();
        this.configureHeadlessProperty();
        SpringApplicationRunListeners listeners = this.getRunListeners(args);
        listeners.starting();

        Collection exceptionReporters;
        try {
            ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
            ConfigurableEnvironment environment = this.prepareEnvironment(listeners, applicationArguments);
            this.configureIgnoreBeanInfo(environment);
            Banner printedBanner = this.printBanner(environment);
            context = this.createApplicationContext();
            exceptionReporters = this.getSpringFactoriesInstances(SpringBootExceptionReporter.class, new Class[]{ConfigurableApplicationContext.class}, new Object[]{context});
            this.prepareContext(context, environment, listeners, applicationArguments, printedBanner);
            this.refreshContext(context);
            this.afterRefresh(context, applicationArguments);
            stopWatch.stop();
            if(this.logStartupInfo) {
                (new StartupInfoLogger(this.mainApplicationClass)).logStarted(this.getApplicationLog(), stopWatch);
            }

            listeners.started(context);
            this.callRunners(context, applicationArguments);
        } catch (Throwable var10) {
            this.handleRunFailure(context, var10, exceptionReporters, listeners);
            throw new IllegalStateException(var10);
        }

        try {
            listeners.running(context);
            return context;
        } catch (Throwable var9) {
            this.handleRunFailure(context, var9, exceptionReporters, (SpringApplicationRunListeners)null);
            throw new IllegalStateException(var9);
        }
    }

  • 首先,启动监听StopWatch,主要进行响应时间的监听和输出
  • 设置java.awt.headless模式,来进行简单的图像处理,无需调用server设备, 很好解决linux环境下可能没有图形处理或者设备工具而带来的报错。这也是前面初始化SpringApplication的时候将java.awt.headless设置为true的原因。
  • 加载SpringApplicationRunListeners实现类,还是熟悉的配方,遍历spring.factory,以接口作为key找寻对应的value。并且触发开始的事件。
  • 接下来几个就是重头戏了。。首先,准备运行环境,prepareEnvironment,这里会根据之前设置的WebApplicationType和webenvironment来创建对应的运行环境,这里创建的是StandardServletEnvironment。具体代码就不贴出来了,它主要有两个方法,一个是设置一些PropertySources参数的方法,第二个方法是在后面的SpringApplication的refresh方法中调用的。
  • configureEnvironment(environment, applicationArguments.getSourceArgs())这个方法不涉及到任何操作,大家可以浏览一下就跳过。
  • printBanner就是我们经常说的打印的那个spring的日志,大家可以写一个文件替换它。
  • createApplicationContext方法,是根据SpringApplication的webApplicationType来实例化对应的上下文,而webApplicationType就是第一大步里面判断并初始化的;如果webApplicationType的值是SERVLET,那么实例化AnnotationConfigServletWebServerApplicationContext,如果是REACTIVE则实例化AnnotationConfigReactiveWebServerApplicationContext(响应式编程,后续再看),如果既不是SERVLET、也不是REACTIVE,那么则是默认情况(也就是我们所说的非web引用),实例化AnnotationConfigApplicationContext。web应用这里就是实例化了AnnotationConfigServletWebServerApplicationContext。
  • prepareContext,这个方法,看名字就能想到是准备上下文,也就是往我们上一步生成的AnnotationConfigServletWebServerApplicationContext上下文中继续填充属性。这里方法较多,主要是将context中的environment替换成SpringApplication中创建的environment,将SpringApplication中的initializers应用到context中,将SpringApplication中的listeners注册到context中,加载两个单例bean到beanFactory中,初始化资源加载器BeanDefinitionLoader,并广播ApplicationPreparedEvent事件。
  • refreshContext方法做的事情更多,这里,才是创建了一个真正的Spring上下文容器。由于这里东西较多,涉及代码量较大,明天再从头到尾跟一遍。不漏一个细节,彻底弄清楚加载的流程。之后也会深入挖掘启动tomcat等的原理和流程。希望有兴趣的可以一起探讨。

猜你喜欢

转载自blog.csdn.net/aizhupo1314/article/details/83347275