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