Spring Cloud eurekaserver启动源码分析

spring-cloud 官网:https://spring.io/projects/spring-cloud
spring-cloud-netflix github:https://github.com/spring-cloud/spring-cloud-netflix eureka相关代码被放在了spring-cloud-netflix这个仓库下了,因为eureka是netflix开源的

注:源码分析使用的spring cloud(spring-cloud-dependencies) 版本是Hoxton.SR12,其源码内部各组件真实版本号是2.2.9.RELEASE;
其源码内依赖的spring-boot版本是2.2.2.RELEASE,不过optional 都被设置了true屏蔽依赖传递了,所以需要手动引入下;

Spring Cloud Eureka 是 Spring Cloud Netflix 微服务套件的一部分,基于 Netflix Eureka 做了二次封装,主要负责完成微服务架构中的服务治理功能,服务治理可以说是微服务架构中最为核心和基础的模块,他主要用来实现各个微服务实例的自动化注册与发现。

下载代码

  • 使用命令将代码下载到本地
git clone https://github.com/spring-cloud/spring-cloud-netflix.git
  • 用idea导入代码
  • 切换到 v2.2.9.RELEASE 标签
git checkout v2.2.9.RELEASE

启动入口

  • 测试工程demo代码已经上传到【gitee】

  • 测试工程demo的pom文件,对eureka-server依赖如下:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>springcloud</artifactId>
        <groupId>com.uu</groupId>
        <version>1.0.0</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>
    <artifactId>eurekaserver</artifactId>
    <properties></properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
        </dependency>
    </dependencies>
</project>
  • 源码入口定位
    由于在demo工程里eureka server只依赖了spring-cloud-starter-netflix-eureka-server,然后我在下载的eureka源码里搜索关键字:
spring-cloud-starter-netflix-eureka-server

这是一个starter,主要用于汇总组件自动装配所需的依赖;内容如下:

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<parent>
		<groupId>org.springframework.cloud</groupId>
		<artifactId>spring-cloud-starter-netflix</artifactId>
		<version>2.0.1.RELEASE</version>
	</parent>
	<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
	<name>Spring Cloud Starter Netflix Eureka Server</name>
	<description>Spring Cloud Starter Netflix Eureka Server</description>
	<url>https://projects.spring.io/spring-cloud</url>
	<organization>
		<name>Pivotal Software, Inc.</name>
		<url>https://www.spring.io</url>
	</organization>
	<dependencies>
		<dependency>
			<groupId>org.springframework.cloud</groupId>
			<artifactId>spring-cloud-starter</artifactId>
		</dependency>
// @A		
		<dependency>
			<groupId>org.springframework.cloud</groupId>
			<artifactId>spring-cloud-netflix-eureka-server</artifactId>
		</dependency>
		...省略部分...
	</dependencies>
</project>

根据第六感,我直接点击跳转到 @A 标记的“spring-cloud-netflix-eureka-server”所在模块,寻找有没有spring boot自动装配所需的配置;
自动装配原理可以看我这一篇文章 Springboot自动装配之spring-autoconfigure-metadata.properties和spring.factories,看完后你也会有第六感!

        <dependency>
			<groupId>org.springframework.cloud</groupId>
			<artifactId>spring-cloud-netflix-eureka-server</artifactId>
		</dependency>

的确不出所料,找到了eureka server的启动入口
在这里插入图片描述
入口就是 org.springframework.cloud.netflix.eureka.server.EurekaServerAutoConfiguration 这个类,我们看看类定义

@Configuration
@Import(EurekaServerInitializerConfiguration.class)
@ConditionalOnBean(EurekaServerMarkerConfiguration.Marker.class)
@EnableConfigurationProperties({
    
     EurekaDashboardProperties.class,InstanceRegistryProperties.class })
@PropertySource("classpath:/eureka/server.properties")
public class EurekaServerAutoConfiguration extends WebMvcConfigurerAdapter {
    
     ... }

熟悉spring-boot自动装配流程的童鞋应该会主动地将注意力放到 @ConditionalOnBean 这个注解上,意思是EurekaServerAutoConfiguration 这个类定义(beanDefinition)加载的前提是EurekaServerMarkerConfiguration.Marker 的beanDefinition被加载到了容器;
接下来我们分析一下 EurekaServerMarkerConfiguration.Marker这个类是怎么加载的,关键操作就是 @EnableEurekaServer 这个注解:

@Target({
    
    ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import({
    
    EurekaServerMarkerConfiguration.class})
public @interface EnableEurekaServer {
    
    }

EurekaServerMarkerConfiguration的源码如下:

@Configuration
public class EurekaServerMarkerConfiguration {
    
    
	@Bean
	public Marker eurekaServerMarkerBean() {
    
    
		return new Marker();
	}
	class Marker {
    
    	}
}

可以看出 EurekaServerMarkerConfiguration.Marker 类的引入是@EnableEurekaServer 这个注解引入的,这个启动类我们会在启动类上进行添加;
那么问题来了,也是一直困扰我的一个问题,这两个beandefinition是如何保证先后顺序的,也就是说为什么 EurekaServerMarkerConfiguration.Marker 就一定会EurekaServerAutoConfiguration比先被解析到?
通过几天的源码分析,得出下面几点:
1:spring-cloud的@EnableEurekaServer和spring-boot的@EnableAutoConfiguration 属于同层次的注解(@Import的复合注解),两者都会在包扫描的情况下扫描进入容器(要明白启动主流程是spring-boot,只是spring-cloud对spring-boot扩展点做了自定义扩展,即做到:对修改关闭 对扩展开放)
2:@EnableEurekaServer类上注解@Import(EurekaServerMarkerConfiguration.class)里@Bean直接引入 EurekaServerMarkerConfiguration.Marker
3:spring-boot的@EnableAutoConfiguration上注解了@EnableAutoConfiguration ,该注解@Import(AutoConfigurationImportSelector.class)方法:selectImports 再进一步解析EurekaServerAutoConfiguration类返回给容器;

  • 为什么直接@Bean会比 selectImports 方法引入的 BeanDefinition要早执行?

这篇文章里ConfigurationClassPostProcessor —— Spring中最!最!最!重要的后置处理器! 做了解答

我另一篇关于的文章 Spring ConfigurationClassPostProcessor 也做了注释解析

可以理解为BeanDefinition其实根据配置也是有加载梯队顺序的!!!

spring-cloud启动流程

一句话:spring-cloud独立运行组件EurekaServer在启动的流程中会启动两个spring容器
第一个容器是spring-boot标准容器,在第一个容器生命周期里spring-cloud对eventListener做了扩展,在监听方法中启动了第二个容器,具体作用和流程分析:

重要类

名称 作用
org.springframework.cloud.bootstrap.BootstrapApplicationListener 在配置文件里进行配置并由初始spring-boot容器加载的,是spring-cloud的bootstrap配置的入口
org.springframework.cloud.bootstrap.BootstrapImportSelectorConfiguration 由BootstrapApplicationListener 的onApplicationEvent方法启动spring-boot应用时容器时指定的source配置类
org.springframework.cloud.bootstrap.BootstrapImportSelector 在BootstrapImportSelectorConfiguration 上@Import引入

这些重要类下面详细解析:

BootstrapApplicationListener

  • 类注册时机,在spring-boot 启动的流程总控制方法run中

  • 方法签名:org.springframework.boot.SpringApplication#run(java.lang.String…)

public ConfigurableApplicationContext run(String... args) {
    
    
		StopWatch stopWatch = new StopWatch();
		stopWatch.start();
		ConfigurableApplicationContext context = null;
		Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
		configureHeadlessProperty();
// @A		
		SpringApplicationRunListeners listeners = getRunListeners(args);
		listeners.starting();
		try {
    
    
			ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
			ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
			configureIgnoreBeanInfo(environment);
			Banner printedBanner = printBanner(environment);
			context = createApplicationContext();
			exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
					new Class[] {
    
     ConfigurableApplicationContext.class }, context);
			prepareContext(context, environment, listeners, applicationArguments, printedBanner);
			refreshContext(context);
			afterRefresh(context, applicationArguments);
			stopWatch.stop();
			if (this.logStartupInfo) {
    
    
				new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
			}
			listeners.started(context);
			callRunners(context, applicationArguments);
		}
		catch (Throwable ex) {
    
    
			handleRunFailure(context, ex, exceptionReporters, listeners);
			throw new IllegalStateException(ex);
		}
		try {
    
    
			listeners.running(context);
		}
		catch (Throwable ex) {
    
    
			handleRunFailure(context, ex, exceptionReporters, null);
			throw new IllegalStateException(ex);
		}
		return context;
	}

// @A:会到类路径下 spring.factories 找key为 org.springframework.context.ApplicationListener 的类进行实例化
在这里插入图片描述
@A 代码:

	private SpringApplicationRunListeners getRunListeners(String[] args) {
    
    
		Class<?>[] types = new Class<?>[] {
    
     SpringApplication.class, String[].class };
		return new SpringApplicationRunListeners(logger,
				getSpringFactoriesInstances(SpringApplicationRunListener.class, types, this, args));
	}

BootstrapImportSelectorConfiguration

  • 类注册时机(方法签名):org.springframework.cloud.bootstrap.BootstrapApplicationListener#bootstrapServiceContext
private ConfigurableApplicationContext bootstrapServiceContext(ConfigurableEnvironment environment, final SpringApplication application,String configName) {
		StandardEnvironment bootstrapEnvironment = new StandardEnvironment();
		MutablePropertySources bootstrapProperties = bootstrapEnvironment.getPropertySources();
		for (PropertySource<?> source : bootstrapProperties) {
			bootstrapProperties.remove(source.getName());
		}
		String configLocation = environment.resolvePlaceholders("${spring.cloud.bootstrap.location:}");
		String configAdditionalLocation = environment.resolvePlaceholders("${spring.cloud.bootstrap.additional-location:}");
		Map<String, Object> bootstrapMap = new HashMap<>();
		bootstrapMap.put("spring.config.name", configName);
// ... 省区部分代码

//@A 构建一个spring-boot应用的builder
		SpringApplicationBuilder builder = new SpringApplicationBuilder()
				.profiles(environment.getActiveProfiles()).bannerMode(Mode.OFF)
				.environment(bootstrapEnvironment)
				// Don't use the default properties in this builder
				.registerShutdownHook(false).logStartupInfo(false)
				.web(WebApplicationType.NONE);
		final SpringApplication builderApplication = builder.application();
		if (builderApplication.getMainApplicationClass() == null) {			
			builder.main(application.getMainApplicationClass());
		}
		
		if (environment.getPropertySources().contains("refreshArgs")) {			
			builderApplication.setListeners(filterListeners(builderApplication.getListeners()));
		}
// @B  设置builder的sources为 BootstrapImportSelectorConfiguration
		builder.sources(BootstrapImportSelectorConfiguration.class);
		final ConfigurableApplicationContext context = builder.run();
// ... 省区部分注代码
		return context;
	}
  • @A:初始化一个 SpringApplicationBuilder 对象,其中也会初始化完毕spring容器对于的事件监听器,其中一个很重要的事件监听器就是 ConfigFileApplicationListener(见下图)
  • @B:设置spring-boot应用启动主类为 BootstrapImportSelectorConfiguration ,后续框架会后置处理该类;
    被注入的容器监听器

BootstrapImportSelector

  • 注入时机:该类是被BootstrapImportSelectorConfiguration 类@import引入的
@Configuration(proxyBeanMethods = false)
@Import(BootstrapImportSelector.class)
public class BootstrapImportSelectorConfiguration {

}

todo 2021.11

猜你喜欢

转载自blog.csdn.net/Aqu415/article/details/120684180
今日推荐